From 608038384841a539e37a2618715f37031eb978cd Mon Sep 17 00:00:00 2001 From: Geoff Lang Date: Wed, 20 May 2020 17:24:49 -0400 Subject: [PATCH] GL: Work around drivers that generate mipmaps in linear color space Mac drivers generate mipmaps in linear color space. To work around this, copy the sRGB texture to a linear texture, generate mipmaps and then copy back. TEST=conformance2/textures/misc/tex-srgb-mipmap.html BUG=angleproject:4646 Change-Id: I8675d0ab004bcd2985f685d64cbb84deff5f1c86 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2211083 Reviewed-by: Jonah Ryan-Davis Reviewed-by: Geoff Lang Commit-Queue: Geoff Lang --- include/platform/FeaturesGL.h | 7 + src/libANGLE/renderer/gl/BlitGL.cpp | 126 +++++++++++++++++- src/libANGLE/renderer/gl/BlitGL.h | 9 ++ src/libANGLE/renderer/gl/StateManagerGL.cpp | 3 +- src/libANGLE/renderer/gl/StateManagerGL.h | 2 + src/libANGLE/renderer/gl/TextureGL.cpp | 52 +++++++- src/libANGLE/renderer/gl/formatutilsgl.h | 24 ++-- src/libANGLE/renderer/gl/renderergl_utils.cpp | 3 + 8 files changed, 206 insertions(+), 20 deletions(-) diff --git a/include/platform/FeaturesGL.h b/include/platform/FeaturesGL.h index 3542dec46..a61dec414 100644 --- a/include/platform/FeaturesGL.h +++ b/include/platform/FeaturesGL.h @@ -441,6 +441,13 @@ struct FeaturesGL : FeatureSetBase Feature disableTimestampQueries = { "disable_timestamp_queries", FeatureCategory::OpenGLWorkarounds, "Disable GL_EXT_disjoint_timer_query extension", &members, "https://crbug.com/811661"}; + + // Some drivers use linear blending when generating mipmaps for sRGB textures. Work around this + // by generating mipmaps in a linear texture and copying back to sRGB. + Feature encodeAndDecodeSRGBForGenerateMipmap = { + "decode_encode_srgb_for_generatemipmap", FeatureCategory::OpenGLWorkarounds, + "Decode and encode before generateMipmap for srgb format textures.", &members, + "http://anglebug.com/4646"}; }; inline FeaturesGL::FeaturesGL() = default; diff --git a/src/libANGLE/renderer/gl/BlitGL.cpp b/src/libANGLE/renderer/gl/BlitGL.cpp index 757168cea..b7794398b 100644 --- a/src/libANGLE/renderer/gl/BlitGL.cpp +++ b/src/libANGLE/renderer/gl/BlitGL.cpp @@ -988,6 +988,85 @@ angle::Result BlitGL::clearRenderableTextureAlphaToOne(const gl::Context *contex return angle::Result::Continue; } +angle::Result BlitGL::generateSRGBMipmap(const gl::Context *context, + TextureGL *source, + GLuint baseLevel, + GLuint levelCount, + const gl::Extents &sourceBaseLevelSize) +{ + ANGLE_TRY(initializeResources(context)); + + const gl::TextureType sourceType = gl::TextureType::_2D; + const gl::TextureTarget sourceTarget = gl::TextureTarget::_2D; + + ScopedGLState scopedState; + ANGLE_TRY(scopedState.enter( + context, gl::Rectangle(0, 0, sourceBaseLevelSize.width, sourceBaseLevelSize.height))); + scopedState.willUseTextureUnit(context, 0); + mStateManager->activeTexture(0); + + // Copy source to a linear intermediate texture. + GLuint linearTexture = mScratchTextures[0]; + mStateManager->bindTexture(sourceType, linearTexture); + ANGLE_GL_TRY(context, mFunctions->texImage2D( + ToGLenum(sourceTarget), 0, mSRGBMipmapGenerationFormat.internalFormat, + sourceBaseLevelSize.width, sourceBaseLevelSize.height, 0, + mSRGBMipmapGenerationFormat.format, mSRGBMipmapGenerationFormat.type, + nullptr)); + + mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mScratchFBO); + ANGLE_GL_TRY(context, + mFunctions->framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + ToGLenum(sourceTarget), linearTexture, 0)); + mStateManager->setFramebufferSRGBEnabled(context, true); + + // Use a shader to do the sRGB to linear conversion. glBlitFramebuffer does not always do this + // conversion for us. + BlitProgram *blitProgram = nullptr; + ANGLE_TRY(getBlitProgram(context, sourceType, GL_FLOAT, GL_FLOAT, &blitProgram)); + + mStateManager->useProgram(blitProgram->program); + ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->sourceTextureLocation, 0)); + ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->scaleLocation, 1.0f, 1.0f)); + ANGLE_GL_TRY(context, mFunctions->uniform2f(blitProgram->offsetLocation, 0.0f, 0.0f)); + ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->multiplyAlphaLocation, 0)); + ANGLE_GL_TRY(context, mFunctions->uniform1i(blitProgram->unMultiplyAlphaLocation, 0)); + + mStateManager->bindTexture(sourceType, source->getTextureID()); + ANGLE_TRY(source->setMinFilter(context, GL_NEAREST)); + + mStateManager->bindVertexArray(mVAO, 0); + ANGLE_GL_TRY(context, mFunctions->drawArrays(GL_TRIANGLES, 0, 3)); + + // Generate mipmaps on the linear texture + mStateManager->bindTexture(sourceType, linearTexture); + ANGLE_GL_TRY_ALWAYS_CHECK(context, mFunctions->generateMipmap(ToGLenum(sourceTarget))); + ANGLE_GL_TRY(context, mFunctions->texParameteri(ToGLenum(sourceTarget), GL_TEXTURE_MIN_FILTER, + GL_NEAREST)); + + // Copy back to the source texture from the mips generated in the linear texture + for (GLuint levelIdx = 0; levelIdx < levelCount; levelIdx++) + { + gl::Extents levelSize(std::max(sourceBaseLevelSize.width >> levelIdx, 1), + std::max(sourceBaseLevelSize.height >> levelIdx, 1), 1); + + ANGLE_GL_TRY(context, mFunctions->framebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, ToGLenum(sourceTarget), + source->getTextureID(), baseLevel + levelIdx)); + mStateManager->setViewport(gl::Rectangle(0, 0, levelSize.width, levelSize.height)); + + ANGLE_GL_TRY(context, mFunctions->texParameteri(ToGLenum(sourceTarget), + GL_TEXTURE_BASE_LEVEL, levelIdx)); + + ANGLE_GL_TRY(context, mFunctions->drawArrays(GL_TRIANGLES, 0, 3)); + } + + ANGLE_TRY(orphanScratchTextures(context)); + + ANGLE_TRY(scopedState.exit(context)); + return angle::Result::Continue; +} + angle::Result BlitGL::initializeResources(const gl::Context *context) { for (size_t i = 0; i < ArraySize(mScratchTextures); i++) @@ -1038,6 +1117,27 @@ angle::Result BlitGL::initializeResources(const gl::Context *context) } } + constexpr GLenum potentialSRGBMipmapGenerationFormats[] = { + GL_RGBA16, GL_RGBA16F, GL_RGBA32F, + GL_RGBA8, // RGBA8 can have precision loss when generating mipmaps of a sRGBA8 texture + }; + for (GLenum internalFormat : potentialSRGBMipmapGenerationFormats) + { + if (nativegl::SupportsNativeRendering(mFunctions, gl::TextureType::_2D, internalFormat)) + { + const gl::InternalFormat &internalFormatInfo = + gl::GetSizedInternalFormatInfo(internalFormat); + + // Pass the 'format' instead of 'internalFormat' to make sure we use unsized formats + // when available to increase support. + mSRGBMipmapGenerationFormat = + nativegl::GetTexImageFormat(mFunctions, mFeatures, internalFormatInfo.format, + internalFormatInfo.format, internalFormatInfo.type); + break; + } + } + ASSERT(mSRGBMipmapGenerationFormat.internalFormat != GL_NONE); + return angle::Result::Continue; } @@ -1049,12 +1149,34 @@ angle::Result BlitGL::orphanScratchTextures(const gl::Context *context) gl::PixelUnpackState unpack; mStateManager->setPixelUnpackState(unpack); mStateManager->setPixelUnpackBuffer(nullptr); - GLint swizzle[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; + if (mFunctions->isAtLeastGL(gl::Version(3, 3))) + { + constexpr GLint swizzle[4] = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; + ANGLE_GL_TRY(context, mFunctions->texParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, + swizzle)); + } + else if (mFunctions->isAtLeastGLES(gl::Version(3, 0))) + { + ANGLE_GL_TRY(context, + mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED)); + ANGLE_GL_TRY(context, + mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN)); + ANGLE_GL_TRY(context, + mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE)); + ANGLE_GL_TRY(context, + mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA)); + } + + ANGLE_GL_TRY(context, mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0)); + ANGLE_GL_TRY(context, mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1000)); + ANGLE_GL_TRY(context, mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_NEAREST_MIPMAP_LINEAR)); ANGLE_GL_TRY(context, - mFunctions->texParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle)); + mFunctions->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); ANGLE_GL_TRY(context, mFunctions->texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr)); } + return angle::Result::Continue; } diff --git a/src/libANGLE/renderer/gl/BlitGL.h b/src/libANGLE/renderer/gl/BlitGL.h index c1fa3249f..f13ec0e0f 100644 --- a/src/libANGLE/renderer/gl/BlitGL.h +++ b/src/libANGLE/renderer/gl/BlitGL.h @@ -13,6 +13,7 @@ #include "common/angleutils.h" #include "libANGLE/Error.h" #include "libANGLE/angletypes.h" +#include "libANGLE/renderer/gl/formatutilsgl.h" #include @@ -136,6 +137,12 @@ class BlitGL : angle::NonCopyable gl::TextureTarget target, size_t level); + angle::Result generateSRGBMipmap(const gl::Context *context, + TextureGL *source, + GLuint baseLevel, + GLuint levelCount, + const gl::Extents &sourceBaseLevelSize); + angle::Result initializeResources(const gl::Context *context); private: @@ -173,6 +180,8 @@ class BlitGL : angle::NonCopyable GLuint mVAO; GLuint mVertexBuffer; + + nativegl::TexImageFormat mSRGBMipmapGenerationFormat; }; } // namespace rx diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp index e2a10cbfd..cff702215 100644 --- a/src/libANGLE/renderer/gl/StateManagerGL.cpp +++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp @@ -133,6 +133,7 @@ StateManagerGL::StateManagerGL(const FunctionsGL *functions, mClearColor(0.0f, 0.0f, 0.0f, 0.0f), mClearDepth(1.0f), mClearStencil(0), + mFramebufferSRGBAvailable(extensions.sRGBWriteControl), mFramebufferSRGBEnabled(false), mDitherEnabled(true), mTextureCubemapSeamlessEnabled(false), @@ -2071,7 +2072,7 @@ angle::Result StateManagerGL::syncState(const gl::Context *context, void StateManagerGL::setFramebufferSRGBEnabled(const gl::Context *context, bool enabled) { - if (!context->getExtensions().sRGBWriteControl) + if (!mFramebufferSRGBAvailable) { return; } diff --git a/src/libANGLE/renderer/gl/StateManagerGL.h b/src/libANGLE/renderer/gl/StateManagerGL.h index d90b84dab..f01d001af 100644 --- a/src/libANGLE/renderer/gl/StateManagerGL.h +++ b/src/libANGLE/renderer/gl/StateManagerGL.h @@ -320,7 +320,9 @@ class StateManagerGL final : angle::NonCopyable float mClearDepth; GLint mClearStencil; + bool mFramebufferSRGBAvailable; bool mFramebufferSRGBEnabled; + bool mDitherEnabled; bool mTextureCubemapSeamlessEnabled; diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp index 66c4ffcf6..f8b568469 100644 --- a/src/libANGLE/renderer/gl/TextureGL.cpp +++ b/src/libANGLE/renderer/gl/TextureGL.cpp @@ -1240,15 +1240,57 @@ angle::Result TextureGL::setImageExternal(const gl::Context *context, angle::Result TextureGL::generateMipmap(const gl::Context *context) { - const FunctionsGL *functions = GetFunctionsGL(context); - StateManagerGL *stateManager = GetStateManagerGL(context); - - stateManager->bindTexture(getType(), mTextureID); - ANGLE_GL_TRY_ALWAYS_CHECK(context, functions->generateMipmap(ToGLenum(getType()))); + const FunctionsGL *functions = GetFunctionsGL(context); + StateManagerGL *stateManager = GetStateManagerGL(context); + const angle::FeaturesGL &features = GetFeaturesGL(context); const GLuint effectiveBaseLevel = mState.getEffectiveBaseLevel(); const GLuint maxLevel = mState.getMipmapMaxLevel(); + const gl::ImageDesc &baseLevelDesc = mState.getBaseLevelDesc(); + const gl::InternalFormat &baseLevelInternalFormat = *baseLevelDesc.format.info; + + stateManager->bindTexture(getType(), mTextureID); + if (baseLevelInternalFormat.colorEncoding == GL_SRGB && + features.encodeAndDecodeSRGBForGenerateMipmap.enabled && getType() == gl::TextureType::_2D) + { + nativegl::TexImageFormat texImageFormat = nativegl::GetTexImageFormat( + functions, features, baseLevelInternalFormat.internalFormat, + baseLevelInternalFormat.format, baseLevelInternalFormat.type); + + // Manually allocate the mip levels of this texture if they don't exist + GLuint levelCount = maxLevel - effectiveBaseLevel + 1; + for (GLuint levelIdx = 1; levelIdx < levelCount; levelIdx++) + { + gl::Extents levelSize(std::max(baseLevelDesc.size.width >> levelIdx, 1), + std::max(baseLevelDesc.size.height >> levelIdx, 1), 1); + + const gl::ImageDesc &levelDesc = + mState.getImageDesc(gl::TextureTarget::_2D, effectiveBaseLevel + levelIdx); + + // Make sure no pixel unpack buffer is bound + stateManager->bindBuffer(gl::BufferBinding::PixelUnpack, 0); + + if (levelDesc.size != levelSize || *levelDesc.format.info != baseLevelInternalFormat) + { + ANGLE_GL_TRY_ALWAYS_CHECK( + context, functions->texImage2D( + ToGLenum(getType()), effectiveBaseLevel + levelIdx, + texImageFormat.internalFormat, levelSize.width, levelSize.height, + 0, texImageFormat.format, texImageFormat.type, nullptr)); + } + } + + // Use the blitter to generate the mips + BlitGL *blitter = GetBlitGL(context); + ANGLE_TRY(blitter->generateSRGBMipmap(context, this, effectiveBaseLevel, levelCount, + baseLevelDesc.size)); + } + else + { + ANGLE_GL_TRY_ALWAYS_CHECK(context, functions->generateMipmap(ToGLenum(getType()))); + } + setLevelInfo(context, getType(), effectiveBaseLevel, maxLevel - effectiveBaseLevel, getBaseLevelInfo()); diff --git a/src/libANGLE/renderer/gl/formatutilsgl.h b/src/libANGLE/renderer/gl/formatutilsgl.h index 1195290b0..1c5b5f7d0 100644 --- a/src/libANGLE/renderer/gl/formatutilsgl.h +++ b/src/libANGLE/renderer/gl/formatutilsgl.h @@ -63,9 +63,9 @@ const InternalFormat &GetInternalFormatInfo(GLenum internalFormat, StandardGL st struct TexImageFormat { - GLenum internalFormat; - GLenum format; - GLenum type; + GLenum internalFormat = GL_NONE; + GLenum format = GL_NONE; + GLenum type = GL_NONE; }; TexImageFormat GetTexImageFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -75,8 +75,8 @@ TexImageFormat GetTexImageFormat(const FunctionsGL *functions, struct TexSubImageFormat { - GLenum format; - GLenum type; + GLenum format = GL_NONE; + GLenum type = GL_NONE; }; TexSubImageFormat GetTexSubImageFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -85,7 +85,7 @@ TexSubImageFormat GetTexSubImageFormat(const FunctionsGL *functions, struct CompressedTexImageFormat { - GLenum internalFormat; + GLenum internalFormat = GL_NONE; }; CompressedTexImageFormat GetCompressedTexImageFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -93,7 +93,7 @@ CompressedTexImageFormat GetCompressedTexImageFormat(const FunctionsGL *function struct CompressedTexSubImageFormat { - GLenum format; + GLenum format = GL_NONE; }; CompressedTexSubImageFormat GetCompressedSubTexImageFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -101,7 +101,7 @@ CompressedTexSubImageFormat GetCompressedSubTexImageFormat(const FunctionsGL *fu struct CopyTexImageImageFormat { - GLenum internalFormat; + GLenum internalFormat = GL_NONE; }; CopyTexImageImageFormat GetCopyTexImageImageFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -110,7 +110,7 @@ CopyTexImageImageFormat GetCopyTexImageImageFormat(const FunctionsGL *functions, struct TexStorageFormat { - GLenum internalFormat; + GLenum internalFormat = GL_NONE; }; TexStorageFormat GetTexStorageFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -118,7 +118,7 @@ TexStorageFormat GetTexStorageFormat(const FunctionsGL *functions, struct RenderbufferFormat { - GLenum internalFormat; + GLenum internalFormat = GL_NONE; }; RenderbufferFormat GetRenderbufferFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, @@ -126,8 +126,8 @@ RenderbufferFormat GetRenderbufferFormat(const FunctionsGL *functions, struct ReadPixelsFormat { - GLenum format; - GLenum type; + GLenum format = GL_NONE; + GLenum type = GL_NONE; }; ReadPixelsFormat GetReadPixelsFormat(const FunctionsGL *functions, const angle::FeaturesGL &features, diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp index a45112944..2f8da585b 100644 --- a/src/libANGLE/renderer/gl/renderergl_utils.cpp +++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp @@ -1743,6 +1743,9 @@ void InitializeFeatures(const FunctionsGL *functions, angle::FeaturesGL *feature IsLinux() && isAMD && isMesa && mesaVersion < (std::array{19, 3, 5})); ANGLE_FEATURE_CONDITION(features, disableTimestampQueries, IsLinux() && isVMWare); + + ANGLE_FEATURE_CONDITION(features, encodeAndDecodeSRGBForGenerateMipmap, + IsApple() && functions->standard == STANDARD_GL_DESKTOP); } void InitializeFrontendFeatures(const FunctionsGL *functions, angle::FrontendFeatures *features)