Loading DDS textures into a cube map array

I’m trying to load some DXT1 compressed textures into a cube map array, and running into a couple errors. I’m using opengl 4.5. The code to read in the files was is based off of this tutorial, and I think it’s working correctly. The results all have consistent height, width, and size. Also, in case it matters, the images do have mipmaps.

My first issue is that glCompressedTexImage3D is giving me an INVALID_VALUE. From the documentation, I think that has to mean my imageSize is invalid. But I’m reading it directly from the the image files as they are loaded. I’m not sure how else I’m supposed to get it.

The second error is INVALID_OPERATION, in glCompressedTexSubImage3D. The docs have a couple things that can cause this, but I’m guessing it’s this one:

GL_INVALID_OPERATION is generated if parameter combinations are not supported by the specific compressed internal format as specified in the specific texture compression extension.

But I’m not really sure what that means, or how to fix it. My format in this case is GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, but I’ve tried using more general ones, like GL_COMPRESSED_RGBA. No luck.

void Visual::LoadTextures()
{
	std::vector<std::string> faces;

	for (int i = 0; i < imguiStatus.textureFolders.size(); i++) {
		std::string folder = imguiStatus.textureFolders[i];
		faces.push_back("../cubemaps/" + folder + "/right_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/left_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/top_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/bottom_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/back_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/front_PNG_DXT1_1.dds");
	}
	glGenTextures(1, &cubemap);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, cubemap);

	int width, height, mipmapCount, format, imageSize;
	unsigned char * image;
	for (GLuint i = 0; i < faces.size(); i++)
	{
		image = LoadDDS(faces[i], &format, &mipmapCount, &width, &height, &imageSize);

		// format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT in my case
		if (i == 0) {
			glCompressedTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY,
				0,                           // level
				format,                      // Internal format
				width, height, faces.size(), // width,height,depth
				0,						     //border
				faces.size() * imageSize,    // imageSize
				0);                          // pointer to data

			GetGlError();
		}

		unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
		unsigned int offset = 0;

		/* load the mipmaps */
		for (unsigned int level = 0; level < mipmapCount && (width || height); ++level)
		{
			int size = ((width + 3) / 4)*((height + 3) / 4)*blockSize;
			glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, level, 0, 0, i, width, height, 1, format, size, image + offset);

			GetGlError();

			offset += size;
			width /= 2;
			height /= 2;
		}
		free(image);
	}

	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}


//http://www.opengl-tutorial.org/beginners-tutorials/tutorial-5-a-textured-cube/
#define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII
#define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII
#define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII
unsigned char * Visual::LoadDDS(std::string imagepath, int * format, int * mipmapCount, int * width, int * height, int * imageSize)
{
	unsigned char header[124];

	FILE *fp;

	/* try to open the file */
	fopen_s(&fp, imagepath.c_str(), "rb");
	if (fp == NULL)
		return 0;

	/* verify the type of file */
	char filecode[4];
	fread(filecode, 1, 4, fp);
	if (strncmp(filecode, "DDS ", 4) != 0) {
		fclose(fp);
		return 0;
	}

	/* get the surface desc */
	fread(&header, 124, 1, fp);

	*height = *(unsigned int*)&(header[8]);
	*width = *(unsigned int*)&(header[12]);
	*mipmapCount = *(unsigned int*)&(header[24]);

	unsigned int linearSize = *(unsigned int*)&(header[16]);
	unsigned int fourCC = *(unsigned int*)&(header[80]);

	unsigned char * buffer;
	/* how big is it going to be including all mipmaps? */
	*imageSize = *mipmapCount > 1 ? linearSize * 2 : linearSize;
	buffer = (unsigned char*)malloc(*imageSize * sizeof(unsigned char));
	fread(buffer, 1, *imageSize, fp);
	/* close the file pointer */
	fclose(fp);

	switch (fourCC)
	{
	case FOURCC_DXT1:
		*format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
		break;
	case FOURCC_DXT3:
		*format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
		break;
	case FOURCC_DXT5:
		*format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
		break;
	default:
		free(buffer);
		return 0;
	}

	return buffer;
}

Any help is much appreciated!

I’ve made some progress, but could still use some help.

The INVALID_VALUE error was caused because imageSize is the size in bytes of the base compressed image PLUS the size of all the compressed mipmaps. I just needed the size of the base image. I’m still not sure about INVALID_OPERATION unfortunately. However, it seems to be caused by the mipmaps. No errors if I only run glCompressedTexSubImage3D with level == 0 (base image). I can then just use glGenerateMipmap, and everything works. However, there is a noticeable load time doing that, so I’d still really like to figure this out.

I have not been able to find a single working example of this. I’d have guessed it was very common to load compressed images with mipmaps. Seems like the obvious thing to do if you care about performance. Am I trying to do something unusual here? I can get almost the same code to work using a non-array target (GL_TEXTURE_2D), so is there something special about GL_TEXTURE_CUBE_MAP_ARRAY?

Your (simplified) code does:


allocate storage (TexImage) w x h x faces for mipmap level zero
for each face
    for each mipmap level
        upload image (TexSubImage) w x h x 1

Of course that doesn’t work, because you can’t upload an image to a mipmap level you never allocated storage for.
This should be blatantly obvious if you inspect your texture in a debugger (or just introspect it with glGetTexLevelParameter) at the point the error is thrown.

I see. So, I’m allocating space for every level now. The errors have gone away, and the textures seem to be loaded correctly if I’m interpreting the results from glGetTexLevelParameteriv correctly. But it seems like the mipmaps aren’t actually being used (result is just black instead of textures). I don’t think this is a problem in another part of my program, since glGenerateMipmap still works fine.

void Visual::LoadTextures()
{
	std::vector<std::string> faces;

	for (int i = 0; i < imguiStatus.textureFolders.size(); i++) {
		std::string folder = imguiStatus.textureFolders[i];

		//source: http://planetpixelemporium.com/earth.html
		//converted from equirectangular to cubemap using this (blender) 
		//https://developers.theta360.com/en/forums/viewtopic.php?f=4&t=1981
		faces.push_back("../cubemaps/" + folder + "/right_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/left_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/top_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/bottom_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/back_PNG_DXT1_1.dds");
		faces.push_back("../cubemaps/" + folder + "/front_PNG_DXT1_1.dds");
	}

	glGenTextures(1, &cubemap);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, cubemap);

	int width, height, mipmapCount, format, linearSize;
	unsigned char * image;
	for (GLuint i = 0; i < faces.size(); i++)
	{
		image = LoadDDS(faces[i], &format, &mipmapCount, &width, &height, &linearSize);

		unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
		unsigned int offset = 0;

		/* load the mipmaps */
		for (unsigned int level = 0; level < mipmapCount && (width || height); level++)
		{
			int size = ((width + 3) / 4)*((height + 3) / 4)*blockSize;

			if (i == 0) {
				glCompressedTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY,
					level,                       // level
					format,                      // Internal format
					width, height, faces.size(), // width,height,depth
					0,						     // border
					faces.size() * size,         // imageSize
					0);                          // pointer to data
			}

			glCompressedTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, level, 0, 0, i, width, height, 1, format, size, image + offset);

			offset += size;
			width /= 2;
			height /= 2;
		}

		free(image);
	}
	int test;
	glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &test); //34603008
	glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, 1, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &test); //8650752
	glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, 2, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &test); //2162688
	glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, 3, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &test); //540672
	glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, 4, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &test); //135168
	glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, 5, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &test); //33792

	int maxlevel;
	glGetTexParameteriv(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAX_LEVEL, &maxlevel); //1000

	//glGenerateMipmap(GL_TEXTURE_CUBE_MAP_ARRAY);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}

I think you have your answers right in front of you.

  • What width & height do you expect your input images to be?
  • How many pixels does 34603008 DXT1 bytes correspond to? (in a square width x height x faces level 0 image)
  • Does that match your expectation?
  • How big is the smallest mipmap you’ve specified?
  • And what happens when the 1x1 mipmap level doesn’t exist?

the base image is 1024 x 1024 = 1048576 pixels
according to wikipedia: 1048576px * (64bits / 16px) * (1byte / 8bits) = 524288 bytes.
And I have 66 faces, so the the total comes out to 34603008 bytes, as expected.

[QUOTE=arekkusu;1288312]* How big is the smallest mipmap you’ve specified?

  • And what happens when the 1x1 mipmap level doesn’t exist?[/QUOTE]
    Yeah, this is embarrassing. Should have been
for (unsigned int level = 0; level < mipmapCount + 1 && (width || height); level++)

I just wasn’t generating the smallest mipmap. Everything works now. Thanks so much for your help and patience