I am making a heightmap raytracer based on the Maximum Mipmap structure.
Long story short, I have manually created a texture with mipmap levels using a "Maximum Filter". In Debug Mode the program runs fine and the texture is OK, but in Release Mode the next to last mipmap level contains trash values that cause severe visual problems in my ray tracer.
A bit of info on maximum mipmaps:
A Maximum Mipmap is a ray traversal structure, pretty much like a quadtree, for heighmaps. In the lowest level of the mipmap (level 0) each pixel will contain the maximum height value of the corresponding patch (4 height samples) in the heightmap. Every level following that, each texel will contain the maximum value of 4 corresponding texels in the previous level. This would be a "Maximum filter" instead of the usual linear filter.
If a ray intersects the highest level of the mipmap, then we can test if it still intersects the level below. If all levels are intersected then we can proceed to test the intersection directly against the raw heightmap data. Without this structure we would have to test against every single patch in the heightmap, so it's a massive gain in performance in most cases.
In my program the heighmap is preprocessed into a format where each pixel represents one patch, and each channel (RGBA) contains a height sample from one of the patch corners. So it's a redundant format, two neighbouring pixels will share two values.
Now for the problem I am having. I am using Visual Studio 2015 Community and OpenGL version 4.5 and compiling with the default settings for Debug and Release mode, except include directories for additional libraries and their linker data.
When I run my program using the Debug Build the program runs fine. When I run it in Release Build, the ray traced heighmap has some very strange artifacts, as if the mipmaps had wrong values.
So I tried to isolate the problem. And, indeed, for some reason the mipmap level next to last has incorrect values in the release build and not in the debug build. But only that level. All the other levels appear to be fine.
The most likely place where I could have problems would be the part where I generate mipmaps. As far as I know there is no "Maximum Filter" for automatic generation of mipmaps, so I am generating them manually.
This is the code I am using to generate the maximum mipmap texture(s):
// Read heightfield texture into 'hfPixels'
// Texture dimensions must be Power-Of-Two+1 (Ex: 1025x1025 )
int nWidth = width - 1;
int nHeight = height - 1;
// Convert heightfield into a compact format
std::unique_ptr< Color4C[] > compactHf{ new Color4C[ nWidth * nHeight ] };
for ( int x = 0; x < nWidth; ++x ) {
for ( int y = 0; y < nHeight; ++y ) {
Color4C& patch = compactHf[ y * nWidth + x ];
patch.setColor(
hfPixels[ y * width + x ].red,
hfPixels[ y * width + x + 1 ].red,
hfPixels[ (y+1) * width + x ].red,
hfPixels[ (y+1) * width + x + 1].red );
}
}
hfTexPixels.deletePixelArray();
if ( !_hfCompact.loadTexture( compactHf.get(), nWidth, nHeight ) ) {
return false;
}
// Generate the finest mipmap level
std::unique_ptr<GLubyte[]> previousMipmapLevel{ new GLubyte[ nWidth * nHeight ] };
for ( int x = 0; x < nWidth; ++x ) {
for ( int y = 0; y < nHeight; ++y ) {
Color4C& patch = compactHf[ y* nWidth + x ];
previousMipmapLevel[ y*nWidth + x ] =
max( max( patch.red, patch.green ),
max( patch.blue, patch.alpha ) );
}
}
if ( !_maximumMipmaps.createSpecialTexture( nWidth, nHeight, GL_R8, GL_RED, previousMipmapLevel.get() ) ) {
std::cout << "Error loading mipmap texture" << std::endl;
return false;
}
int maxLevel = (int)log2( nWidth );
std::cout << "Max LOD: " << maxLevel << std::endl;
// Generate remaining mipmap levels
for ( int level = 1; level <= maxLevel; ++level ) {
int prevWidth = nWidth;
int prevHeight = nHeight;
nWidth /= 2;
nHeight /= 2;
std::unique_ptr<GLubyte[]> nextMipmapLevel{ new GLubyte[ nWidth * nHeight ] };
for ( int x = 0; x < nWidth; ++x ) {
for ( int y = 0; y < nHeight; ++y ) {
int pix = x * 2;
nextMipmapLevel[ y * nWidth + x ] =
max(
max( previousMipmapLevel[ (y*2) * prevWidth + pix ], previousMipmapLevel[ (y*2) * prevWidth + pix + 1 ] ),
max( previousMipmapLevel[ (y*2 + 1 ) * prevWidth + pix ], previousMipmapLevel[ (y*2+1) * prevWidth + pix + 1 ] ) );
}
}
_maximumMipmaps.bindTexture();
glTexImage2D( GL_TEXTURE_2D, level, GL_R8, nWidth, nHeight, 0, GL_RED, GL_UNSIGNED_BYTE, nextMipmapLevel.get() );
if ( getOpenGLError( "Error loading mipmap level." ) ) {
std::cout << "Error loading mipmap level(" << level << ")" << std::endl;
return false;
}
_maximumMipmaps.unbindTexture();
previousMipmapLevel.swap( nextMipmapLevel );
}
_lastLevel = maxLevel;
_maximumMipmaps.setMinMagFilter( GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST );
_maximumMipmaps.setWrapMode( GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE );
return true;
In case you're wondering, the 'createSpecialTexture' function will use this code:
bool Texture::createSpecialTexture( int width, int height, GLenum internalFormat, GLenum format, void* data ) {
glGenTextures( 1, &textureId );
if ( getOpenGLError( "Unable to generate TextureID." ) ) {
return false;
}
glBindTexture( GL_TEXTURE_2D, textureId );
glTexImage2D( GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
Here's what the mipmap levels, built from a 1025x1025 heightmap (1024x1024 after pre-processing), look like:
Release Build - Note how the next to last level (2x2 mipmap) has two black texels. The last level is not shown.
This code seems to work in debug mode but not in release mode where the second to last mipmap level contains trash values in two texels.
I have tried to hardcode the data sent to that specific mipmap level but it has no effect. Even went as far as recoding this function, the same thing keeps happening. Did I miss some kind of initialization? Did I use the wrong OpenGL function? Am I falling in one of those so called pitfalls? Could it be a compiler/opengl bug..?
Any kind of hint as to what could be wrong would be very welcome.
EDIT 1: Today I tried changing the texture format to a regular texture's using GL_RGBA. The mipmap corruption no longer happens, but now my mipmap requires all 4 color channels for storage when I only need one. Is there any kind of incompatibility between mipmaps and GL_R8 / GL_RED?