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
698 lines
27 KiB
C++
698 lines
27 KiB
C++
/*
|
|
* Copyright (C) 2015 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 "GlopBuilder.h"
|
|
|
|
#include "Caches.h"
|
|
#include "GlLayer.h"
|
|
#include "Glop.h"
|
|
#include "Layer.h"
|
|
#include "Matrix.h"
|
|
#include "Patch.h"
|
|
#include "PathCache.h"
|
|
#include "renderstate/MeshState.h"
|
|
#include "renderstate/RenderState.h"
|
|
#include "SkiaShader.h"
|
|
#include "Texture.h"
|
|
#include "utils/PaintUtils.h"
|
|
#include "VertexBuffer.h"
|
|
|
|
#include <GLES2/gl2.h>
|
|
#include <SkPaint.h>
|
|
|
|
#define DEBUG_GLOP_BUILDER 0
|
|
|
|
#if DEBUG_GLOP_BUILDER
|
|
|
|
#define TRIGGER_STAGE(stageFlag) \
|
|
LOG_ALWAYS_FATAL_IF((stageFlag) & mStageFlags, "Stage %d cannot be run twice", (stageFlag)); \
|
|
mStageFlags = static_cast<StageFlags>(mStageFlags | (stageFlag))
|
|
|
|
#define REQUIRE_STAGES(requiredFlags) \
|
|
LOG_ALWAYS_FATAL_IF((mStageFlags & (requiredFlags)) != (requiredFlags), \
|
|
"not prepared for current stage")
|
|
|
|
#else
|
|
|
|
#define TRIGGER_STAGE(stageFlag) ((void)0)
|
|
#define REQUIRE_STAGES(requiredFlags) ((void)0)
|
|
|
|
#endif
|
|
|
|
namespace android {
|
|
namespace uirenderer {
|
|
|
|
static void setUnitQuadTextureCoords(Rect uvs, TextureVertex* quadVertex) {
|
|
quadVertex[0] = {0, 0, uvs.left, uvs.top};
|
|
quadVertex[1] = {1, 0, uvs.right, uvs.top};
|
|
quadVertex[2] = {0, 1, uvs.left, uvs.bottom};
|
|
quadVertex[3] = {1, 1, uvs.right, uvs.bottom};
|
|
}
|
|
|
|
GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop)
|
|
: mRenderState(renderState)
|
|
, mCaches(caches)
|
|
, mShader(nullptr)
|
|
, mOutGlop(outGlop) {
|
|
mStageFlags = kInitialStage;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Mesh
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
|
|
mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
vbo,
|
|
VertexAttribFlags::TextureCoord,
|
|
nullptr, (const void*) kMeshTextureOffset, nullptr,
|
|
kTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = elementCount;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshUnitQuad() {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
|
|
mOutGlop->mesh.indices = { 0, nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
mRenderState.meshState().getUnitQuadVBO(),
|
|
VertexAttribFlags::None,
|
|
nullptr, nullptr, nullptr,
|
|
kTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = 4;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshTexturedUnitQuad(const UvMapper* uvMapper) {
|
|
if (uvMapper) {
|
|
// can't use unit quad VBO, so build UV vertices manually
|
|
return setMeshTexturedUvQuad(uvMapper, Rect(1, 1));
|
|
}
|
|
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
|
|
mOutGlop->mesh.indices = { 0, nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
mRenderState.meshState().getUnitQuadVBO(),
|
|
VertexAttribFlags::TextureCoord,
|
|
nullptr, (const void*) kMeshTextureOffset, nullptr,
|
|
kTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = 4;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshTexturedUvQuad(const UvMapper* uvMapper, Rect uvs) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
if (CC_UNLIKELY(uvMapper)) {
|
|
uvMapper->map(uvs);
|
|
}
|
|
setUnitQuadTextureCoords(uvs, &mOutGlop->mesh.mappedVertices[0]);
|
|
|
|
const TextureVertex* textureVertex = mOutGlop->mesh.mappedVertices;
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
|
|
mOutGlop->mesh.indices = { 0, nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
0,
|
|
VertexAttribFlags::TextureCoord,
|
|
&textureVertex[0].x, &textureVertex[0].u, nullptr,
|
|
kTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = 4;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshIndexedQuads(Vertex* vertexData, int quadCount) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
|
|
mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
0,
|
|
VertexAttribFlags::None,
|
|
vertexData, nullptr, nullptr,
|
|
kVertexStride };
|
|
mOutGlop->mesh.elementCount = 6 * quadCount;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshTexturedIndexedQuads(TextureVertex* vertexData, int elementCount) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
|
|
mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
0,
|
|
VertexAttribFlags::TextureCoord,
|
|
&vertexData[0].x, &vertexData[0].u, nullptr,
|
|
kTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = elementCount;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, int elementCount) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
|
|
mOutGlop->mesh.indices = { 0, nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
0,
|
|
VertexAttribFlags::TextureCoord | VertexAttribFlags::Color,
|
|
&vertexData[0].x, &vertexData[0].u, &vertexData[0].r,
|
|
kColorTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = elementCount;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshVertexBuffer(const VertexBuffer& vertexBuffer) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
const VertexBuffer::MeshFeatureFlags flags = vertexBuffer.getMeshFeatureFlags();
|
|
|
|
bool alphaVertex = flags & VertexBuffer::kAlpha;
|
|
bool indices = flags & VertexBuffer::kIndices;
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP;
|
|
mOutGlop->mesh.indices = { 0, vertexBuffer.getIndices() };
|
|
mOutGlop->mesh.vertices = {
|
|
0,
|
|
alphaVertex ? VertexAttribFlags::Alpha : VertexAttribFlags::None,
|
|
vertexBuffer.getBuffer(), nullptr, nullptr,
|
|
alphaVertex ? kAlphaVertexStride : kVertexStride };
|
|
mOutGlop->mesh.elementCount = indices
|
|
? vertexBuffer.getIndexCount() : vertexBuffer.getVertexCount();
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setMeshPatchQuads(const Patch& patch) {
|
|
TRIGGER_STAGE(kMeshStage);
|
|
|
|
mOutGlop->mesh.primitiveMode = GL_TRIANGLES;
|
|
mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr };
|
|
mOutGlop->mesh.vertices = {
|
|
mCaches.patchCache.getMeshBuffer(),
|
|
VertexAttribFlags::TextureCoord,
|
|
(void*)patch.positionOffset, (void*)patch.textureOffset, nullptr,
|
|
kTextureVertexStride };
|
|
mOutGlop->mesh.elementCount = patch.indexCount;
|
|
return *this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Fill
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void GlopBuilder::setFill(int color, float alphaScale,
|
|
SkBlendMode mode, Blend::ModeOrderSwap modeUsage,
|
|
const SkShader* shader, const SkColorFilter* colorFilter) {
|
|
if (mode != SkBlendMode::kClear) {
|
|
if (!shader) {
|
|
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 {
|
|
mOutGlop->fill.color = { 0, 0, 0, 1 };
|
|
}
|
|
|
|
mOutGlop->blend = { GL_ZERO, GL_ZERO };
|
|
if (mOutGlop->fill.color.a < 1.0f
|
|
|| (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha)
|
|
|| (mOutGlop->fill.texture.texture && mOutGlop->fill.texture.texture->blend)
|
|
|| mOutGlop->roundRectClipState
|
|
|| PaintUtils::isBlendedShader(shader)
|
|
|| PaintUtils::isBlendedColorFilter(colorFilter)
|
|
|| mode != SkBlendMode::kSrcOver) {
|
|
if (CC_LIKELY(mode <= SkBlendMode::kScreen)) {
|
|
Blend::getFactors(mode, modeUsage,
|
|
&mOutGlop->blend.src, &mOutGlop->blend.dst);
|
|
} else {
|
|
// These blend modes are not supported by OpenGL directly and have
|
|
// to be implemented using shaders. Since the shader will perform
|
|
// the blending, don't enable GL blending off here
|
|
// If the blend mode cannot be implemented using shaders, fall
|
|
// back to the default SrcOver blend mode instead
|
|
if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) {
|
|
mDescription.framebufferMode = mode;
|
|
mDescription.swapSrcDst = (modeUsage == Blend::ModeOrderSwap::Swap);
|
|
// blending in shader, don't enable
|
|
} else {
|
|
// unsupported
|
|
Blend::getFactors(SkBlendMode::kSrcOver, modeUsage,
|
|
&mOutGlop->blend.src, &mOutGlop->blend.dst);
|
|
}
|
|
}
|
|
}
|
|
mShader = shader; // shader resolved in ::build()
|
|
|
|
if (colorFilter) {
|
|
SkColor color;
|
|
SkBlendMode bmode;
|
|
SkScalar srcColorMatrix[20];
|
|
if (colorFilter->asColorMode(&color, &bmode)) {
|
|
mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend;
|
|
mDescription.colorMode = bmode;
|
|
mOutGlop->fill.filter.color.set(color);
|
|
} else if (colorFilter->asColorMatrix(srcColorMatrix)) {
|
|
mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix;
|
|
|
|
float* colorMatrix = mOutGlop->fill.filter.matrix.matrix;
|
|
memcpy(colorMatrix, srcColorMatrix, 4 * sizeof(float));
|
|
memcpy(&colorMatrix[4], &srcColorMatrix[5], 4 * sizeof(float));
|
|
memcpy(&colorMatrix[8], &srcColorMatrix[10], 4 * sizeof(float));
|
|
memcpy(&colorMatrix[12], &srcColorMatrix[15], 4 * sizeof(float));
|
|
|
|
// 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] = EOCF(srcColorMatrix[4] / 255.0f);
|
|
colorVector[1] = EOCF(srcColorMatrix[9] / 255.0f);
|
|
colorVector[2] = EOCF(srcColorMatrix[14] / 255.0f);
|
|
colorVector[3] = srcColorMatrix[19] / 255.0f; // alpha is linear
|
|
} else {
|
|
LOG_ALWAYS_FATAL("unsupported ColorFilter");
|
|
}
|
|
} else {
|
|
mOutGlop->fill.filterMode = ProgramDescription::ColorFilterMode::None;
|
|
}
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillTexturePaint(Texture& texture,
|
|
const int textureFillFlags, const SkPaint* paint, float alphaScale) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
GLenum filter = (textureFillFlags & TextureFillFlags::ForceFilter)
|
|
? GL_LINEAR : PaintUtils::getFilter(paint);
|
|
mOutGlop->fill.texture = { &texture, filter, GL_CLAMP_TO_EDGE, nullptr };
|
|
|
|
if (paint) {
|
|
int color = paint->getColor();
|
|
SkShader* shader = paint->getShader();
|
|
|
|
if (!(textureFillFlags & TextureFillFlags::IsAlphaMaskTexture)) {
|
|
// Texture defines color, so disable shaders, and reset all non-alpha color channels
|
|
color |= 0x00FFFFFF;
|
|
shader = nullptr;
|
|
}
|
|
setFill(color, alphaScale,
|
|
paint->getBlendMode(), Blend::ModeOrderSwap::NoSwap,
|
|
shader, paint->getColorFilter());
|
|
} else {
|
|
mOutGlop->fill.color = { alphaScale, alphaScale, alphaScale, alphaScale };
|
|
|
|
if (alphaScale < 1.0f
|
|
|| (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha)
|
|
|| texture.blend
|
|
|| mOutGlop->roundRectClipState) {
|
|
Blend::getFactors(SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap,
|
|
&mOutGlop->blend.src, &mOutGlop->blend.dst);
|
|
} else {
|
|
mOutGlop->blend = { GL_ZERO, GL_ZERO };
|
|
}
|
|
}
|
|
|
|
if (textureFillFlags & TextureFillFlags::IsAlphaMaskTexture) {
|
|
mDescription.modulate = mOutGlop->fill.color.isNotBlack();
|
|
mDescription.hasAlpha8Texture = true;
|
|
} else {
|
|
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillPaint(const SkPaint& paint, float alphaScale, bool shadowInterp) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
if (CC_LIKELY(!shadowInterp)) {
|
|
mOutGlop->fill.texture = {
|
|
nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
|
} else {
|
|
mOutGlop->fill.texture = {
|
|
mCaches.textureState().getShadowLutTexture(),
|
|
GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
|
}
|
|
|
|
setFill(paint.getColor(), alphaScale,
|
|
paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
|
|
paint.getShader(), paint.getColorFilter());
|
|
mDescription.useShadowAlphaInterp = shadowInterp;
|
|
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillPathTexturePaint(PathTexture& texture,
|
|
const SkPaint& paint, float alphaScale) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
//specify invalid filter/clamp, since these are always static for PathTextures
|
|
mOutGlop->fill.texture = { &texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
|
|
|
setFill(paint.getColor(), alphaScale,
|
|
paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
|
|
paint.getShader(), paint.getColorFilter());
|
|
|
|
mDescription.hasAlpha8Texture = true;
|
|
mDescription.modulate = mOutGlop->fill.color.isNotBlack();
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillShadowTexturePaint(ShadowTexture& texture, int shadowColor,
|
|
const SkPaint& paint, float alphaScale) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
//specify invalid filter/clamp, since these are always static for ShadowTextures
|
|
mOutGlop->fill.texture = { &texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
|
|
|
const int ALPHA_BITMASK = SK_ColorBLACK;
|
|
const int COLOR_BITMASK = ~ALPHA_BITMASK;
|
|
if ((shadowColor & ALPHA_BITMASK) == ALPHA_BITMASK) {
|
|
// shadow color is fully opaque: override its alpha with that of paint
|
|
shadowColor &= paint.getColor() | COLOR_BITMASK;
|
|
}
|
|
|
|
setFill(shadowColor, alphaScale,
|
|
paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
|
|
paint.getShader(), paint.getColorFilter());
|
|
|
|
mDescription.hasAlpha8Texture = true;
|
|
mDescription.modulate = mOutGlop->fill.color.isNotBlack();
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillBlack() {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
|
setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap,
|
|
nullptr, nullptr);
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillClear() {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
mOutGlop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr };
|
|
setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kClear, Blend::ModeOrderSwap::NoSwap,
|
|
nullptr, nullptr);
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* colorFilter,
|
|
float alpha, SkBlendMode mode, Blend::ModeOrderSwap modeUsage) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
mOutGlop->fill.texture = { &texture, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr };
|
|
|
|
setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter);
|
|
|
|
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillTextureLayer(GlLayer& layer, float alpha) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
mOutGlop->fill.texture = { &(layer.getTexture()),
|
|
GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() };
|
|
|
|
setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap,
|
|
nullptr, layer.getColorFilter());
|
|
|
|
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
|
|
mDescription.hasTextureTransform = true;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform) {
|
|
TRIGGER_STAGE(kFillStage);
|
|
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
|
|
|
|
mOutGlop->fill.texture = { &texture, GL_LINEAR, GL_CLAMP_TO_EDGE, &textureTransform };
|
|
|
|
setFill(SK_ColorWHITE, 1.0f, SkBlendMode::kSrc, Blend::ModeOrderSwap::NoSwap,
|
|
nullptr, nullptr);
|
|
|
|
mDescription.modulate = mOutGlop->fill.color.a < 1.0f;
|
|
mDescription.hasTextureTransform = true;
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setGammaCorrection(bool enabled) {
|
|
REQUIRE_STAGES(kFillStage);
|
|
|
|
mDescription.hasGammaCorrection = enabled;
|
|
return *this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Transform
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) {
|
|
TRIGGER_STAGE(kTransformStage);
|
|
|
|
mOutGlop->transform.canvas = canvas;
|
|
mOutGlop->transform.transformFlags = transformFlags;
|
|
return *this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// ModelView
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
GlopBuilder& GlopBuilder::setModelViewMapUnitToRect(const Rect destination) {
|
|
TRIGGER_STAGE(kModelViewStage);
|
|
|
|
mOutGlop->transform.modelView.loadTranslate(destination.left, destination.top, 0.0f);
|
|
mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f);
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setModelViewMapUnitToRectSnap(const Rect destination) {
|
|
TRIGGER_STAGE(kModelViewStage);
|
|
REQUIRE_STAGES(kTransformStage | kFillStage);
|
|
|
|
float left = destination.left;
|
|
float top = destination.top;
|
|
|
|
const Matrix4& meshTransform = mOutGlop->transform.meshTransform();
|
|
if (CC_LIKELY(meshTransform.isPureTranslate())) {
|
|
// snap by adjusting the model view matrix
|
|
const float translateX = meshTransform.getTranslateX();
|
|
const float translateY = meshTransform.getTranslateY();
|
|
|
|
left = (int) floorf(left + translateX + 0.5f) - translateX;
|
|
top = (int) floorf(top + translateY + 0.5f) - translateY;
|
|
mOutGlop->fill.texture.filter = GL_NEAREST;
|
|
}
|
|
|
|
mOutGlop->transform.modelView.loadTranslate(left, top, 0.0f);
|
|
mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f);
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setModelViewOffsetRect(float offsetX, float offsetY, const Rect source) {
|
|
TRIGGER_STAGE(kModelViewStage);
|
|
|
|
mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f);
|
|
return *this;
|
|
}
|
|
|
|
GlopBuilder& GlopBuilder::setModelViewOffsetRectSnap(float offsetX, float offsetY, const Rect source) {
|
|
TRIGGER_STAGE(kModelViewStage);
|
|
REQUIRE_STAGES(kTransformStage | kFillStage);
|
|
|
|
const Matrix4& meshTransform = mOutGlop->transform.meshTransform();
|
|
if (CC_LIKELY(meshTransform.isPureTranslate())) {
|
|
// snap by adjusting the model view matrix
|
|
const float translateX = meshTransform.getTranslateX();
|
|
const float translateY = meshTransform.getTranslateY();
|
|
|
|
offsetX = (int) floorf(offsetX + translateX + source.left + 0.5f) - translateX - source.left;
|
|
offsetY = (int) floorf(offsetY + translateY + source.top + 0.5f) - translateY - source.top;
|
|
mOutGlop->fill.texture.filter = GL_NEAREST;
|
|
}
|
|
|
|
mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f);
|
|
return *this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// RoundRectClip
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
GlopBuilder& GlopBuilder::setRoundRectClipState(const RoundRectClipState* roundRectClipState) {
|
|
TRIGGER_STAGE(kRoundRectClipStage);
|
|
|
|
mOutGlop->roundRectClipState = roundRectClipState;
|
|
mDescription.hasRoundRectClip = roundRectClipState != nullptr;
|
|
return *this;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Build
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void verify(const ProgramDescription& description, const Glop& glop) {
|
|
if (glop.fill.texture.texture != nullptr) {
|
|
LOG_ALWAYS_FATAL_IF(((description.hasTexture && description.hasExternalTexture)
|
|
|| (!description.hasTexture
|
|
&& !description.hasExternalTexture
|
|
&& !description.useShadowAlphaInterp)
|
|
|| ((glop.mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) == 0
|
|
&& !description.useShadowAlphaInterp)),
|
|
"Texture %p, hT%d, hET %d, attribFlags %x",
|
|
glop.fill.texture.texture,
|
|
description.hasTexture, description.hasExternalTexture,
|
|
glop.mesh.vertices.attribFlags);
|
|
} else {
|
|
LOG_ALWAYS_FATAL_IF((description.hasTexture
|
|
|| description.hasExternalTexture
|
|
|| ((glop.mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) != 0)),
|
|
"No texture, hT%d, hET %d, attribFlags %x",
|
|
description.hasTexture, description.hasExternalTexture,
|
|
glop.mesh.vertices.attribFlags);
|
|
}
|
|
|
|
if ((glop.mesh.vertices.attribFlags & VertexAttribFlags::Alpha)
|
|
&& glop.mesh.vertices.bufferObject) {
|
|
LOG_ALWAYS_FATAL("VBO and alpha attributes are not currently compatible");
|
|
}
|
|
|
|
if (description.hasTextureTransform != (glop.fill.texture.textureTransform != nullptr)) {
|
|
LOG_ALWAYS_FATAL("Texture transform incorrectly specified");
|
|
}
|
|
}
|
|
|
|
void GlopBuilder::build() {
|
|
REQUIRE_STAGES(kAllStages);
|
|
if (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) {
|
|
if (mOutGlop->fill.texture.texture->target() == GL_TEXTURE_2D) {
|
|
mDescription.hasTexture = true;
|
|
} else {
|
|
mDescription.hasExternalTexture = true;
|
|
}
|
|
Texture* texture = mOutGlop->fill.texture.texture;
|
|
mDescription.hasLinearTexture = texture->isLinear();
|
|
mDescription.hasColorSpaceConversion = texture->hasColorSpaceConversion();
|
|
mDescription.transferFunction = texture->getTransferFunctionType();
|
|
mDescription.hasTranslucentConversion = texture->blend;
|
|
}
|
|
|
|
mDescription.hasColors = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Color;
|
|
mDescription.hasVertexAlpha = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha;
|
|
|
|
// Enable debug highlight when what we're about to draw is tested against
|
|
// the stencil buffer and if stencil highlight debugging is on
|
|
mDescription.hasDebugHighlight = !Properties::debugOverdraw
|
|
&& Properties::debugStencilClip == StencilClipDebug::ShowHighlight
|
|
&& mRenderState.stencil().isTestEnabled();
|
|
|
|
// serialize shader info into ShaderData
|
|
GLuint textureUnit = mOutGlop->fill.texture.texture ? 1 : 0;
|
|
|
|
if (CC_LIKELY(!mShader)) {
|
|
mOutGlop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType;
|
|
} else {
|
|
Matrix4 shaderMatrix;
|
|
if (mOutGlop->transform.transformFlags & TransformFlags::MeshIgnoresCanvasTransform) {
|
|
// canvas level transform was built into the modelView and geometry,
|
|
// so the shader matrix must reverse this
|
|
shaderMatrix.loadInverse(mOutGlop->transform.canvas);
|
|
shaderMatrix.multiply(mOutGlop->transform.modelView);
|
|
} else {
|
|
shaderMatrix = mOutGlop->transform.modelView;
|
|
}
|
|
SkiaShader::store(mCaches, *mShader, shaderMatrix,
|
|
&textureUnit, &mDescription, &(mOutGlop->fill.skiaShaderData));
|
|
}
|
|
|
|
// duplicates ProgramCache's definition of color uniform presence
|
|
const bool singleColor = !mDescription.hasTexture
|
|
&& !mDescription.hasExternalTexture
|
|
&& !mDescription.hasGradient
|
|
&& !mDescription.hasBitmap;
|
|
mOutGlop->fill.colorEnabled = mDescription.modulate || singleColor;
|
|
|
|
verify(mDescription, *mOutGlop);
|
|
|
|
// Final step: populate program and map bounds into render target space
|
|
mOutGlop->fill.program = mCaches.programCache.get(mDescription);
|
|
}
|
|
|
|
void GlopBuilder::dump(const Glop& glop) {
|
|
ALOGD("Glop Mesh");
|
|
const Glop::Mesh& mesh = glop.mesh;
|
|
ALOGD(" primitive mode: %d", mesh.primitiveMode);
|
|
ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices);
|
|
|
|
const Glop::Mesh::Vertices& vertices = glop.mesh.vertices;
|
|
ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d",
|
|
vertices.bufferObject, vertices.attribFlags,
|
|
vertices.position, vertices.texCoord, vertices.color, vertices.stride);
|
|
ALOGD(" element count: %d", mesh.elementCount);
|
|
|
|
ALOGD("Glop Fill");
|
|
const Glop::Fill& fill = glop.fill;
|
|
ALOGD(" program %p", fill.program);
|
|
if (fill.texture.texture) {
|
|
ALOGD(" texture %p, target %d, filter %d, clamp %d",
|
|
fill.texture.texture, fill.texture.texture->target(),
|
|
fill.texture.filter, fill.texture.clamp);
|
|
if (fill.texture.textureTransform) {
|
|
fill.texture.textureTransform->dump("texture transform");
|
|
}
|
|
}
|
|
ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f",
|
|
fill.color.a, fill.color.r, fill.color.g, fill.color.b);
|
|
ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None,
|
|
" filterMode %d", (int)fill.filterMode);
|
|
ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d",
|
|
fill.skiaShaderData.skiaShaderType);
|
|
|
|
ALOGD("Glop transform");
|
|
glop.transform.modelView.dump(" model view");
|
|
glop.transform.canvas.dump(" canvas");
|
|
ALOGD_IF(glop.transform.transformFlags, " transformFlags 0x%x", glop.transform.transformFlags);
|
|
|
|
ALOGD_IF(glop.roundRectClipState, "Glop RRCS %p", glop.roundRectClipState);
|
|
|
|
ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst);
|
|
}
|
|
|
|
} /* namespace uirenderer */
|
|
} /* namespace android */
|