diff options
-rw-r--r-- | libs/hwui/Android.mk | 1 | ||||
-rw-r--r-- | libs/hwui/GlLayer.h | 2 | ||||
-rw-r--r-- | libs/hwui/GlopBuilder.cpp | 6 | ||||
-rw-r--r-- | libs/hwui/Program.h | 53 | ||||
-rw-r--r-- | libs/hwui/ProgramCache.cpp | 137 | ||||
-rw-r--r-- | libs/hwui/SkiaShader.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/Texture.cpp | 89 | ||||
-rw-r--r-- | libs/hwui/Texture.h | 35 | ||||
-rw-r--r-- | libs/hwui/hwui/Bitmap.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/renderstate/RenderState.cpp | 37 | ||||
-rw-r--r-- | libs/hwui/utils/Color.cpp | 58 | ||||
-rw-r--r-- | libs/hwui/utils/Color.h | 13 |
12 files changed, 378 insertions, 67 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index ec8d63ecde98..fde0e4760d61 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -50,6 +50,7 @@ hwui_src_files := \ service/GraphicsStatsService.cpp \ thread/TaskManager.cpp \ utils/Blur.cpp \ + utils/Color.cpp \ utils/GLUtils.cpp \ utils/LinearAllocator.cpp \ utils/StringUtils.cpp \ diff --git a/libs/hwui/GlLayer.h b/libs/hwui/GlLayer.h index 20aaf4a35ac1..c4f7fe2a56b8 100644 --- a/libs/hwui/GlLayer.h +++ b/libs/hwui/GlLayer.h @@ -44,7 +44,7 @@ public: } void setSize(uint32_t width, uint32_t height) override { - texture.updateSize(width, height, texture.internalFormat(), texture.format(), + texture.updateLayout(width, height, texture.internalFormat(), texture.format(), texture.target()); } diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 5cf52c69f0fd..3e7a246bb281 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -605,7 +605,11 @@ void GlopBuilder::build() { } else { mDescription.hasExternalTexture = true; } - mDescription.hasLinearTexture = mOutGlop->fill.texture.texture->isLinear(); + Texture* texture = mOutGlop->fill.texture.texture; + mDescription.hasLinearTexture = texture->isLinear(); + mDescription.hasColorSpaceConversion = texture->hasColorSpaceConversion(); + mDescription.transferFunction = texture->getTransferFunctionType(); + mDescription.hasTranslucentConversion = texture->blend; } mDescription.hasColors = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Color; diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index 5c8f8e93fa3d..2becfcb709c3 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -28,6 +28,7 @@ #include "FloatColor.h" #include "Matrix.h" #include "Properties.h" +#include "utils/Color.h" namespace android { namespace uirenderer { @@ -56,11 +57,11 @@ namespace uirenderer { #define PROGRAM_KEY_BITMAP_NPOT 0x80 #define PROGRAM_KEY_BITMAP_EXTERNAL 0x100 -#define PROGRAM_KEY_SWAP_SRC_DST 0x2000 - #define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600 #define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800 +#define PROGRAM_KEY_SWAP_SRC_DST_SHIFT 13 + // Encode the xfermodes on 6 bits #define PROGRAM_MAX_XFERMODE 0x1f #define PROGRAM_XFERMODE_SHADER_SHIFT 26 @@ -89,6 +90,10 @@ namespace uirenderer { #define PROGRAM_HAS_GAMMA_CORRECTION 44 #define PROGRAM_HAS_LINEAR_TEXTURE 45 +#define PROGRAM_HAS_COLOR_SPACE_CONVERSION 46 +#define PROGRAM_TRANSFER_FUNCTION 47 // 2 bits for transfer function +#define PROGRAM_HAS_TRANSLUCENT_CONVERSION 49 + /////////////////////////////////////////////////////////////////////////////// // Types /////////////////////////////////////////////////////////////////////////////// @@ -105,13 +110,13 @@ typedef uint64_t programid; * A ProgramDescription must be used in conjunction with a ProgramCache. */ struct ProgramDescription { - enum class ColorFilterMode { + enum class ColorFilterMode : int8_t { None = 0, Matrix, Blend }; - enum Gradient { + enum Gradient : int8_t { kGradientLinear = 0, kGradientCircular, kGradientSweep @@ -168,6 +173,11 @@ struct ProgramDescription { // Set when sampling an image in linear space bool hasLinearTexture; + bool hasColorSpaceConversion; + TransferFunctionType transferFunction; + // Indicates whether the bitmap to convert between color spaces is translucent + bool hasTranslucentConversion; + /** * Resets this description. All fields are reset back to the default * values they hold after building a new instance. @@ -210,6 +220,10 @@ struct ProgramDescription { hasGammaCorrection = false; hasLinearTexture = false; + + hasColorSpaceConversion = false; + transferFunction = TransferFunctionType::None; + hasTranslucentConversion = false; } /** @@ -263,24 +277,27 @@ struct ProgramDescription { break; case ColorFilterMode::Blend: key |= PROGRAM_KEY_COLOR_BLEND; - key |= ((int)colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT; + key |= ((int) colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT; break; case ColorFilterMode::None: break; } - key |= ((int)framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; - if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST; - if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT; - if (hasVertexAlpha) key |= programid(0x1) << PROGRAM_HAS_VERTEX_ALPHA_SHIFT; - if (useShadowAlphaInterp) key |= programid(0x1) << PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT; - if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT; - if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT; - if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT; - if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS; - if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT; - if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP; - if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION; - if (hasLinearTexture) key |= programid(0x1) << PROGRAM_HAS_LINEAR_TEXTURE; + key |= ((int) framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; + key |= programid(swapSrcDst) << PROGRAM_KEY_SWAP_SRC_DST_SHIFT; + key |= programid(modulate) << PROGRAM_MODULATE_SHIFT; + key |= programid(hasVertexAlpha) << PROGRAM_HAS_VERTEX_ALPHA_SHIFT; + key |= programid(useShadowAlphaInterp) << PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT; + key |= programid(hasExternalTexture) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT; + key |= programid(hasTextureTransform) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT; + key |= programid(isSimpleGradient) << PROGRAM_IS_SIMPLE_GRADIENT; + key |= programid(hasColors) << PROGRAM_HAS_COLORS; + key |= programid(hasDebugHighlight) << PROGRAM_HAS_DEBUG_HIGHLIGHT; + key |= programid(hasRoundRectClip) << PROGRAM_HAS_ROUND_RECT_CLIP; + key |= programid(hasGammaCorrection) << PROGRAM_HAS_GAMMA_CORRECTION; + key |= programid(hasLinearTexture) << PROGRAM_HAS_LINEAR_TEXTURE; + key |= programid(hasColorSpaceConversion) << PROGRAM_HAS_COLOR_SPACE_CONVERSION; + key |= programid(transferFunction) << PROGRAM_TRANSFER_FUNCTION; + key |= programid(hasTranslucentConversion) << PROGRAM_HAS_TRANSLUCENT_CONVERSION; return key; } diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 38c23e4babe8..1f78e09b5a58 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -161,17 +161,61 @@ const char* gFS_Uniforms_HasRoundRectClip = "uniform vec4 roundRectInnerRectLTRB;\n" "uniform float roundRectRadius;\n"; +const char* gFS_Uniforms_ColorSpaceConversion = + // TODO: Should we use a 3D LUT to combine the matrix and transfer functions? + // 32x32x32 fp16 LUTs (for scRGB output) are large and heavy to generate... + "uniform mat3 colorSpaceMatrix;\n"; + +const char* gFS_Uniforms_TransferFunction[4] = { + // In this order: g, a, b, c, d, e, f + // See ColorSpace::TransferParameters + // We'll use hardware sRGB conversion as much as possible + "", + "uniform float transferFunction[7];\n", + "uniform float transferFunction[5];\n", + "uniform float transferFunctionGamma;\n" +}; + const char* gFS_OETF[2] = { - "\nvec4 OETF(const vec4 linear) {\n" - " return linear;\n" - "}\n", - // We expect linear data to be scRGB so we mirror the gamma function - "\nvec4 OETF(const vec4 linear) {" - " return vec4(sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)), linear.a);\n" - "}\n", + R"__SHADER__( + vec4 OETF(const vec4 linear) { + return linear; + } + )__SHADER__", + // We expect linear data to be scRGB so we mirror the gamma function + R"__SHADER__( + vec4 OETF(const vec4 linear) { + return vec4(sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)), linear.a); + } + )__SHADER__" +}; + +const char* gFS_ColorConvert[3] = { + // Just OETF + R"__SHADER__( + vec4 colorConvert(const vec4 color) { + return OETF(color); + } + )__SHADER__", + // Full color conversion for opaque bitmaps + R"__SHADER__( + vec4 colorConvert(const vec4 color) { + return OETF(vec4(colorSpaceMatrix * EOTF_Parametric(color.rgb), color.a)); + } + )__SHADER__", + // Full color conversion for translucent bitmaps + // Note: 0.5/256=0.0019 + R"__SHADER__( + vec4 colorConvert(in vec4 color) { + color.rgb /= color.a + 0.0019; + color = OETF(vec4(colorSpaceMatrix * EOTF_Parametric(color.rgb), color.a)); + color.rgb *= color.a + 0.0019; + return color; + } + )__SHADER__", }; -const char* gFS_Transfer_Functions = R"__SHADER__( +const char* gFS_sRGB_TransferFunctions = R"__SHADER__( float OETF_sRGB(const float linear) { // IEC 61966-2-1:1999 return linear <= 0.0031308 ? linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055; @@ -187,12 +231,56 @@ const char* gFS_Transfer_Functions = R"__SHADER__( } )__SHADER__"; +const char* gFS_TransferFunction[4] = { + // Conversion done by the texture unit (sRGB) + R"__SHADER__( + vec3 EOTF_Parametric(const vec3 x) { + return x; + } + )__SHADER__", + // Full transfer function + // TODO: We should probably use a 1D LUT (256x1 with texelFetch() since input is 8 bit) + // TODO: That would cause 3 dependent texture fetches. Is it worth it? + R"__SHADER__( + float EOTF_Parametric(float x) { + return x <= transferFunction[4] + ? transferFunction[3] * x + transferFunction[6] + : pow(transferFunction[1] * x + transferFunction[2], transferFunction[0]) + + transferFunction[5]; + } + + vec3 EOTF_Parametric(const vec3 x) { + return vec3(EOTF_Parametric(x.r), EOTF_Parametric(x.g), EOTF_Parametric(x.b)); + } + )__SHADER__", + // Limited transfer function, e = f = 0.0 + R"__SHADER__( + float EOTF_Parametric(float x) { + return x <= transferFunction[4] + ? transferFunction[3] * x + : pow(transferFunction[1] * x + transferFunction[2], transferFunction[0]); + } + + vec3 EOTF_Parametric(const vec3 x) { + return vec3(EOTF_Parametric(x.r), EOTF_Parametric(x.g), EOTF_Parametric(x.b)); + } + )__SHADER__", + // Gamma transfer function, e = f = 0.0 + R"__SHADER__( + vec3 EOTF_Parametric(const vec3 x) { + return vec3(pow(x.r, transferFunctionGamma), + pow(x.g, transferFunctionGamma), + pow(x.b, transferFunctionGamma)); + } + )__SHADER__" +}; + // Dithering must be done in the quantization space // When we are writing to an sRGB framebuffer, we must do the following: // EOTF(OETF(color) + dither) // The dithering pattern is generated with a triangle noise generator in the range [-1.0,1.0] // TODO: Handle linear fp16 render targets -const char* gFS_Gradient_Functions = R"__SHADER__( +const char* gFS_GradientFunctions = R"__SHADER__( float triangleNoise(const highp vec2 n) { highp vec2 p = fract(n * vec2(5.3987, 5.4421)); p += dot(p.yx, p.xy + vec2(21.5351, 14.3137)); @@ -200,7 +288,8 @@ const char* gFS_Gradient_Functions = R"__SHADER__( return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; } )__SHADER__"; -const char* gFS_Gradient_Preamble[2] = { + +const char* gFS_GradientPreamble[2] = { // Linear framebuffer R"__SHADER__( vec4 dither(const vec4 color) { @@ -259,9 +348,9 @@ const char* gFS_Main_ApplyVertexAlphaShadowInterp = " fragColor *= texture2D(baseSampler, vec2(alpha, 0.5)).a;\n"; const char* gFS_Main_FetchTexture[2] = { // Don't modulate - " fragColor = OETF(texture2D(baseSampler, outTexCoords));\n", + " fragColor = colorConvert(texture2D(baseSampler, outTexCoords));\n", // Modulate - " fragColor = color * texture2D(baseSampler, outTexCoords);\n" + " fragColor = color * colorConvert(texture2D(baseSampler, outTexCoords));\n" }; const char* gFS_Main_FetchA8Texture[4] = { // Don't modulate @@ -290,9 +379,9 @@ const char* gFS_Main_FetchGradient[6] = { " vec4 gradientColor = gradientMix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n" }; const char* gFS_Main_FetchBitmap = - " vec4 bitmapColor = OETF(texture2D(bitmapSampler, outBitmapTexCoords));\n"; + " vec4 bitmapColor = colorConvert(texture2D(bitmapSampler, outBitmapTexCoords));\n"; const char* gFS_Main_FetchBitmapNpot = - " vec4 bitmapColor = OETF(texture2D(bitmapSampler, wrap(outBitmapTexCoords)));\n"; + " vec4 bitmapColor = colorConvert(texture2D(bitmapSampler, wrap(outBitmapTexCoords)));\n"; const char* gFS_Main_BlendShadersBG = " fragColor = blendShaders(gradientColor, bitmapColor)"; const char* gFS_Main_BlendShadersGB = @@ -627,6 +716,11 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } shader.append(gFS_Uniforms_ColorOp[static_cast<int>(description.colorOp)]); + if (description.hasColorSpaceConversion) { + shader.append(gFS_Uniforms_ColorSpaceConversion); + } + shader.append(gFS_Uniforms_TransferFunction[static_cast<int>(description.transferFunction)]); + // Generate required functions if (description.hasGradient && description.hasBitmap) { generateBlend(shader, "blendShaders", description.shadersMode); @@ -640,16 +734,21 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.useShaderBasedWrap) { generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); } - if (description.hasGradient || description.hasLinearTexture) { - shader.append(gFS_Transfer_Functions); + if (description.hasGradient || description.hasLinearTexture + || description.hasColorSpaceConversion) { + shader.append(gFS_sRGB_TransferFunctions); } if (description.hasBitmap || ((description.hasTexture || description.hasExternalTexture) && !description.hasAlpha8Texture)) { - shader.append(gFS_OETF[description.hasLinearTexture && !mHasLinearBlending]); + shader.append(gFS_TransferFunction[static_cast<int>(description.transferFunction)]); + shader.append(gFS_OETF[(description.hasLinearTexture || description.hasColorSpaceConversion) + && !mHasLinearBlending]); + shader.append(gFS_ColorConvert[description.hasColorSpaceConversion + ? 1 + description.hasTranslucentConversion : 0]); } if (description.hasGradient) { - shader.append(gFS_Gradient_Functions); - shader.append(gFS_Gradient_Preamble[mHasLinearBlending]); + shader.append(gFS_GradientFunctions); + shader.append(gFS_GradientPreamble[mHasLinearBlending]); } // Begin the shader diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 4f7f9d7f9b9a..8a504d4431c4 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -216,8 +216,13 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model const float width = outData->bitmapTexture->width(); const float height = outData->bitmapTexture->height(); + Texture* texture = outData->bitmapTexture; + description->hasBitmap = true; - description->hasLinearTexture = outData->bitmapTexture->isLinear(); + description->hasLinearTexture = texture->isLinear(); + description->hasColorSpaceConversion = texture->hasColorSpaceConversion(); + description->transferFunction = texture->getTransferFunctionType(); + description->hasTranslucentConversion = texture->blend; description->isShaderBitmapExternal = hwuiBitmap->isHardware(); // gralloc doesn't support non-clamp modes if (hwuiBitmap->isHardware() || (!caches.extensions().hasNPot() diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index cfc2744e61b2..8b71086e1625 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -17,10 +17,13 @@ #include "Caches.h" #include "Texture.h" #include "utils/GLUtils.h" +#include "utils/MathUtils.h" #include "utils/TraceUtils.h" #include <utils/Log.h> +#include <math/mat4.h> + #include <SkCanvas.h> namespace android { @@ -48,12 +51,7 @@ static int bytesPerPixel(GLint glFormat) { } } -bool Texture::isLinear() const { - return mInternalFormat == GL_RGBA16F; -} - void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force) { - if (force || wrapS != mWrapS || wrapT != mWrapT) { mWrapS = wrapS; mWrapT = wrapT; @@ -94,7 +92,7 @@ void Texture::deleteTexture() { } } -bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, +bool Texture::updateLayout(uint32_t width, uint32_t height, GLint internalFormat, GLint format, GLenum target) { if (mWidth == width && mHeight == height @@ -122,7 +120,7 @@ void Texture::resetCachedParams() { void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, GLenum type, const void* pixels) { GL_CHECKPOINT(MODERATE); - bool needsAlloc = updateSize(width, height, internalFormat, format, GL_TEXTURE_2D); + bool needsAlloc = updateLayout(width, height, internalFormat, format, GL_TEXTURE_2D); if (!mId) { glGenTextures(1, &mId); needsAlloc = true; @@ -224,7 +222,6 @@ void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType color *outType = GL_UNSIGNED_BYTE; break; case kGray_8_SkColorType: - // TODO: Handle sRGB *outFormat = GL_LUMINANCE; *outInternalFormat = GL_LUMINANCE; *outType = GL_UNSIGNED_BYTE; @@ -252,15 +249,14 @@ SkBitmap Texture::uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, return rgbaBitmap; } -bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending, - SkColorSpace* sRGB) { - bool needSRGB = info.colorSpace() == sRGB; +bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending) { return info.colorType() == kARGB_4444_SkColorType || info.colorType() == kIndex_8_SkColorType - || (info.colorType() == kRGB_565_SkColorType && hasLinearBlending && needSRGB); + || (info.colorType() == kRGB_565_SkColorType + && hasLinearBlending + && info.colorSpace()->isSRGB()); } - void Texture::upload(Bitmap& bitmap) { if (!bitmap.readyToDraw()) { ALOGE("Cannot generate texture from bitmap"); @@ -284,23 +280,59 @@ void Texture::upload(Bitmap& bitmap) { setDefaultParams = true; } - sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); - bool needSRGB = bitmap.info().colorSpace() == sRGB.get(); + bool hasLinearBlending = mCaches.extensions().hasLinearBlending(); + bool needSRGB = transferFunctionCloseToSRGB(bitmap.info().colorSpace()); GLint internalFormat, format, type; - colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type); + colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), + needSRGB && hasLinearBlending, &internalFormat, &format, &type); + + mConnector.reset(); + + // RGBA16F is always extended sRGB, alpha masks don't have color profiles + if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA) { + SkColorSpace* colorSpace = bitmap.info().colorSpace(); + // If the bitmap is sRGB we don't need conversion + if (colorSpace != nullptr && !colorSpace->isSRGB()) { + SkMatrix44 xyzMatrix(SkMatrix44::kUninitialized_Constructor); + if (!colorSpace->toXYZD50(&xyzMatrix)) { + ALOGW("Incompatible color space!"); + } else { + SkColorSpaceTransferFn fn; + if (!colorSpace->isNumericalTransferFn(&fn)) { + ALOGW("Incompatible color space, no numerical transfer function!"); + } else { + float data[16]; + xyzMatrix.asColMajorf(data); + + ColorSpace::TransferParameters p = + {fn.fG, fn.fA, fn.fB, fn.fC, fn.fD, fn.fE, fn.fF}; + ColorSpace src("Unnamed", mat4f((const float*) &data[0]).upperLeft(), p); + mConnector.reset(new ColorSpaceConnector(src, ColorSpace::sRGB())); + + // A non-sRGB color space might have a transfer function close enough to sRGB + // that we can save shader instructions by using an sRGB sampler + // This is only possible if we have hardware support for sRGB textures + if (needSRGB && internalFormat == GL_RGBA + && mCaches.extensions().hasSRGB() && !bitmap.isHardware()) { + internalFormat = GL_SRGB8_ALPHA8; + } + } + } + } + } GLenum target = bitmap.isHardware() ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; - needsAlloc |= updateSize(bitmap.width(), bitmap.height(), internalFormat, format, target); + needsAlloc |= updateLayout(bitmap.width(), bitmap.height(), internalFormat, format, target); blend = !bitmap.isOpaque(); mCaches.textureState().bindTexture(mTarget, mId); // TODO: Handle sRGB gray bitmaps - bool hasLinearBlending = mCaches.extensions().hasLinearBlending(); - if (CC_UNLIKELY(hasUnsupportedColorType(bitmap.info(), hasLinearBlending, sRGB.get()))) { + if (CC_UNLIKELY(hasUnsupportedColorType(bitmap.info(), hasLinearBlending))) { SkBitmap skBitmap; bitmap.getSkBitmap(&skBitmap); + sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); SkBitmap rgbaBitmap = uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB)); uploadToTexture(needsAlloc, internalFormat, format, type, rgbaBitmap.rowBytesAsPixels(), rgbaBitmap.bytesPerPixel(), rgbaBitmap.width(), @@ -333,9 +365,28 @@ void Texture::wrap(GLuint id, uint32_t width, uint32_t height, mFormat = format; mInternalFormat = internalFormat; mTarget = target; + mConnector.reset(); // We're wrapping an existing texture, so don't double count this memory notifySizeChanged(0); } +TransferFunctionType Texture::getTransferFunctionType() const { + if (mConnector.get() != nullptr && mInternalFormat != GL_SRGB8_ALPHA8) { + const ColorSpace::TransferParameters& p = mConnector->getSource().getTransferParameters(); + if (MathUtils::isZero(p.e) && MathUtils::isZero(p.f)) { + if (MathUtils::areEqual(p.a, 1.0f) && MathUtils::isZero(p.b) + && MathUtils::isZero(p.c) && MathUtils::isZero(p.d)) { + if (MathUtils::areEqual(p.g, 1.0f)) { + return TransferFunctionType::None; + } + return TransferFunctionType::Gamma; + } + return TransferFunctionType::Limited; + } + return TransferFunctionType::Full; + } + return TransferFunctionType::None; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index e7fbf20cd898..052c01890317 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -19,6 +19,13 @@ #include "GpuMemoryTracker.h" #include "hwui/Bitmap.h" +#include "utils/Color.h" + +#include <memory> + +#include <math/mat3.h> + +#include <ui/ColorSpace.h> #include <GLES2/gl2.h> #include <EGL/egl.h> @@ -42,8 +49,7 @@ class Texture : public GpuMemoryTracker { public: static SkBitmap uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, sk_sp<SkColorSpace> sRGB); - static bool hasUnsupportedColorType(const SkImageInfo& info, - bool hasLinearBlending, SkColorSpace* sRGB); + static bool hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending); static void colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, bool needSRGB, GLint* outInternalFormat, GLint* outFormat, GLint* outType); @@ -130,9 +136,26 @@ public: } /** + * Returns nullptr if this texture does not require color space conversion + * to sRGB, or a valid pointer to a ColorSpaceConnector if a conversion + * is required. + */ + constexpr const ColorSpaceConnector* getColorSpaceConnector() const { + return mConnector.get(); + } + + constexpr bool hasColorSpaceConversion() const { + return mConnector.get() != nullptr; + } + + TransferFunctionType getTransferFunctionType() const; + + /** * Returns true if this texture uses a linear encoding format. */ - bool isLinear() const; + constexpr bool isLinear() const { + return mInternalFormat == GL_RGBA16F; + } /** * Generation of the backing bitmap, @@ -171,8 +194,8 @@ private: // and external texture wrapper friend class GlLayer; - // Returns true if the size changed, false if it was the same - bool updateSize(uint32_t width, uint32_t height, GLint internalFormat, + // Returns true if the texture layout (size, format, etc.) changed, false if it was the same + bool updateLayout(uint32_t width, uint32_t height, GLint internalFormat, GLint format, GLenum target); void uploadHardwareBitmapToTexture(GraphicBuffer* buffer); void resetCachedParams(); @@ -196,6 +219,8 @@ private: GLenum mMagFilter = GL_LINEAR; Caches& mCaches; + + std::unique_ptr<ColorSpaceConnector> mConnector; }; // struct Texture class AutoTexture { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 72a9f4e34a2e..eed5b242c1d2 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -19,6 +19,7 @@ #include "renderthread/EglManager.h" #include "renderthread/RenderThread.h" #include "renderthread/RenderProxy.h" +#include "utils/Color.h" #include <sys/mman.h> @@ -223,8 +224,7 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr return nullptr; } - sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); - bool needSRGB = skBitmap.info().colorSpace() == sRGB.get(); + bool needSRGB = uirenderer::transferFunctionCloseToSRGB(skBitmap.info().colorSpace()); bool hasLinearBlending = caches.extensions().hasLinearBlending(); GLint format, type, internalFormat; uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(), @@ -245,7 +245,8 @@ sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(uirenderer::renderthread::RenderThr SkBitmap bitmap; if (CC_UNLIKELY(uirenderer::Texture::hasUnsupportedColorType(skBitmap.info(), - hasLinearBlending, sRGB.get()))) { + hasLinearBlending))) { + sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); bitmap = uirenderer::Texture::uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB)); } else { bitmap = skBitmap; diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index 8bce990129de..c8833d2a7489 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -22,8 +22,11 @@ #include "renderthread/CanvasContext.h" #include "renderthread/EglManager.h" #include "utils/GLUtils.h" + #include <algorithm> +#include <ui/ColorSpace.h> + namespace android { namespace uirenderer { @@ -359,6 +362,40 @@ void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { fill.skiaShaderData.bitmapData.bitmapTexture : nullptr; const AutoTexture autoCleanup(texture); + // If we have a shader and a base texture, the base texture is assumed to be an alpha mask + // which means the color space conversion applies to the shader's bitmap + Texture* colorSpaceTexture = texture != nullptr ? texture : fill.texture.texture; + if (colorSpaceTexture != nullptr) { + if (colorSpaceTexture->hasColorSpaceConversion()) { + const ColorSpaceConnector* connector = colorSpaceTexture->getColorSpaceConnector(); + glUniformMatrix3fv(fill.program->getUniform("colorSpaceMatrix"), 1, + GL_FALSE, connector->getTransform().asArray()); + } + + TransferFunctionType transferFunction = colorSpaceTexture->getTransferFunctionType(); + if (transferFunction != TransferFunctionType::None) { + const ColorSpaceConnector* connector = colorSpaceTexture->getColorSpaceConnector(); + const ColorSpace& source = connector->getSource(); + + switch (transferFunction) { + case TransferFunctionType::None: + break; + case TransferFunctionType::Full: + glUniform1fv(fill.program->getUniform("transferFunction"), 7, + reinterpret_cast<const float*>(&source.getTransferParameters().g)); + break; + case TransferFunctionType::Limited: + glUniform1fv(fill.program->getUniform("transferFunction"), 5, + reinterpret_cast<const float*>(&source.getTransferParameters().g)); + break; + case TransferFunctionType::Gamma: + glUniform1f(fill.program->getUniform("transferFunctionGamma"), + source.getTransferParameters().g); + break; + } + } + } + // ------------------------------------ // ---------- GL state setup ---------- // ------------------------------------ diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp new file mode 100644 index 000000000000..7d234b06b8ca --- /dev/null +++ b/libs/hwui/utils/Color.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Color.h" + +#include <cmath> + +namespace android { +namespace uirenderer { + +static inline bool almostEqual(float a, float b) { + return std::abs(a - b) < 1e-2f; +} + +bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace) { + if (colorSpace == nullptr) return true; + if (colorSpace->isSRGB()) return true; + + SkColorSpaceTransferFn transferFunction; + if (colorSpace->isNumericalTransferFn(&transferFunction)) { + // sRGB transfer function params: + const float sRGBParamA = 1 / 1.055f; + const float sRGBParamB = 0.055f / 1.055f; + const float sRGBParamC = 1 / 12.92f; + const float sRGBParamD = 0.04045f; + const float sRGBParamE = 0.0f; + const float sRGBParamF = 0.0f; + const float sRGBParamG = 2.4f; + + // This comparison will catch Display P3 + return + almostEqual(sRGBParamA, transferFunction.fA) + && almostEqual(sRGBParamB, transferFunction.fB) + && almostEqual(sRGBParamC, transferFunction.fC) + && almostEqual(sRGBParamD, transferFunction.fD) + && almostEqual(sRGBParamE, transferFunction.fE) + && almostEqual(sRGBParamF, transferFunction.fF) + && almostEqual(sRGBParamG, transferFunction.fG); + } + + return false; +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 4a27ca2f327a..9c096601c826 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -19,6 +19,7 @@ #include <math.h> #include <SkColor.h> +#include <SkColorSpace.h> namespace android { namespace uirenderer { @@ -82,6 +83,13 @@ namespace uirenderer { }; static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color); + enum class TransferFunctionType : int8_t { + None = 0, + Full, + Limited, + Gamma + }; + // Opto-electronic conversion function for the sRGB color space // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value static constexpr float OECF_sRGB(float linear) { @@ -118,6 +126,11 @@ namespace uirenderer { return srgb; #endif } + + // Returns whether the specified color space's transfer function can be + // approximated with the native sRGB transfer function. This method + // returns true for sRGB, gamma 2.2 and Display P3 for instance + bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace); } /* namespace uirenderer */ } /* namespace android */ |