I finally made it work
I'll explain the changes here, and make a patch for it later.
COpenGLTexture::uploadTexture
Code: Select all
if (newTexture)
{
...
}
else
{
if (IsCompressed)
{
if(ColorFormat == ECF_DXT1)
compressedDataSize = ((image->getDimension().Width + 3) / 4) * ((image->getDimension().Height + 3) / 4) * 8;
else if (ColorFormat == ECF_DXT2 || ColorFormat == ECF_DXT3 || ColorFormat == ECF_DXT4 || ColorFormat == ECF_DXT5)
compressedDataSize = ((image->getDimension().Width + 3) / 4) * ((image->getDimension().Height + 3) / 4) * 16;
Driver->extGlCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, image->getDimension().Width,
image->getDimension().Height, InternalFormat, compressedDataSize, source);
}
}
I changed the parameter PixelFormat in the extGlCompressedTexSubImage2D call to InternalFormat. For DXT compressed textures, the format must be either GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT or GL_COMPRESSED_RGBA_S3TC_DXT5_EXT.
COpenGLTexture::lock
Code: Select all
void* COpenGLTexture::lock(E_TEXTURE_LOCK_MODE mode, u32 mipmapLevel)
{
// store info about which image is locked
IImage* image = (mipmapLevel==0)?Image:MipImage;
ReadOnlyLock |= (mode==ETLM_READ_ONLY);
MipLevelStored = mipmapLevel;
if (!ReadOnlyLock && mipmapLevel)
{
#ifdef GL_SGIS_generate_mipmap
if (Driver->queryFeature(EVDF_MIP_MAP_AUTO_UPDATE))
{
// do not automatically generate and update mipmaps
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);
}
#endif
AutomaticMipmapUpdate=false;
}
// if data not available or might have changed on GPU download it
if (!image || IsRenderTarget)
{
// prepare the data storage if necessary
if (!image)
{
if (mipmapLevel)
{
u32 i=0;
u32 width = TextureSize.Width;
u32 height = TextureSize.Height;
do
{
if (width>1)
width>>=1;
if (height>1)
height>>=1;
++i;
}
while (i != mipmapLevel);
MipImage = image = Driver->createImage(ColorFormat, core::dimension2du(width,height));
} else {
Image = image = Driver->createImage(ColorFormat, ImageSize);
}
}
if (!image)
return 0;
if (mode != ETLM_WRITE_ONLY)
{
u8* pixels = static_cast<u8*>(image->lock());
if (!pixels)
return 0;
// we need to keep the correct texture bound later on
GLint tmpTexture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &tmpTexture);
glBindTexture(GL_TEXTURE_2D, TextureName);
if (Driver->testGLError())
os::Printer::log("Could not bind TextureName", ELL_ERROR);
// we need to flip textures vertical
// however, it seems that this does not hold for mipmap
// textures, for unknown reasons.
// allows to read pixels in top-to-bottom order
#ifdef GL_MESA_pack_invert
if (!mipmapLevel && Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert))
glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE);
#endif
// download GPU data as ARGB8 to pixels;
if (IsCompressed) {
glGetCompressedTexImage(GL_TEXTURE_2D, mipmapLevel, pixels);
if (Driver->testGLError())
os::Printer::log("Could not download compressed GPU data", ELL_ERROR);
} else {
glGetTexImage(GL_TEXTURE_2D, mipmapLevel, GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels);
if (Driver->testGLError())
os::Printer::log("Could not download GPU data", ELL_ERROR);
}
if (!mipmapLevel)
{
#ifdef GL_MESA_pack_invert
if (Driver->queryOpenGLFeature(COpenGLExtensionHandler::IRR_MESA_pack_invert))
glPixelStorei(GL_PACK_INVERT_MESA, GL_FALSE);
else
#endif
{
if (!IsCompressed) {
// opengl images are horizontally flipped, so we have to fix that here.
const s32 pitch=image->getPitch();
u8* p2 = pixels + (image->getDimension().Height - 1) * pitch;
u8* tmpBuffer = new u8[pitch];
for (u32 i=0; i < image->getDimension().Height; i += 2)
{
memcpy(tmpBuffer, pixels, pitch);
memcpy(pixels, p2, pitch);
memcpy(p2, tmpBuffer, pitch);
pixels += pitch;
p2 -= pitch;
}
delete [] tmpBuffer;
}
}
}
image->unlock();
//reset old bound texture
glBindTexture(GL_TEXTURE_2D, tmpTexture);
}
}
return image->lock();
}
In order to be able to use all compressed formats, the createImage call now takes the ColorFormat dynamically. I am not 100% sure if this is correct, i simply made the assumption that the ColorFormat Variable is always already set with the correct value. The part of the donwnload from the GPU is handled by glGetCompressedTexImage in case of a compressed texture. The fix for the flipped texture must not be done for compressed textures, i assume because they probably are inverted again.
CImage::initData()
Code: Select all
void CImage::initData()
{
#ifdef _DEBUG
setDebugName("CImage");
#endif
u32 dataSize = 0;
if (Format == ECF_DXT1 || Format == ECF_DXT2 || Format == ECF_DXT3 || Format == ECF_DXT4 || Format == ECF_DXT5) {
u32 compressedDataSize = 0;
if (Format == ECF_DXT1) {
compressedDataSize = ((Size.Width + 3) / 4) * ((Size.Height + 3) / 4) * 8;
} else {
compressedDataSize = ((Size.Width + 3) / 4) * ((Size.Height + 3) / 4) * 16;
}
dataSize = compressedDataSize;
} else {
BytesPerPixel = getBitsPerPixelFromFormat(Format) / 8;
// Pitch should be aligned...
Pitch = BytesPerPixel * Size.Width;
dataSize = Size.Height * Pitch;
}
if (!Data)
{
DeleteMemory=true;
Data = new u8[dataSize];
}
}
I took the calculation of the data size of the Image directly from the uploadTexture method. The DXT sizes which were returned from getBitsPerPixelFromFormat were wrong in my opinion. For ECF_DXT2 to ECF_DXT5 it should be 8 bits per Pixel. For ECF_DXT1 it's 4 bits per Pixel. The code as it was would break DXT1 and cause a crash because it would result in 0.5 bytes per pixel which is not handled well by a u32 and then lead to an alocation of 0 bytes. The image size has to match the size of the texture which has been uploaded as raw data to the GPU.
COpenGLTexture::unlock
Code: Select all
//! unlock function
void COpenGLTexture::unlock()
{
// test if miplevel or main texture was locked
IImage* image = MipImage?MipImage:Image;
if (!image)
return;
// unlock image to see changes
image->unlock();
// copy texture data to GPU
if (!ReadOnlyLock)
uploadTexture(false, 0, MipLevelStored);
ReadOnlyLock = false;
// cleanup local image
if (MipImage)
{
MipImage->drop();
MipImage=0;
}
else if (!KeepImage)
{
Image->drop();
Image=0;
}
// update information
if (Image)
ColorFormat=Image->getColorFormat();
else
ColorFormat=ECF_A8R8G8B8;
}
There was not much to do here, i just removed the IsCompressed "barrier", so that the raw texture is uploaded into the texture array on the GPU.
COpenGLTextureArray::uploadTexture
Code: Select all
void COpenGLTextureArray::uploadTexture(const core::array<ITexture*> &surfaces)
{
const u32 nfiles = surfaces.size();
const u32 w = surfaces[0]->getSize().Width;
const u32 h = surfaces[0]->getSize().Height;
u32 i;
const COpenGLTexture * const tex = (COpenGLTexture *) surfaces[0];
ECOLOR_FORMAT color = tex->getColorFormat();
GLint filter;
InternalFormat = getOpenGLFormatAndParametersFromColorFormat(
color, filter, PixelFormat, PixelType);
if (color == ECF_DXT1 || color == ECF_DXT2 || color == ECF_DXT3 || color == ECF_DXT4 || color == ECF_DXT5) {
ColorFormat = color;
IsCompressed = true;
}
Driver->setActiveTexture(0, this);
if (Driver->testGLError())
os::Printer::log("Could not bind Texture", ELL_ERROR);
GLint compressedSize = 0;
// Define it
if (IsCompressed) {
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedSize);
glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, InternalFormat, w, h, nfiles, 0, compressedSize * nfiles, NULL);
} else {
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, InternalFormat, w, h, nfiles, 0, PixelFormat, PixelType, NULL);
}
if (Driver->testGLError())
os::Printer::log("Could not define Texture array", ELL_ERROR);
for (i = 0; i < nfiles; i++)
{
void *src = surfaces[i]->lock();
Driver->setActiveTexture(0, this);
if (Driver->testGLError())
os::Printer::log("Could not bind Texture", ELL_ERROR);
if (IsCompressed) {
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compressedSize);
glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, w, h, 1, InternalFormat, compressedSize, src);
} else {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, w, h, 1, PixelFormat, PixelType, src);
}
if (Driver->testGLError())
os::Printer::log("Could not glTexSubImage3D", ELL_ERROR);
surfaces[i]->unlock();
}
Driver->extGlGenerateMipmap(GL_TEXTURE_2D_ARRAY);
}
First the format is checked in order to know if we have compressed textures. Then, in the compressed case, the size of the last raw image uploaded to the GPU is retrieved. This was the simplest way, because it is difficult to calculate the byte sizes which do not match the pixel amount of the data. Then the array is defined by using glCompressedTexImage3D.
For the array elements, the byte size is also retrieved from the uploaded GPU data. Also note that the glCompressedTexSubImage3D call uses InternalFormat and not PixelFormat, since again for compressed textures the formats GL_COMPRESSED_RGBA_S3TC* (GL_COMPRESSED_RGB_S3TC* in case of DXT1) must be used.
isRenderTargetOnlyFormat (IImage.h)
Code: Select all
static bool isRenderTargetOnlyFormat(const ECOLOR_FORMAT format)
{
switch(format)
{
case ECF_A1R5G5B5:
case ECF_R5G6B5:
case ECF_R8G8B8:
case ECF_A8R8G8B8:
case ECF_DXT1:
case ECF_DXT2:
case ECF_DXT3:
case ECF_DXT4:
case ECF_DXT5:
return false;
default:
return true;
}
}
To be able to pass all the compressed color fomats dynamically to the CImage contructor, i added ECF_DXT1 - ECF_DXT5 to isRenderTargetOnlyFormat.