diff options
author | 2017-03-27 00:40:21 -0700 | |
---|---|---|
committer | 2017-03-28 18:35:49 -0700 | |
commit | caaaa66e57293e4a6f312649bf472eab84d5c7fe (patch) | |
tree | 66beeca493da1046b482736293441a70f29474b3 | |
parent | b7980a3bbee067eae4665c8abbe8d39aefb2d36a (diff) |
Convert bitmaps to sRGB/scRGB when they have a color profile
This change also fixes an issue with RGBA16F bitmaps when modulated
with a color (for instance by setting an alpha on the Paint object).
The color space conversion is currently done entirely in the shader,
by doing these operations in order:
1. Sample the texture
2. Un-premultiply alpha
3. Apply the EOTF
4. Multiply by the 3x3 color space matrix
5. Apply the OETF
6. Premultiply alpha
Optimizations:
- Steps 2 & 6 are skipped for opaque (common) bitmaps
- Step 3 is skipped when the color space's EOTF is close
to sRGB (Display P3 for instance). Instead, we use
a hardware sRGB fetch (when the GPU supports it)
- When step 3 is necessary, we use one of four standard
EOTF implementations, to save cycles when possible:
+ Linear (doesn't do anything)
+ Full parametric (ICC parametric curve type 4 as defined
in ICC.1:2004-10, section 10.15)
+ Limited parametric (ICC parametric curve type 3)
+ Gamma (ICC parametric curve type 0)
Color space conversion could be done using texture samplers
instead, for instance 3D LUTs, with or without transfer
functions baked in, or 1D LUTs for transfer functions. This
would result in dependent texture fetches which may or may
not be an advantage over an ALU based implementation. The
current solution favor the use of ALUs to save precious
bandwidth.
Test: CtsUiRenderingTests, CtsGraphicsTests
Bug: 32984164
Change-Id: I10bc3db515e13973b45220f129c66b23f0f7f8fe
-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 */ |