From 253f2c213f6ecda63b6872aee77bd30d5ec07c82 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Wed, 28 Sep 2016 17:34:42 -0700 Subject: Linear blending, step 1 NOTE: Linear blending is currently disabled in this CL as the feature is still a work in progress Android currently performs all blending (any kind of linear math on colors really) on gamma-encoded colors. Since Android assumes that the default color space is sRGB, all bitmaps and colors are encoded with the sRGB Opto-Electronic Conversion Function (OECF, which can be approximated with a power function). Since the power curve is not linear, our linear math is incorrect. The result is that we generate colors that tend to be too dark; this affects blending but also anti-aliasing, gradients, blurs, etc. The solution is to convert gamma-encoded colors back to linear space before doing any math on them, using the sRGB Electo-Optical Conversion Function (EOCF). This is achieved in different ways in different parts of the pipeline: - Using hardware conversions when sampling from OpenGL textures or writing into OpenGL frame buffers - Using software conversion functions, to translate app-supplied colors to and from sRGB - Using Skia's color spaces Any type of processing on colors must roughly ollow these steps: [sRGB input]->EOCF->[linear data]->[processing]->OECF->[sRGB output] For the sRGB color space, the conversion functions are defined as follows: OECF(linear) := linear <= 0.0031308 ? linear * 12.92 : (pow(linear, 1/2.4) * 1.055) - 0.055 EOCF(srgb) := srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4) The EOCF is simply the reciprocal of the OECF. While it is highly recommended to use the exact sRGB conversion functions everywhere possible, it is sometimes useful or beneficial to rely on approximations: - pow(x,2.2) and pow(x,1/2.2) - x^2 and sqrt(x) The latter is particularly useful in fragment shaders (for instance to apply dithering in sRGB space), especially if the sqrt() can be replaced with an inversesqrt(). Here is a fairly exhaustive list of modifications implemented in this CL: - Set TARGET_ENABLE_LINEAR_BLENDING := false in BoardConfig.mk to disable linear blending. This is only for GLES 2.0 GPUs with no hardware sRGB support. This flag is currently assumed to be false (see note above) - sRGB writes are disabled when entering a functor (WebView). This will need to be fixed at some point - Skia bitmaps are created with the sRGB color space - Bitmaps using a 565 config are expanded to 888 - Linear blending is disabled when entering a functor - External textures are not properly sampled (see below) - Gradients are interpolated in linear space - Texture-based dithering was replaced with analytical dithering - Dithering is done in the quantization color space, which is why we must do EOCF(OECF(color)+dither) - Text is now gamma corrected differently depending on the luminance of the source pixel. The asumption is that a bright pixel will be blended on a dark background and the other way around. The source alpha is gamma corrected to thicken dark on bright and thin bright on dark to match the intended design of fonts. This also matches the behavior of popular design/drawing applications - Removed the asset atlas. It did not contain anything useful and could not be sampled in sRGB without a yet-to-be-defined GL extension - The last column of color matrices is converted to linear space because its value are added to linear colors Missing features: - Resource qualifier? - Regeneration of goldeng images for automated tests - Handle alpha8/grey8 properly - Disable sRGB write for layers with external textures Test: Manual testing while work in progress Bug: 29940137 Change-Id: I6a07b15ab49b554377cd33a36b6d9971a15e9a0b --- libs/hwui/Android.mk | 11 +- libs/hwui/AssetAtlas.cpp | 130 ------------------- libs/hwui/AssetAtlas.h | 174 -------------------------- libs/hwui/BakedOpDispatcher.cpp | 52 +++----- libs/hwui/BakedOpRenderer.cpp | 6 +- libs/hwui/Caches.cpp | 2 - libs/hwui/Caches.h | 15 ++- libs/hwui/Dither.cpp | 98 --------------- libs/hwui/Dither.h | 56 --------- libs/hwui/Extensions.cpp | 9 ++ libs/hwui/Extensions.h | 4 + libs/hwui/FloatColor.h | 9 +- libs/hwui/FontRenderer.cpp | 13 +- libs/hwui/FrameBuilder.cpp | 2 - libs/hwui/GammaFontRenderer.cpp | 3 +- libs/hwui/GammaFontRenderer.h | 13 +- libs/hwui/GlopBuilder.cpp | 41 +++--- libs/hwui/GlopBuilder.h | 2 + libs/hwui/GradientCache.cpp | 72 ++++------- libs/hwui/GradientCache.h | 23 ++-- libs/hwui/Layer.h | 2 +- libs/hwui/PatchCache.cpp | 6 +- libs/hwui/PatchCache.h | 4 +- libs/hwui/Program.h | 7 ++ libs/hwui/ProgramCache.cpp | 172 ++++++++++++++++--------- libs/hwui/ProgramCache.h | 1 + libs/hwui/Properties.h | 4 +- libs/hwui/PropertyValuesHolder.cpp | 23 ++-- libs/hwui/Readback.cpp | 2 +- libs/hwui/RecordedOp.h | 1 - libs/hwui/SkiaShader.cpp | 23 ++-- libs/hwui/SkiaShader.h | 5 +- libs/hwui/Texture.cpp | 83 +++++++----- libs/hwui/Texture.h | 15 ++- libs/hwui/TextureCache.cpp | 25 +--- libs/hwui/TextureCache.h | 30 +---- libs/hwui/VectorDrawable.cpp | 8 +- libs/hwui/Vertex.h | 17 ++- libs/hwui/font/CacheTexture.cpp | 7 +- libs/hwui/renderstate/OffscreenBufferPool.cpp | 3 +- libs/hwui/renderstate/RenderState.cpp | 12 +- libs/hwui/renderstate/RenderState.h | 3 - libs/hwui/renderthread/CanvasContext.cpp | 5 - libs/hwui/renderthread/CanvasContext.h | 3 - libs/hwui/renderthread/EglManager.cpp | 42 ++----- libs/hwui/renderthread/EglManager.h | 7 -- libs/hwui/renderthread/RenderProxy.cpp | 17 --- libs/hwui/renderthread/RenderProxy.h | 1 - libs/hwui/tests/common/TestUtils.h | 3 +- libs/hwui/tests/unit/SkiaBehaviorTests.cpp | 7 ++ libs/hwui/utils/Color.h | 24 ++++ libs/hwui/utils/TestWindowContext.cpp | 3 +- 52 files changed, 438 insertions(+), 862 deletions(-) delete mode 100644 libs/hwui/AssetAtlas.cpp delete mode 100644 libs/hwui/AssetAtlas.h delete mode 100644 libs/hwui/Dither.cpp delete mode 100644 libs/hwui/Dither.h (limited to 'libs') diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 81a183126cce..2d9b764f5504 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -45,7 +45,6 @@ hwui_src_files := \ AnimationContext.cpp \ Animator.cpp \ AnimatorManager.cpp \ - AssetAtlas.cpp \ BakedOpDispatcher.cpp \ BakedOpRenderer.cpp \ BakedOpState.cpp \ @@ -56,7 +55,6 @@ hwui_src_files := \ DeferredLayerUpdater.cpp \ DeviceInfo.cpp \ DisplayList.cpp \ - Dither.cpp \ Extensions.cpp \ FboCache.cpp \ FontRenderer.cpp \ @@ -130,6 +128,14 @@ ifeq ($(TARGET_USES_HWC2),true) hwui_cflags += -DUSE_HWC2 endif +# TODO: Linear blending should be enabled by default, but we are +# TODO: making it an opt-in while it's a work in progress +# TODO: The final test should be: +# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false) +ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true) + hwui_cflags += -DANDROID_ENABLE_LINEAR_BLENDING +endif + # GCC false-positives on this warning, and since we -Werror that's # a problem hwui_cflags += -Wno-free-nonheap-object @@ -143,7 +149,6 @@ ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE)) hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE endif - ifndef HWUI_COMPILE_SYMBOLS hwui_cflags += -fvisibility=hidden endif diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp deleted file mode 100644 index e2e7037202b8..000000000000 --- a/libs/hwui/AssetAtlas.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2013 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 "AssetAtlas.h" -#include "Caches.h" -#include "Image.h" - -#include - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Lifecycle -/////////////////////////////////////////////////////////////////////////////// - -void AssetAtlas::init(const sp& buffer, int64_t* map, int count) { - if (mImage) { - return; - } - - ATRACE_NAME("AssetAtlas::init"); - - mImage = new Image(buffer); - if (mImage->getTexture()) { - if (!mTexture) { - Caches& caches = Caches::getInstance(); - mTexture = new Texture(caches); - mTexture->wrap(mImage->getTexture(), - buffer->getWidth(), buffer->getHeight(), GL_RGBA); - createEntries(caches, map, count); - } - } else { - ALOGW("Could not create atlas image"); - terminate(); - } -} - -void AssetAtlas::terminate() { - delete mImage; - mImage = nullptr; - delete mTexture; - mTexture = nullptr; - mEntries.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Entries -/////////////////////////////////////////////////////////////////////////////// - -AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const { - auto result = mEntries.find(pixelRef); - return result != mEntries.end() ? result->second.get() : nullptr; -} - -Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const { - auto result = mEntries.find(pixelRef); - return result != mEntries.end() ? result->second->texture : nullptr; -} - -/** - * Delegates changes to wrapping and filtering to the base atlas texture - * instead of applying the changes to the virtual textures. - */ -struct DelegateTexture: public Texture { - DelegateTexture(Caches& caches, Texture* delegate) - : Texture(caches), mDelegate(delegate) { } - - virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, - bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override { - mDelegate->setWrapST(wrapS, wrapT, bindTexture, force, renderTarget); - } - - virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, - bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override { - mDelegate->setFilterMinMag(min, mag, bindTexture, force, renderTarget); - } - -private: - Texture* const mDelegate; -}; // struct DelegateTexture - -void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { - const float width = float(mTexture->width()); - const float height = float(mTexture->height()); - - for (int i = 0; i < count; ) { - SkPixelRef* pixelRef = reinterpret_cast(map[i++]); - // NOTE: We're converting from 64 bit signed values to 32 bit - // signed values. This is guaranteed to be safe because the "x" - // and "y" coordinate values are guaranteed to be representable - // with 32 bits. The array is 64 bits wide so that it can carry - // pointers on 64 bit architectures. - const int x = static_cast(map[i++]); - const int y = static_cast(map[i++]); - - // Bitmaps should never be null, we're just extra paranoid - if (!pixelRef) continue; - - const UvMapper mapper( - x / width, (x + pixelRef->info().width()) / width, - y / height, (y + pixelRef->info().height()) / height); - - Texture* texture = new DelegateTexture(caches, mTexture); - texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType()); - texture->wrap(mTexture->id(), pixelRef->info().width(), - pixelRef->info().height(), mTexture->format()); - - std::unique_ptr entry(new Entry(pixelRef, texture, mapper, *this)); - texture->uvMapper = &entry->uvMapper; - - mEntries.emplace(entry->pixelRef, std::move(entry)); - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h deleted file mode 100644 index b32e51851b94..000000000000 --- a/libs/hwui/AssetAtlas.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -#ifndef ANDROID_HWUI_ASSET_ATLAS_H -#define ANDROID_HWUI_ASSET_ATLAS_H - -#include "Texture.h" -#include "UvMapper.h" - -#include -#include -#include -#include - -#include -#include - -namespace android { -namespace uirenderer { - -class Caches; -class Image; - -/** - * An asset atlas holds a collection of framework bitmaps in a single OpenGL - * texture. Each bitmap is associated with a location, defined in pixels, - * inside the atlas. The atlas is generated by the framework and bound as - * an external texture using the EGLImageKHR extension. - */ -class AssetAtlas { -public: - /** - * Entry representing the texture and uvMapper of a PixelRef in the - * atlas - */ - class Entry { - public: - /* - * A "virtual texture" object that represents the texture - * this entry belongs to. This texture should never be - * modified. - */ - Texture* texture; - - /** - * Maps texture coordinates in the [0..1] range into the - * correct range to sample this entry from the atlas. - */ - const UvMapper uvMapper; - - /** - * Unique identifier used to merge bitmaps and 9-patches stored - * in the atlas. - */ - const void* getMergeId() const { - return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey; - } - - ~Entry() { - delete texture; - } - - private: - /** - * The pixel ref that generated this atlas entry. - */ - SkPixelRef* pixelRef; - - /** - * Atlas this entry belongs to. - */ - const AssetAtlas& atlas; - - Entry(SkPixelRef* pixelRef, Texture* texture, const UvMapper& mapper, - const AssetAtlas& atlas) - : texture(texture) - , uvMapper(mapper) - , pixelRef(pixelRef) - , atlas(atlas) { - } - - friend class AssetAtlas; - }; - - AssetAtlas(): mTexture(nullptr), mImage(nullptr), - mBlendKey(true), mOpaqueKey(false) { } - ~AssetAtlas() { terminate(); } - - /** - * Initializes the atlas with the specified buffer and - * map. The buffer is a gralloc'd texture that will be - * used as an EGLImage. The map is a list of SkBitmap* - * and their (x, y) positions - * - * This method returns immediately if the atlas is already - * initialized. To re-initialize the atlas, you must - * first call terminate(). - */ - ANDROID_API void init(const sp& buffer, int64_t* map, int count); - - /** - * Destroys the atlas texture. This object can be - * re-initialized after calling this method. - * - * After calling this method, the width, height - * and texture are set to 0. - */ - void terminate(); - - /** - * Returns the width of this atlas in pixels. - * Can return 0 if the atlas is not initialized. - */ - uint32_t getWidth() const { - return mTexture ? mTexture->width() : 0; - } - - /** - * Returns the height of this atlas in pixels. - * Can return 0 if the atlas is not initialized. - */ - uint32_t getHeight() const { - return mTexture ? mTexture->height() : 0; - } - - /** - * Returns the OpenGL name of the texture backing this atlas. - * Can return 0 if the atlas is not initialized. - */ - GLuint getTexture() const { - return mTexture ? mTexture->id() : 0; - } - - /** - * Returns the entry in the atlas associated with the specified - * pixelRef. If the pixelRef is not in the atlas, return NULL. - */ - Entry* getEntry(const SkPixelRef* pixelRef) const; - - /** - * Returns the texture for the atlas entry associated with the - * specified pixelRef. If the pixelRef is not in the atlas, return NULL. - */ - Texture* getEntryTexture(const SkPixelRef* pixelRef) const; - -private: - void createEntries(Caches& caches, int64_t* map, int count); - - Texture* mTexture; - Image* mImage; - - const bool mBlendKey; - const bool mOpaqueKey; - - std::unordered_map> mEntries; -}; // class AssetAtlas - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_ASSET_ATLAS_H diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 8b3f1722dddf..699503991e02 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -35,11 +35,11 @@ namespace android { namespace uirenderer { -static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) { - vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top }; - vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top }; - vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom }; - vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom }; +static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds) { + vertices[0] = { bounds.left, bounds.top, 0, 0 }; + vertices[1] = { bounds.right, bounds.top, 1, 0 }; + vertices[2] = { bounds.left, bounds.bottom, 0, 1 }; + vertices[3] = { bounds.right, bounds.bottom, 1, 1 }; } void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, @@ -48,16 +48,11 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, const BakedOpState& firstState = *(opList.states[0]); const SkBitmap* bitmap = (static_cast(opList.states[0]->op))->bitmap; - AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef()); - Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap); + Texture* texture = renderer.caches().textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); TextureVertex vertices[opList.count * 4]; - Rect texCoords(0, 0, 1, 1); - if (entry) { - entry->uvMapper.map(texCoords); - } for (size_t i = 0; i < opList.count; i++) { const BakedOpState& state = *(opList.states[i]); TextureVertex* rectVerts = &vertices[i * 4]; @@ -69,7 +64,7 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, // pure translate, so snap (same behavior as onBitmapOp) opBounds.snapToPixelBoundaries(); } - storeTexturedRect(rectVerts, opBounds, texCoords); + storeTexturedRect(rectVerts, opBounds); renderer.dirtyRenderTarget(opBounds); } @@ -92,8 +87,6 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, const MergedBakedOpList& opList) { const PatchOp& firstOp = *(static_cast(opList.states[0]->op)); const BakedOpState& firstState = *(opList.states[0]); - AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry( - firstOp.bitmap->pixelRef()); // Batches will usually contain a small number of items so it's // worth performing a first iteration to count the exact number @@ -105,7 +98,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, // TODO: cache mesh lookups const Patch* opMesh = renderer.caches().patchCache.get( - entry, op.bitmap->width(), op.bitmap->height(), + op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); totalVertices += opMesh->verticesCount; } @@ -126,7 +119,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, // TODO: cache mesh lookups const Patch* opMesh = renderer.caches().patchCache.get( - entry, op.bitmap->width(), op.bitmap->height(), + op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); @@ -172,7 +165,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, } - Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap); + Texture* texture = renderer.caches().textureCache.get(firstOp.bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -442,7 +435,12 @@ void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op } void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) { - const static UvMapper defaultUvMapper; + Texture* texture = renderer.caches().textureCache.get(op.bitmap); + if (!texture) { + return; + } + const AutoTexture autoCleanup(texture); + const uint32_t elementCount = op.meshWidth * op.meshHeight * 6; std::unique_ptr mesh(new ColorTextureVertex[elementCount]); @@ -457,9 +455,6 @@ void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMe colors = tempColors.get(); } - Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef()); - const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper); - for (int32_t y = 0; y < op.meshHeight; y++) { for (int32_t x = 0; x < op.meshWidth; x++) { uint32_t i = (y * (op.meshWidth + 1) + x) * 2; @@ -469,8 +464,6 @@ void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMe float v1 = float(y) / op.meshHeight; float v2 = float(y + 1) / op.meshHeight; - mapper.map(u1, v1, u2, v2); - int ax = i + (op.meshWidth + 1) * 2; int ay = ax + 1; int bx = i; @@ -491,14 +484,6 @@ void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMe } } - if (!texture) { - texture = renderer.caches().textureCache.get(op.bitmap); - if (!texture) { - return; - } - } - const AutoTexture autoCleanup(texture); - /* * TODO: handle alpha_8 textures correctly by applying paint color, but *not* * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh. @@ -599,12 +584,11 @@ void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, } // TODO: avoid redoing the below work each frame: - AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef()); const Patch* mesh = renderer.caches().patchCache.get( - entry, op.bitmap->width(), op.bitmap->height(), + op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); - Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap); + Texture* texture = renderer.caches().textureCache.get(op.bitmap); if (CC_LIKELY(texture)) { const AutoTexture autoCleanup(texture); Glop glop; diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 6db345ac0006..ac7a600af85f 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -182,11 +182,7 @@ void BakedOpRenderer::clearColorBuffer(const Rect& rect) { } Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) { - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); - if (!texture) { - return mCaches.textureCache.get(bitmap); - } - return texture; + return mCaches.textureCache.get(bitmap); } void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 741cdccaa778..b463e45fb39b 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -53,7 +53,6 @@ Caches::Caches(RenderState& renderState) : gradientCache(mExtensions) , patchCache(renderState) , programCache(mExtensions) - , dither(*this) , mRenderState(&renderState) , mInitialized(false) { INIT_LOGD("Creating OpenGL renderer caches"); @@ -238,7 +237,6 @@ void Caches::flush(FlushMode mode) { gradientCache.clear(); fontRenderer.clear(); fboCache.clear(); - dither.clear(); // fall through case FlushMode::Moderate: fontRenderer.flush(); diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 344ee71a2cc5..7c2e78c7d3b8 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -16,8 +16,6 @@ #pragma once -#include "AssetAtlas.h" -#include "Dither.h" #include "Extensions.h" #include "FboCache.h" #include "GammaFontRenderer.h" @@ -129,6 +127,15 @@ public: */ TextureVertex* getRegionMesh(); + /** + * Returns the GL RGBA internal format to use for the current device + * If the device supports linear blending and needSRGB is true, + * this function returns GL_SRGB8_ALPHA8, otherwise it returns GL_RGBA + */ + constexpr GLint rgbaInternalFormat(bool needSRGB = true) const { + return extensions().hasSRGB() && needSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA; + } + /** * Displays the memory usage of each cache and the total sum. */ @@ -157,8 +164,6 @@ public: TaskManager tasks; - Dither dither; - bool gpuPixelBuffersEnabled; // Debug methods @@ -169,7 +174,7 @@ public: void setProgram(const ProgramDescription& description); void setProgram(Program* program); - Extensions& extensions() { return mExtensions; } + const Extensions& extensions() const { return mExtensions; } Program& program() { return *mProgram; } PixelBufferState& pixelBufferState() { return *mPixelBufferState; } TextureState& textureState() { return *mTextureState; } diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp deleted file mode 100644 index ec2013e27401..000000000000 --- a/libs/hwui/Dither.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2012 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 "Caches.h" -#include "Dither.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Lifecycle -/////////////////////////////////////////////////////////////////////////////// - -Dither::Dither(Caches& caches) - : mCaches(caches) - , mInitialized(false) - , mDitherTexture(0) { -} - -void Dither::bindDitherTexture() { - if (!mInitialized) { - glGenTextures(1, &mDitherTexture); - mCaches.textureState().bindTexture(mDitherTexture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - - if (mCaches.extensions().hasFloatTextures()) { - // We use a R16F texture, let's remap the alpha channel to the - // red channel to avoid changing the shader sampling code on GL ES 3.0+ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED); - - float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE); - const GLfloat pattern[] = { - 0 * dither, 8 * dither, 2 * dither, 10 * dither, - 12 * dither, 4 * dither, 14 * dither, 6 * dither, - 3 * dither, 11 * dither, 1 * dither, 9 * dither, - 15 * dither, 7 * dither, 13 * dither, 5 * dither - }; - - glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, - GL_RED, GL_FLOAT, &pattern); - } else { - const uint8_t pattern[] = { - 0, 8, 2, 10, - 12, 4, 14, 6, - 3, 11, 1, 9, - 15, 7, 13, 5 - }; - - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); - } - - mInitialized = true; - } else { - mCaches.textureState().bindTexture(mDitherTexture); - } -} - -void Dither::clear() { - if (mInitialized) { - mCaches.textureState().deleteTexture(mDitherTexture); - mInitialized = false; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Program management -/////////////////////////////////////////////////////////////////////////////// - -void Dither::setupProgram(Program& program, GLuint* textureUnit) { - GLuint textureSlot = (*textureUnit)++; - mCaches.textureState().activateTexture(textureSlot); - - bindDitherTexture(); - - glUniform1i(program.getUniform("ditherSampler"), textureSlot); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h deleted file mode 100644 index 6af3e8384472..000000000000 --- a/libs/hwui/Dither.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -#ifndef ANDROID_HWUI_DITHER_H -#define ANDROID_HWUI_DITHER_H - -#include - -namespace android { -namespace uirenderer { - -class Caches; -class Extensions; -class Program; - -// Must be a power of two -#define DITHER_KERNEL_SIZE 4 -// These must not use the .0f notation as they are used from GLSL -#define DITHER_KERNEL_SIZE_INV (1.0 / 4.0) -#define DITHER_KERNEL_SIZE_INV_SQUARE (1.0 / 16.0) - -/** - * Handles dithering for programs. - */ -class Dither { -public: - explicit Dither(Caches& caches); - - void clear(); - void setupProgram(Program& program, GLuint* textureUnit); - -private: - void bindDitherTexture(); - - Caches& mCaches; - bool mInitialized; - GLuint mDitherTexture; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_DITHER_H diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index 02caaa49e99c..4dc7536d60bc 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -22,6 +22,7 @@ #include #include + #include namespace android { @@ -44,6 +45,14 @@ Extensions::Extensions() { mHas1BitStencil = extensions.has("GL_OES_stencil1"); mHas4BitStencil = extensions.has("GL_OES_stencil4"); mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage"); + mHasSRGB = extensions.has("GL_EXT_sRGB"); + mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control"); + + // If linear blending is enabled, the device must have ES3.0 and GL_EXT_sRGB_write_control +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + assert(mVersionMajor >= 3 || mHasSRGB); + assert(mHasSRGBWriteControl); +#endif const char* version = (const char*) glGetString(GL_VERSION); diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 67cc747015e0..cc73c2317720 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -43,6 +43,8 @@ public: inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } inline bool hasFloatTextures() const { return mVersionMajor >= 3; } + inline bool hasSRGB() const { return mVersionMajor >= 3 || mHasSRGB; } + inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; } inline int getMajorGlVersion() const { return mVersionMajor; } inline int getMinorGlVersion() const { return mVersionMinor; } @@ -55,6 +57,8 @@ private: bool mHas1BitStencil; bool mHas4BitStencil; bool mHasUnpackSubImage; + bool mHasSRGB; + bool mHasSRGBWriteControl; int mVersionMajor; int mVersionMinor; diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h index 9a39ec28aa3d..6d19b7ccdd79 100644 --- a/libs/hwui/FloatColor.h +++ b/libs/hwui/FloatColor.h @@ -16,6 +16,7 @@ #ifndef FLOATCOLOR_H #define FLOATCOLOR_H +#include "utils/Color.h" #include "utils/Macros.h" #include "utils/MathUtils.h" @@ -25,11 +26,13 @@ namespace android { namespace uirenderer { struct FloatColor { + // "color" is a gamma-encoded sRGB color + // After calling this method, the color is stored as a pre-multiplied linear color void set(uint32_t color) { a = ((color >> 24) & 0xff) / 255.0f; - r = a * ((color >> 16) & 0xff) / 255.0f; - g = a * ((color >> 8) & 0xff) / 255.0f; - b = a * ((color ) & 0xff) / 255.0f; + r = a * EOCF_sRGB(((color >> 16) & 0xff) / 255.0f); + g = a * EOCF_sRGB(((color >> 8) & 0xff) / 255.0f); + b = a * EOCF_sRGB(((color ) & 0xff) / 255.0f); } bool isNotBlack() { diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 9b60dfcb5867..effc65ea967f 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -60,11 +60,17 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { } int transformFlags = pureTranslate ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + bool gammaCorrection = true; +#else + bool gammaCorrection = false; +#endif Glop glop; GlopBuilder(renderer->renderState(), renderer->caches(), &glop) .setRoundRectClipState(bakedState->roundRectClipState) .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha) + .setGammaCorrection(gammaCorrection) .setTransform(bakedState->computedState.transform, transformFlags) .setModelViewIdentityEmptyBounds() .build(); @@ -287,24 +293,23 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp // Copy the glyph image, taking the mask format into account switch (format) { case SkMask::kA8_Format: { - uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; // write leading border line memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); // write glyph data if (mGammaTable) { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { + for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; - for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { + for (uint32_t cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { uint8_t tempCol = bitmapBuffer[bY + bX]; cacheBuffer[row + cacheX] = mGammaTable[tempCol]; } cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; } } else { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { + for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index be4fdac5f800..17ad0e36fa90 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -612,7 +612,6 @@ void FrameBuilder::deferBitmapOp(const BitmapOp& op) { && op.bitmap->colorType() != kAlpha_8_SkColorType && hasMergeableClip(*bakedState)) { mergeid_t mergeId = reinterpret_cast(op.bitmap->getGenerationID()); - // TODO: AssetAtlas in mergeId currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId); } else { currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); @@ -687,7 +686,6 @@ void FrameBuilder::deferPatchOp(const PatchOp& op) { && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode && hasMergeableClip(*bakedState)) { mergeid_t mergeId = reinterpret_cast(op.bitmap->getGenerationID()); - // TODO: AssetAtlas in mergeId // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId); diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp index 96cac86386b5..8aff0a24b4f0 100644 --- a/libs/hwui/GammaFontRenderer.cpp +++ b/libs/hwui/GammaFontRenderer.cpp @@ -24,12 +24,13 @@ namespace uirenderer { GammaFontRenderer::GammaFontRenderer() { INIT_LOGD("Creating lookup gamma font renderer"); +#ifndef ANDROID_ENABLE_LINEAR_BLENDING // Compute the gamma tables const float gamma = 1.0f / Properties::textGamma; - for (uint32_t i = 0; i <= 255; i++) { mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f)); } +#endif } void GammaFontRenderer::endPrecaching() { diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index bd27a1a72060..c9cf69bc7d9f 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -18,11 +18,6 @@ #define ANDROID_HWUI_GAMMA_FONT_RENDERER_H #include "FontRenderer.h" -#include "Program.h" - -#include - -#include namespace android { namespace uirenderer { @@ -43,7 +38,11 @@ public: FontRenderer& getFontRenderer() { if (!mRenderer) { - mRenderer.reset(new FontRenderer(&mGammaTable[0])); + const uint8_t* table = nullptr; +#ifndef ANDROID_ENABLE_LINEAR_BLENDING + table = &mGammaTable[0]; +#endif + mRenderer.reset(new FontRenderer(table)); } return *mRenderer; } @@ -64,7 +63,9 @@ public: private: std::unique_ptr mRenderer; +#ifndef ANDROID_ENABLE_LINEAR_BLENDING uint8_t mGammaTable[256]; +#endif }; }; // namespace uirenderer diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 1091736ab9c3..ff88d020030c 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -223,16 +223,16 @@ void GlopBuilder::setFill(int color, float alphaScale, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage, const SkShader* shader, const SkColorFilter* colorFilter) { if (mode != SkXfermode::kClear_Mode) { - float alpha = (SkColorGetA(color) / 255.0f) * alphaScale; if (!shader) { - float colorScale = alpha / 255.0f; - mOutGlop->fill.color = { - colorScale * SkColorGetR(color), - colorScale * SkColorGetG(color), - colorScale * SkColorGetB(color), - alpha - }; + FloatColor c; + c.set(color); + c.r *= alphaScale; + c.g *= alphaScale; + c.b *= alphaScale; + c.a *= alphaScale; + mOutGlop->fill.color = c; } else { + float alpha = (SkColorGetA(color) / 255.0f) * alphaScale; mOutGlop->fill.color = { 1, 1, 1, alpha }; } } else { @@ -276,15 +276,7 @@ void GlopBuilder::setFill(int color, float alphaScale, if (colorFilter->asColorMode(&color, &mode)) { mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend; mDescription.colorMode = mode; - - const float alpha = SkColorGetA(color) / 255.0f; - float colorScale = alpha / 255.0f; - mOutGlop->fill.filter.color = { - colorScale * SkColorGetR(color), - colorScale * SkColorGetG(color), - colorScale * SkColorGetB(color), - alpha, - }; + mOutGlop->fill.filter.color.set(color); } else if (colorFilter->asColorMatrix(srcColorMatrix)) { mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix; @@ -297,10 +289,10 @@ void GlopBuilder::setFill(int color, float alphaScale, // Skia uses the range [0..255] for the addition vector, but we need // the [0..1] range to apply the vector in GLSL float* colorVector = mOutGlop->fill.filter.matrix.vector; - colorVector[0] = srcColorMatrix[4] / 255.0f; - colorVector[1] = srcColorMatrix[9] / 255.0f; - colorVector[2] = srcColorMatrix[14] / 255.0f; - colorVector[3] = srcColorMatrix[19] / 255.0f; + colorVector[0] = EOCF_sRGB(srcColorMatrix[4] / 255.0f); + colorVector[1] = EOCF_sRGB(srcColorMatrix[9] / 255.0f); + colorVector[2] = EOCF_sRGB(srcColorMatrix[14] / 255.0f); + colorVector[3] = EOCF_sRGB(srcColorMatrix[19] / 255.0f); } else { LOG_ALWAYS_FATAL("unsupported ColorFilter"); } @@ -481,6 +473,13 @@ GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& text return *this; } +GlopBuilder& GlopBuilder::setGammaCorrection(bool enabled) { + REQUIRE_STAGES(kFillStage); + + mDescription.hasGammaCorrection = enabled; + return *this; +} + //////////////////////////////////////////////////////////////////////////////// // Transform //////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 11524615074e..1f3b53abdb29 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -105,6 +105,8 @@ public: GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState); + GlopBuilder& setGammaCorrection(bool enabled); + void build(); static void dump(const Glop& glop); diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index c8f5e9435594..8573ab078aaf 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -67,7 +67,8 @@ GradientCache::GradientCache(Extensions& extensions) , mSize(0) , mMaxSize(Properties::gradientCacheSize) , mUseFloatTexture(extensions.hasFloatTextures()) - , mHasNpot(extensions.hasNPot()){ + , mHasNpot(extensions.hasNPot()) + , mHasSRGB(extensions.hasSRGB()) { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); mCache.setOnEntryRemovedListener(this); @@ -176,71 +177,50 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, size_t GradientCache::bytesPerPixel() const { // We use 4 channels (RGBA) - return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); -} - -void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const { - outColor.r = (inColor >> 16) & 0xff; - outColor.g = (inColor >> 8) & 0xff; - outColor.b = (inColor >> 0) & 0xff; - outColor.a = (inColor >> 24) & 0xff; + return 4 * (mUseFloatTexture ? /* fp16 */ 2 : sizeof(uint8_t)); } -void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const { - outColor.r = ((inColor >> 16) & 0xff) / 255.0f; - outColor.g = ((inColor >> 8) & 0xff) / 255.0f; - outColor.b = ((inColor >> 0) & 0xff) / 255.0f; - outColor.a = ((inColor >> 24) & 0xff) / 255.0f; +size_t GradientCache::sourceBytesPerPixel() const { + // We use 4 channels (RGBA) and upload from floats (not half floats) + return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); } -void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount, +void GradientCache::mixBytes(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; - const float alpha = start.a * oppAmount + end.a * amount; - const float a = alpha / 255.0f; - - *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount)); - *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount)); - *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount)); - *dst++ = uint8_t(alpha); + *dst++ = uint8_t(OECF_sRGB((start.r * oppAmount + end.r * amount) * 255.0f)); + *dst++ = uint8_t(OECF_sRGB((start.g * oppAmount + end.g * amount) * 255.0f)); + *dst++ = uint8_t(OECF_sRGB((start.b * oppAmount + end.b * amount) * 255.0f)); + *dst++ = uint8_t( (start.a * oppAmount + end.a * amount) * 255.0f); } -void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount, +void GradientCache::mixFloats(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; - const float a = start.a * oppAmount + end.a * amount; - float* d = (float*) dst; - *d++ = a * (start.r * oppAmount + end.r * amount); - *d++ = a * (start.g * oppAmount + end.g * amount); - *d++ = a * (start.b * oppAmount + end.b * amount); - *d++ = a; - + *d++ = start.r * oppAmount + end.r * amount; + *d++ = start.g * oppAmount + end.g * amount; + *d++ = start.b * oppAmount + end.b * amount; + *d++ = start.a * oppAmount + end.a * amount; dst += 4 * sizeof(float); } void GradientCache::generateTexture(uint32_t* colors, float* positions, const uint32_t width, const uint32_t height, Texture* texture) { - const GLsizei rowBytes = width * bytesPerPixel(); + const GLsizei rowBytes = width * sourceBytesPerPixel(); uint8_t pixels[rowBytes * height]; - static ChannelSplitter gSplitters[] = { - &android::uirenderer::GradientCache::splitToBytes, - &android::uirenderer::GradientCache::splitToFloats, - }; - ChannelSplitter split = gSplitters[mUseFloatTexture]; - static ChannelMixer gMixers[] = { - &android::uirenderer::GradientCache::mixBytes, - &android::uirenderer::GradientCache::mixFloats, + &android::uirenderer::GradientCache::mixBytes, // colors are stored gamma-encoded + &android::uirenderer::GradientCache::mixFloats, // colors are stored in linear }; ChannelMixer mix = gMixers[mUseFloatTexture]; - GradientColor start; - (this->*split)(colors[0], start); + FloatColor start; + start.set(colors[0]); - GradientColor end; - (this->*split)(colors[1], end); + FloatColor end; + end.set(colors[1]); int currentPos = 1; float startPos = positions[0]; @@ -255,7 +235,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, currentPos++; - (this->*split)(colors[currentPos], end); + end.set(colors[currentPos]); distance = positions[currentPos] - startPos; } @@ -266,10 +246,10 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, memcpy(pixels + rowBytes, pixels, rowBytes); if (mUseFloatTexture) { - // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels); } else { - texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + GLint internalFormat = mHasSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA; + texture->upload(internalFormat, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); } texture->setFilter(GL_LINEAR); diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h index 49be19ab1c81..3fd8e80dd64b 100644 --- a/libs/hwui/GradientCache.h +++ b/libs/hwui/GradientCache.h @@ -26,6 +26,8 @@ #include #include +#include "FloatColor.h" + namespace android { namespace uirenderer { @@ -150,25 +152,13 @@ private: void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info); size_t bytesPerPixel() const; + size_t sourceBytesPerPixel() const; - struct GradientColor { - float r; - float g; - float b; - float a; - }; - - typedef void (GradientCache::*ChannelSplitter)(uint32_t inColor, - GradientColor& outColor) const; - - void splitToBytes(uint32_t inColor, GradientColor& outColor) const; - void splitToFloats(uint32_t inColor, GradientColor& outColor) const; - - typedef void (GradientCache::*ChannelMixer)(GradientColor& start, GradientColor& end, + typedef void (GradientCache::*ChannelMixer)(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const; - void mixBytes(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const; - void mixFloats(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const; + void mixBytes(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const; + void mixFloats(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const; LruCache mCache; @@ -178,6 +168,7 @@ private: GLint mMaxTextureSize; bool mUseFloatTexture; bool mHasNpot; + bool mHasSRGB; mutable Mutex mLock; }; // class GradientCache diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index c688a96aa8cd..01650ef5745f 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -75,7 +75,7 @@ public: } void setSize(uint32_t width, uint32_t height) { - texture.updateSize(width, height, texture.format()); + texture.updateSize(width, height, texture.internalFormat(), texture.format()); } inline void setBlend(bool blend) { diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index a6c281dc9839..52c62cc34670 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -225,17 +225,15 @@ void PatchCache::setupMesh(Patch* newMesh) { static const UvMapper sIdentity; -const Patch* PatchCache::get(const AssetAtlas::Entry* entry, - const uint32_t bitmapWidth, const uint32_t bitmapHeight, +const Patch* PatchCache::get( const uint32_t bitmapWidth, const uint32_t bitmapHeight, const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); const Patch* mesh = mCache.get(description); if (!mesh) { - const UvMapper& mapper = entry ? entry->uvMapper : sIdentity; Patch* newMesh = new Patch(bitmapWidth, bitmapHeight, - pixelWidth, pixelHeight, mapper, patch); + pixelWidth, pixelHeight, sIdentity, patch); if (newMesh->vertices) { setupMesh(newMesh); diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 6e6a730ffe2b..0624c355332c 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -22,7 +22,6 @@ #include -#include "AssetAtlas.h" #include "Debug.h" #include "utils/Pair.h" @@ -54,8 +53,7 @@ public: explicit PatchCache(RenderState& renderState); ~PatchCache(); - const Patch* get(const AssetAtlas::Entry* entry, - const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch); void clear(); diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index e5200a516777..f5beb62a84ed 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -85,6 +85,8 @@ namespace uirenderer { #define PROGRAM_HAS_DEBUG_HIGHLIGHT 42 #define PROGRAM_HAS_ROUND_RECT_CLIP 43 +#define PROGRAM_HAS_GAMMA_CORRECTION 44 + /////////////////////////////////////////////////////////////////////////////// // Types /////////////////////////////////////////////////////////////////////////////// @@ -158,6 +160,8 @@ struct ProgramDescription { bool hasDebugHighlight; bool hasRoundRectClip; + bool hasGammaCorrection; + /** * Resets this description. All fields are reset back to the default * values they hold after building a new instance. @@ -196,6 +200,8 @@ struct ProgramDescription { hasDebugHighlight = false; hasRoundRectClip = false; + + hasGammaCorrection = false; } /** @@ -262,6 +268,7 @@ struct ProgramDescription { 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; return key; } diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 59225e108ac7..315c60eccb3e 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -17,8 +17,8 @@ #include #include "Caches.h" -#include "Dither.h" #include "ProgramCache.h" +#include "Properties.h" namespace android { namespace uirenderer { @@ -69,22 +69,16 @@ const char* gVS_Header_Varyings_HasBitmap = "varying highp vec2 outBitmapTexCoords;\n"; const char* gVS_Header_Varyings_HasGradient[6] = { // Linear - "varying highp vec2 linear;\n" - "varying vec2 ditherTexCoords;\n", - "varying float linear;\n" - "varying vec2 ditherTexCoords;\n", + "varying highp vec2 linear;\n", + "varying float linear;\n", // Circular - "varying highp vec2 circular;\n" - "varying vec2 ditherTexCoords;\n", - "varying highp vec2 circular;\n" - "varying vec2 ditherTexCoords;\n", + "varying highp vec2 circular;\n", + "varying highp vec2 circular;\n", // Sweep - "varying highp vec2 sweep;\n" - "varying vec2 ditherTexCoords;\n", - "varying highp vec2 sweep;\n" - "varying vec2 ditherTexCoords;\n", + "varying highp vec2 sweep;\n", + "varying highp vec2 sweep;\n", }; const char* gVS_Header_Varyings_HasRoundRectClip = "varying highp vec2 roundRectPos;\n"; @@ -98,22 +92,16 @@ const char* gVS_Main_OutTransformedTexCoords = " outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n"; const char* gVS_Main_OutGradient[6] = { // Linear - " linear = vec2((screenSpace * position).x, 0.5);\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", - " linear = (screenSpace * position).x;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", + " linear = vec2((screenSpace * position).x, 0.5);\n", + " linear = (screenSpace * position).x;\n", // Circular - " circular = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", - " circular = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", + " circular = (screenSpace * position).xy;\n", + " circular = (screenSpace * position).xy;\n", // Sweep + " sweep = (screenSpace * position).xy;\n", " sweep = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", - " sweep = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", }; const char* gVS_Main_OutBitmapTexCoords = " outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; @@ -147,12 +135,11 @@ const char* gFS_Uniforms_TextureSampler = "uniform sampler2D baseSampler;\n"; const char* gFS_Uniforms_ExternalTextureSampler = "uniform samplerExternalOES baseSampler;\n"; -const char* gFS_Uniforms_Dither = - "uniform sampler2D ditherSampler;"; const char* gFS_Uniforms_GradientSampler[2] = { - "%s\n" + "uniform vec2 screenSize;\n" "uniform sampler2D gradientSampler;\n", - "%s\n" + + "uniform vec2 screenSize;\n" "uniform vec4 startColor;\n" "uniform vec4 endColor;\n" }; @@ -172,18 +159,51 @@ const char* gFS_Uniforms_HasRoundRectClip = "uniform vec4 roundRectInnerRectLTRB;\n" "uniform float roundRectRadius;\n"; +// Dithering must be done in the quantization space +// When we are writing to an sRGB framebuffer, we must do the following: +// EOCF(OECF(color) + dither) +// We approximate the transfer functions with gamma 2.0 to avoid branches and pow() +// The dithering pattern is generated with a triangle noise generator in the range [-0.5,1.5[ +// TODO: Handle linear fp16 render targets +const char* gFS_Dither_Functions = + "\nmediump float triangleNoise(const highp vec2 n) {\n" + " highp vec2 p = fract(n * vec2(5.3987, 5.4421));\n" + " p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));\n" + " highp float xy = p.x * p.y;\n" + " return fract(xy * 95.4307) + fract(xy * 75.04961) - 0.5;\n" + "}\n"; +const char* gFS_Dither_Preamble[2] = { + // Linear framebuffer + "\nvec4 dither(const vec4 color) {\n" + " return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);" + "}\n", + // sRGB framebuffer + "\nvec4 dither(const vec4 color) {\n" + " vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0);\n" + " return vec4(dithered * dithered, color.a);\n" + "}\n" +}; + +// Uses luminance coefficients from Rec.709 to choose the appropriate gamma +// The gamma() function assumes that bright text will be displayed on a dark +// background and that dark text will be displayed on bright background +// The gamma coefficient is chosen to thicken or thin the text accordingly +// The dot product used to compute the luminance could be approximated with +// a simple max(color.r, color.g, color.b) +const char* gFS_Gamma_Preamble = + "\n#define GAMMA (%.2f)\n" + "#define GAMMA_INV (%.2f)\n" + "\nfloat gamma(float a, const vec3 color) {\n" + " float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n" + " return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);\n" + "}\n"; + const char* gFS_Main = "\nvoid main(void) {\n" - " lowp vec4 fragColor;\n"; + " vec4 fragColor;\n"; -const char* gFS_Main_Dither[2] = { - // ES 2.0 - "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE), - // ES 3.0 - "texture2D(ditherSampler, ditherTexCoords).a" -}; -const char* gFS_Main_AddDitherToGradient = - " gradientColor += %s;\n"; +const char* gFS_Main_AddDither = + " fragColor = dither(fragColor);\n"; // Fast cases const char* gFS_Fast_SingleColor = @@ -202,24 +222,32 @@ const char* gFS_Fast_SingleA8Texture = "\nvoid main(void) {\n" " gl_FragColor = texture2D(baseSampler, outTexCoords);\n" "}\n\n"; +const char* gFS_Fast_SingleA8Texture_ApplyGamma = + "\nvoid main(void) {\n" + " gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, GAMMA));\n" + "}\n\n"; const char* gFS_Fast_SingleModulateA8Texture = "\nvoid main(void) {\n" " gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n" "}\n\n"; +const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma = + "\nvoid main(void) {\n" + " gl_FragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n" + "}\n\n"; const char* gFS_Fast_SingleGradient[2] = { "\nvoid main(void) {\n" - " gl_FragColor = %s + texture2D(gradientSampler, linear);\n" + " gl_FragColor = dither(texture2D(gradientSampler, linear));\n" "}\n\n", "\nvoid main(void) {\n" - " gl_FragColor = %s + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" + " gl_FragColor = dither(mix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n" "}\n\n", }; const char* gFS_Fast_SingleModulateGradient[2] = { "\nvoid main(void) {\n" - " gl_FragColor = %s + color.a * texture2D(gradientSampler, linear);\n" + " gl_FragColor = dither(color.a * texture2D(gradientSampler, linear));\n" "}\n\n", "\nvoid main(void) {\n" - " gl_FragColor = %s + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" + " gl_FragColor = dither(color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n" "}\n\n" }; @@ -239,11 +267,13 @@ const char* gFS_Main_FetchTexture[2] = { // Modulate " fragColor = color * texture2D(baseSampler, outTexCoords);\n" }; -const char* gFS_Main_FetchA8Texture[2] = { +const char* gFS_Main_FetchA8Texture[4] = { // Don't modulate " fragColor = texture2D(baseSampler, outTexCoords);\n", + " fragColor = texture2D(baseSampler, outTexCoords);\n", // Modulate " fragColor = color * texture2D(baseSampler, outTexCoords).a;\n", + " fragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n", }; const char* gFS_Main_FetchGradient[6] = { // Linear @@ -271,29 +301,38 @@ const char* gFS_Main_BlendShadersBG = " fragColor = blendShaders(gradientColor, bitmapColor)"; const char* gFS_Main_BlendShadersGB = " fragColor = blendShaders(bitmapColor, gradientColor)"; -const char* gFS_Main_BlendShaders_Modulate[3] = { +const char* gFS_Main_BlendShaders_Modulate[6] = { // Don't modulate ";\n", + ";\n", // Modulate " * color.a;\n", + " * color.a;\n", // Modulate with alpha 8 texture " * texture2D(baseSampler, outTexCoords).a;\n", + " * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n", }; -const char* gFS_Main_GradientShader_Modulate[3] = { +const char* gFS_Main_GradientShader_Modulate[6] = { // Don't modulate " fragColor = gradientColor;\n", + " fragColor = gradientColor;\n", // Modulate " fragColor = gradientColor * color.a;\n", + " fragColor = gradientColor * color.a;\n", // Modulate with alpha 8 texture " fragColor = gradientColor * texture2D(baseSampler, outTexCoords).a;\n", + " fragColor = gradientColor * gamma(texture2D(baseSampler, outTexCoords).a, gradientColor.rgb);\n", }; -const char* gFS_Main_BitmapShader_Modulate[3] = { +const char* gFS_Main_BitmapShader_Modulate[6] = { // Don't modulate " fragColor = bitmapColor;\n", + " fragColor = bitmapColor;\n", // Modulate " fragColor = bitmapColor * color.a;\n", + " fragColor = bitmapColor * color.a;\n", // Modulate with alpha 8 texture " fragColor = bitmapColor * texture2D(baseSampler, outTexCoords).a;\n", + " fragColor = bitmapColor * gamma(texture2D(baseSampler, outTexCoords).a, bitmapColor.rgb);\n", }; const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; @@ -385,7 +424,8 @@ const char* gBlendOps[18] = { /////////////////////////////////////////////////////////////////////////////// ProgramCache::ProgramCache(Extensions& extensions) - : mHasES3(extensions.getMajorGlVersion() >= 3) { + : mHasES3(extensions.getMajorGlVersion() >= 3) + , mHasSRGB(extensions.hasSRGB()) { } ProgramCache::~ProgramCache() { @@ -518,6 +558,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description static bool shaderOp(const ProgramDescription& description, String8& shader, const int modulateOp, const char** snippets) { int op = description.hasAlpha8Texture ? MODULATE_OP_MODULATE_A8 : modulateOp; + op = op * 2 + description.hasGammaCorrection; shader.append(snippets[op]); return description.hasAlpha8Texture; } @@ -570,13 +611,16 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gFS_Uniforms_ExternalTextureSampler); } if (description.hasGradient) { - shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient], - gFS_Uniforms_Dither); + shader.append(gFS_Uniforms_GradientSampler[description.isSimpleGradient]); } if (description.hasRoundRectClip) { shader.append(gFS_Uniforms_HasRoundRectClip); } + if (description.hasGammaCorrection) { + shader.appendFormat(gFS_Gamma_Preamble, Properties::textGamma, 1.0f / Properties::textGamma); + } + // Optimization for common cases if (!description.hasVertexAlpha && !blendFramebuffer @@ -607,18 +651,26 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti fast = true; } else if (singleA8Texture) { if (!description.modulate) { - shader.append(gFS_Fast_SingleA8Texture); + if (description.hasGammaCorrection) { + shader.append(gFS_Fast_SingleA8Texture_ApplyGamma); + } else { + shader.append(gFS_Fast_SingleA8Texture); + } } else { - shader.append(gFS_Fast_SingleModulateA8Texture); + if (description.hasGammaCorrection) { + shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma); + } else { + shader.append(gFS_Fast_SingleModulateA8Texture); + } } fast = true; } else if (singleGradient) { + shader.append(gFS_Dither_Functions); + shader.append(gFS_Dither_Preamble[mHasSRGB]); if (!description.modulate) { - shader.appendFormat(gFS_Fast_SingleGradient[description.isSimpleGradient], - gFS_Main_Dither[mHasES3]); + shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]); } else { - shader.appendFormat(gFS_Fast_SingleModulateGradient[description.isSimpleGradient], - gFS_Main_Dither[mHasES3]); + shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]); } fast = true; } @@ -652,6 +704,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.isBitmapNpot) { generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); } + if (description.hasGradient) { + shader.append(gFS_Dither_Functions); + shader.append(gFS_Dither_Preamble[mHasSRGB]); + } // Begin the shader shader.append(gFS_Main); { @@ -659,7 +715,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.hasTexture || description.hasExternalTexture) { if (description.hasAlpha8Texture) { if (!description.hasGradient && !description.hasBitmap) { - shader.append(gFS_Main_FetchA8Texture[modulateOp]); + shader.append( + gFS_Main_FetchA8Texture[modulateOp * 2 + description.hasGammaCorrection]); } } else { shader.append(gFS_Main_FetchTexture[modulateOp]); @@ -671,7 +728,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } if (description.hasGradient) { shader.append(gFS_Main_FetchGradient[gradientIndex(description)]); - shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]); } if (description.hasBitmap) { if (!description.isBitmapNpot) { @@ -715,6 +771,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } } + if (description.hasGradient) { + shader.append(gFS_Main_AddDither); + } + // Output the fragment if (!blendFramebuffer) { shader.append(gFS_Main_FragColor); diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h index 9ac885b665e7..292ecdfdc2b6 100644 --- a/libs/hwui/ProgramCache.h +++ b/libs/hwui/ProgramCache.h @@ -59,6 +59,7 @@ private: std::map> mCache; const bool mHasES3; + const bool mHasSRGB; }; // class ProgramCache }; // namespace uirenderer diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index eedc9e7d5333..92a49d28c408 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -203,7 +203,7 @@ enum DebugLevel { #define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width" #define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height" -// Gamma (>= 1.0, <= 10.0) +// Gamma (>= 1.0, <= 3.0) #define PROPERTY_TEXT_GAMMA "hwui.text_gamma" /////////////////////////////////////////////////////////////////////////////// @@ -222,7 +222,7 @@ enum DebugLevel { #define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f -#define DEFAULT_TEXT_GAMMA 1.4f +#define DEFAULT_TEXT_GAMMA 1.45f // Match design tools // cap to 256 to limite paths in the path cache #define DEFAULT_PATH_TEXTURE_CAP 256 diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp index 6ba0ab59a88c..2a03e6a3ebc5 100644 --- a/libs/hwui/PropertyValuesHolder.cpp +++ b/libs/hwui/PropertyValuesHolder.cpp @@ -16,6 +16,7 @@ #include "PropertyValuesHolder.h" +#include "utils/Color.h" #include "utils/VectorDrawableUtils.h" #include @@ -25,18 +26,26 @@ namespace uirenderer { using namespace VectorDrawable; -inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { - return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); +inline constexpr float lerp(float fromValue, float toValue, float fraction) { + return float (fromValue * (1 - fraction) + toValue * fraction); +} + +inline constexpr float linearize(U8CPU component) { + return EOCF_sRGB(component / 255.0f); } // TODO: Add a test for this void ColorEvaluator::evaluate(SkColor* outColor, const SkColor& fromColor, const SkColor& toColor, float fraction) const { - U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); - U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); - U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); - U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); - *outColor = SkColorSetARGB(alpha, red, green, blue); + float a = lerp(SkColorGetA(fromColor) / 255.0f, SkColorGetA(toColor) / 255.0f, fraction); + float r = lerp(linearize(SkColorGetR(fromColor)), linearize(SkColorGetR(toColor)), fraction); + float g = lerp(linearize(SkColorGetG(fromColor)), linearize(SkColorGetG(toColor)), fraction); + float b = lerp(linearize(SkColorGetB(fromColor)), linearize(SkColorGetB(toColor)), fraction); + *outColor = SkColorSetARGB( + (U8CPU) roundf(a * 255.0f), + (U8CPU) roundf(OECF_sRGB(r) * 255.0f), + (U8CPU) roundf(OECF_sRGB(g) * 255.0f), + (U8CPU) roundf(OECF_sRGB(b) * 255.0f)); } void PathEvaluator::evaluate(PathData* out, diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index ddca122788d1..22c6dfc6b55a 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -197,7 +197,7 @@ CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread, Texture sourceTexture(caches); sourceTexture.wrap(sourceTexId, - sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */); + sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0, 0 /* total lie */); CopyResult copyResult = copyTextureInto(caches, renderThread.renderState(), sourceTexture, texTransform, srcRect, bitmap); diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index a65c22c3a555..ebc41b18ed8c 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -216,7 +216,6 @@ struct BitmapOp : RecordedOp { : SUPER(BitmapOp) , bitmap(bitmap) {} const SkBitmap* bitmap; - // TODO: asset atlas/texture id lookup? }; struct BitmapMeshOp : RecordedOp { diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 6f4a6839be4e..9838df2f6013 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -177,11 +177,12 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode outData->endColor.set(gradInfo.fColors[1]); } - outData->ditherSampler = (*textureUnit)++; return true; } -void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data) { +void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data, + const GLsizei width, const GLsizei height) { + if (CC_UNLIKELY(data.gradientTexture)) { caches.textureState().activateTexture(data.gradientSampler); bindTexture(&caches, data.gradientTexture, data.wrapST, data.wrapST); @@ -191,10 +192,7 @@ void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& dat bindUniformColor(caches.program().getUniform("endColor"), data.endColor); } - // TODO: remove sampler slot incrementing from dither.setupProgram, - // since this assignment of slots is done at store, not apply time - GLuint ditherSampler = data.ditherSampler; - caches.dither.setupProgram(caches.program(), &ditherSampler); + glUniform2f(caches.program().getUniform("screenSize"), 1.0f / width, 1.0f / height); glUniformMatrix4fv(caches.program().getUniform("screenSpace"), 1, GL_FALSE, &data.screenSpace.data[0]); } @@ -208,13 +206,7 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model return false; } - /* - * Bypass the AssetAtlas, since those textures: - * 1) require UV mapping, which isn't implemented in matrix computation below - * 2) can't handle REPEAT simply - * 3) are safe to upload here (outside of sync stage), since they're static - */ - outData->bitmapTexture = caches.textureCache.getAndBypassAtlas(&bitmap); + outData->bitmapTexture = caches.textureCache.get(&bitmap); if (!outData->bitmapTexture) return false; outData->bitmapSampler = (*textureUnit)++; @@ -388,11 +380,12 @@ void SkiaShader::store(Caches& caches, const SkShader& shader, const Matrix4& mo outData->skiaShaderType = kNone_SkiaShaderType; } -void SkiaShader::apply(Caches& caches, const SkiaShaderData& data) { +void SkiaShader::apply(Caches& caches, const SkiaShaderData& data, + const GLsizei width, const GLsizei height) { if (!data.skiaShaderType) return; if (data.skiaShaderType & kGradient_SkiaShaderType) { - applyGradient(caches, data.gradientData); + applyGradient(caches, data.gradientData, width, height); } if (data.skiaShaderType & kBitmap_SkiaShaderType) { applyBitmap(caches, data.bitmapData); diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index 884196d9fc62..5854289f49a7 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -62,7 +62,6 @@ struct SkiaShaderData { } bitmapData; struct GradientShaderData { Matrix4 screenSpace; - GLuint ditherSampler; // simple gradient FloatColor startColor; @@ -72,7 +71,6 @@ struct SkiaShaderData { Texture* gradientTexture; GLuint gradientSampler; GLenum wrapST; - } gradientData; struct LayerShaderData { Layer* layer; @@ -90,7 +88,8 @@ public: static void store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, GLuint* textureUnit, ProgramDescription* description, SkiaShaderData* outData); - static void apply(Caches& caches, const SkiaShaderData& data); + static void apply(Caches& caches, const SkiaShaderData& data, + const GLsizei width, const GLsizei height); }; }; // namespace uirenderer diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 4f49a3518be0..908f57265906 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -26,19 +26,23 @@ namespace android { namespace uirenderer { +// Number of bytes used by a texture in the given format static int bytesPerPixel(GLint glFormat) { switch (glFormat) { // The wrapped-texture case, usually means a SurfaceTexture case 0: return 0; + case GL_LUMINANCE: case GL_ALPHA: return 1; + case GL_SRGB8: case GL_RGB: return 3; + case GL_SRGB8_ALPHA8: case GL_RGBA: return 4; case GL_RGBA16F: - return 16; + return 8; default: LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat); } @@ -83,14 +87,16 @@ void Texture::deleteTexture() { mId = 0; } -bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) { - if (mWidth == width && mHeight == height && mFormat == format) { +bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) { + if (mWidth == width && mHeight == height && + mFormat == format && mInternalFormat == internalFormat) { return false; } mWidth = width; mHeight = height; mFormat = format; - notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat)); + mInternalFormat = internalFormat; + notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat)); return true; } @@ -101,10 +107,10 @@ void Texture::resetCachedParams() { mMagFilter = GL_LINEAR; } -void Texture::upload(GLint internalformat, uint32_t width, uint32_t height, +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); + bool needsAlloc = updateSize(width, height, internalFormat, format); if (!mId) { glGenTextures(1, &mId); needsAlloc = true; @@ -112,17 +118,17 @@ void Texture::upload(GLint internalformat, uint32_t width, uint32_t height, } mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); if (needsAlloc) { - glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); } else if (pixels) { - glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); } GL_CHECKPOINT(MODERATE); } -static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp, - GLsizei width, GLsizei height, const GLvoid * data) { +static void uploadToTexture(bool resize, GLint internalFormat, GLenum format, GLenum type, + GLsizei stride, GLsizei bpp, GLsizei width, GLsizei height, const GLvoid * data) { const bool useStride = stride != width && Caches::getInstance().extensions().hasUnpackRowLength(); @@ -132,7 +138,7 @@ static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei str } if (resize) { - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); } @@ -156,7 +162,7 @@ static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei str } if (resize) { - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, temp); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); } @@ -166,31 +172,44 @@ static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei str } static void uploadSkBitmapToTexture(const SkBitmap& bitmap, - bool resize, GLenum format, GLenum type) { - uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(), - bitmap.width(), bitmap.height(), bitmap.getPixels()); + bool resize, GLint internalFormat, GLenum format, GLenum type) { + uploadToTexture(resize, internalFormat, format, type, bitmap.rowBytesAsPixels(), + bitmap.bytesPerPixel(), bitmap.width(), bitmap.height(), bitmap.getPixels()); } -static void colorTypeToGlFormatAndType(SkColorType colorType, - GLint* outFormat, GLint* outType) { +static void colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, + bool needSRGB, GLint* outInternalFormat, GLint* outFormat, GLint* outType) { switch (colorType) { case kAlpha_8_SkColorType: *outFormat = GL_ALPHA; + *outInternalFormat = GL_ALPHA; *outType = GL_UNSIGNED_BYTE; break; case kRGB_565_SkColorType: - *outFormat = GL_RGB; - *outType = GL_UNSIGNED_SHORT_5_6_5; + if (needSRGB) { + // We would ideally use a GL_RGB/GL_SRGB8 texture but the + // intermediate Skia bitmap needs to be ARGB_8888 + *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(); + *outType = GL_UNSIGNED_BYTE; + } else { + *outFormat = GL_RGB; + *outInternalFormat = GL_RGB; + *outType = GL_UNSIGNED_SHORT_5_6_5; + } break; // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 case kARGB_4444_SkColorType: case kIndex_8_SkColorType: case kN32_SkColorType: *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(needSRGB); *outType = GL_UNSIGNED_BYTE; break; case kGray_8_SkColorType: + // TODO: Handle sRGB *outFormat = GL_LUMINANCE; + *outInternalFormat = GL_LUMINANCE; *outType = GL_UNSIGNED_BYTE; break; default: @@ -224,29 +243,36 @@ void Texture::upload(const SkBitmap& bitmap) { setDefaultParams = true; } - GLint format, type; - colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type); + sk_sp sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + bool needSRGB = bitmap.colorSpace() == sRGB.get(); - if (updateSize(bitmap.width(), bitmap.height(), format)) { + GLint internalFormat, format, type; + colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type); + + if (updateSize(bitmap.width(), bitmap.height(), internalFormat, format)) { needsAlloc = true; } blend = !bitmap.isOpaque(); mCaches.textureState().bindTexture(mId); + // TODO: Handle sRGB gray bitmaps + bool hasSRGB = mCaches.extensions().hasSRGB(); if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType - || bitmap.colorType() == kIndex_8_SkColorType)) { + || bitmap.colorType() == kIndex_8_SkColorType + || (bitmap.colorType() == kRGB_565_SkColorType && hasSRGB && needSRGB))) { + SkBitmap rgbaBitmap; - rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight, - bitmap.alphaType())); + rgbaBitmap.allocPixels(SkImageInfo::MakeN32( + mWidth, mHeight, bitmap.alphaType(), hasSRGB ? sRGB : nullptr)); rgbaBitmap.eraseColor(0); SkCanvas canvas(rgbaBitmap); canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); - uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type); + uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, internalFormat, format, type); } else { - uploadSkBitmapToTexture(bitmap, needsAlloc, format, type); + uploadSkBitmapToTexture(bitmap, needsAlloc, internalFormat, format, type); } if (canMipMap) { @@ -262,11 +288,12 @@ void Texture::upload(const SkBitmap& bitmap) { } } -void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) { +void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format) { mId = id; mWidth = width; mHeight = height; mFormat = format; + mInternalFormat = internalFormat; // We're wrapping an existing texture, so don't double count this memory notifySizeChanged(0); } diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index b72742f45654..aa8a6d3fae82 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -69,8 +69,8 @@ public: * * The image data is undefined after calling this. */ - void resize(uint32_t width, uint32_t height, GLint format) { - upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr); + void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) { + upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr); } /** @@ -85,13 +85,13 @@ public: /** * Basically glTexImage2D/glTexSubImage2D. */ - void upload(GLint internalformat, uint32_t width, uint32_t height, + void upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, GLenum type, const void* pixels); /** * Wraps an existing texture. */ - void wrap(GLuint id, uint32_t width, uint32_t height, GLint format); + void wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format); GLuint id() const { return mId; @@ -109,6 +109,10 @@ public: return mFormat; } + GLint internalFormat() const { + return mInternalFormat; + } + /** * Generation of the backing bitmap, */ @@ -148,13 +152,14 @@ private: friend class Layer; // Returns true if the size changed, false if it was the same - bool updateSize(uint32_t width, uint32_t height, GLint format); + bool updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format); void resetCachedParams(); GLuint mId = 0; uint32_t mWidth = 0; uint32_t mHeight = 0; GLint mFormat = 0; + GLint mInternalFormat = 0; /* See GLES spec section 3.8.14 * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 523924af5ef1..5ccdbda67e74 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -18,7 +18,6 @@ #include -#include "AssetAtlas.h" #include "Caches.h" #include "Texture.h" #include "TextureCache.h" @@ -36,8 +35,7 @@ TextureCache::TextureCache() : mCache(LruCache::kUnlimitedCapacity) , mSize(0) , mMaxSize(Properties::textureCacheSize) - , mFlushRate(Properties::textureCacheFlushRate) - , mAssetAtlas(nullptr) { + , mFlushRate(Properties::textureCacheFlushRate) { mCache.setOnEntryRemovedListener(this); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); @@ -84,10 +82,6 @@ void TextureCache::operator()(uint32_t&, Texture*& texture) { // Caching /////////////////////////////////////////////////////////////////////////////// -void TextureCache::setAssetAtlas(AssetAtlas* assetAtlas) { - mAssetAtlas = assetAtlas; -} - void TextureCache::resetMarkInUse(void* ownerToken) { LruCache::Iterator iter(mCache); while (iter.next()) { @@ -108,14 +102,7 @@ bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) { // Returns a prepared Texture* that either is already in the cache or can fit // in the cache (and is thus added to the cache) -Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { - if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) { - AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef()); - if (CC_UNLIKELY(entry)) { - return entry->texture; - } - } - +Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) { Texture* texture = mCache.get(bitmap->pixelRef()->getStableID()); if (!texture) { @@ -160,7 +147,7 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType a } bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) { - Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use); + Texture* texture = getCachedTexture(bitmap); if (texture) { texture->isInUse = ownerToken; } @@ -168,11 +155,11 @@ bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap } bool TextureCache::prefetch(const SkBitmap* bitmap) { - return getCachedTexture(bitmap, AtlasUsageType::Use); + return getCachedTexture(bitmap); } -Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { - Texture* texture = getCachedTexture(bitmap, atlasUsageType); +Texture* TextureCache::get(const SkBitmap* bitmap) { + Texture* texture = getCachedTexture(bitmap); if (!texture) { if (!canMakeTextureFromBitmap(bitmap)) { diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index 0a61b6b1a522..88ef7711e844 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -47,8 +47,6 @@ class Texture; // Classes /////////////////////////////////////////////////////////////////////////////// -class AssetAtlas; - /** * A simple LRU texture cache. The cache has a maximum size expressed in bytes. * Any texture added to the cache causing the cache to grow beyond the maximum @@ -85,20 +83,10 @@ public: bool prefetch(const SkBitmap* bitmap); /** - * Returns the texture associated with the specified bitmap from either within the cache, or - * the AssetAtlas. If the texture cannot be found in the cache, a new texture is generated. - */ - Texture* get(const SkBitmap* bitmap) { - return get(bitmap, AtlasUsageType::Use); - } - - /** - * Returns the texture associated with the specified bitmap. If the texture cannot be found in - * the cache, a new texture is generated, even if it resides in the AssetAtlas. + * Returns the texture associated with the specified bitmap from within the cache. + * If the texture cannot be found in the cache, a new texture is generated. */ - Texture* getAndBypassAtlas(const SkBitmap* bitmap) { - return get(bitmap, AtlasUsageType::Bypass); - } + Texture* get(const SkBitmap* bitmap); /** * Removes the texture associated with the specified pixelRef. This is meant @@ -130,18 +118,10 @@ public: */ void flush(); - void setAssetAtlas(AssetAtlas* assetAtlas); - private: - enum class AtlasUsageType { - Use, - Bypass, - }; - bool canMakeTextureFromBitmap(const SkBitmap* bitmap); - Texture* get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType); - Texture* getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType); + Texture* getCachedTexture(const SkBitmap* bitmap); LruCache mCache; @@ -155,8 +135,6 @@ private: std::vector mGarbage; mutable Mutex mLock; - - AssetAtlas* mAssetAtlas; }; // class TextureCache }; // namespace uirenderer diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 2b7994139641..4e5b9ad2f0a3 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -561,8 +561,12 @@ void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) { bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) { if (!canReuseBitmap(*outCache, width, height)) { - SkImageInfo info = SkImageInfo::Make(width, height, - kN32_SkColorType, kPremul_SkAlphaType); +#ifndef ANDROID_ENABLE_LINEAR_BLENDING + sk_sp colorSpace = nullptr; +#else + sk_sp colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); +#endif + SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); outCache->setInfo(info); // TODO: Count the bitmap cache against app's java heap outCache->allocPixels(info); diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index c1bf980658b2..db982ad0c8f4 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -19,6 +19,7 @@ #include "Vector.h" +#include "FloatColor.h" #include "utils/Macros.h" namespace android { @@ -76,21 +77,19 @@ struct TextureVertex { REQUIRE_COMPATIBLE_LAYOUT(TextureVertex); /** - * Simple structure to describe a vertex with a position, texture UV and ARGB color. + * Simple structure to describe a vertex with a position, texture UV and an + * sRGB color with alpha. The color is stored pre-multiplied in linear space. */ struct ColorTextureVertex { float x, y; float u, v; - float r, g, b, a; + float r, g, b, a; // pre-multiplied linear static inline void set(ColorTextureVertex* vertex, float x, float y, - float u, float v, int color) { - - float a = ((color >> 24) & 0xff) / 255.0f; - float r = a * ((color >> 16) & 0xff) / 255.0f; - float g = a * ((color >> 8) & 0xff) / 255.0f; - float b = a * ((color) & 0xff) / 255.0f; - *vertex = { x, y, u, v, r, g, b, a }; + float u, float v, uint32_t color) { + FloatColor c; + c.set(color); + *vertex = { x, y, u, v, c.r, c.g, c.b, c.a }; } }; // struct ColorTextureVertex diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 49e9f65582ae..e2844ad0649a 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -180,7 +180,12 @@ void CacheTexture::allocatePixelBuffer() { mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight()); } - mTexture.resize(mWidth, mHeight, mFormat); + GLint internalFormat = mFormat; + if (mFormat == GL_RGBA) { + internalFormat = mCaches.rgbaInternalFormat(); + } + + mTexture.resize(mWidth, mHeight, internalFormat, mFormat); mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST); mTexture.setWrap(GL_CLAMP_TO_EDGE); } diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp index 10a26e08f897..a9bbb273dbb5 100644 --- a/libs/hwui/renderstate/OffscreenBufferPool.cpp +++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp @@ -22,6 +22,7 @@ #include "utils/FatVector.h" #include "utils/TraceUtils.h" +#include #include #include @@ -44,7 +45,7 @@ OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches, uint32_t height = computeIdealDimension(viewportHeight); ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height); caches.textureState().activateTexture(0); - texture.resize(width, height, GL_RGBA); + texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA); texture.blend = true; texture.setWrap(GL_CLAMP_TO_EDGE); // not setting filter on texture, since it's set when drawing, based on transform diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index ee4619d2c222..84ab3f31e7ab 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -52,7 +52,6 @@ void RenderState::onGLContextCreated() { mCaches = &Caches::createInstance(*this); } mCaches->init(); - mCaches->textureCache.setAssetAtlas(&mAssetAtlas); } static void layerLostGlContext(Layer* layer) { @@ -64,7 +63,6 @@ void RenderState::onGLContextDestroyed() { // TODO: reset all cached state in state objects std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); - mAssetAtlas.terminate(); mCaches->terminate(); @@ -147,9 +145,17 @@ void RenderState::interruptForFunctorInvoke() { meshState().resetVertexPointers(); meshState().disableTexCoordsVertexArray(); debugOverdraw(false, false); + // TODO: We need a way to know whether the functor is sRGB aware (b/32072673) + if (mCaches->extensions().hasSRGBWriteControl()) { + glDisable(GL_FRAMEBUFFER_SRGB_EXT); + } } void RenderState::resumeFromFunctorInvoke() { + if (mCaches->extensions().hasSRGBWriteControl()) { + glEnable(GL_FRAMEBUFFER_SRGB_EXT); + } + glViewport(0, 0, mViewportWidth, mViewportHeight); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); debugOverdraw(false, false); @@ -308,7 +314,7 @@ void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { glVertexAttribPointer(alphaLocation, 1, GL_FLOAT, GL_FALSE, vertices.stride, alphaCoords); } // Shader uniforms - SkiaShader::apply(*mCaches, fill.skiaShaderData); + SkiaShader::apply(*mCaches, fill.skiaShaderData, mViewportWidth, mViewportHeight); GL_CHECKPOINT(MODERATE); Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ? diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 9e0fb121be65..3d119dc9e290 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,7 +16,6 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_H -#include "AssetAtlas.h" #include "Caches.h" #include "Glop.h" #include "renderstate/Blend.h" @@ -92,7 +91,6 @@ public: void render(const Glop& glop, const Matrix4& orthoMatrix); - AssetAtlas& assetAtlas() { return mAssetAtlas; } Blend& blend() { return *mBlend; } MeshState& meshState() { return *mMeshState; } Scissor& scissor() { return *mScissor; } @@ -120,7 +118,6 @@ private: OffscreenBufferPool mLayerPool; - AssetAtlas mAssetAtlas; std::set mActiveLayers; std::set mRegisteredContexts; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 0f2d55bc209d..fe0f56ad1332 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -545,11 +545,6 @@ DeferredLayerUpdater* CanvasContext::createTextureLayer() { return mRenderPipeline->createTextureLayer(); } -void CanvasContext::setTextureAtlas(RenderThread& thread, - const sp& buffer, int64_t* map, size_t mapSize) { - thread.eglManager().setTextureAtlas(buffer, map, mapSize); -} - void CanvasContext::dumpFrames(int fd) { FILE* file = fdopen(fd, "a"); fprintf(file, "\n\n---PROFILEDATA---\n"); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 652cddd968bb..41b658e083da 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -119,9 +119,6 @@ public: DeferredLayerUpdater* createTextureLayer(); - ANDROID_API static void setTextureAtlas(RenderThread& thread, - const sp& buffer, int64_t* map, size_t mapSize); - void stopDrawing(); void notifyFramePending(); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 86731c9581be..beda0455c145 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -91,9 +91,7 @@ EglManager::EglManager(RenderThread& thread) , mEglConfig(nullptr) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) - , mCurrentSurface(EGL_NO_SURFACE) - , mAtlasMap(nullptr) - , mAtlasMapSize(0) { + , mCurrentSurface(EGL_NO_SURFACE) { } void EglManager::initialize() { @@ -128,7 +126,6 @@ void EglManager::initialize() { makeCurrent(mPBufferSurface); DeviceInfo::initialize(); mRenderThread.renderState().onGLContextCreated(); - initAtlas(); } void EglManager::initExtensions() { @@ -191,32 +188,6 @@ void EglManager::createContext() { "Failed to create context, error = %s", egl_error_str()); } -void EglManager::setTextureAtlas(const sp& buffer, - int64_t* map, size_t mapSize) { - - // Already initialized - if (mAtlasBuffer.get()) { - ALOGW("Multiple calls to setTextureAtlas!"); - delete map; - return; - } - - mAtlasBuffer = buffer; - mAtlasMap = map; - mAtlasMapSize = mapSize; - - if (hasEglContext()) { - initAtlas(); - } -} - -void EglManager::initAtlas() { - if (mAtlasBuffer.get()) { - mRenderThread.renderState().assetAtlas().init(mAtlasBuffer, - mAtlasMap, mAtlasMapSize); - } -} - void EglManager::createPBufferSurface() { LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, "usePBufferSurface() called on uninitialized GlobalContext!"); @@ -229,7 +200,16 @@ void EglManager::createPBufferSurface() { EGLSurface EglManager::createSurface(EGLNativeWindowType window) { initialize(); - EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr); + + EGLint attribs[] = { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, + EGL_COLORSPACE, EGL_COLORSPACE_sRGB, +#endif + EGL_NONE + }; + + EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs); LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, "Failed to create EGLSurface for window %p, eglErr = %s", (void*) window, egl_error_str()); diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 41047fecf960..ba4a3e1c5192 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -79,8 +79,6 @@ public: // Returns true iff the surface is now preserving buffers. bool setPreserveBuffer(EGLSurface surface, bool preserve); - void setTextureAtlas(const sp& buffer, int64_t* map, size_t mapSize); - void fence(); private: @@ -94,7 +92,6 @@ private: void createPBufferSurface(); void loadConfig(); void createContext(); - void initAtlas(); EGLint queryBufferAge(EGLSurface surface); RenderThread& mRenderThread; @@ -106,10 +103,6 @@ private: EGLSurface mCurrentSurface; - sp mAtlasBuffer; - int64_t* mAtlasMap; - size_t mAtlasMapSize; - enum class SwapBehavior { Discard, Preserved, diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index dcbc980763ec..c2ed8643c0ad 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -480,23 +480,6 @@ void RenderProxy::dumpGraphicsMemory(int fd) { staticPostAndWait(task); } -CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, - size_t size) { - CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size); - args->buffer->decStrong(nullptr); - return nullptr; -} - -void RenderProxy::setTextureAtlas(const sp& buffer, int64_t* map, size_t size) { - SETUP_TASK(setTextureAtlas); - args->thread = &mRenderThread; - args->buffer = buffer.get(); - args->buffer->incStrong(nullptr); - args->map = map; - args->size = size; - post(task); -} - CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) { args->thread->jankTracker().switchStorageToAshmem(args->fd); close(args->fd); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index d4aaea6d7280..50a6f64fe5ca 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -112,7 +112,6 @@ public: uint32_t frameTimePercentile(int p); ANDROID_API static void dumpGraphicsMemory(int fd); - ANDROID_API void setTextureAtlas(const sp& buffer, int64_t* map, size_t size); ANDROID_API void setProcessStatsBuffer(int fd); ANDROID_API int getRenderThreadTid(); diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 78e9bc475826..51c0a05fb6d7 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -124,8 +124,9 @@ public: static SkBitmap createSkBitmap(int width, int height, SkColorType colorType = kN32_SkColorType) { SkBitmap bitmap; + sk_sp colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); SkImageInfo info = SkImageInfo::Make(width, height, - colorType, kPremul_SkAlphaType); + colorType, kPremul_SkAlphaType, colorSpace); bitmap.setInfo(info); bitmap.allocPixels(info); return bitmap; diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index a30ada0df453..5cab04d26c2a 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -82,3 +83,9 @@ TEST(SkiaBehavior, porterDuffCreateIsCached) { paint.setXfermodeMode(SkXfermode::kOverlay_Mode); ASSERT_EQ(expected, paint.getXfermode()); } + +TEST(SkiaBehavior, srgbColorSpaceIsSingleton) { + sk_sp sRGB1 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp sRGB2 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + ASSERT_EQ(sRGB1.get(), sRGB2.get()); +} diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index b5157f401438..c8f8c7071075 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -16,6 +16,8 @@ #ifndef COLOR_H #define COLOR_H +#include + #include namespace android { @@ -80,6 +82,28 @@ namespace uirenderer { }; static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color); + // Opto-electronic conversion function for the sRGB color space + // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value + static constexpr float OECF_sRGB(float linear) { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + // IEC 61966-2-1:1999 + return linear <= 0.0031308f ? + linear * 12.92f : (powf(linear, 1.0f / 2.4f) * 1.055f) - 0.055f; +#else + return linear; +#endif + } + + // Electro-optical conversion function for the sRGB color space + // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value + static constexpr float EOCF_sRGB(float srgb) { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + // IEC 61966-2-1:1999 + return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f); +#else + return srgb; +#endif + } } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp index b879f781bce1..624d20763384 100644 --- a/libs/hwui/utils/TestWindowContext.cpp +++ b/libs/hwui/utils/TestWindowContext.cpp @@ -110,9 +110,10 @@ public: } bool capturePixels(SkBitmap* bmp) { + sk_sp colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); SkImageInfo destinationConfig = SkImageInfo::Make(mSize.width(), mSize.height(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType); + kRGBA_8888_SkColorType, kPremul_SkAlphaType, colorSpace); bmp->allocPixels(destinationConfig); android_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED, mSize.width() * mSize.height() * 4); -- cgit v1.2.3-59-g8ed1b