This change also fixes an issue with RGBA16F bitmaps when modulated with a color (for instance by setting an alpha on the Paint object). The color space conversion is currently done entirely in the shader, by doing these operations in order: 1. Sample the texture 2. Un-premultiply alpha 3. Apply the EOTF 4. Multiply by the 3x3 color space matrix 5. Apply the OETF 6. Premultiply alpha Optimizations: - Steps 2 & 6 are skipped for opaque (common) bitmaps - Step 3 is skipped when the color space's EOTF is close to sRGB (Display P3 for instance). Instead, we use a hardware sRGB fetch (when the GPU supports it) - When step 3 is necessary, we use one of four standard EOTF implementations, to save cycles when possible: + Linear (doesn't do anything) + Full parametric (ICC parametric curve type 4 as defined in ICC.1:2004-10, section 10.15) + Limited parametric (ICC parametric curve type 3) + Gamma (ICC parametric curve type 0) Color space conversion could be done using texture samplers instead, for instance 3D LUTs, with or without transfer functions baked in, or 1D LUTs for transfer functions. This would result in dependent texture fetches which may or may not be an advantage over an ALU based implementation. The current solution favor the use of ALUs to save precious bandwidth. Test: CtsUiRenderingTests, CtsGraphicsTests Bug: 32984164 Change-Id: I10bc3db515e13973b45220f129c66b23f0f7f8fe
364 lines
14 KiB
C++
364 lines
14 KiB
C++
/*
|
|
* 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.setUnPreMultiplied(gradInfo.fColors[0]);
|
|
outData->endColor.setUnPreMultiplied(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
|