| /* |
| * Copyright (C) 2010 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 "SkiaShader.h" |
| |
| #include "Caches.h" |
| #include "Extensions.h" |
| #include "Matrix.h" |
| #include "Texture.h" |
| #include "hwui/Bitmap.h" |
| |
| #include <SkMatrix.h> |
| #include <utils/Log.h> |
| |
| namespace android { |
| namespace uirenderer { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Support |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| static constexpr GLenum gTileModes[] = { |
| GL_CLAMP_TO_EDGE, // == SkShader::kClamp_TileMode |
| GL_REPEAT, // == SkShader::kRepeat_Mode |
| GL_MIRRORED_REPEAT // == SkShader::kMirror_TileMode |
| }; |
| |
| static_assert(gTileModes[SkShader::kClamp_TileMode] == GL_CLAMP_TO_EDGE, |
| "SkShader TileModes have changed"); |
| static_assert(gTileModes[SkShader::kRepeat_TileMode] == GL_REPEAT, |
| "SkShader TileModes have changed"); |
| static_assert(gTileModes[SkShader::kMirror_TileMode] == GL_MIRRORED_REPEAT, |
| "SkShader TileModes have changed"); |
| |
| /** |
| * This function does not work for n == 0. |
| */ |
| static inline bool isPowerOfTwo(unsigned int n) { |
| return !(n & (n - 1)); |
| } |
| |
| static inline void bindUniformColor(int slot, FloatColor color) { |
| glUniform4fv(slot, 1, reinterpret_cast<const float*>(&color)); |
| } |
| |
| static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) { |
| caches->textureState().bindTexture(texture->target(), texture->id()); |
| texture->setWrapST(wrapS, wrapT); |
| } |
| |
| /** |
| * Compute the matrix to transform to screen space. |
| * @param screenSpace Output param for the computed matrix. |
| * @param unitMatrix The unit matrix for gradient shaders, as returned by SkShader::asAGradient, |
| * or identity. |
| * @param localMatrix Local matrix, as returned by SkShader::getLocalMatrix(). |
| * @param modelViewMatrix Model view matrix, as supplied by the OpenGLRenderer. |
| */ |
| static void computeScreenSpaceMatrix(mat4& screenSpace, const SkMatrix& unitMatrix, |
| const SkMatrix& localMatrix, const mat4& modelViewMatrix) { |
| mat4 shaderMatrix; |
| // uses implicit construction |
| shaderMatrix.loadInverse(localMatrix); |
| // again, uses implicit construction |
| screenSpace.loadMultiply(unitMatrix, shaderMatrix); |
| screenSpace.multiply(modelViewMatrix); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Gradient shader matrix helpers |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| static void toLinearUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) { |
| SkVector vec = pts[1] - pts[0]; |
| const float mag = vec.length(); |
| const float inv = mag ? 1.0f / mag : 0; |
| |
| vec.scale(inv); |
| matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); |
| matrix->postTranslate(-pts[0].fX, -pts[0].fY); |
| matrix->postScale(inv, inv); |
| } |
| |
| static void toCircularUnitMatrix(const float x, const float y, const float radius, |
| SkMatrix* matrix) { |
| const float inv = 1.0f / radius; |
| matrix->setTranslate(-x, -y); |
| matrix->postScale(inv, inv); |
| } |
| |
| static void toSweepUnitMatrix(const float x, const float y, SkMatrix* matrix) { |
| matrix->setTranslate(-x, -y); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Common gradient code |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| static bool isSimpleGradient(const SkShader::GradientInfo& gradInfo) { |
| return gradInfo.fColorCount == 2 && gradInfo.fTileMode == SkShader::kClamp_TileMode; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Store / apply |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 modelViewMatrix, |
| GLuint* textureUnit, ProgramDescription* description, |
| SkiaShaderData::GradientShaderData* outData) { |
| SkShader::GradientInfo gradInfo; |
| gradInfo.fColorCount = 0; |
| gradInfo.fColors = nullptr; |
| gradInfo.fColorOffsets = nullptr; |
| |
| SkMatrix unitMatrix; |
| switch (shader.asAGradient(&gradInfo)) { |
| case SkShader::kLinear_GradientType: |
| description->gradientType = ProgramDescription::kGradientLinear; |
| |
| toLinearUnitMatrix(gradInfo.fPoint, &unitMatrix); |
| break; |
| case SkShader::kRadial_GradientType: |
| description->gradientType = ProgramDescription::kGradientCircular; |
| |
| toCircularUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, gradInfo.fRadius[0], |
| &unitMatrix); |
| break; |
| case SkShader::kSweep_GradientType: |
| description->gradientType = ProgramDescription::kGradientSweep; |
| |
| toSweepUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, &unitMatrix); |
| break; |
| default: |
| // Do nothing. This shader is unsupported. |
| return false; |
| } |
| description->hasGradient = true; |
| description->isSimpleGradient = isSimpleGradient(gradInfo); |
| |
| computeScreenSpaceMatrix(outData->screenSpace, unitMatrix, shader.getLocalMatrix(), |
| modelViewMatrix); |
| |
| // re-query shader to get full color / offset data |
| std::unique_ptr<SkColor[]> colorStorage(new SkColor[gradInfo.fColorCount]); |
| std::unique_ptr<SkScalar[]> colorOffsets(new SkScalar[gradInfo.fColorCount]); |
| gradInfo.fColors = &colorStorage[0]; |
| gradInfo.fColorOffsets = &colorOffsets[0]; |
| shader.asAGradient(&gradInfo); |
| |
| if (CC_UNLIKELY(!description->isSimpleGradient)) { |
| outData->gradientSampler = (*textureUnit)++; |
| |
| #ifndef SK_SCALAR_IS_FLOAT |
| #error Need to convert gradInfo.fColorOffsets to float! |
| #endif |
| outData->gradientTexture = caches.gradientCache.get( |
| gradInfo.fColors, gradInfo.fColorOffsets, gradInfo.fColorCount); |
| outData->wrapST = gTileModes[gradInfo.fTileMode]; |
| } else { |
| outData->gradientSampler = 0; |
| outData->gradientTexture = nullptr; |
| |
| outData->startColor.set(gradInfo.fColors[0]); |
| outData->endColor.set(gradInfo.fColors[1]); |
| } |
| |
| return true; |
| } |
| |
| 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); |
| glUniform1i(caches.program().getUniform("gradientSampler"), data.gradientSampler); |
| } else { |
| bindUniformColor(caches.program().getUniform("startColor"), data.startColor); |
| bindUniformColor(caches.program().getUniform("endColor"), data.endColor); |
| } |
| |
| glUniform2f(caches.program().getUniform("screenSize"), 1.0f / width, 1.0f / height); |
| glUniformMatrix4fv(caches.program().getUniform("screenSpace"), 1, GL_FALSE, |
| &data.screenSpace.data[0]); |
| } |
| |
| bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, |
| GLuint* textureUnit, ProgramDescription* description, |
| SkiaShaderData::BitmapShaderData* outData) { |
| SkBitmap bitmap; |
| SkShader::TileMode xy[2]; |
| if (!shader.isABitmap(&bitmap, nullptr, xy)) { |
| return false; |
| } |
| |
| // TODO: create hwui-owned BitmapShader. |
| Bitmap* hwuiBitmap = static_cast<Bitmap*>(bitmap.pixelRef()); |
| outData->bitmapTexture = caches.textureCache.get(hwuiBitmap); |
| if (!outData->bitmapTexture) return false; |
| |
| outData->bitmapSampler = (*textureUnit)++; |
| |
| const float width = outData->bitmapTexture->width(); |
| const float height = outData->bitmapTexture->height(); |
| |
| Texture* texture = outData->bitmapTexture; |
| |
| description->hasBitmap = true; |
| description->hasLinearTexture = texture->isLinear(); |
| description->hasColorSpaceConversion = texture->hasColorSpaceConversion(); |
| description->transferFunction = texture->getTransferFunctionType(); |
| description->hasTranslucentConversion = texture->blend; |
| description->isShaderBitmapExternal = hwuiBitmap->isHardware(); |
| // gralloc doesn't support non-clamp modes |
| if (hwuiBitmap->isHardware() || |
| (!caches.extensions().hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) && |
| (xy[0] != SkShader::kClamp_TileMode || xy[1] != SkShader::kClamp_TileMode))) { |
| // need non-clamp mode, but it's not supported for this draw, |
| // so enable custom shader logic to mimic |
| description->useShaderBasedWrap = true; |
| description->bitmapWrapS = gTileModes[xy[0]]; |
| description->bitmapWrapT = gTileModes[xy[1]]; |
| |
| outData->wrapS = GL_CLAMP_TO_EDGE; |
| outData->wrapT = GL_CLAMP_TO_EDGE; |
| } else { |
| outData->wrapS = gTileModes[xy[0]]; |
| outData->wrapT = gTileModes[xy[1]]; |
| } |
| |
| computeScreenSpaceMatrix(outData->textureTransform, SkMatrix::I(), shader.getLocalMatrix(), |
| modelViewMatrix); |
| outData->textureDimension[0] = 1.0f / width; |
| outData->textureDimension[1] = 1.0f / height; |
| |
| return true; |
| } |
| |
| void applyBitmap(Caches& caches, const SkiaShaderData::BitmapShaderData& data) { |
| caches.textureState().activateTexture(data.bitmapSampler); |
| bindTexture(&caches, data.bitmapTexture, data.wrapS, data.wrapT); |
| data.bitmapTexture->setFilter(GL_LINEAR); |
| |
| glUniform1i(caches.program().getUniform("bitmapSampler"), data.bitmapSampler); |
| glUniformMatrix4fv(caches.program().getUniform("textureTransform"), 1, GL_FALSE, |
| &data.textureTransform.data[0]); |
| glUniform2fv(caches.program().getUniform("textureDimension"), 1, &data.textureDimension[0]); |
| } |
| |
| SkiaShaderType getComposeSubType(const SkShader& shader) { |
| // First check for a gradient shader. |
| switch (shader.asAGradient(nullptr)) { |
| case SkShader::kNone_GradientType: |
| // Not a gradient shader. Fall through to check for other types. |
| break; |
| case SkShader::kLinear_GradientType: |
| case SkShader::kRadial_GradientType: |
| case SkShader::kSweep_GradientType: |
| return kGradient_SkiaShaderType; |
| default: |
| // This is a Skia gradient that has no SkiaShader equivalent. Return None to skip. |
| return kNone_SkiaShaderType; |
| } |
| |
| // The shader is not a gradient. Check for a bitmap shader. |
| if (shader.isABitmap()) { |
| return kBitmap_SkiaShaderType; |
| } |
| return kNone_SkiaShaderType; |
| } |
| |
| void storeCompose(Caches& caches, const SkShader& bitmapShader, const SkShader& gradientShader, |
| const Matrix4& modelViewMatrix, GLuint* textureUnit, |
| ProgramDescription* description, SkiaShaderData* outData) { |
| LOG_ALWAYS_FATAL_IF(!tryStoreBitmap(caches, bitmapShader, modelViewMatrix, textureUnit, |
| description, &outData->bitmapData), |
| "failed storing bitmap shader data"); |
| LOG_ALWAYS_FATAL_IF(!tryStoreGradient(caches, gradientShader, modelViewMatrix, textureUnit, |
| description, &outData->gradientData), |
| "failing storing gradient shader data"); |
| } |
| |
| bool tryStoreCompose(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, |
| GLuint* textureUnit, ProgramDescription* description, |
| SkiaShaderData* outData) { |
| SkShader::ComposeRec rec; |
| if (!shader.asACompose(&rec)) return false; |
| |
| const SkiaShaderType shaderAType = getComposeSubType(*rec.fShaderA); |
| const SkiaShaderType shaderBType = getComposeSubType(*rec.fShaderB); |
| |
| // check that type enum values are the 2 flags that compose the kCompose value |
| if ((shaderAType & shaderBType) != 0) return false; |
| if ((shaderAType | shaderBType) != kCompose_SkiaShaderType) return false; |
| |
| mat4 transform; |
| computeScreenSpaceMatrix(transform, SkMatrix::I(), shader.getLocalMatrix(), modelViewMatrix); |
| if (shaderAType == kBitmap_SkiaShaderType) { |
| description->isBitmapFirst = true; |
| storeCompose(caches, *rec.fShaderA, *rec.fShaderB, transform, textureUnit, description, |
| outData); |
| } else { |
| description->isBitmapFirst = false; |
| storeCompose(caches, *rec.fShaderB, *rec.fShaderA, transform, textureUnit, description, |
| outData); |
| } |
| description->shadersMode = rec.fBlendMode; |
| return true; |
| } |
| |
| void SkiaShader::store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, |
| GLuint* textureUnit, ProgramDescription* description, |
| SkiaShaderData* outData) { |
| if (tryStoreGradient(caches, shader, modelViewMatrix, textureUnit, description, |
| &outData->gradientData)) { |
| outData->skiaShaderType = kGradient_SkiaShaderType; |
| return; |
| } |
| |
| if (tryStoreBitmap(caches, shader, modelViewMatrix, textureUnit, description, |
| &outData->bitmapData)) { |
| outData->skiaShaderType = kBitmap_SkiaShaderType; |
| return; |
| } |
| |
| if (tryStoreCompose(caches, shader, modelViewMatrix, textureUnit, description, outData)) { |
| outData->skiaShaderType = kCompose_SkiaShaderType; |
| return; |
| } |
| |
| // Unknown/unsupported type, so explicitly ignore shader |
| outData->skiaShaderType = kNone_SkiaShaderType; |
| } |
| |
| 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, width, height); |
| } |
| if (data.skiaShaderType & kBitmap_SkiaShaderType) { |
| applyBitmap(caches, data.bitmapData); |
| } |
| } |
| |
| }; // namespace uirenderer |
| }; // namespace android |