/* * 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" #include "Glop.h" #include "GlopBuilder.h" #include "Patch.h" #include "PathTessellator.h" #include "renderstate/OffscreenBufferPool.h" #include "renderstate/RenderState.h" #include "utils/GLUtils.h" #include "VertexBuffer.h" #include <algorithm> #include <math.h> #include <SkPaintDefaults.h> #include <SkPathOps.h> namespace android { namespace uirenderer { static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) { vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top }; vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top }; vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom }; vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom }; } void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, const MergedBakedOpList& opList) { const BakedOpState& firstState = *(opList.states[0]); const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap; AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef()); Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); TextureVertex vertices[opList.count * 4]; Rect texCoords(0, 0, 1, 1); if (entry) { entry->uvMapper.map(texCoords); } for (size_t i = 0; i < opList.count; i++) { const BakedOpState& state = *(opList.states[i]); TextureVertex* rectVerts = &vertices[i * 4]; // calculate unclipped bounds, since they'll determine texture coordinates Rect opBounds = state.op->unmappedBounds; state.computedState.transform.mapRect(opBounds); if (CC_LIKELY(state.computedState.transform.isPureTranslate())) { // pure translate, so snap (same behavior as onBitmapOp) opBounds.snapToPixelBoundaries(); } storeTexturedRect(rectVerts, opBounds, texCoords); renderer.dirtyRenderTarget(opBounds); } const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; 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) .setModelViewIdentityEmptyBounds() .build(); ClipRect renderTargetClip(opList.clip); const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr; renderer.renderGlop(nullptr, clip, glop); } void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, const MergedBakedOpList& opList) { const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op)); const BakedOpState& firstState = *(opList.states[0]); AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry( firstOp.bitmap->pixelRef()); // Batches will usually contain a small number of items so it's // worth performing a first iteration to count the exact number // 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( entry, op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); 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( entry, op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); 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 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); // Copy & transform all the vertices for the current operation TextureVertex* opVertices = opMesh->vertices.get(); for (uint32_t j = 0; j < vertexCount; j++, opVertices++) { TextureVertex::set(vertex++, opVertices->x + tx, opVertices->y + ty, opVertices->u, opVertices->v); } // 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) { renderer.dirtyRenderTarget(Rect(tx, ty, tx + op.unmappedBounds.getWidth(), ty + op.unmappedBounds.getHeight())); } 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; renderer.dirtyRenderTarget(Rect(x, y, x + quadBounds.getWidth(), y + quadBounds.getHeight())); } } } indexCount += opMesh->indexCount; } Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap); 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() .build(); ClipRect renderTargetClip(opList.clip); const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr; renderer.renderGlop(nullptr, clip, glop); } static void renderTextShadow(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& textOpState) { if (CC_LIKELY(!PaintUtils::hasTextShadow(op.paint))) return; FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); fontRenderer.setFont(op.paint, SkMatrix::I()); 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( op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions); // 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) .setRoundRectClipState(textOpState.roundRectClipState) .setMeshTexturedUnitQuad(nullptr) .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha) .setTransform(textOpState.computedState.transform, TransformFlags::None) .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height())) .build(); // 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; Rect shadowBounds = op.unmappedBounds; // STROKE 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 && (!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) { shadowBounds.outset(0.5f); } auto clipState = textOpState.computedState.clipState; if (clipState->mode != ClipMode::Rectangle || !clipState->rect.contains(shadowBounds)) { // 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); } enum class TextRenderType { Defer, Flush }; static void renderText(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state, const ClipBase* renderClip, TextRenderType renderType) { 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); fontRenderer.setFont(op.paint, SkMatrix::MakeScale( roundf(std::max(1.0f, sx)), roundf(std::max(1.0f, sy)))); 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; SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); TextDrawFunctor functor(&renderer, &state, renderClip, x, y, pureTranslate, alpha, mode, op.paint); bool forceFinish = (renderType == TextRenderType::Flush); bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr; fontRenderer.renderPosText(op.paint, localOpClip, op.glyphs, op.glyphCount, x, y, op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish); if (mustDirtyRenderTarget) { if (!pureTranslate) { transform.mapRect(layerBounds); } renderer.dirtyRenderTarget(layerBounds); } } void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer, const MergedBakedOpList& opList) { 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); } ClipRect renderTargetClip(opList.clip); const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr; for (size_t i = 0; i < opList.count; i++) { const BakedOpState& state = *(opList.states[i]); const TextOp& op = *(static_cast<const TextOp*>(state.op)); TextRenderType renderType = (i + 1 == opList.count) ? TextRenderType::Flush : TextRenderType::Defer; renderText(renderer, op, state, clip, renderType); } } namespace VertexBufferRenderFlags { enum { Offset = 0x1, ShadowInterp = 0x2, }; } static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state, const VertexBuffer& vertexBuffer, float translateX, float translateY, const SkPaint& paint, int vertexBufferRenderFlags) { if (CC_LIKELY(vertexBuffer.getVertexCount())) { bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp; const int transformFlags = vertexBufferRenderFlags & VertexBufferRenderFlags::Offset ? TransformFlags::OffsetByFudgeFactor : 0; Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshVertexBuffer(vertexBuffer) .setFillPaint(paint, state.alpha, shadowInterp) .setTransform(state.computedState.transform, transformFlags) .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds()) .build(); renderer.renderGlop(state, glop); } } static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& state, const SkPath& path, const SkPaint& paint) { 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); } static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state, float xOffset, float yOffset, PathTexture& texture, const SkPaint& paint) { Rect dest(texture.width(), texture.height()); dest.translate(xOffset + texture.left - texture.offset, yOffset + texture.top - texture.offset); Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshTexturedUnitQuad(nullptr) .setFillPathTexturePaint(texture, paint, state.alpha) .setTransform(state.computedState.transform, TransformFlags::None) .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; } void BakedOpDispatcher::onArcOp(BakedOpRenderer& renderer, const ArcOp& op, const BakedOpState& state) { // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) if (op.paint->getStyle() != SkPaint::kStroke_Style || op.paint->getPathEffect() != nullptr || op.useCenter) { PathTexture* texture = renderer.caches().pathCache.getArc( op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.startAngle, op.sweepAngle, op.useCenter, op.paint); const AutoTexture holder(texture); if (CC_LIKELY(holder.texture)) { renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, *texture, *(op.paint)); } } 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)); } } void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) { Texture* texture = renderer.getTexture(op.bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; 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) .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height())) .build(); renderer.renderGlop(state, glop); } void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) { const static UvMapper defaultUvMapper; 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(); } Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef()); const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper); for (int32_t y = 0; y < op.meshHeight; y++) { for (int32_t x = 0; x < op.meshWidth; x++) { uint32_t i = (y * (op.meshWidth + 1) + x) * 2; 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; mapper.map(u1, v1, u2, v2); 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]); } } if (!texture) { texture = renderer.caches().textureCache.get(op.bitmap); if (!texture) { return; } } const AutoTexture autoCleanup(texture); /* * TODO: handle alpha_8 textures correctly by applying paint color, but *not* * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh. */ 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) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewOffsetRect(0, 0, op.unmappedBounds) .build(); renderer.renderGlop(state, glop); } void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op, const BakedOpState& state) { Texture* texture = renderer.getTexture(op.bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); 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())); const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth()) && MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight()); 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); } void BakedOpDispatcher::onColorOp(BakedOpRenderer& renderer, const ColorOp& op, const BakedOpState& state) { SkPaint paint; paint.setColor(op.color); paint.setXfermodeMode(op.mode); 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); } void BakedOpDispatcher::onFunctorOp(BakedOpRenderer& renderer, const FunctorOp& op, const BakedOpState& state) { renderer.renderFunctor(op, state); } void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) { VertexBuffer buffer; PathTessellator::tessellateLines(op.points, op.floatCount, op.paint, state.computedState.transform, buffer); int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); } void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, const BakedOpState& state) { 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)) { renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, *texture, *(op.paint)); } } else { SkPath path; SkRect rect = getBoundsOfFill(op); path.addOval(rect); 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); } renderConvexPath(renderer, state, path, *(op.paint)); } } void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, const BakedOpState& state) { // 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: AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef()); const Patch* mesh = renderer.caches().patchCache.get( entry, op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap); 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) .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top, Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) .build(); renderer.renderGlop(state, glop); } } void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, const BakedOpState& state) { PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint); const AutoTexture holder(texture); if (CC_LIKELY(holder.texture)) { // 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)); } } void BakedOpDispatcher::onPointsOp(BakedOpRenderer& renderer, const PointsOp& op, const BakedOpState& state) { VertexBuffer buffer; PathTessellator::tessellatePoints(op.points, op.floatCount, op.paint, state.computedState.transform, buffer); 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) void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) { 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"); 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)); } } 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); } } } void BakedOpDispatcher::onRoundRectOp(BakedOpRenderer& renderer, const RoundRectOp& op, const BakedOpState& state) { if (op.paint->getPathEffect() != nullptr) { PathTexture* texture = renderer.caches().pathCache.getRoundRect( op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry, op.paint); const AutoTexture holder(texture); if (CC_LIKELY(holder.texture)) { renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, *texture, *(op.paint)); } } else { const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect( 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); } } static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha, const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) { SkPaint paint; paint.setAntiAlias(true); // want to use AlphaVertex // 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)); renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0, paint, VertexBufferRenderFlags::ShadowInterp); } 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)); renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0, paint, VertexBufferRenderFlags::ShadowInterp); } } void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) { TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult(); renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second); } void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) { 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); } void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) { renderTextShadow(renderer, op, state); renderText(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush); } void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) { // Note: can't trust clipSideFlags since we record with unmappedBounds == clip. // TODO: respect clipSideFlags, once we record with bounds auto renderTargetClip = state.computedState.clipState; 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; SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); TextDrawFunctor functor(&renderer, &state, renderTargetClip, 0.0f, 0.0f, false, alpha, mode, op.paint); bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); const Rect localSpaceClip = state.computedState.computeLocalSpaceClip(); if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip, op.glyphs, op.glyphCount, op.path, op.hOffset, op.vOffset, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) { if (mustDirtyRenderTarget) { // manually dirty render target, since TextDrawFunctor won't state.computedState.transform.mapRect(layerBounds); renderer.dirtyRenderTarget(layerBounds); } } } void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const TextureLayerOp& op, const BakedOpState& state) { const bool tryToSnap = !op.layer->getForceFilter(); float alpha = (op.layer->getAlpha() / 255.0f) * state.alpha; Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) .setRoundRectClipState(state.roundRectClipState) .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO .setFillTextureLayer(*(op.layer), alpha) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewMapUnitToRectOptionalSnap(tryToSnap, Rect(op.layer->getWidth(), op.layer->getHeight())) .build(); renderer.renderGlop(state, glop); } void renderRectForLayer(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state, int color, SkXfermode::Mode mode, SkColorFilter* colorFilter) { SkPaint paint; paint.setColor(color); paint.setXfermodeMode(mode); paint.setColorFilter(colorFilter); RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint); BakedOpDispatcher::onRectOp(renderer, rectOp, state); } void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { // Note that we don't use op->paint in this function - it's never set on a LayerOp OffscreenBuffer* buffer = *op.layerHandle; 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) .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap) .setTransform(state.computedState.transform, TransformFlags::None) .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top, Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) .build(); renderer.renderGlop(state, glop); if (!buffer->hasRenderedSinceRepaint) { buffer->hasRenderedSinceRepaint = true; if (CC_UNLIKELY(Properties::debugLayersUpdates)) { // render debug layer highlight renderRectForLayer(renderer, op, state, 0x7f00ff00, SkXfermode::Mode::kSrcOver_Mode, nullptr); } else if (CC_UNLIKELY(Properties::debugOverdraw)) { // render transparent to increment overdraw for repaint area renderRectForLayer(renderer, op, state, SK_ColorTRANSPARENT, SkXfermode::Mode::kSrcOver_Mode, nullptr); } } } void BakedOpDispatcher::onCopyToLayerOp(BakedOpRenderer& renderer, const CopyToLayerOp& op, const BakedOpState& state) { 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"); } void BakedOpDispatcher::onCopyFromLayerOp(BakedOpRenderer& renderer, const CopyFromLayerOp& op, const BakedOpState& state) { 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()); layerPaint.setXfermodeMode(SkXfermode::kDstIn_Mode); layerPaint.setColorFilter(op.paint->getColorFilter()); RectOp rectOp(state.computedState.clippedBounds, Matrix4::identity(), nullptr, &layerPaint); BakedOpDispatcher::onRectOp(renderer, rectOp, state); } OffscreenBuffer& layer = **(op.layerHandle); auto mode = PaintUtils::getXfermodeDirect(op.paint); 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); } renderer.renderState().layerPool().putOrDelete(*op.layerHandle); } } // namespace uirenderer } // namespace android