2015-11-25 13:27:33 -08:00
|
|
|
/*
|
|
|
|
* 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 "BakedOpDispatcher.h"
|
|
|
|
|
|
|
|
#include "BakedOpRenderer.h"
|
|
|
|
#include "Caches.h"
|
2017-01-19 15:37:02 -08:00
|
|
|
#include "DeferredLayerUpdater.h"
|
2015-11-25 13:27:33 -08:00
|
|
|
#include "Glop.h"
|
|
|
|
#include "GlopBuilder.h"
|
2015-12-08 17:21:58 -08:00
|
|
|
#include "Patch.h"
|
2015-12-07 17:08:25 -08:00
|
|
|
#include "PathTessellator.h"
|
2017-11-03 10:12:19 -07:00
|
|
|
#include "VertexBuffer.h"
|
2015-11-25 13:27:33 -08:00
|
|
|
#include "renderstate/OffscreenBufferPool.h"
|
|
|
|
#include "renderstate/RenderState.h"
|
|
|
|
#include "utils/GLUtils.h"
|
|
|
|
|
2015-12-07 17:08:25 -08:00
|
|
|
#include <SkPaintDefaults.h>
|
2016-03-01 13:27:54 -08:00
|
|
|
#include <SkPathOps.h>
|
2017-11-03 10:12:19 -07:00
|
|
|
#include <math.h>
|
|
|
|
#include <algorithm>
|
2015-11-25 13:27:33 -08:00
|
|
|
|
|
|
|
namespace android {
|
|
|
|
namespace uirenderer {
|
|
|
|
|
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
2016-09-28 17:34:42 -07:00
|
|
|
static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds) {
|
2017-11-03 10:12:19 -07:00
|
|
|
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};
|
2015-12-03 12:16:56 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
|
2017-11-03 10:12:19 -07:00
|
|
|
const MergedBakedOpList& opList) {
|
2015-12-03 12:16:56 -08:00
|
|
|
const BakedOpState& firstState = *(opList.states[0]);
|
2016-10-20 18:39:04 -07:00
|
|
|
Bitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap;
|
2015-12-03 12:16:56 -08:00
|
|
|
|
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
2016-09-28 17:34:42 -07:00
|
|
|
Texture* texture = renderer.caches().textureCache.get(bitmap);
|
2015-12-03 12:16:56 -08:00
|
|
|
if (!texture) return;
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
|
|
|
|
TextureVertex vertices[opList.count * 4];
|
|
|
|
for (size_t i = 0; i < opList.count; i++) {
|
|
|
|
const BakedOpState& state = *(opList.states[i]);
|
|
|
|
TextureVertex* rectVerts = &vertices[i * 4];
|
2016-03-02 12:29:56 -08:00
|
|
|
|
|
|
|
// calculate unclipped bounds, since they'll determine texture coordinates
|
|
|
|
Rect opBounds = state.op->unmappedBounds;
|
|
|
|
state.computedState.transform.mapRect(opBounds);
|
2015-12-03 12:16:56 -08:00
|
|
|
if (CC_LIKELY(state.computedState.transform.isPureTranslate())) {
|
|
|
|
// pure translate, so snap (same behavior as onBitmapOp)
|
|
|
|
opBounds.snapToPixelBoundaries();
|
|
|
|
}
|
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
2016-09-28 17:34:42 -07:00
|
|
|
storeTexturedRect(rectVerts, opBounds);
|
2015-12-03 12:16:56 -08:00
|
|
|
renderer.dirtyRenderTarget(opBounds);
|
|
|
|
}
|
|
|
|
|
|
|
|
const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
|
2017-11-03 10:12:19 -07:00
|
|
|
? TextureFillFlags::IsAlphaMaskTexture
|
|
|
|
: TextureFillFlags::None;
|
2015-12-03 12:16:56 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(firstState.roundRectClipState)
|
|
|
|
.setMeshTexturedIndexedQuads(vertices, opList.count * 6)
|
|
|
|
.setFillTexturePaint(*texture, textureFillFlags, firstState.op->paint, firstState.alpha)
|
|
|
|
.setTransform(Matrix4::identity(), TransformFlags::None)
|
2015-12-08 17:21:58 -08:00
|
|
|
.setModelViewIdentityEmptyBounds()
|
|
|
|
.build();
|
2015-12-22 16:32:23 -08:00
|
|
|
ClipRect renderTargetClip(opList.clip);
|
|
|
|
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
|
|
|
|
renderer.renderGlop(nullptr, clip, glop);
|
2015-12-08 17:21:58 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
|
2017-11-03 10:12:19 -07:00
|
|
|
const MergedBakedOpList& opList) {
|
2015-12-08 17:21:58 -08:00
|
|
|
const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op));
|
|
|
|
const BakedOpState& firstState = *(opList.states[0]);
|
|
|
|
|
|
|
|
// Batches will usually contain a small number of items so it's
|
|
|
|
// worth performing a first iteration to count the exact number
|
|
|
|
// of vertices we need in the new mesh
|
|
|
|
uint32_t totalVertices = 0;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < opList.count; i++) {
|
|
|
|
const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
|
|
|
|
|
|
|
|
// TODO: cache mesh lookups
|
|
|
|
const Patch* opMesh = renderer.caches().patchCache.get(
|
2017-11-03 10:12:19 -07:00
|
|
|
op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(),
|
|
|
|
op.unmappedBounds.getHeight(), op.patch);
|
2015-12-08 17:21:58 -08:00
|
|
|
totalVertices += opMesh->verticesCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bool dirtyRenderTarget = renderer.offscreenRenderTarget();
|
|
|
|
|
|
|
|
uint32_t indexCount = 0;
|
|
|
|
|
|
|
|
TextureVertex vertices[totalVertices];
|
|
|
|
TextureVertex* vertex = &vertices[0];
|
|
|
|
// Create a mesh that contains the transformed vertices for all the
|
|
|
|
// 9-patch objects that are part of the batch. Note that onDefer()
|
|
|
|
// enforces ops drawn by this function to have a pure translate or
|
|
|
|
// identity matrix
|
|
|
|
for (size_t i = 0; i < opList.count; i++) {
|
|
|
|
const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
|
|
|
|
const BakedOpState& state = *opList.states[i];
|
|
|
|
|
|
|
|
// TODO: cache mesh lookups
|
|
|
|
const Patch* opMesh = renderer.caches().patchCache.get(
|
2017-11-03 10:12:19 -07:00
|
|
|
op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(),
|
|
|
|
op.unmappedBounds.getHeight(), op.patch);
|
2015-12-08 17:21:58 -08:00
|
|
|
|
|
|
|
uint32_t vertexCount = opMesh->verticesCount;
|
|
|
|
if (vertexCount == 0) continue;
|
|
|
|
|
|
|
|
// We use the bounds to know where to translate our vertices
|
|
|
|
// Using patchOp->state.mBounds wouldn't work because these
|
|
|
|
// bounds are clipped
|
2017-11-03 10:12:19 -07:00
|
|
|
const float tx = floorf(state.computedState.transform.getTranslateX() +
|
|
|
|
op.unmappedBounds.left + 0.5f);
|
|
|
|
const float ty = floorf(state.computedState.transform.getTranslateY() +
|
|
|
|
op.unmappedBounds.top + 0.5f);
|
2015-12-08 17:21:58 -08:00
|
|
|
|
|
|
|
// Copy & transform all the vertices for the current operation
|
|
|
|
TextureVertex* opVertices = opMesh->vertices.get();
|
|
|
|
for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {
|
2017-11-03 10:12:19 -07:00
|
|
|
TextureVertex::set(vertex++, opVertices->x + tx, opVertices->y + ty, opVertices->u,
|
|
|
|
opVertices->v);
|
2015-12-08 17:21:58 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Dirty the current layer if possible. When the 9-patch does not
|
|
|
|
// contain empty quads we can take a shortcut and simply set the
|
|
|
|
// dirty rect to the object's bounds.
|
|
|
|
if (dirtyRenderTarget) {
|
|
|
|
if (!opMesh->hasEmptyQuads) {
|
2017-11-03 10:12:19 -07:00
|
|
|
renderer.dirtyRenderTarget(Rect(tx, ty, tx + op.unmappedBounds.getWidth(),
|
|
|
|
ty + op.unmappedBounds.getHeight()));
|
2015-12-08 17:21:58 -08:00
|
|
|
} else {
|
|
|
|
const size_t count = opMesh->quads.size();
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
const Rect& quadBounds = opMesh->quads[i];
|
|
|
|
const float x = tx + quadBounds.left;
|
|
|
|
const float y = ty + quadBounds.top;
|
2017-11-03 10:12:19 -07:00
|
|
|
renderer.dirtyRenderTarget(
|
|
|
|
Rect(x, y, x + quadBounds.getWidth(), y + quadBounds.getHeight()));
|
2015-12-08 17:21:58 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
indexCount += opMesh->indexCount;
|
|
|
|
}
|
|
|
|
|
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
2016-09-28 17:34:42 -07:00
|
|
|
Texture* texture = renderer.caches().textureCache.get(firstOp.bitmap);
|
2015-12-08 17:21:58 -08:00
|
|
|
if (!texture) return;
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
|
|
|
|
// 9 patches are built for stretching - always filter
|
|
|
|
int textureFillFlags = TextureFillFlags::ForceFilter;
|
|
|
|
if (firstOp.bitmap->colorType() == kAlpha_8_SkColorType) {
|
|
|
|
textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
|
|
|
|
}
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(firstState.roundRectClipState)
|
|
|
|
.setMeshTexturedIndexedQuads(vertices, indexCount)
|
|
|
|
.setFillTexturePaint(*texture, textureFillFlags, firstOp.paint, firstState.alpha)
|
|
|
|
.setTransform(Matrix4::identity(), TransformFlags::None)
|
|
|
|
.setModelViewIdentityEmptyBounds()
|
2015-12-03 12:16:56 -08:00
|
|
|
.build();
|
2015-12-22 16:32:23 -08:00
|
|
|
ClipRect renderTargetClip(opList.clip);
|
|
|
|
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
|
|
|
|
renderer.renderGlop(nullptr, clip, glop);
|
2015-12-03 12:16:56 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
static void renderTextShadow(BakedOpRenderer& renderer, const TextOp& op,
|
|
|
|
const BakedOpState& textOpState) {
|
2016-05-10 13:58:12 -07:00
|
|
|
if (CC_LIKELY(!PaintUtils::hasTextShadow(op.paint))) return;
|
|
|
|
|
|
|
|
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
|
|
|
|
fontRenderer.setFont(op.paint, SkMatrix::I());
|
2015-12-03 12:16:56 -08:00
|
|
|
renderer.caches().textureState().activateTexture(0);
|
|
|
|
|
|
|
|
PaintUtils::TextShadow textShadow;
|
|
|
|
if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
|
|
|
|
LOG_ALWAYS_FATAL("failed to query shadow attributes");
|
|
|
|
}
|
|
|
|
|
|
|
|
renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
|
|
|
|
ShadowTexture* texture = renderer.caches().dropShadowCache.get(
|
2016-02-05 20:10:50 -08:00
|
|
|
op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions);
|
2015-12-03 12:16:56 -08:00
|
|
|
// If the drop shadow exceeds the max texture size or couldn't be
|
|
|
|
// allocated, skip drawing
|
|
|
|
if (!texture) return;
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
|
|
|
|
const float sx = op.x - texture->left + textShadow.dx;
|
|
|
|
const float sy = op.y - texture->top + textShadow.dy;
|
|
|
|
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
2016-03-30 18:09:17 -07:00
|
|
|
.setRoundRectClipState(textOpState.roundRectClipState)
|
2015-12-03 12:16:56 -08:00
|
|
|
.setMeshTexturedUnitQuad(nullptr)
|
2016-03-30 18:09:17 -07:00
|
|
|
.setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha)
|
|
|
|
.setTransform(textOpState.computedState.transform, TransformFlags::None)
|
2015-11-10 12:19:17 -08:00
|
|
|
.setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
|
2015-12-03 12:16:56 -08:00
|
|
|
.build();
|
2016-03-30 18:09:17 -07:00
|
|
|
|
|
|
|
// Compute damage bounds and clip (since may differ from those in textOpState).
|
|
|
|
// Bounds should be same as text op, but with dx/dy offset and radius outset
|
|
|
|
// applied in local space.
|
|
|
|
auto& transform = textOpState.computedState.transform;
|
2017-11-03 10:12:19 -07:00
|
|
|
Rect shadowBounds = op.unmappedBounds; // STROKE
|
2016-03-30 18:09:17 -07:00
|
|
|
const bool expandForStroke = op.paint->getStyle() != SkPaint::kFill_Style;
|
|
|
|
if (expandForStroke) {
|
|
|
|
shadowBounds.outset(op.paint->getStrokeWidth() * 0.5f);
|
|
|
|
}
|
|
|
|
shadowBounds.translate(textShadow.dx, textShadow.dy);
|
|
|
|
shadowBounds.outset(textShadow.radius, textShadow.radius);
|
|
|
|
transform.mapRect(shadowBounds);
|
|
|
|
if (CC_UNLIKELY(expandForStroke &&
|
2017-11-03 10:12:19 -07:00
|
|
|
(!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) {
|
2016-03-30 18:09:17 -07:00
|
|
|
shadowBounds.outset(0.5f);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto clipState = textOpState.computedState.clipState;
|
2017-11-03 10:12:19 -07:00
|
|
|
if (clipState->mode != ClipMode::Rectangle || !clipState->rect.contains(shadowBounds)) {
|
2016-03-30 18:09:17 -07:00
|
|
|
// need clip, so pass it and clip bounds
|
|
|
|
shadowBounds.doIntersect(clipState->rect);
|
|
|
|
} else {
|
|
|
|
// don't need clip, ignore
|
|
|
|
clipState = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
renderer.renderGlop(&shadowBounds, clipState, glop);
|
2015-12-03 12:16:56 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
enum class TextRenderType { Defer, Flush };
|
2015-12-03 12:16:56 -08:00
|
|
|
|
2016-05-10 13:58:12 -07:00
|
|
|
static void renderText(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
|
2017-11-03 10:12:19 -07:00
|
|
|
const ClipBase* renderClip, TextRenderType renderType) {
|
2015-12-03 12:16:56 -08:00
|
|
|
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
|
|
|
|
float x = op.x;
|
|
|
|
float y = op.y;
|
|
|
|
const Matrix4& transform = state.computedState.transform;
|
|
|
|
const bool pureTranslate = transform.isPureTranslate();
|
|
|
|
if (CC_LIKELY(pureTranslate)) {
|
|
|
|
x = floorf(x + transform.getTranslateX() + 0.5f);
|
|
|
|
y = floorf(y + transform.getTranslateY() + 0.5f);
|
|
|
|
fontRenderer.setFont(op.paint, SkMatrix::I());
|
|
|
|
fontRenderer.setTextureFiltering(false);
|
|
|
|
} else if (CC_UNLIKELY(transform.isPerspective())) {
|
|
|
|
fontRenderer.setFont(op.paint, SkMatrix::I());
|
|
|
|
fontRenderer.setTextureFiltering(true);
|
|
|
|
} else {
|
|
|
|
// We only pass a partial transform to the font renderer. That partial
|
|
|
|
// matrix defines how glyphs are rasterized. Typically we want glyphs
|
|
|
|
// to be rasterized at their final size on screen, which means the partial
|
|
|
|
// matrix needs to take the scale factor into account.
|
|
|
|
// When a partial matrix is used to transform glyphs during rasterization,
|
|
|
|
// the mesh is generated with the inverse transform (in the case of scale,
|
|
|
|
// the mesh is generated at 1.0 / scale for instance.) This allows us to
|
|
|
|
// apply the full transform matrix at draw time in the vertex shader.
|
|
|
|
// Applying the full matrix in the shader is the easiest way to handle
|
|
|
|
// rotation and perspective and allows us to always generated quads in the
|
|
|
|
// font renderer which greatly simplifies the code, clipping in particular.
|
|
|
|
float sx, sy;
|
|
|
|
transform.decomposeScale(sx, sy);
|
2017-11-03 10:12:19 -07:00
|
|
|
fontRenderer.setFont(op.paint, SkMatrix::MakeScale(roundf(std::max(1.0f, sx)),
|
|
|
|
roundf(std::max(1.0f, sy))));
|
2015-12-03 12:16:56 -08:00
|
|
|
fontRenderer.setTextureFiltering(true);
|
|
|
|
}
|
|
|
|
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
|
|
|
|
|
|
|
|
int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
|
2016-10-07 15:59:20 -04:00
|
|
|
SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint);
|
2017-11-03 10:12:19 -07:00
|
|
|
TextDrawFunctor functor(&renderer, &state, renderClip, x, y, pureTranslate, alpha, mode,
|
|
|
|
op.paint);
|
2015-12-03 12:16:56 -08:00
|
|
|
|
|
|
|
bool forceFinish = (renderType == TextRenderType::Flush);
|
|
|
|
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
|
2015-12-22 16:32:23 -08:00
|
|
|
const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr;
|
2017-11-03 10:12:19 -07:00
|
|
|
fontRenderer.renderPosText(op.paint, localOpClip, op.glyphs, op.glyphCount, x, y, op.positions,
|
|
|
|
mustDirtyRenderTarget ? &layerBounds : nullptr, &functor,
|
|
|
|
forceFinish);
|
2015-12-03 12:16:56 -08:00
|
|
|
|
|
|
|
if (mustDirtyRenderTarget) {
|
|
|
|
if (!pureTranslate) {
|
|
|
|
transform.mapRect(layerBounds);
|
|
|
|
}
|
|
|
|
renderer.dirtyRenderTarget(layerBounds);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
|
2017-11-03 10:12:19 -07:00
|
|
|
const MergedBakedOpList& opList) {
|
2016-05-10 13:58:12 -07:00
|
|
|
for (size_t i = 0; i < opList.count; i++) {
|
|
|
|
const BakedOpState& state = *(opList.states[i]);
|
|
|
|
const TextOp& op = *(static_cast<const TextOp*>(state.op));
|
|
|
|
renderTextShadow(renderer, op, state);
|
|
|
|
}
|
|
|
|
|
2015-12-22 16:32:23 -08:00
|
|
|
ClipRect renderTargetClip(opList.clip);
|
|
|
|
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
|
2015-12-03 12:16:56 -08:00
|
|
|
for (size_t i = 0; i < opList.count; i++) {
|
|
|
|
const BakedOpState& state = *(opList.states[i]);
|
|
|
|
const TextOp& op = *(static_cast<const TextOp*>(state.op));
|
2017-11-03 10:12:19 -07:00
|
|
|
TextRenderType renderType =
|
|
|
|
(i + 1 == opList.count) ? TextRenderType::Flush : TextRenderType::Defer;
|
2016-05-10 13:58:12 -07:00
|
|
|
renderText(renderer, op, state, clip, renderType);
|
2015-12-03 12:16:56 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-07 17:08:25 -08:00
|
|
|
namespace VertexBufferRenderFlags {
|
2017-11-03 10:12:19 -07:00
|
|
|
enum {
|
|
|
|
Offset = 0x1,
|
|
|
|
ShadowInterp = 0x2,
|
|
|
|
};
|
2015-12-07 17:08:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state,
|
2017-11-03 10:12:19 -07:00
|
|
|
const VertexBuffer& vertexBuffer, float translateX, float translateY,
|
|
|
|
const SkPaint& paint, int vertexBufferRenderFlags) {
|
2015-12-07 17:08:25 -08:00
|
|
|
if (CC_LIKELY(vertexBuffer.getVertexCount())) {
|
|
|
|
bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp;
|
2016-04-20 14:20:18 -07:00
|
|
|
const int transformFlags = vertexBufferRenderFlags & VertexBufferRenderFlags::Offset
|
2017-11-03 10:12:19 -07:00
|
|
|
? TransformFlags::OffsetByFudgeFactor
|
|
|
|
: 0;
|
2016-04-28 16:59:42 -07:00
|
|
|
|
2015-12-07 17:08:25 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
2016-04-28 16:59:42 -07:00
|
|
|
.setMeshVertexBuffer(vertexBuffer)
|
|
|
|
.setFillPaint(paint, state.alpha, shadowInterp)
|
2015-12-07 17:08:25 -08:00
|
|
|
.setTransform(state.computedState.transform, transformFlags)
|
|
|
|
.setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds())
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& state,
|
2017-11-03 10:12:19 -07:00
|
|
|
const SkPath& path, const SkPaint& paint) {
|
2015-12-07 17:08:25 -08:00
|
|
|
VertexBuffer vertexBuffer;
|
|
|
|
// TODO: try clipping large paths to viewport
|
|
|
|
PathTessellator::tessellatePath(path, &paint, state.computedState.transform, vertexBuffer);
|
|
|
|
renderVertexBuffer(renderer, state, vertexBuffer, 0.0f, 0.0f, paint, 0);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state, float xOffset,
|
|
|
|
float yOffset, PathTexture& texture, const SkPaint& paint) {
|
2015-11-10 12:19:17 -08:00
|
|
|
Rect dest(texture.width(), texture.height());
|
2017-11-03 10:12:19 -07:00
|
|
|
dest.translate(xOffset + texture.left - texture.offset, yOffset + texture.top - texture.offset);
|
2015-12-07 17:08:25 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshTexturedUnitQuad(nullptr)
|
2016-02-22 16:42:24 -08:00
|
|
|
.setFillPathTexturePaint(texture, paint, state.alpha)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
2015-12-07 17:08:25 -08:00
|
|
|
.setModelViewMapUnitToRect(dest)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
|
|
|
SkRect getBoundsOfFill(const RecordedOp& op) {
|
|
|
|
SkRect bounds = op.unmappedBounds.toSkRect();
|
|
|
|
if (op.paint->getStyle() == SkPaint::kStrokeAndFill_Style) {
|
|
|
|
float outsetDistance = op.paint->getStrokeWidth() / 2;
|
|
|
|
bounds.outset(outsetDistance, outsetDistance);
|
|
|
|
}
|
|
|
|
return bounds;
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onArcOp(BakedOpRenderer& renderer, const ArcOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
// TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
|
2017-11-03 10:12:19 -07:00
|
|
|
if (op.paint->getStyle() != SkPaint::kStroke_Style || op.paint->getPathEffect() != nullptr ||
|
|
|
|
op.useCenter) {
|
2015-12-07 17:08:25 -08:00
|
|
|
PathTexture* texture = renderer.caches().pathCache.getArc(
|
2017-11-03 10:12:19 -07:00
|
|
|
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.startAngle,
|
|
|
|
op.sweepAngle, op.useCenter, op.paint);
|
2015-12-07 17:08:25 -08:00
|
|
|
const AutoTexture holder(texture);
|
|
|
|
if (CC_LIKELY(holder.texture)) {
|
2016-02-29 13:34:43 -08:00
|
|
|
renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
|
2017-11-03 10:12:19 -07:00
|
|
|
*texture, *(op.paint));
|
2015-12-07 17:08:25 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
SkRect rect = getBoundsOfFill(op);
|
|
|
|
SkPath path;
|
|
|
|
if (op.useCenter) {
|
|
|
|
path.moveTo(rect.centerX(), rect.centerY());
|
|
|
|
}
|
|
|
|
path.arcTo(rect, op.startAngle, op.sweepAngle, !op.useCenter);
|
|
|
|
if (op.useCenter) {
|
|
|
|
path.close();
|
|
|
|
}
|
|
|
|
renderConvexPath(renderer, state, path, *(op.paint));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-11-25 13:27:33 -08:00
|
|
|
Texture* texture = renderer.getTexture(op.bitmap);
|
|
|
|
if (!texture) return;
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
|
|
|
|
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
|
2017-11-03 10:12:19 -07:00
|
|
|
? TextureFillFlags::IsAlphaMaskTexture
|
|
|
|
: TextureFillFlags::None;
|
2015-11-25 13:27:33 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshTexturedUnitQuad(texture->uvMapper)
|
|
|
|
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
|
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
2015-11-10 12:19:17 -08:00
|
|
|
.setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
|
2015-11-25 13:27:33 -08:00
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op,
|
|
|
|
const BakedOpState& state) {
|
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
2016-09-28 17:34:42 -07:00
|
|
|
Texture* texture = renderer.caches().textureCache.get(op.bitmap);
|
|
|
|
if (!texture) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
|
2015-12-08 17:21:58 -08:00
|
|
|
const uint32_t elementCount = op.meshWidth * op.meshHeight * 6;
|
|
|
|
|
|
|
|
std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]);
|
|
|
|
ColorTextureVertex* vertex = &mesh[0];
|
|
|
|
|
|
|
|
const int* colors = op.colors;
|
|
|
|
std::unique_ptr<int[]> tempColors;
|
|
|
|
if (!colors) {
|
|
|
|
uint32_t colorsCount = (op.meshWidth + 1) * (op.meshHeight + 1);
|
|
|
|
tempColors.reset(new int[colorsCount]);
|
|
|
|
memset(tempColors.get(), 0xff, colorsCount * sizeof(int));
|
|
|
|
colors = tempColors.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
float u1 = float(x) / op.meshWidth;
|
|
|
|
float u2 = float(x + 1) / op.meshWidth;
|
|
|
|
float v1 = float(y) / op.meshHeight;
|
|
|
|
float v2 = float(y + 1) / op.meshHeight;
|
|
|
|
|
|
|
|
int ax = i + (op.meshWidth + 1) * 2;
|
|
|
|
int ay = ax + 1;
|
|
|
|
int bx = i;
|
|
|
|
int by = bx + 1;
|
|
|
|
int cx = i + 2;
|
|
|
|
int cy = cx + 1;
|
|
|
|
int dx = i + (op.meshWidth + 1) * 2 + 2;
|
|
|
|
int dy = dx + 1;
|
|
|
|
|
|
|
|
const float* vertices = op.vertices;
|
|
|
|
ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
|
|
|
|
ColorTextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2, colors[ax / 2]);
|
|
|
|
ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
|
|
|
|
|
|
|
|
ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
|
|
|
|
ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
|
|
|
|
ColorTextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1, colors[cx / 2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO: handle alpha_8 textures correctly by applying paint color, but *not*
|
|
|
|
* shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh.
|
|
|
|
*/
|
|
|
|
const int textureFillFlags = TextureFillFlags::None;
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshColoredTexturedMesh(mesh.get(), elementCount)
|
|
|
|
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
2015-12-08 17:21:58 -08:00
|
|
|
.setModelViewOffsetRect(0, 0, op.unmappedBounds)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-08 17:21:58 -08:00
|
|
|
Texture* texture = renderer.getTexture(op.bitmap);
|
|
|
|
if (!texture) return;
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
|
2015-11-10 12:19:17 -08:00
|
|
|
Rect uv(std::max(0.0f, op.src.left / texture->width()),
|
|
|
|
std::max(0.0f, op.src.top / texture->height()),
|
|
|
|
std::min(1.0f, op.src.right / texture->width()),
|
|
|
|
std::min(1.0f, op.src.bottom / texture->height()));
|
2015-12-08 17:21:58 -08:00
|
|
|
|
|
|
|
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
|
2017-11-03 10:12:19 -07:00
|
|
|
? TextureFillFlags::IsAlphaMaskTexture
|
|
|
|
: TextureFillFlags::None;
|
|
|
|
const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth()) &&
|
|
|
|
MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight());
|
2015-12-08 17:21:58 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshTexturedUvQuad(texture->uvMapper, uv)
|
|
|
|
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
|
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
|
|
|
.setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onColorOp(BakedOpRenderer& renderer, const ColorOp& op,
|
|
|
|
const BakedOpState& state) {
|
2016-03-25 14:17:49 -07:00
|
|
|
SkPaint paint;
|
|
|
|
paint.setColor(op.color);
|
2016-10-07 15:59:20 -04:00
|
|
|
paint.setBlendMode(op.mode);
|
2016-03-25 14:17:49 -07:00
|
|
|
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshUnitQuad()
|
|
|
|
.setFillPaint(paint, state.alpha)
|
|
|
|
.setTransform(Matrix4::identity(), TransformFlags::None)
|
|
|
|
.setModelViewMapUnitToRect(state.computedState.clipState->rect)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onFunctorOp(BakedOpRenderer& renderer, const FunctorOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-10 16:25:13 -08:00
|
|
|
renderer.renderFunctor(op, state);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
VertexBuffer buffer;
|
|
|
|
PathTessellator::tessellateLines(op.points, op.floatCount, op.paint,
|
2017-11-03 10:12:19 -07:00
|
|
|
state.computedState.transform, buffer);
|
2015-12-07 17:08:25 -08:00
|
|
|
int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset;
|
|
|
|
renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags);
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
if (op.paint->getPathEffect() != nullptr) {
|
|
|
|
PathTexture* texture = renderer.caches().pathCache.getOval(
|
|
|
|
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint);
|
|
|
|
const AutoTexture holder(texture);
|
|
|
|
if (CC_LIKELY(holder.texture)) {
|
2016-04-26 15:35:20 -07:00
|
|
|
renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
|
2017-11-03 10:12:19 -07:00
|
|
|
*texture, *(op.paint));
|
2015-12-07 17:08:25 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
SkPath path;
|
|
|
|
SkRect rect = getBoundsOfFill(op);
|
|
|
|
path.addOval(rect);
|
2016-03-01 13:27:54 -08:00
|
|
|
|
|
|
|
if (state.computedState.localProjectionPathMask != nullptr) {
|
|
|
|
// Mask the ripple path by the local space projection mask in local space.
|
|
|
|
// Note that this can create CCW paths.
|
|
|
|
Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path);
|
|
|
|
}
|
2015-12-07 17:08:25 -08:00
|
|
|
renderConvexPath(renderer, state, path, *(op.paint));
|
|
|
|
}
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-08 17:21:58 -08:00
|
|
|
// 9 patches are built for stretching - always filter
|
|
|
|
int textureFillFlags = TextureFillFlags::ForceFilter;
|
|
|
|
if (op.bitmap->colorType() == kAlpha_8_SkColorType) {
|
|
|
|
textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: avoid redoing the below work each frame:
|
2017-11-03 10:12:19 -07:00
|
|
|
const Patch* mesh = renderer.caches().patchCache.get(op.bitmap->width(), op.bitmap->height(),
|
|
|
|
op.unmappedBounds.getWidth(),
|
|
|
|
op.unmappedBounds.getHeight(), op.patch);
|
2015-12-08 17:21:58 -08:00
|
|
|
|
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
2016-09-28 17:34:42 -07:00
|
|
|
Texture* texture = renderer.caches().textureCache.get(op.bitmap);
|
2016-01-12 13:45:34 -08:00
|
|
|
if (CC_LIKELY(texture)) {
|
|
|
|
const AutoTexture autoCleanup(texture);
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshPatchQuads(*mesh)
|
|
|
|
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
|
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setModelViewOffsetRectSnap(
|
|
|
|
op.unmappedBounds.left, op.unmappedBounds.top,
|
2016-01-12 13:45:34 -08:00
|
|
|
Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
2015-12-08 17:21:58 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint);
|
|
|
|
const AutoTexture holder(texture);
|
|
|
|
if (CC_LIKELY(holder.texture)) {
|
2016-02-22 16:42:24 -08:00
|
|
|
// Unlike other callers to renderPathTexture, no offsets are used because PathOp doesn't
|
|
|
|
// have any translate built in, other than what's in the SkPath itself
|
|
|
|
renderPathTexture(renderer, state, 0, 0, *texture, *(op.paint));
|
2015-12-07 17:08:25 -08:00
|
|
|
}
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onPointsOp(BakedOpRenderer& renderer, const PointsOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
VertexBuffer buffer;
|
|
|
|
PathTessellator::tessellatePoints(op.points, op.floatCount, op.paint,
|
2017-11-03 10:12:19 -07:00
|
|
|
state.computedState.transform, buffer);
|
2015-12-07 17:08:25 -08:00
|
|
|
int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset;
|
|
|
|
renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
// See SkPaintDefaults.h
|
|
|
|
#define SkPaintDefaults_MiterLimit SkIntToScalar(4)
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
if (op.paint->getStyle() != SkPaint::kFill_Style) {
|
|
|
|
// only fill + default miter is supported by drawConvexPath, since others must handle joins
|
|
|
|
static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed");
|
2017-11-03 10:12:19 -07:00
|
|
|
if (CC_UNLIKELY(op.paint->getPathEffect() != nullptr ||
|
|
|
|
op.paint->getStrokeJoin() != SkPaint::kMiter_Join ||
|
|
|
|
op.paint->getStrokeMiter() != SkPaintDefaults_MiterLimit)) {
|
|
|
|
PathTexture* texture = renderer.caches().pathCache.getRect(
|
|
|
|
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint);
|
|
|
|
const AutoTexture holder(texture);
|
|
|
|
if (CC_LIKELY(holder.texture)) {
|
|
|
|
renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
|
|
|
|
*texture, *(op.paint));
|
|
|
|
}
|
2015-12-07 17:08:25 -08:00
|
|
|
} else {
|
|
|
|
SkPath path;
|
|
|
|
path.addRect(getBoundsOfFill(op));
|
|
|
|
renderConvexPath(renderer, state, path, *(op.paint));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (op.paint->isAntiAlias() && !state.computedState.transform.isSimple()) {
|
|
|
|
SkPath path;
|
|
|
|
path.addRect(op.unmappedBounds.toSkRect());
|
|
|
|
renderConvexPath(renderer, state, path, *(op.paint));
|
|
|
|
} else {
|
|
|
|
// render simple unit quad, no tessellation required
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshUnitQuad()
|
|
|
|
.setFillPaint(*op.paint, state.alpha)
|
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
|
|
|
.setModelViewMapUnitToRect(op.unmappedBounds)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onRoundRectOp(BakedOpRenderer& renderer, const RoundRectOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-07 17:08:25 -08:00
|
|
|
if (op.paint->getPathEffect() != nullptr) {
|
|
|
|
PathTexture* texture = renderer.caches().pathCache.getRoundRect(
|
2017-11-03 10:12:19 -07:00
|
|
|
op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry,
|
|
|
|
op.paint);
|
2015-12-07 17:08:25 -08:00
|
|
|
const AutoTexture holder(texture);
|
|
|
|
if (CC_LIKELY(holder.texture)) {
|
2016-02-22 16:42:24 -08:00
|
|
|
renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top,
|
2017-11-03 10:12:19 -07:00
|
|
|
*texture, *(op.paint));
|
2015-12-07 17:08:25 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect(
|
2017-11-03 10:12:19 -07:00
|
|
|
state.computedState.transform, *(op.paint), op.unmappedBounds.getWidth(),
|
|
|
|
op.unmappedBounds.getHeight(), op.rx, op.ry);
|
|
|
|
renderVertexBuffer(renderer, state, *buffer, op.unmappedBounds.left, op.unmappedBounds.top,
|
|
|
|
*(op.paint), 0);
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha,
|
2017-11-03 10:12:19 -07:00
|
|
|
const VertexBuffer* ambientShadowVertexBuffer,
|
|
|
|
const VertexBuffer* spotShadowVertexBuffer) {
|
2015-11-25 13:27:33 -08:00
|
|
|
SkPaint paint;
|
2017-11-03 10:12:19 -07:00
|
|
|
paint.setAntiAlias(true); // want to use AlphaVertex
|
2015-11-25 13:27:33 -08:00
|
|
|
|
|
|
|
// The caller has made sure casterAlpha > 0.
|
|
|
|
uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha;
|
|
|
|
if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
|
|
|
|
ambientShadowAlpha = Properties::overrideAmbientShadowStrength;
|
|
|
|
}
|
|
|
|
if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
|
|
|
|
paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha));
|
2017-11-03 10:12:19 -07:00
|
|
|
renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0, paint,
|
|
|
|
VertexBufferRenderFlags::ShadowInterp);
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha;
|
|
|
|
if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
|
|
|
|
spotShadowAlpha = Properties::overrideSpotShadowStrength;
|
|
|
|
}
|
|
|
|
if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
|
|
|
|
paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha));
|
2017-11-03 10:12:19 -07:00
|
|
|
renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0, paint,
|
|
|
|
VertexBufferRenderFlags::ShadowInterp);
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op,
|
|
|
|
const BakedOpState& state) {
|
2016-02-03 15:52:25 -08:00
|
|
|
TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult();
|
2015-11-25 13:27:33 -08:00
|
|
|
renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-11-25 13:27:33 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
|
|
|
|
.setFillPaint(*op.paint, state.alpha)
|
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
|
|
|
.setModelViewOffsetRect(0, 0, op.unmappedBounds)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op,
|
|
|
|
const BakedOpState& state) {
|
2016-05-10 13:58:12 -07:00
|
|
|
renderTextShadow(renderer, op, state);
|
|
|
|
renderText(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush);
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op,
|
|
|
|
const BakedOpState& state) {
|
2015-12-15 10:34:36 -08:00
|
|
|
// Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
|
|
|
|
// TODO: respect clipSideFlags, once we record with bounds
|
2015-12-22 16:32:23 -08:00
|
|
|
auto renderTargetClip = state.computedState.clipState;
|
2015-12-15 10:34:36 -08:00
|
|
|
|
|
|
|
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
|
|
|
|
fontRenderer.setFont(op.paint, SkMatrix::I());
|
|
|
|
fontRenderer.setTextureFiltering(true);
|
|
|
|
|
|
|
|
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
|
|
|
|
|
|
|
|
int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
|
2016-10-07 15:59:20 -04:00
|
|
|
SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint);
|
2017-11-03 10:12:19 -07:00
|
|
|
TextDrawFunctor functor(&renderer, &state, renderTargetClip, 0.0f, 0.0f, false, alpha, mode,
|
|
|
|
op.paint);
|
2015-12-15 10:34:36 -08:00
|
|
|
|
|
|
|
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
|
|
|
|
const Rect localSpaceClip = state.computedState.computeLocalSpaceClip();
|
2017-11-03 10:12:19 -07:00
|
|
|
if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip, op.glyphs, op.glyphCount, op.path,
|
|
|
|
op.hOffset, op.vOffset,
|
|
|
|
mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) {
|
2015-12-15 10:34:36 -08:00
|
|
|
if (mustDirtyRenderTarget) {
|
|
|
|
// manually dirty render target, since TextDrawFunctor won't
|
|
|
|
state.computedState.transform.mapRect(layerBounds);
|
|
|
|
renderer.dirtyRenderTarget(layerBounds);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const TextureLayerOp& op,
|
|
|
|
const BakedOpState& state) {
|
2017-01-19 15:37:02 -08:00
|
|
|
GlLayer* layer = static_cast<GlLayer*>(op.layerHandle->backingLayer());
|
|
|
|
if (!layer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const bool tryToSnap = layer->getForceFilter();
|
|
|
|
float alpha = (layer->getAlpha() / 255.0f) * state.alpha;
|
2015-12-16 14:27:20 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO
|
2017-01-19 15:37:02 -08:00
|
|
|
.setFillTextureLayer(*(layer), alpha)
|
2015-12-16 14:27:20 -08:00
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setModelViewMapUnitToRectOptionalSnap(tryToSnap,
|
|
|
|
Rect(layer->getWidth(), layer->getHeight()))
|
2015-12-16 14:27:20 -08:00
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
|
|
|
|
2016-05-12 17:48:51 -07:00
|
|
|
void renderRectForLayer(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state,
|
2017-11-03 10:12:19 -07:00
|
|
|
int color, SkBlendMode mode, SkColorFilter* colorFilter) {
|
2016-05-12 17:48:51 -07:00
|
|
|
SkPaint paint;
|
|
|
|
paint.setColor(color);
|
2016-10-07 15:59:20 -04:00
|
|
|
paint.setBlendMode(mode);
|
|
|
|
paint.setColorFilter(sk_ref_sp(colorFilter));
|
2016-05-12 17:48:51 -07:00
|
|
|
RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint);
|
|
|
|
BakedOpDispatcher::onRectOp(renderer, rectOp, state);
|
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op,
|
|
|
|
const BakedOpState& state) {
|
2016-02-02 16:10:32 -08:00
|
|
|
// Note that we don't use op->paint in this function - it's never set on a LayerOp
|
2015-11-25 13:27:33 -08:00
|
|
|
OffscreenBuffer* buffer = *op.layerHandle;
|
|
|
|
|
2016-05-31 14:18:02 -07:00
|
|
|
if (CC_UNLIKELY(!buffer)) return;
|
|
|
|
|
|
|
|
float layerAlpha = op.alpha * state.alpha;
|
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode,
|
|
|
|
Blend::ModeOrderSwap::NoSwap)
|
2016-05-31 14:18:02 -07:00
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
2017-11-03 10:12:19 -07:00
|
|
|
.setModelViewOffsetRectSnap(
|
|
|
|
op.unmappedBounds.left, op.unmappedBounds.top,
|
2016-05-31 14:18:02 -07:00
|
|
|
Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
2016-05-12 17:48:51 -07:00
|
|
|
|
2016-05-31 14:18:02 -07:00
|
|
|
if (!buffer->hasRenderedSinceRepaint) {
|
2016-05-12 17:48:51 -07:00
|
|
|
buffer->hasRenderedSinceRepaint = true;
|
|
|
|
if (CC_UNLIKELY(Properties::debugLayersUpdates)) {
|
|
|
|
// render debug layer highlight
|
2017-11-03 10:12:19 -07:00
|
|
|
renderRectForLayer(renderer, op, state, 0x7f00ff00, SkBlendMode::kSrcOver, nullptr);
|
2016-05-12 17:48:51 -07:00
|
|
|
} else if (CC_UNLIKELY(Properties::debugOverdraw)) {
|
|
|
|
// render transparent to increment overdraw for repaint area
|
2017-11-03 10:12:19 -07:00
|
|
|
renderRectForLayer(renderer, op, state, SK_ColorTRANSPARENT, SkBlendMode::kSrcOver,
|
|
|
|
nullptr);
|
2016-05-12 17:48:51 -07:00
|
|
|
}
|
|
|
|
}
|
2015-11-25 13:27:33 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onCopyToLayerOp(BakedOpRenderer& renderer, const CopyToLayerOp& op,
|
|
|
|
const BakedOpState& state) {
|
2016-01-07 17:41:40 -08:00
|
|
|
LOG_ALWAYS_FATAL_IF(*(op.layerHandle) != nullptr, "layer already exists!");
|
|
|
|
*(op.layerHandle) = renderer.copyToLayer(state.computedState.clippedBounds);
|
|
|
|
LOG_ALWAYS_FATAL_IF(*op.layerHandle == nullptr, "layer copy failed");
|
2016-01-06 09:16:05 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
void BakedOpDispatcher::onCopyFromLayerOp(BakedOpRenderer& renderer, const CopyFromLayerOp& op,
|
|
|
|
const BakedOpState& state) {
|
2016-01-07 17:41:40 -08:00
|
|
|
LOG_ALWAYS_FATAL_IF(*op.layerHandle == nullptr, "no layer to draw underneath!");
|
|
|
|
if (!state.computedState.clippedBounds.isEmpty()) {
|
|
|
|
if (op.paint && op.paint->getAlpha() < 255) {
|
|
|
|
SkPaint layerPaint;
|
|
|
|
layerPaint.setAlpha(op.paint->getAlpha());
|
2016-10-07 15:59:20 -04:00
|
|
|
layerPaint.setBlendMode(SkBlendMode::kDstIn);
|
|
|
|
layerPaint.setColorFilter(sk_ref_sp(op.paint->getColorFilter()));
|
2017-11-03 10:12:19 -07:00
|
|
|
RectOp rectOp(state.computedState.clippedBounds, Matrix4::identity(), nullptr,
|
|
|
|
&layerPaint);
|
2016-01-07 17:41:40 -08:00
|
|
|
BakedOpDispatcher::onRectOp(renderer, rectOp, state);
|
|
|
|
}
|
|
|
|
|
|
|
|
OffscreenBuffer& layer = **(op.layerHandle);
|
2016-10-07 15:59:20 -04:00
|
|
|
auto mode = PaintUtils::getBlendModeDirect(op.paint);
|
2016-01-07 17:41:40 -08:00
|
|
|
Glop glop;
|
|
|
|
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
|
|
|
|
.setRoundRectClipState(state.roundRectClipState)
|
|
|
|
.setMeshTexturedUvQuad(nullptr, layer.getTextureCoordinates())
|
|
|
|
.setFillLayer(layer.texture, nullptr, 1.0f, mode, Blend::ModeOrderSwap::Swap)
|
|
|
|
.setTransform(state.computedState.transform, TransformFlags::None)
|
|
|
|
.setModelViewMapUnitToRect(state.computedState.clippedBounds)
|
|
|
|
.build();
|
|
|
|
renderer.renderGlop(state, glop);
|
|
|
|
}
|
2016-01-15 13:17:09 -08:00
|
|
|
renderer.renderState().layerPool().putOrDelete(*op.layerHandle);
|
2016-01-06 09:16:05 -08:00
|
|
|
}
|
|
|
|
|
2017-11-03 10:12:19 -07:00
|
|
|
} // namespace uirenderer
|
|
|
|
} // namespace android
|