When I was (re)implementing PhotoAlbum sample application I got stuck in one place. Program was about to load a list of textures (like 16 maybe) and suddenly I noticed that the whole process takes quite long time. More disappointing thing was that I got memory access errors for some images and configurations. This stopped the "development" for a time and I decided that I need to dig inside my image loading library: dig into SOIL.
SOIL library is a well known, public domain, very easy to use and small library for loading images. It is designed to work with OpenGL, usually as a static library. Simple case:
tex_ID = SOIL_load_OGL_texture("test.jpg", SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS | SOIL_FLAG_COMPRESS_TO_DXT);
I like the library because of it simplicity. There is almost no need to learn new API. For example SOIL returns raw
GLuintfor texture objects so it can be easily embedded into your existing wrapper for Texture objects. It is multiplatform as well so it aligns well with freeGLUT for instance.
What was wrong?
The FirstLet's go to the mentioned memory access violation errors. It occurred when I tried to load RGB image of size
725x544. The problem was very simple to fix, but I took me some time to solve.
I started debugging:
- To be able to go inside
SOIL_load_OGL_textureit had to be recompiled with DEBUG flag. SOIL comes with the source code by default so I quickly setup solution under VS 2012 and press recompile.
- Soil.c is the main file for the library. It is quite large - around 2000 lines - and it is written in C style. Unfortunately the C style means, in this case, large functions (for instance 400 lines in once example!) and usually no or "little" SRP. Fortunately after a while the code seems to be readable.
SOIL_load_OGL_textureis a simple function that loads data from a file and then redirects all the work to
SOIL_internal_create_OGL_texture. Alternatively for DDS files
SOIL_direct_load_DDSis used. When loading the data Soil can force additional channels (from RGB to RGBA for instance)
SOIL_internal_create_OGL_textureis this long 400-line function. It does almost everything :). Its main purpose is to create OpenGL texture object and push the previously loaded data into OpenGL. To do that it needs to support various SOIL flags like INVERT_Y, NTSC_SAFE_RGB, MULTIPLY_ALPHA...
- For some flags GL extension are loaded - like for NPOT textures or texture rectangles. The work is done by
query_**_capability()functions. Basically GL extensions are loaded "manually", no separate library is used for that.
- After the pixel transformations are done GL texture object is created.
- Then data is uploaded to OpenGL. In most cases
- When compression is selected then SOIL can compress loaded pixel using DXT1 or DXT5 custom implementations and then use
glCompressedTexImageto push the data.
- Another step is to create mipmaps. This step is done using custom "rescaling" algorithms and mipmaps are generated for POT textures only.
- The last step involves setting proper texture parameters and a memory cleanup.
- If everything goes as expected then the function returns valid ID (
GLuint) to OpenGL Texture Object.
In the end I got one idea regarding my memory errors: use proper data alignment! Simply, I had to setup
glPixelStoreproperly because data from my image was not aligned to 4 bytes. I wrongly assumed that SOIL can deduce the alignment. Silly thing, but it motivated me to debug the code and learn library internals.
It was related to the performance. One image loaded quite fast, but when I needed to upload 16 images it was not. I tried to change flags in the loader. Usually I pass
SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y. When, for instance, I removed mipmap generation it worked faster! My images was not that big: around
540x700, so I thought they should be read quickly.
But while debugging those memory errors several ideas came to my mind about the SOIL and its performance:
- When MIPMAP flag is passed SOIL does not only create mipmaps but before that rescales the texture to be POT. Simply: it cannot make mipmaps out of NPOT texture directly. In my case for an image
540x700it was upscaled to
1024x1024! This could take some time!
- Algorithms for scaling, mipmapping, etc are all in software, no hardware acceleration is used.
The debugging and learning a bit of SOIL internals made me to think about the following upgrades that could be done to the library:
- Add ability to use
glGenerateMipmap. This requires only single line in code to call, works for NPOT textures and should be hardware accelerated. Moreover nowadays it should be supported almost everywhere. Basically
GL_EXT_framebuffer_objectis only needed.
Use modern style of loading extensions. Use
glGetStringi. This should make SOIL to work in Core GL profile. Additionally GL_CLAMP needs to be changed into GL_CLAMP_TO_BORDER.
- Add ability to use immutable texture storage.
- Add some more examples and tests.
Next time I will try to describe changes that I tried to make to the library. For now you can take a look at my github repository SOIL_ext.