Make compressed textures work with mutable storage

1. glTexImage*D does not accept compressed formats.

2. Replacing it with glCompressedTexImage*D is not an option as per GLES 2.0 spec
since passing null data is not allowed.

3. glCompressedTexSubImage*D must always be preceded by a glCompressedTexImage*d
(or glTexStorage*D) call.

4. Therefore the only way is to do nothing in allocateStorage() and switch to
glCompressedTexImage*D in setCompressedData() whenever mutable storage is in use.

This makes ETC1 textures working on the Beaglebone (and presumably others).

Change-Id: I21a040f6ed4aecaa494b6e5a6c6cd75b7389c15c
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
This commit is contained in:
Laszlo Agocs 2015-08-06 12:03:06 +02:00
parent 9861d2bf14
commit 87666d391c
3 changed files with 266 additions and 61 deletions

View File

@ -444,20 +444,23 @@ static bool isTextureTargetMultisample(QOpenGLTexture::Target target)
return false; return false;
} }
bool QOpenGLTexturePrivate::isUsingImmutableStorage() const
{
// Use immutable storage whenever possible, falling back to mutable
// Note that if multisample textures are not supported at all, we'll still fail into
// the mutable storage allocation
return isSizedTextureFormat(format)
&& (isTextureTargetMultisample(target)
? features.testFlag(QOpenGLTexture::ImmutableMultisampleStorage)
: features.testFlag(QOpenGLTexture::ImmutableStorage));
}
void QOpenGLTexturePrivate::allocateStorage(QOpenGLTexture::PixelFormat pixelFormat, QOpenGLTexture::PixelType pixelType) void QOpenGLTexturePrivate::allocateStorage(QOpenGLTexture::PixelFormat pixelFormat, QOpenGLTexture::PixelType pixelType)
{ {
// Resolve the actual number of mipmap levels we can use // Resolve the actual number of mipmap levels we can use
mipLevels = evaluateMipLevels(); mipLevels = evaluateMipLevels();
// Use immutable storage whenever possible, falling back to mutable if (isUsingImmutableStorage())
// Note that if multisample textures are not supported at all, we'll still fail into
// the mutable storage allocation
const bool useImmutableStorage = isSizedTextureFormat(format)
&& (isTextureTargetMultisample(target)
? features.testFlag(QOpenGLTexture::ImmutableMultisampleStorage)
: features.testFlag(QOpenGLTexture::ImmutableStorage));
if (useImmutableStorage)
allocateImmutableStorage(); allocateImmutableStorage();
else else
allocateMutableStorage(pixelFormat, pixelType); allocateMutableStorage(pixelFormat, pixelType);
@ -861,8 +864,133 @@ static QOpenGLTexture::PixelType pixelTypeCompatibleWithInternalFormat(QOpenGLTe
return QOpenGLTexture::NoPixelType; return QOpenGLTexture::NoPixelType;
} }
static bool isCompressedFormat(QOpenGLTexture::TextureFormat internalFormat)
{
switch (internalFormat) {
case QOpenGLTexture::NoFormat:
case QOpenGLTexture::R8_UNorm:
case QOpenGLTexture::RG8_UNorm:
case QOpenGLTexture::RGB8_UNorm:
case QOpenGLTexture::RGBA8_UNorm:
case QOpenGLTexture::R16_UNorm:
case QOpenGLTexture::RG16_UNorm:
case QOpenGLTexture::RGB16_UNorm:
case QOpenGLTexture::RGBA16_UNorm:
case QOpenGLTexture::R8_SNorm:
case QOpenGLTexture::RG8_SNorm:
case QOpenGLTexture::RGB8_SNorm:
case QOpenGLTexture::RGBA8_SNorm:
case QOpenGLTexture::R16_SNorm:
case QOpenGLTexture::RG16_SNorm:
case QOpenGLTexture::RGB16_SNorm:
case QOpenGLTexture::RGBA16_SNorm:
case QOpenGLTexture::R8U:
case QOpenGLTexture::RG8U:
case QOpenGLTexture::RGB8U:
case QOpenGLTexture::RGBA8U:
case QOpenGLTexture::R16U:
case QOpenGLTexture::RG16U:
case QOpenGLTexture::RGB16U:
case QOpenGLTexture::RGBA16U:
case QOpenGLTexture::R32U:
case QOpenGLTexture::RG32U:
case QOpenGLTexture::RGB32U:
case QOpenGLTexture::RGBA32U:
case QOpenGLTexture::R8I:
case QOpenGLTexture::RG8I:
case QOpenGLTexture::RGB8I:
case QOpenGLTexture::RGBA8I:
case QOpenGLTexture::R16I:
case QOpenGLTexture::RG16I:
case QOpenGLTexture::RGB16I:
case QOpenGLTexture::RGBA16I:
case QOpenGLTexture::R32I:
case QOpenGLTexture::RG32I:
case QOpenGLTexture::RGB32I:
case QOpenGLTexture::RGBA32I:
case QOpenGLTexture::R16F:
case QOpenGLTexture::RG16F:
case QOpenGLTexture::RGB16F:
case QOpenGLTexture::RGBA16F:
case QOpenGLTexture::R32F:
case QOpenGLTexture::RG32F:
case QOpenGLTexture::RGB32F:
case QOpenGLTexture::RGBA32F:
case QOpenGLTexture::RGB9E5:
case QOpenGLTexture::RG11B10F:
case QOpenGLTexture::RG3B2:
case QOpenGLTexture::R5G6B5:
case QOpenGLTexture::RGB5A1:
case QOpenGLTexture::RGBA4:
case QOpenGLTexture::RGB10A2:
case QOpenGLTexture::D16:
case QOpenGLTexture::D24:
case QOpenGLTexture::D32:
case QOpenGLTexture::D32F:
case QOpenGLTexture::D24S8:
case QOpenGLTexture::D32FS8X24:
case QOpenGLTexture::S8:
return false;
case QOpenGLTexture::RGB_DXT1:
case QOpenGLTexture::RGBA_DXT1:
case QOpenGLTexture::RGBA_DXT3:
case QOpenGLTexture::RGBA_DXT5:
case QOpenGLTexture::R_ATI1N_UNorm:
case QOpenGLTexture::R_ATI1N_SNorm:
case QOpenGLTexture::RG_ATI2N_UNorm:
case QOpenGLTexture::RG_ATI2N_SNorm:
case QOpenGLTexture::RGB_BP_UNSIGNED_FLOAT:
case QOpenGLTexture::RGB_BP_SIGNED_FLOAT:
case QOpenGLTexture::RGB_BP_UNorm:
case QOpenGLTexture::SRGB8:
case QOpenGLTexture::SRGB8_Alpha8:
case QOpenGLTexture::SRGB_DXT1:
case QOpenGLTexture::SRGB_Alpha_DXT1:
case QOpenGLTexture::SRGB_Alpha_DXT3:
case QOpenGLTexture::SRGB_Alpha_DXT5:
case QOpenGLTexture::SRGB_BP_UNorm:
case QOpenGLTexture::R11_EAC_UNorm:
case QOpenGLTexture::R11_EAC_SNorm:
case QOpenGLTexture::RG11_EAC_UNorm:
case QOpenGLTexture::RG11_EAC_SNorm:
case QOpenGLTexture::RGB8_ETC2:
case QOpenGLTexture::SRGB8_ETC2:
case QOpenGLTexture::RGB8_PunchThrough_Alpha1_ETC2:
case QOpenGLTexture::SRGB8_PunchThrough_Alpha1_ETC2:
case QOpenGLTexture::RGBA8_ETC2_EAC:
case QOpenGLTexture::SRGB8_Alpha8_ETC2_EAC:
case QOpenGLTexture::RGB8_ETC1:
return true;
case QOpenGLTexture::DepthFormat:
case QOpenGLTexture::AlphaFormat:
case QOpenGLTexture::RGBFormat:
case QOpenGLTexture::RGBAFormat:
case QOpenGLTexture::LuminanceFormat:
case QOpenGLTexture::LuminanceAlphaFormat:
return false;
}
Q_UNREACHABLE();
return false;
}
void QOpenGLTexturePrivate::allocateMutableStorage(QOpenGLTexture::PixelFormat pixelFormat, QOpenGLTexture::PixelType pixelType) void QOpenGLTexturePrivate::allocateMutableStorage(QOpenGLTexture::PixelFormat pixelFormat, QOpenGLTexture::PixelType pixelType)
{ {
// There is no way to allocate mutable storage for compressed textures in in
// versions older than OpenGL 3.1 and OpenGL ES 3.0, because the older specs
// do not mandate accepting null data pointers for glCompressedTexImage*D,
// unlike glTexImage*D (which in turn does not accept compressed formats).
if (isCompressedFormat(format)) {
storageAllocated = true;
return;
}
switch (target) { switch (target) {
case QOpenGLTexture::TargetBuffer: case QOpenGLTexture::TargetBuffer:
// Buffer textures get their storage from an external OpenGL buffer // Buffer textures get their storage from an external OpenGL buffer
@ -1200,86 +1328,121 @@ void QOpenGLTexturePrivate::setCompressedData(int mipLevel, int layer, QOpenGLTe
int dataSize, const void *data, int dataSize, const void *data,
const QOpenGLPixelTransferOptions * const options) const QOpenGLPixelTransferOptions * const options)
{ {
if (!isCompressedFormat(format)) {
qWarning("Cannot set compressed data for non-compressed format 0x%x", format);
return;
}
const bool needsFullSpec = !isUsingImmutableStorage(); // was allocateStorage() a no-op?
switch (target) { switch (target) {
case QOpenGLTexture::Target1D: case QOpenGLTexture::Target1D:
Q_UNUSED(layer); Q_UNUSED(layer);
Q_UNUSED(cubeFace); Q_UNUSED(cubeFace);
texFuncs->glCompressedTextureSubImage1D(textureId, target, bindingTarget, mipLevel, if (needsFullSpec) {
0, mipLevelSize( mipLevel, dimensions[0] ), texFuncs->glCompressedTextureImage1D(textureId, target, bindingTarget, mipLevel,
format, dataSize, data, options); format,
mipLevelSize(mipLevel, dimensions[0]),
0, dataSize, data, options);
} else {
texFuncs->glCompressedTextureSubImage1D(textureId, target, bindingTarget, mipLevel,
0, mipLevelSize( mipLevel, dimensions[0] ),
format, dataSize, data, options);
}
break; break;
case QOpenGLTexture::Target1DArray: case QOpenGLTexture::Target1DArray:
Q_UNUSED(cubeFace); Q_UNUSED(cubeFace);
texFuncs->glCompressedTextureSubImage2D(textureId, target, bindingTarget, mipLevel, if (!needsFullSpec) {
0, layer, texFuncs->glCompressedTextureSubImage2D(textureId, target, bindingTarget, mipLevel,
mipLevelSize(mipLevel, dimensions[0]), 0, layer,
1, mipLevelSize(mipLevel, dimensions[0]),
format, dataSize, data, options); 1,
format, dataSize, data, options);
}
break; break;
case QOpenGLTexture::Target2D: case QOpenGLTexture::Target2D:
Q_UNUSED(layer); Q_UNUSED(layer);
Q_UNUSED(cubeFace); Q_UNUSED(cubeFace);
texFuncs->glCompressedTextureSubImage2D(textureId, target, bindingTarget, mipLevel, if (needsFullSpec) {
0, 0, texFuncs->glCompressedTextureImage2D(textureId, target, bindingTarget, mipLevel,
mipLevelSize(mipLevel, dimensions[0]), format,
mipLevelSize(mipLevel, dimensions[1]), mipLevelSize(mipLevel, dimensions[0]),
format, dataSize, data, options); mipLevelSize(mipLevel, dimensions[1]),
0, dataSize, data, options);
} else {
texFuncs->glCompressedTextureSubImage2D(textureId, target, bindingTarget, mipLevel,
0, 0,
mipLevelSize(mipLevel, dimensions[0]),
mipLevelSize(mipLevel, dimensions[1]),
format, dataSize, data, options);
}
break; break;
case QOpenGLTexture::Target2DArray: case QOpenGLTexture::Target2DArray:
Q_UNUSED(cubeFace); Q_UNUSED(cubeFace);
texFuncs->glCompressedTextureSubImage3D(textureId, target, bindingTarget, mipLevel, if (!needsFullSpec) {
0, 0, layer, texFuncs->glCompressedTextureSubImage3D(textureId, target, bindingTarget, mipLevel,
mipLevelSize(mipLevel, dimensions[0]), 0, 0, layer,
mipLevelSize(mipLevel, dimensions[1]), mipLevelSize(mipLevel, dimensions[0]),
1, mipLevelSize(mipLevel, dimensions[1]),
format, dataSize, data, options); 1,
format, dataSize, data, options);
}
break; break;
case QOpenGLTexture::Target3D: case QOpenGLTexture::Target3D:
Q_UNUSED(cubeFace); Q_UNUSED(cubeFace);
texFuncs->glCompressedTextureSubImage3D(textureId, target, bindingTarget, mipLevel, if (needsFullSpec) {
0, 0, layer, texFuncs->glCompressedTextureImage3D(textureId, target, bindingTarget, mipLevel,
mipLevelSize(mipLevel, dimensions[0]), format,
mipLevelSize(mipLevel, dimensions[1]), mipLevelSize(mipLevel, dimensions[0]),
mipLevelSize(mipLevel, dimensions[2]), mipLevelSize(mipLevel, dimensions[1]),
format, dataSize, data, options); mipLevelSize(mipLevel, dimensions[2]),
0, dataSize, data, options);
} else {
texFuncs->glCompressedTextureSubImage3D(textureId, target, bindingTarget, mipLevel,
0, 0, layer,
mipLevelSize(mipLevel, dimensions[0]),
mipLevelSize(mipLevel, dimensions[1]),
mipLevelSize(mipLevel, dimensions[2]),
format, dataSize, data, options);
}
break; break;
case QOpenGLTexture::TargetCubeMap: case QOpenGLTexture::TargetCubeMap:
Q_UNUSED(layer); Q_UNUSED(layer);
texFuncs->glCompressedTextureSubImage2D(textureId, cubeFace, bindingTarget, mipLevel, if (needsFullSpec) {
0, 0, texFuncs->glCompressedTextureImage2D(textureId, cubeFace, bindingTarget, mipLevel,
mipLevelSize(mipLevel, dimensions[0]), format,
mipLevelSize(mipLevel, dimensions[1]), mipLevelSize(mipLevel, dimensions[0]),
format, dataSize, data, options); mipLevelSize(mipLevel, dimensions[1]),
0, dataSize, data, options);
} else {
texFuncs->glCompressedTextureSubImage2D(textureId, cubeFace, bindingTarget, mipLevel,
0, 0,
mipLevelSize(mipLevel, dimensions[0]),
mipLevelSize(mipLevel, dimensions[1]),
format, dataSize, data, options);
}
break; break;
case QOpenGLTexture::TargetCubeMapArray: { case QOpenGLTexture::TargetCubeMapArray: {
int faceIndex = cubeFace - QOpenGLTexture::CubeMapPositiveX; int faceIndex = cubeFace - QOpenGLTexture::CubeMapPositiveX;
int layerFace = 6 * layer + faceIndex; int layerFace = 6 * layer + faceIndex;
texFuncs->glCompressedTextureSubImage3D(textureId, target, bindingTarget, mipLevel, if (!needsFullSpec) {
0, 0, layerFace, texFuncs->glCompressedTextureSubImage3D(textureId, target, bindingTarget, mipLevel,
mipLevelSize(mipLevel, dimensions[0]), 0, 0, layerFace,
mipLevelSize(mipLevel, dimensions[1]), mipLevelSize(mipLevel, dimensions[0]),
1, mipLevelSize(mipLevel, dimensions[1]),
format, dataSize, data, options); 1,
format, dataSize, data, options);
}
break; break;
} }
case QOpenGLTexture::TargetRectangle: case QOpenGLTexture::TargetRectangle:
Q_UNUSED(mipLevel);
Q_UNUSED(layer);
Q_UNUSED(cubeFace);
texFuncs->glCompressedTextureSubImage2D(textureId, target, bindingTarget, 0,
0, 0,
dimensions[0],
dimensions[1],
format, dataSize, data, options);
break;
case QOpenGLTexture::Target2DMultisample: case QOpenGLTexture::Target2DMultisample:
case QOpenGLTexture::Target2DMultisampleArray: case QOpenGLTexture::Target2DMultisampleArray:
case QOpenGLTexture::TargetBuffer: case QOpenGLTexture::TargetBuffer:
@ -3464,6 +3627,8 @@ QPair<int, int> QOpenGLTexture::mipLevelRange() const
The automatic mipmap generation is enabled by default. The automatic mipmap generation is enabled by default.
\note Mipmap generation is not supported for compressed textures with OpenGL ES 2.0.
\sa isAutoMipMapGenerationEnabled(), generateMipMaps() \sa isAutoMipMapGenerationEnabled(), generateMipMaps()
*/ */
void QOpenGLTexture::setAutoMipMapGenerationEnabled(bool enabled) void QOpenGLTexture::setAutoMipMapGenerationEnabled(bool enabled)
@ -3489,6 +3654,9 @@ bool QOpenGLTexture::isAutoMipMapGenerationEnabled() const
have disabled automatic mipmap generation then you need to call this function have disabled automatic mipmap generation then you need to call this function
or the overload to create the mipmap chain. or the overload to create the mipmap chain.
\note Mipmap generation is not supported for compressed textures with OpenGL
ES 2.0.
\sa setAutoMipMapGenerationEnabled(), setMipLevels(), mipLevels() \sa setAutoMipMapGenerationEnabled(), setMipLevels(), mipLevels()
*/ */
void QOpenGLTexture::generateMipMaps() void QOpenGLTexture::generateMipMaps()
@ -3496,6 +3664,11 @@ void QOpenGLTexture::generateMipMaps()
Q_D(QOpenGLTexture); Q_D(QOpenGLTexture);
Q_ASSERT(d->texFuncs); Q_ASSERT(d->texFuncs);
Q_ASSERT(d->textureId); Q_ASSERT(d->textureId);
if (isCompressedFormat(d->format)) {
if (QOpenGLContext *ctx = QOpenGLContext::currentContext())
if (ctx->isOpenGLES() && ctx->format().majorVersion() < 3)
return;
}
d->texFuncs->glGenerateTextureMipmap(d->textureId, d->target, d->bindingTarget); d->texFuncs->glGenerateTextureMipmap(d->textureId, d->target, d->bindingTarget);
} }
@ -3516,6 +3689,11 @@ void QOpenGLTexture::generateMipMaps(int baseLevel, bool resetBaseLevel)
Q_D(QOpenGLTexture); Q_D(QOpenGLTexture);
Q_ASSERT(d->texFuncs); Q_ASSERT(d->texFuncs);
Q_ASSERT(d->textureId); Q_ASSERT(d->textureId);
if (isCompressedFormat(d->format)) {
if (QOpenGLContext *ctx = QOpenGLContext::currentContext())
if (ctx->isOpenGLES() && ctx->format().majorVersion() < 3)
return;
}
int oldBaseLevel; int oldBaseLevel;
if (resetBaseLevel) if (resetBaseLevel)
oldBaseLevel = mipBaseLevel(); oldBaseLevel = mipBaseLevel();

View File

@ -117,6 +117,8 @@ public:
return std::floor(double(qMax(1, baseLevelSize >> mipLevel))); return std::floor(double(qMax(1, baseLevelSize >> mipLevel)));
} }
bool isUsingImmutableStorage() const;
QOpenGLTexture *q_ptr; QOpenGLTexture *q_ptr;
QOpenGLContext *context; QOpenGLContext *context;
QOpenGLTexture::Target target; QOpenGLTexture::Target target;

View File

@ -253,23 +253,48 @@ public:
inline void glCompressedTextureImage1D(GLuint texture, GLenum target, GLenum bindingTarget, GLint level, inline void glCompressedTextureImage1D(GLuint texture, GLenum target, GLenum bindingTarget, GLint level,
GLenum internalFormat, GLsizei width, GLenum internalFormat, GLsizei width,
GLint border, GLsizei imageSize, const GLvoid *bits) GLint border, GLsizei imageSize, const GLvoid *bits,
const QOpenGLPixelTransferOptions * const options = 0)
{ {
(this->*CompressedTextureImage1D)(texture, target, bindingTarget, level, internalFormat, width, border, imageSize, bits); if (options) {
QOpenGLPixelTransferOptions oldOptions = savePixelUploadOptions();
setPixelUploadOptions(*options);
(this->*CompressedTextureImage1D)(texture, target, bindingTarget, level, internalFormat, width, border, imageSize, bits);
setPixelUploadOptions(oldOptions);
} else {
(this->*CompressedTextureImage1D)(texture, target, bindingTarget, level, internalFormat, width, border, imageSize, bits);
}
} }
inline void glCompressedTextureImage2D(GLuint texture, GLenum target, GLenum bindingTarget, GLint level, inline void glCompressedTextureImage2D(GLuint texture, GLenum target, GLenum bindingTarget, GLint level,
GLenum internalFormat, GLsizei width, GLsizei height, GLenum internalFormat, GLsizei width, GLsizei height,
GLint border, GLsizei imageSize, const GLvoid *bits) GLint border, GLsizei imageSize, const GLvoid *bits,
const QOpenGLPixelTransferOptions * const options = 0)
{ {
(this->*CompressedTextureImage2D)(texture, target, bindingTarget, level, internalFormat, width, height, border, imageSize, bits); if (options) {
QOpenGLPixelTransferOptions oldOptions = savePixelUploadOptions();
setPixelUploadOptions(*options);
(this->*CompressedTextureImage2D)(texture, target, bindingTarget, level, internalFormat, width, height, border, imageSize, bits);
setPixelUploadOptions(oldOptions);
} else {
(this->*CompressedTextureImage2D)(texture, target, bindingTarget, level, internalFormat, width, height, border, imageSize, bits);
}
} }
inline void glCompressedTextureImage3D(GLuint texture, GLenum target, GLenum bindingTarget, GLint level, inline void glCompressedTextureImage3D(GLuint texture, GLenum target, GLenum bindingTarget, GLint level,
GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
GLint border, GLsizei imageSize, const GLvoid *bits) GLint border, GLsizei imageSize, const GLvoid *bits,
const QOpenGLPixelTransferOptions * const options = 0)
{ {
(this->*CompressedTextureImage3D)(texture, target, bindingTarget, level, internalFormat, width, height, depth, border, imageSize, bits); if (options) {
QOpenGLPixelTransferOptions oldOptions = savePixelUploadOptions();
setPixelUploadOptions(*options);
(this->*CompressedTextureImage3D)(texture, target, bindingTarget, level, internalFormat, width, height, depth, border, imageSize, bits);
setPixelUploadOptions(oldOptions);
} else {
(this->*CompressedTextureImage3D)(texture, target, bindingTarget, level, internalFormat, width, height, depth, border, imageSize, bits);
}
} }
private: private: