Stencil support in new recorder/reorderer
bug:22480459 bug:26358504 Adds complex (non-rectangular) clipping support, and overdraw visualization. Doesn't support stencil clipping in layers. Change-Id: I8d10c7f1d2769ab5756774ca672344cc09901f87
This commit is contained in:
@@ -108,6 +108,7 @@ ifeq (true, $(HWUI_NEW_OPS))
|
||||
hwui_src_files += \
|
||||
BakedOpDispatcher.cpp \
|
||||
BakedOpRenderer.cpp \
|
||||
BakedOpState.cpp \
|
||||
OpReorderer.cpp \
|
||||
RecordingCanvas.cpp
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer,
|
||||
.setTransform(Matrix4::identity(), TransformFlags::None)
|
||||
.setModelViewIdentityEmptyBounds()
|
||||
.build();
|
||||
renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
|
||||
ClipRect renderTargetClip(opList.clip);
|
||||
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
|
||||
renderer.renderGlop(nullptr, clip, glop);
|
||||
}
|
||||
|
||||
void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
|
||||
@@ -183,7 +185,9 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
|
||||
.setTransform(Matrix4::identity(), TransformFlags::None)
|
||||
.setModelViewIdentityEmptyBounds()
|
||||
.build();
|
||||
renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
|
||||
ClipRect renderTargetClip(opList.clip);
|
||||
const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr;
|
||||
renderer.renderGlop(nullptr, clip, glop);
|
||||
}
|
||||
|
||||
static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
|
||||
@@ -224,7 +228,7 @@ enum class TextRenderType {
|
||||
};
|
||||
|
||||
static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
|
||||
const Rect* renderClip, TextRenderType renderType) {
|
||||
const ClipBase* renderClip, TextRenderType renderType) {
|
||||
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
|
||||
|
||||
if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
|
||||
@@ -272,7 +276,7 @@ static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const Bake
|
||||
|
||||
bool forceFinish = (renderType == TextRenderType::Flush);
|
||||
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
|
||||
const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect : nullptr;
|
||||
const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr;
|
||||
fontRenderer.renderPosText(op.paint, localOpClip,
|
||||
(const char*) op.glyphs, op.glyphCount, x, y,
|
||||
op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish);
|
||||
@@ -287,7 +291,8 @@ static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const Bake
|
||||
|
||||
void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
|
||||
const MergedBakedOpList& opList) {
|
||||
const Rect* clip = opList.clipSideFlags ? &opList.clip : nullptr;
|
||||
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));
|
||||
@@ -701,14 +706,13 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR
|
||||
}
|
||||
|
||||
void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
|
||||
const Rect* clip = state.computedState.clipSideFlags ? &state.computedState.clipRect : nullptr;
|
||||
renderTextOp(renderer, op, state, clip, TextRenderType::Flush);
|
||||
renderTextOp(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
|
||||
const Rect* renderTargetClip = &state.computedState.clipRect;
|
||||
auto renderTargetClip = state.computedState.clipState;
|
||||
|
||||
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
|
||||
fontRenderer.setFont(op.paint, SkMatrix::I());
|
||||
|
||||
@@ -62,15 +62,17 @@ void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const
|
||||
void BakedOpRenderer::endLayer() {
|
||||
mRenderTarget.offscreenBuffer->updateMeshFromRegion();
|
||||
mRenderTarget.offscreenBuffer = nullptr;
|
||||
mRenderTarget.lastStencilClip = nullptr;
|
||||
|
||||
// Detach the texture from the FBO
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
|
||||
mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
|
||||
mRenderTarget.frameBufferId = -1;
|
||||
mRenderTarget.frameBufferId = 0;
|
||||
}
|
||||
|
||||
void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {
|
||||
LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "primary framebufferId must be 0");
|
||||
mRenderState.bindFramebuffer(0);
|
||||
setViewport(width, height);
|
||||
mCaches.clearGarbage();
|
||||
@@ -78,9 +80,39 @@ void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& re
|
||||
if (!mOpaque) {
|
||||
clearColorBuffer(repaintRect);
|
||||
}
|
||||
|
||||
mRenderState.debugOverdraw(true, true);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::endFrame() {
|
||||
void BakedOpRenderer::endFrame(const Rect& repaintRect) {
|
||||
if (CC_UNLIKELY(Properties::debugOverdraw)) {
|
||||
ClipRect overdrawClip(repaintRect);
|
||||
Rect viewportRect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight);
|
||||
// overdraw visualization
|
||||
for (int i = 1; i <= 4; i++) {
|
||||
if (i < 4) {
|
||||
// nth level of overdraw tests for n+1 draws per pixel
|
||||
mRenderState.stencil().enableDebugTest(i + 1, false);
|
||||
} else {
|
||||
// 4th level tests for 4 or higher draws per pixel
|
||||
mRenderState.stencil().enableDebugTest(4, true);
|
||||
}
|
||||
|
||||
SkPaint paint;
|
||||
paint.setColor(mCaches.getOverdrawColor(i));
|
||||
Glop glop;
|
||||
GlopBuilder(mRenderState, mCaches, &glop)
|
||||
.setRoundRectClipState(nullptr)
|
||||
.setMeshUnitQuad()
|
||||
.setFillPaint(paint, 1.0f)
|
||||
.setTransform(Matrix4::identity(), TransformFlags::None)
|
||||
.setModelViewMapUnitToRect(viewportRect)
|
||||
.build();
|
||||
renderGlop(nullptr, &overdrawClip, glop);
|
||||
}
|
||||
mRenderState.stencil().disable();
|
||||
}
|
||||
|
||||
mCaches.pathCache.trim();
|
||||
mCaches.tessellationCache.trim();
|
||||
|
||||
@@ -128,12 +160,104 @@ Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
|
||||
return texture;
|
||||
}
|
||||
|
||||
void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const Rect* clip) {
|
||||
// clears and re-fills stencil with provided rendertarget space quads,
|
||||
// and then put stencil into test mode
|
||||
void BakedOpRenderer::setupStencilQuads(std::vector<Vertex>& quadVertices,
|
||||
int incrementThreshold) {
|
||||
mRenderState.stencil().enableWrite(incrementThreshold);
|
||||
mRenderState.stencil().clear();
|
||||
Glop glop;
|
||||
GlopBuilder(mRenderState, mCaches, &glop)
|
||||
.setRoundRectClipState(nullptr)
|
||||
.setMeshIndexedQuads(quadVertices.data(), quadVertices.size() / 4)
|
||||
.setFillBlack()
|
||||
.setTransform(Matrix4::identity(), TransformFlags::None)
|
||||
.setModelViewIdentityEmptyBounds()
|
||||
.build();
|
||||
mRenderState.render(glop, mRenderTarget.orthoMatrix);
|
||||
mRenderState.stencil().enableTest(incrementThreshold);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) {
|
||||
auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
|
||||
int quadCount = rectList.getTransformedRectanglesCount();
|
||||
std::vector<Vertex> rectangleVertices;
|
||||
rectangleVertices.reserve(quadCount * 4);
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
const TransformedRectangle& tr(rectList.getTransformedRectangle(i));
|
||||
const Matrix4& transform = tr.getTransform();
|
||||
Rect bounds = tr.getBounds();
|
||||
if (transform.rectToRect()) {
|
||||
// If rectToRect, can simply map bounds before storing verts
|
||||
transform.mapRect(bounds);
|
||||
bounds.doIntersect(clip->rect);
|
||||
if (bounds.isEmpty()) {
|
||||
continue; // will be outside of scissor, skip
|
||||
}
|
||||
}
|
||||
|
||||
rectangleVertices.push_back(Vertex{bounds.left, bounds.top});
|
||||
rectangleVertices.push_back(Vertex{bounds.right, bounds.top});
|
||||
rectangleVertices.push_back(Vertex{bounds.left, bounds.bottom});
|
||||
rectangleVertices.push_back(Vertex{bounds.right, bounds.bottom});
|
||||
|
||||
if (!transform.rectToRect()) {
|
||||
// If not rectToRect, must map each point individually
|
||||
for (auto cur = rectangleVertices.end() - 4; cur < rectangleVertices.end(); cur++) {
|
||||
transform.mapPoint(cur->x, cur->y);
|
||||
}
|
||||
}
|
||||
}
|
||||
setupStencilQuads(rectangleVertices, rectList.getTransformedRectanglesCount());
|
||||
}
|
||||
|
||||
void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) {
|
||||
auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region;
|
||||
|
||||
std::vector<Vertex> regionVertices;
|
||||
SkRegion::Cliperator it(region, clip->rect.toSkIRect());
|
||||
while (!it.done()) {
|
||||
const SkIRect& r = it.rect();
|
||||
regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fTop});
|
||||
regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fTop});
|
||||
regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fBottom});
|
||||
regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fBottom});
|
||||
it.next();
|
||||
}
|
||||
setupStencilQuads(regionVertices, 0);
|
||||
}
|
||||
|
||||
void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) {
|
||||
// prepare scissor / stencil
|
||||
mRenderState.scissor().setEnabled(clip != nullptr);
|
||||
if (clip) {
|
||||
mRenderState.scissor().set(clip->left, mRenderTarget.viewportHeight - clip->bottom,
|
||||
clip->getWidth(), clip->getHeight());
|
||||
mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect);
|
||||
if (CC_LIKELY(!Properties::debugOverdraw)) {
|
||||
// only modify stencil mode and content when it's not used for overdraw visualization
|
||||
if (CC_UNLIKELY(clip->mode != ClipMode::Rectangle)) {
|
||||
// NOTE: this pointer check is only safe for non-rect clips,
|
||||
// since rect clips may be created on the stack
|
||||
if (mRenderTarget.lastStencilClip != clip) {
|
||||
// Stencil needed, but current stencil isn't up to date
|
||||
mRenderTarget.lastStencilClip = clip;
|
||||
|
||||
if (mRenderTarget.offscreenBuffer) {
|
||||
LOG_ALWAYS_FATAL("prepare layer stencil");
|
||||
}
|
||||
|
||||
if (clip->mode == ClipMode::RectangleList) {
|
||||
setupStencilRectList(clip);
|
||||
} else {
|
||||
setupStencilRegion(clip);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mRenderState.stencil().disable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dirty offscreenbuffer
|
||||
if (dirtyBounds && mRenderTarget.offscreenBuffer) {
|
||||
// register layer damage to draw-back region
|
||||
android::Rect dirty(dirtyBounds->left, dirtyBounds->top,
|
||||
@@ -142,17 +266,18 @@ void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const Rect* clip) {
|
||||
}
|
||||
}
|
||||
|
||||
void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop) {
|
||||
void BakedOpRenderer::renderGlop(const Rect* dirtyBounds, const ClipBase* clip,
|
||||
const Glop& glop) {
|
||||
prepareRender(dirtyBounds, clip);
|
||||
mRenderState.render(glop, mRenderTarget.orthoMatrix);
|
||||
if (!mRenderTarget.frameBufferId) mHasDrawn = true;
|
||||
}
|
||||
|
||||
void BakedOpRenderer::renderFunctor(const FunctorOp& op, const BakedOpState& state) {
|
||||
prepareRender(&state.computedState.clippedBounds, &state.computedState.clipRect);
|
||||
prepareRender(&state.computedState.clippedBounds, state.computedState.getClipIfNeeded());
|
||||
|
||||
DrawGlInfo info;
|
||||
auto&& clip = state.computedState.clipRect;
|
||||
auto&& clip = state.computedState.clipRect();
|
||||
info.clipLeft = clip.left;
|
||||
info.clipTop = clip.top;
|
||||
info.clipRight = clip.right;
|
||||
|
||||
@@ -27,6 +27,7 @@ class Caches;
|
||||
struct Glop;
|
||||
class Layer;
|
||||
class RenderState;
|
||||
struct ClipBase;
|
||||
|
||||
/**
|
||||
* Main rendering manager for a collection of work - one frame + any contained FBOs.
|
||||
@@ -59,7 +60,7 @@ public:
|
||||
Caches& caches() { return mCaches; }
|
||||
|
||||
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect);
|
||||
void endFrame();
|
||||
void endFrame(const Rect& repaintRect);
|
||||
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height);
|
||||
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect);
|
||||
void endLayer();
|
||||
@@ -68,21 +69,23 @@ public:
|
||||
const LightInfo& getLightInfo() const { return mLightInfo; }
|
||||
|
||||
void renderGlop(const BakedOpState& state, const Glop& glop) {
|
||||
bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
|
||||
renderGlop(&state.computedState.clippedBounds,
|
||||
useScissor ? &state.computedState.clipRect : nullptr,
|
||||
state.computedState.getClipIfNeeded(),
|
||||
glop);
|
||||
}
|
||||
void renderFunctor(const FunctorOp& op, const BakedOpState& state);
|
||||
|
||||
void renderGlop(const Rect* dirtyBounds, const Rect* clip, const Glop& glop);
|
||||
void renderGlop(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop);
|
||||
bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; }
|
||||
void dirtyRenderTarget(const Rect& dirtyRect);
|
||||
bool didDraw() const { return mHasDrawn; }
|
||||
private:
|
||||
void setViewport(uint32_t width, uint32_t height);
|
||||
void clearColorBuffer(const Rect& clearRect);
|
||||
void prepareRender(const Rect* dirtyBounds, const Rect* clip);
|
||||
void prepareRender(const Rect* dirtyBounds, const ClipBase* clip);
|
||||
void setupStencilRectList(const ClipBase* clip);
|
||||
void setupStencilRegion(const ClipBase* clip);
|
||||
void setupStencilQuads(std::vector<Vertex>& quadVertices, int incrementThreshold);
|
||||
|
||||
RenderState& mRenderState;
|
||||
Caches& mCaches;
|
||||
@@ -97,6 +100,7 @@ private:
|
||||
uint32_t viewportWidth = 0;
|
||||
uint32_t viewportHeight = 0;
|
||||
Matrix4 orthoMatrix;
|
||||
const ClipBase* lastStencilClip = nullptr;
|
||||
} mRenderTarget;
|
||||
|
||||
const LightInfo mLightInfo;
|
||||
|
||||
79
libs/hwui/BakedOpState.cpp
Normal file
79
libs/hwui/BakedOpState.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 "BakedOpState.h"
|
||||
|
||||
#include "ClipArea.h"
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
|
||||
const RecordedOp& recordedOp, bool expandForStroke) {
|
||||
// resolvedMatrix = parentMatrix * localMatrix
|
||||
transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
|
||||
|
||||
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
|
||||
clippedBounds = recordedOp.unmappedBounds;
|
||||
if (CC_UNLIKELY(expandForStroke)) {
|
||||
// account for non-hairline stroke
|
||||
clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
|
||||
}
|
||||
transform.mapRect(clippedBounds);
|
||||
if (CC_UNLIKELY(expandForStroke
|
||||
&& (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
|
||||
// account for hairline stroke when stroke may be < 1 scaled pixel
|
||||
// Non translate || strokeWidth < 1 is conservative, but will cover all cases
|
||||
clippedBounds.outset(0.5f);
|
||||
}
|
||||
|
||||
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
|
||||
clipState = snapshot.mutateClipArea().serializeIntersectedClip(allocator,
|
||||
recordedOp.localClip, *(snapshot.transform));
|
||||
LOG_ALWAYS_FATAL_IF(!clipState, "must clip!");
|
||||
|
||||
const Rect& clipRect = clipState->rect;
|
||||
if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) {
|
||||
// Rejected based on either empty clip, or bounds not intersecting with clip
|
||||
if (clipState) {
|
||||
allocator.rewindIfLastAlloc(clipState);
|
||||
clipState = nullptr;
|
||||
}
|
||||
clippedBounds.setEmpty();
|
||||
} else {
|
||||
// Not rejected! compute true clippedBounds and clipSideFlags
|
||||
if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
|
||||
if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
|
||||
if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
|
||||
if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
|
||||
clippedBounds.doIntersect(clipRect);
|
||||
}
|
||||
}
|
||||
|
||||
ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) {
|
||||
transform = *snapshot.transform;
|
||||
|
||||
// Since the op doesn't have known bounds, we conservatively set the mapped bounds
|
||||
// to the current clipRect, and clipSideFlags to Full.
|
||||
clipState = snapshot.mutateClipArea().serializeClip(allocator);
|
||||
LOG_ALWAYS_FATAL_IF(!clipState, "clipState required");
|
||||
clippedBounds = clipState->rect;
|
||||
transform.mapRect(clippedBounds);
|
||||
clipSideFlags = OpClipSideFlags::Full;
|
||||
}
|
||||
|
||||
} // namespace uirenderer
|
||||
} // namespace android
|
||||
@@ -52,89 +52,35 @@ struct MergedBakedOpList {
|
||||
*/
|
||||
class ResolvedRenderState {
|
||||
public:
|
||||
// TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates
|
||||
ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke) {
|
||||
/* TODO: benchmark a fast path for translate-only matrices, such as:
|
||||
if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate
|
||||
&& recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) {
|
||||
float translateX = snapshot.transform->getTranslateX() + recordedOp.localMatrix.getTranslateX();
|
||||
float translateY = snapshot.transform->getTranslateY() + recordedOp.localMatrix.getTranslateY();
|
||||
transform.loadTranslate(translateX, translateY, 0);
|
||||
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot,
|
||||
const RecordedOp& recordedOp, bool expandForStroke);
|
||||
|
||||
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
|
||||
clipRect = recordedOp.localClipRect;
|
||||
clipRect.translate(translateX, translateY);
|
||||
clipRect.doIntersect(snapshot.getClipRect());
|
||||
clipRect.snapToPixelBoundaries();
|
||||
|
||||
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
|
||||
clippedBounds = recordedOp.unmappedBounds;
|
||||
clippedBounds.translate(translateX, translateY);
|
||||
} ... */
|
||||
|
||||
// resolvedMatrix = parentMatrix * localMatrix
|
||||
transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix);
|
||||
|
||||
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
|
||||
clipRect = recordedOp.localClipRect;
|
||||
snapshot.transform->mapRect(clipRect);
|
||||
clipRect.doIntersect(snapshot.getRenderTargetClip());
|
||||
clipRect.snapToPixelBoundaries();
|
||||
|
||||
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
|
||||
clippedBounds = recordedOp.unmappedBounds;
|
||||
if (CC_UNLIKELY(expandForStroke)) {
|
||||
// account for non-hairline stroke
|
||||
clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f);
|
||||
}
|
||||
transform.mapRect(clippedBounds);
|
||||
if (CC_UNLIKELY(expandForStroke
|
||||
&& (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) {
|
||||
// account for hairline stroke when stroke may be < 1 scaled pixel
|
||||
// Non translate || strokeWidth < 1 is conservative, but will cover all cases
|
||||
clippedBounds.outset(0.5f);
|
||||
}
|
||||
|
||||
if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left;
|
||||
if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top;
|
||||
if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right;
|
||||
if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom;
|
||||
clippedBounds.doIntersect(clipRect);
|
||||
|
||||
/**
|
||||
* TODO: once we support complex clips, we may want to reject to avoid that work where
|
||||
* possible. Should we:
|
||||
* 1 - quickreject based on clippedBounds, quick early (duplicating logic in resolvedOp)
|
||||
* 2 - merge stuff into tryConstruct factory method, so it can handle quickRejection
|
||||
* and early return null in one place.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for unbounded ops without transform/clip (namely shadows)
|
||||
*
|
||||
* Since the op doesn't have known bounds, we conservatively set the mapped bounds
|
||||
* to the current clipRect, and clipSideFlags to Full.
|
||||
*/
|
||||
ResolvedRenderState(const Snapshot& snapshot) {
|
||||
transform = *snapshot.transform;
|
||||
clipRect = snapshot.getRenderTargetClip();
|
||||
clippedBounds = clipRect;
|
||||
transform.mapRect(clippedBounds);
|
||||
clipSideFlags = OpClipSideFlags::Full;
|
||||
}
|
||||
// Constructor for unbounded ops without transform/clip (namely shadows)
|
||||
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
|
||||
|
||||
Rect computeLocalSpaceClip() const {
|
||||
Matrix4 inverse;
|
||||
inverse.loadInverse(transform);
|
||||
|
||||
Rect outClip(clipRect);
|
||||
Rect outClip(clipRect());
|
||||
inverse.mapRect(outClip);
|
||||
return outClip;
|
||||
}
|
||||
|
||||
Matrix4 transform;
|
||||
Rect clipRect;
|
||||
const Rect& clipRect() const {
|
||||
return clipState->rect;
|
||||
}
|
||||
bool requiresClip() const {
|
||||
return clipSideFlags != OpClipSideFlags::None
|
||||
|| CC_UNLIKELY(clipState->mode != ClipMode::Rectangle);
|
||||
}
|
||||
|
||||
// returns the clip if it's needed to draw the operation, otherwise nullptr
|
||||
const ClipBase* getClipIfNeeded() const {
|
||||
return requiresClip() ? clipState : nullptr;
|
||||
}
|
||||
const ClipBase* clipState = nullptr;
|
||||
int clipSideFlags = 0;
|
||||
Rect clippedBounds;
|
||||
};
|
||||
@@ -147,8 +93,9 @@ public:
|
||||
class BakedOpState {
|
||||
public:
|
||||
static BakedOpState* tryConstruct(LinearAllocator& allocator,
|
||||
const Snapshot& snapshot, const RecordedOp& recordedOp) {
|
||||
BakedOpState* bakedState = new (allocator) BakedOpState(snapshot, recordedOp, false);
|
||||
Snapshot& snapshot, const RecordedOp& recordedOp) {
|
||||
BakedOpState* bakedState = new (allocator) BakedOpState(
|
||||
allocator, snapshot, recordedOp, false);
|
||||
if (bakedState->computedState.clippedBounds.isEmpty()) {
|
||||
// bounds are empty, so op is rejected
|
||||
allocator.rewindIfLastAlloc(bakedState);
|
||||
@@ -165,13 +112,13 @@ public:
|
||||
};
|
||||
|
||||
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
|
||||
const Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
|
||||
Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
|
||||
bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
|
||||
? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
|
||||
: true;
|
||||
|
||||
BakedOpState* bakedState = new (allocator) BakedOpState(
|
||||
snapshot, recordedOp, expandForStroke);
|
||||
allocator, snapshot, recordedOp, expandForStroke);
|
||||
if (bakedState->computedState.clippedBounds.isEmpty()) {
|
||||
// bounds are empty, so op is rejected
|
||||
allocator.rewindIfLastAlloc(bakedState);
|
||||
@@ -181,11 +128,11 @@ public:
|
||||
}
|
||||
|
||||
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
|
||||
const Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
|
||||
Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
|
||||
if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
|
||||
|
||||
// clip isn't empty, so construct the op
|
||||
return new (allocator) BakedOpState(snapshot, shadowOpPtr);
|
||||
return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
|
||||
}
|
||||
|
||||
static void* operator new(size_t size, LinearAllocator& allocator) {
|
||||
@@ -202,15 +149,16 @@ public:
|
||||
const RecordedOp* op;
|
||||
|
||||
private:
|
||||
BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp, bool expandForStroke)
|
||||
: computedState(snapshot, recordedOp, expandForStroke)
|
||||
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot,
|
||||
const RecordedOp& recordedOp, bool expandForStroke)
|
||||
: computedState(allocator, snapshot, recordedOp, expandForStroke)
|
||||
, alpha(snapshot.alpha)
|
||||
, roundRectClipState(snapshot.roundRectClipState)
|
||||
, projectionPathMask(snapshot.projectionPathMask)
|
||||
, op(&recordedOp) {}
|
||||
|
||||
BakedOpState(const Snapshot& snapshot, const ShadowOp* shadowOpPtr)
|
||||
: computedState(snapshot)
|
||||
BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
|
||||
: computedState(allocator, snapshot)
|
||||
, alpha(snapshot.alpha)
|
||||
, roundRectClipState(snapshot.roundRectClipState)
|
||||
, projectionPathMask(snapshot.projectionPathMask)
|
||||
|
||||
@@ -45,6 +45,21 @@ CanvasState::~CanvasState() {
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) {
|
||||
if (mWidth != viewportWidth || mHeight != viewportHeight) {
|
||||
mWidth = viewportWidth;
|
||||
mHeight = viewportHeight;
|
||||
mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
|
||||
mCanvas.onViewportInitialized();
|
||||
}
|
||||
|
||||
freeAllSnapshots();
|
||||
mSnapshot = allocSnapshot(&mFirstSnapshot,
|
||||
SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
mSnapshot->setRelativeLightCenter(Vector3());
|
||||
mSaveCount = 1;
|
||||
}
|
||||
|
||||
void CanvasState::initializeSaveStack(
|
||||
int viewportWidth, int viewportHeight,
|
||||
float clipLeft, float clipTop,
|
||||
|
||||
@@ -76,6 +76,12 @@ public:
|
||||
CanvasState(CanvasStateClient& renderer);
|
||||
~CanvasState();
|
||||
|
||||
/**
|
||||
* Initializes the first snapshot, computing the projection matrix,
|
||||
* and stores the dimensions of the render target.
|
||||
*/
|
||||
void initializeRecordingSaveStack(int viewportWidth, int viewportHeight);
|
||||
|
||||
/**
|
||||
* Initializes the first snapshot, computing the projection matrix,
|
||||
* and stores the dimensions of the render target.
|
||||
@@ -168,6 +174,7 @@ private:
|
||||
void freeAllSnapshots();
|
||||
|
||||
/// indicates that the clip has been changed since the last time it was consumed
|
||||
// TODO: delete when switching to HWUI_NEW_OPS
|
||||
bool mDirtyClip;
|
||||
|
||||
/// Dimensions of the drawing surface
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
#include "ClipArea.h"
|
||||
|
||||
#include "utils/LinearAllocator.h"
|
||||
|
||||
#include <SkPath.h>
|
||||
#include <limits>
|
||||
|
||||
#include "Rect.h"
|
||||
#include <type_traits>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
@@ -171,12 +172,18 @@ SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
|
||||
return rectangleListAsRegion;
|
||||
}
|
||||
|
||||
void RectangleList::transform(const Matrix4& transform) {
|
||||
for (int index = 0; index < mTransformedRectanglesCount; index++) {
|
||||
mTransformedRectangles[index].transform(transform);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ClipArea
|
||||
*/
|
||||
|
||||
ClipArea::ClipArea()
|
||||
: mMode(Mode::Rectangle) {
|
||||
: mMode(ClipMode::Rectangle) {
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -184,39 +191,44 @@ ClipArea::ClipArea()
|
||||
*/
|
||||
|
||||
void ClipArea::setViewportDimensions(int width, int height) {
|
||||
mPostViewportClipObserved = false;
|
||||
mViewportBounds.set(0, 0, width, height);
|
||||
mClipRect = mViewportBounds;
|
||||
}
|
||||
|
||||
void ClipArea::setEmpty() {
|
||||
mMode = Mode::Rectangle;
|
||||
onClipUpdated();
|
||||
mMode = ClipMode::Rectangle;
|
||||
mClipRect.setEmpty();
|
||||
mClipRegion.setEmpty();
|
||||
mRectangleList.setEmpty();
|
||||
}
|
||||
|
||||
void ClipArea::setClip(float left, float top, float right, float bottom) {
|
||||
mMode = Mode::Rectangle;
|
||||
onClipUpdated();
|
||||
mMode = ClipMode::Rectangle;
|
||||
mClipRect.set(left, top, right, bottom);
|
||||
mClipRegion.setEmpty();
|
||||
}
|
||||
|
||||
void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
|
||||
SkRegion::Op op) {
|
||||
onClipUpdated();
|
||||
switch (mMode) {
|
||||
case Mode::Rectangle:
|
||||
case ClipMode::Rectangle:
|
||||
rectangleModeClipRectWithTransform(r, transform, op);
|
||||
break;
|
||||
case Mode::RectangleList:
|
||||
case ClipMode::RectangleList:
|
||||
rectangleListModeClipRectWithTransform(r, transform, op);
|
||||
break;
|
||||
case Mode::Region:
|
||||
case ClipMode::Region:
|
||||
regionModeClipRectWithTransform(r, transform, op);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
|
||||
onClipUpdated();
|
||||
enterRegionMode();
|
||||
mClipRegion.op(region, op);
|
||||
onClipRegionUpdated();
|
||||
@@ -224,6 +236,7 @@ void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
|
||||
|
||||
void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
|
||||
SkRegion::Op op) {
|
||||
onClipUpdated();
|
||||
SkMatrix skTransform;
|
||||
transform->copyTo(skTransform);
|
||||
SkPath transformed;
|
||||
@@ -241,7 +254,7 @@ void ClipArea::enterRectangleMode() {
|
||||
// Entering rectangle mode discards any
|
||||
// existing clipping information from the other modes.
|
||||
// The only way this occurs is by a clip setting operation.
|
||||
mMode = Mode::Rectangle;
|
||||
mMode = ClipMode::Rectangle;
|
||||
}
|
||||
|
||||
void ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
|
||||
@@ -276,8 +289,8 @@ void ClipArea::enterRectangleListMode() {
|
||||
// Is is only legal to enter rectangle list mode from
|
||||
// rectangle mode, since rectangle list mode cannot represent
|
||||
// all clip areas that can be represented by a region.
|
||||
ALOG_ASSERT(mMode == Mode::Rectangle);
|
||||
mMode = Mode::RectangleList;
|
||||
ALOG_ASSERT(mMode == ClipMode::Rectangle);
|
||||
mMode = ClipMode::RectangleList;
|
||||
mRectangleList.set(mClipRect, Matrix4::identity());
|
||||
}
|
||||
|
||||
@@ -295,12 +308,11 @@ void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
|
||||
*/
|
||||
|
||||
void ClipArea::enterRegionMode() {
|
||||
Mode oldMode = mMode;
|
||||
mMode = Mode::Region;
|
||||
if (oldMode != Mode::Region) {
|
||||
if (oldMode == Mode::Rectangle) {
|
||||
mClipRegion.setRect(mClipRect.left, mClipRect.top,
|
||||
mClipRect.right, mClipRect.bottom);
|
||||
ClipMode oldMode = mMode;
|
||||
mMode = ClipMode::Region;
|
||||
if (oldMode != ClipMode::Region) {
|
||||
if (oldMode == ClipMode::Rectangle) {
|
||||
mClipRegion.setRect(mClipRect.toSkIRect());
|
||||
} else {
|
||||
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
|
||||
onClipRegionUpdated();
|
||||
@@ -330,5 +342,172 @@ void ClipArea::onClipRegionUpdated() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clip serialization
|
||||
*/
|
||||
|
||||
const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) {
|
||||
if (!mPostViewportClipObserved) {
|
||||
// Only initial clip-to-viewport observed, so no serialization of clip necessary
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static_assert(std::is_trivially_destructible<Rect>::value,
|
||||
"expect Rect to be trivially destructible");
|
||||
static_assert(std::is_trivially_destructible<RectangleList>::value,
|
||||
"expect RectangleList to be trivially destructible");
|
||||
|
||||
if (mLastSerialization == nullptr) {
|
||||
switch (mMode) {
|
||||
case ClipMode::Rectangle:
|
||||
mLastSerialization = allocator.create<ClipRect>(mClipRect);
|
||||
break;
|
||||
case ClipMode::RectangleList:
|
||||
mLastSerialization = allocator.create<ClipRectList>(mRectangleList);
|
||||
break;
|
||||
case ClipMode::Region:
|
||||
mLastSerialization = allocator.create<ClipRegion>(mClipRegion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mLastSerialization;
|
||||
}
|
||||
|
||||
inline static const Rect& getRect(const ClipBase* scb) {
|
||||
return reinterpret_cast<const ClipRect*>(scb)->rect;
|
||||
}
|
||||
|
||||
inline static const RectangleList& getRectList(const ClipBase* scb) {
|
||||
return reinterpret_cast<const ClipRectList*>(scb)->rectList;
|
||||
}
|
||||
|
||||
inline static const SkRegion& getRegion(const ClipBase* scb) {
|
||||
return reinterpret_cast<const ClipRegion*>(scb)->region;
|
||||
}
|
||||
|
||||
// Conservative check for too many rectangles to fit in rectangle list.
|
||||
// For simplicity, doesn't account for rect merging
|
||||
static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
|
||||
int currentRectCount = clipArea.isRectangleList()
|
||||
? clipArea.getRectangleList().getTransformedRectanglesCount()
|
||||
: 1;
|
||||
int recordedRectCount = (scb->mode == ClipMode::RectangleList)
|
||||
? getRectList(scb).getTransformedRectanglesCount()
|
||||
: 1;
|
||||
return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
|
||||
}
|
||||
|
||||
const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
|
||||
const ClipBase* recordedClip, const Matrix4& recordedClipTransform) {
|
||||
// if no recordedClip passed, just serialize current state
|
||||
if (!recordedClip) return serializeClip(allocator);
|
||||
|
||||
if (!mLastResolutionResult
|
||||
|| recordedClip != mLastResolutionClip
|
||||
|| recordedClipTransform != mLastResolutionTransform) {
|
||||
mLastResolutionClip = recordedClip;
|
||||
mLastResolutionTransform = recordedClipTransform;
|
||||
|
||||
if (CC_LIKELY(mMode == ClipMode::Rectangle
|
||||
&& recordedClip->mode == ClipMode::Rectangle
|
||||
&& recordedClipTransform.rectToRect())) {
|
||||
// common case - result is a single rectangle
|
||||
auto rectClip = allocator.create<ClipRect>(getRect(recordedClip));
|
||||
recordedClipTransform.mapRect(rectClip->rect);
|
||||
rectClip->rect.doIntersect(mClipRect);
|
||||
mLastResolutionResult = rectClip;
|
||||
} else if (CC_UNLIKELY(mMode == ClipMode::Region
|
||||
|| recordedClip->mode == ClipMode::Region
|
||||
|| cannotFitInRectangleList(*this, recordedClip))) {
|
||||
// region case
|
||||
SkRegion other;
|
||||
switch (recordedClip->mode) {
|
||||
case ClipMode::Rectangle:
|
||||
if (CC_LIKELY(recordedClipTransform.rectToRect())) {
|
||||
// simple transform, skip creating SkPath
|
||||
Rect resultClip(getRect(recordedClip));
|
||||
recordedClipTransform.mapRect(resultClip);
|
||||
other.setRect(resultClip.toSkIRect());
|
||||
} else {
|
||||
SkPath transformedRect = pathFromTransformedRectangle(getRect(recordedClip),
|
||||
recordedClipTransform);
|
||||
other.setPath(transformedRect, createViewportRegion());
|
||||
}
|
||||
break;
|
||||
case ClipMode::RectangleList: {
|
||||
RectangleList transformedList(getRectList(recordedClip));
|
||||
transformedList.transform(recordedClipTransform);
|
||||
other = transformedList.convertToRegion(createViewportRegion());
|
||||
break;
|
||||
}
|
||||
case ClipMode::Region:
|
||||
other = getRegion(recordedClip);
|
||||
|
||||
// TODO: handle non-translate transforms properly!
|
||||
other.translate(recordedClipTransform.getTranslateX(),
|
||||
recordedClipTransform.getTranslateY());
|
||||
}
|
||||
|
||||
ClipRegion* regionClip = allocator.create<ClipRegion>();
|
||||
switch (mMode) {
|
||||
case ClipMode::Rectangle:
|
||||
regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
|
||||
break;
|
||||
case ClipMode::RectangleList:
|
||||
regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
|
||||
other, SkRegion::kIntersect_Op);
|
||||
break;
|
||||
case ClipMode::Region:
|
||||
regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
|
||||
break;
|
||||
}
|
||||
regionClip->rect.set(regionClip->region.getBounds());
|
||||
mLastResolutionResult = regionClip;
|
||||
} else {
|
||||
auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
|
||||
auto&& rectList = rectListClip->rectList;
|
||||
if (mMode == ClipMode::Rectangle) {
|
||||
rectList.set(mClipRect, Matrix4::identity());
|
||||
}
|
||||
|
||||
if (recordedClip->mode == ClipMode::Rectangle) {
|
||||
rectList.intersectWith(getRect(recordedClip), recordedClipTransform);
|
||||
} else {
|
||||
const RectangleList& other = getRectList(recordedClip);
|
||||
for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
|
||||
auto&& tr = other.getTransformedRectangle(i);
|
||||
Matrix4 totalTransform(recordedClipTransform);
|
||||
totalTransform.multiply(tr.getTransform());
|
||||
rectList.intersectWith(tr.getBounds(), totalTransform);
|
||||
}
|
||||
}
|
||||
rectListClip->rect = rectList.calculateBounds();
|
||||
mLastResolutionResult = rectListClip;
|
||||
}
|
||||
}
|
||||
return mLastResolutionResult;
|
||||
}
|
||||
|
||||
void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
|
||||
if (!clip) return; // nothing to do
|
||||
|
||||
if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
|
||||
clipRectWithTransform(getRect(clip), &transform, SkRegion::kIntersect_Op);
|
||||
} else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
|
||||
auto&& rectList = getRectList(clip);
|
||||
for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
|
||||
auto&& tr = rectList.getTransformedRectangle(i);
|
||||
Matrix4 totalTransform(transform);
|
||||
totalTransform.multiply(tr.getTransform());
|
||||
clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
|
||||
}
|
||||
} else {
|
||||
SkRegion region(getRegion(clip));
|
||||
// TODO: handle non-translate transforms properly!
|
||||
region.translate(transform.getTranslateX(), transform.getTranslateY());
|
||||
clipRegion(region, SkRegion::kIntersect_Op);
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
#ifndef CLIPAREA_H
|
||||
#define CLIPAREA_H
|
||||
|
||||
#include <SkRegion.h>
|
||||
|
||||
#include "Matrix.h"
|
||||
#include "Rect.h"
|
||||
#include "utils/Pair.h"
|
||||
|
||||
#include <SkRegion.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class LinearAllocator;
|
||||
|
||||
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);
|
||||
|
||||
class TransformedRectangle {
|
||||
@@ -50,6 +52,12 @@ public:
|
||||
return mTransform;
|
||||
}
|
||||
|
||||
void transform(const Matrix4& transform) {
|
||||
Matrix4 t;
|
||||
t.loadMultiply(transform, mTransform);
|
||||
mTransform = t;
|
||||
}
|
||||
|
||||
private:
|
||||
Rect mBounds;
|
||||
Matrix4 mTransform;
|
||||
@@ -66,27 +74,62 @@ public:
|
||||
void setEmpty();
|
||||
void set(const Rect& bounds, const Matrix4& transform);
|
||||
bool intersectWith(const Rect& bounds, const Matrix4& transform);
|
||||
void transform(const Matrix4& transform);
|
||||
|
||||
SkRegion convertToRegion(const SkRegion& clip) const;
|
||||
Rect calculateBounds() const;
|
||||
|
||||
private:
|
||||
enum {
|
||||
kMaxTransformedRectangles = 5
|
||||
};
|
||||
|
||||
private:
|
||||
int mTransformedRectanglesCount;
|
||||
TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
|
||||
};
|
||||
|
||||
class ClipArea {
|
||||
private:
|
||||
enum class Mode {
|
||||
Rectangle,
|
||||
Region,
|
||||
RectangleList
|
||||
};
|
||||
enum class ClipMode {
|
||||
Rectangle,
|
||||
RectangleList,
|
||||
|
||||
// region and path - intersected. if either is empty, don't use
|
||||
Region
|
||||
};
|
||||
|
||||
struct ClipBase {
|
||||
ClipBase(ClipMode mode)
|
||||
: mode(mode) {}
|
||||
ClipBase(const Rect& rect)
|
||||
: mode(ClipMode::Rectangle)
|
||||
, rect(rect) {}
|
||||
const ClipMode mode;
|
||||
// Bounds of the clipping area, used to define the scissor, and define which
|
||||
// portion of the stencil is updated/used
|
||||
Rect rect;
|
||||
};
|
||||
|
||||
struct ClipRect : ClipBase {
|
||||
ClipRect(const Rect& rect)
|
||||
: ClipBase(rect) {}
|
||||
};
|
||||
|
||||
struct ClipRectList : ClipBase {
|
||||
ClipRectList(const RectangleList& rectList)
|
||||
: ClipBase(ClipMode::RectangleList)
|
||||
, rectList(rectList) {}
|
||||
RectangleList rectList;
|
||||
};
|
||||
|
||||
struct ClipRegion : ClipBase {
|
||||
ClipRegion(const SkRegion& region)
|
||||
: ClipBase(ClipMode::Region)
|
||||
, region(region) {}
|
||||
ClipRegion()
|
||||
: ClipBase(ClipMode::Region) {}
|
||||
SkRegion region;
|
||||
};
|
||||
|
||||
class ClipArea {
|
||||
public:
|
||||
ClipArea();
|
||||
|
||||
@@ -117,17 +160,22 @@ public:
|
||||
}
|
||||
|
||||
bool isRegion() const {
|
||||
return Mode::Region == mMode;
|
||||
return ClipMode::Region == mMode;
|
||||
}
|
||||
|
||||
bool isSimple() const {
|
||||
return mMode == Mode::Rectangle;
|
||||
return mMode == ClipMode::Rectangle;
|
||||
}
|
||||
|
||||
bool isRectangleList() const {
|
||||
return mMode == Mode::RectangleList;
|
||||
return mMode == ClipMode::RectangleList;
|
||||
}
|
||||
|
||||
const ClipBase* serializeClip(LinearAllocator& allocator);
|
||||
const ClipBase* serializeIntersectedClip(LinearAllocator& allocator,
|
||||
const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
|
||||
void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform);
|
||||
|
||||
private:
|
||||
void enterRectangleMode();
|
||||
void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
|
||||
@@ -145,6 +193,13 @@ private:
|
||||
void ensureClipRegion();
|
||||
void onClipRegionUpdated();
|
||||
|
||||
// Called by every state modifying public method.
|
||||
void onClipUpdated() {
|
||||
mPostViewportClipObserved = true;
|
||||
mLastSerialization = nullptr;
|
||||
mLastResolutionResult = nullptr;
|
||||
}
|
||||
|
||||
SkRegion createViewportRegion() {
|
||||
return SkRegion(mViewportBounds.toSkIRect());
|
||||
}
|
||||
@@ -155,7 +210,22 @@ private:
|
||||
pathAsRegion.setPath(path, createViewportRegion());
|
||||
}
|
||||
|
||||
Mode mMode;
|
||||
ClipMode mMode;
|
||||
bool mPostViewportClipObserved = false;
|
||||
|
||||
/**
|
||||
* If mLastSerialization is non-null, it represents an already serialized copy
|
||||
* of the current clip state. If null, it has not been computed.
|
||||
*/
|
||||
const ClipBase* mLastSerialization = nullptr;
|
||||
|
||||
/**
|
||||
* This pair of pointers is a single entry cache of most recently seen
|
||||
*/
|
||||
const ClipBase* mLastResolutionResult = nullptr;
|
||||
const ClipBase* mLastResolutionClip = nullptr;
|
||||
Matrix4 mLastResolutionTransform;
|
||||
|
||||
Rect mViewportBounds;
|
||||
Rect mClipRect;
|
||||
SkRegion mClipRegion;
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace uirenderer {
|
||||
#if HWUI_NEW_OPS
|
||||
class BakedOpState;
|
||||
class BakedOpRenderer;
|
||||
struct ClipBase;
|
||||
#else
|
||||
class OpenGLRenderer;
|
||||
#endif
|
||||
@@ -57,7 +58,7 @@ public:
|
||||
#if HWUI_NEW_OPS
|
||||
BakedOpRenderer* renderer,
|
||||
const BakedOpState* bakedState,
|
||||
const Rect* clip,
|
||||
const ClipBase* clip,
|
||||
#else
|
||||
OpenGLRenderer* renderer,
|
||||
#endif
|
||||
@@ -81,7 +82,7 @@ public:
|
||||
#if HWUI_NEW_OPS
|
||||
BakedOpRenderer* renderer;
|
||||
const BakedOpState* bakedState;
|
||||
const Rect* clip;
|
||||
const ClipBase* clip;
|
||||
#else
|
||||
OpenGLRenderer* renderer;
|
||||
#endif
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace TransformFlags {
|
||||
|
||||
// Canvas transform isn't applied to the mesh at draw time,
|
||||
//since it's already built in.
|
||||
MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove
|
||||
MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove for HWUI_NEW_OPS
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -460,7 +460,7 @@ void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
|
||||
deferBeginLayerOp(*new (mAllocator) BeginLayerOp(
|
||||
saveLayerBounds,
|
||||
Matrix4::identity(),
|
||||
saveLayerBounds,
|
||||
nullptr, // no record-time clip - need only respect defer-time one
|
||||
&saveLayerPaint));
|
||||
deferNodeOps(node);
|
||||
deferEndLayerOp(*new (mAllocator) EndLayerOp());
|
||||
@@ -604,7 +604,7 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
|
||||
mCanvasState.getLocalClipBounds(),
|
||||
mCanvasState.currentSnapshot()->getRelativeLightCenter());
|
||||
BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct(
|
||||
mAllocator, *mCanvasState.currentSnapshot(), shadowOp);
|
||||
mAllocator, *mCanvasState.writableSnapshot(), shadowOp);
|
||||
if (CC_LIKELY(bakedOpState)) {
|
||||
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow);
|
||||
}
|
||||
@@ -681,10 +681,10 @@ void OpReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) {
|
||||
if (op.renderNode->nothingToDraw()) return;
|
||||
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
|
||||
|
||||
// apply state from RecordedOp
|
||||
// apply state from RecordedOp (clip first, since op's clip is transformed by current matrix)
|
||||
mCanvasState.writableSnapshot()->mutateClipArea().applyClip(op.localClip,
|
||||
*mCanvasState.currentSnapshot()->transform);
|
||||
mCanvasState.concatMatrix(op.localMatrix);
|
||||
mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top,
|
||||
op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op);
|
||||
|
||||
// then apply state from node properties, and defer ops
|
||||
deferNodePropsAndOps(*op.renderNode);
|
||||
@@ -706,7 +706,7 @@ void OpReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
|
||||
BakedOpState::StrokeBehavior strokeBehavior) {
|
||||
// Note: here we account for stroke when baking the op
|
||||
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
|
||||
mAllocator, *mCanvasState.currentSnapshot(), op, strokeBehavior);
|
||||
mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
|
||||
if (!bakedState) return; // quick rejected
|
||||
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
|
||||
}
|
||||
@@ -769,7 +769,7 @@ void OpReorderer::deferCirclePropsOp(const CirclePropsOp& op) {
|
||||
const OvalOp* resolvedOp = new (mAllocator) OvalOp(
|
||||
unmappedBounds,
|
||||
op.localMatrix,
|
||||
op.localClipRect,
|
||||
op.localClip,
|
||||
op.paint);
|
||||
deferOvalOp(*resolvedOp);
|
||||
}
|
||||
@@ -829,7 +829,7 @@ void OpReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
|
||||
const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp(
|
||||
Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
|
||||
op.localMatrix,
|
||||
op.localClipRect,
|
||||
op.localClip,
|
||||
op.paint, *op.rx, *op.ry);
|
||||
deferRoundRectOp(*resolvedOp);
|
||||
}
|
||||
@@ -953,7 +953,7 @@ void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) {
|
||||
LayerOp* drawLayerOp = new (mAllocator) LayerOp(
|
||||
beginLayerOp.unmappedBounds,
|
||||
beginLayerOp.localMatrix,
|
||||
beginLayerOp.localClipRect,
|
||||
beginLayerOp.localClip,
|
||||
beginLayerOp.paint,
|
||||
&mLayerReorderers[finishedLayerIndex].offscreenBuffer);
|
||||
BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
|
||||
|
||||
@@ -192,7 +192,7 @@ public:
|
||||
const LayerReorderer& fbo0 = mLayerReorderers[0];
|
||||
renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect);
|
||||
fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
|
||||
renderer.endFrame();
|
||||
renderer.endFrame(fbo0.repaintRect);
|
||||
}
|
||||
|
||||
void dump() const {
|
||||
@@ -223,7 +223,7 @@ private:
|
||||
LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
|
||||
|
||||
BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
|
||||
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
|
||||
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp);
|
||||
}
|
||||
|
||||
// should always be surrounded by a save/restore pair, and not called if DisplayList is null
|
||||
|
||||
@@ -33,6 +33,7 @@ class SkPaint;
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
struct ClipBase;
|
||||
class OffscreenBuffer;
|
||||
class RenderNode;
|
||||
struct Vertex;
|
||||
@@ -91,10 +92,10 @@ namespace RecordedOpId {
|
||||
static_assert(RecordedOpId::ArcOp == 0,
|
||||
"First index must be zero for LUTs to work");
|
||||
|
||||
#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint
|
||||
#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect
|
||||
#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, paint)
|
||||
#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, nullptr)
|
||||
#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint
|
||||
#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip
|
||||
#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, paint)
|
||||
#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, nullptr)
|
||||
|
||||
struct RecordedOp {
|
||||
/* ID from RecordedOpId - generally used for jumping into function tables */
|
||||
@@ -106,8 +107,8 @@ struct RecordedOp {
|
||||
/* transform in recording space (vs DisplayList origin) */
|
||||
const Matrix4 localMatrix;
|
||||
|
||||
/* clip in recording space */
|
||||
const Rect localClipRect;
|
||||
/* clip in recording space - nullptr if not clipped */
|
||||
const ClipBase* localClip;
|
||||
|
||||
/* optional paint, stored in base object to simplify merging logic */
|
||||
const SkPaint* paint;
|
||||
@@ -116,7 +117,7 @@ protected:
|
||||
: opId(opId)
|
||||
, unmappedBounds(unmappedBounds)
|
||||
, localMatrix(localMatrix)
|
||||
, localClipRect(localClipRect)
|
||||
, localClip(localClip)
|
||||
, paint(paint) {}
|
||||
};
|
||||
|
||||
@@ -187,9 +188,9 @@ struct BitmapRectOp : RecordedOp {
|
||||
};
|
||||
|
||||
struct CirclePropsOp : RecordedOp {
|
||||
CirclePropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
|
||||
CirclePropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
|
||||
float* x, float* y, float* radius)
|
||||
: RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClipRect, paint)
|
||||
: RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClip, paint)
|
||||
, x(x)
|
||||
, y(y)
|
||||
, radius(radius) {}
|
||||
@@ -259,9 +260,9 @@ struct RoundRectOp : RecordedOp {
|
||||
};
|
||||
|
||||
struct RoundRectPropsOp : RecordedOp {
|
||||
RoundRectPropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
|
||||
RoundRectPropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint,
|
||||
float* left, float* top, float* right, float* bottom, float *rx, float *ry)
|
||||
: RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClipRect, paint)
|
||||
: RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClip, paint)
|
||||
, left(left)
|
||||
, top(top)
|
||||
, right(right)
|
||||
@@ -286,12 +287,13 @@ struct RoundRectPropsOp : RecordedOp {
|
||||
*/
|
||||
struct ShadowOp : RecordedOp {
|
||||
ShadowOp(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath,
|
||||
const Rect& clipRect, const Vector3& lightCenter)
|
||||
: RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), clipRect, nullptr)
|
||||
const Rect& localClipRect, const Vector3& lightCenter)
|
||||
: RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr)
|
||||
, shadowMatrixXY(casterOp.localMatrix)
|
||||
, shadowMatrixZ(casterOp.localMatrix)
|
||||
, casterAlpha(casterAlpha)
|
||||
, casterPath(casterPath)
|
||||
, localClipRect(localClipRect)
|
||||
, lightCenter(lightCenter) {
|
||||
const RenderNode& node = *casterOp.renderNode;
|
||||
node.applyViewPropertyTransforms(shadowMatrixXY, false);
|
||||
@@ -301,6 +303,7 @@ struct ShadowOp : RecordedOp {
|
||||
Matrix4 shadowMatrixZ;
|
||||
const float casterAlpha;
|
||||
const SkPath* casterPath;
|
||||
const Rect localClipRect;
|
||||
const Vector3 lightCenter;
|
||||
};
|
||||
|
||||
@@ -374,7 +377,7 @@ struct BeginLayerOp : RecordedOp {
|
||||
*/
|
||||
struct EndLayerOp : RecordedOp {
|
||||
EndLayerOp()
|
||||
: RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), Rect(), nullptr) {}
|
||||
: RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -394,7 +397,7 @@ struct LayerOp : RecordedOp {
|
||||
, destroy(true) {}
|
||||
|
||||
LayerOp(RenderNode& node)
|
||||
: RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), Rect(node.getWidth(), node.getHeight()), nullptr)
|
||||
: RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
|
||||
, layerHandle(node.getLayerHandle())
|
||||
, alpha(node.properties().layerProperties().alpha() / 255.0f)
|
||||
, mode(node.properties().layerProperties().xferMode())
|
||||
|
||||
@@ -39,7 +39,7 @@ void RecordingCanvas::reset(int width, int height) {
|
||||
"prepareDirty called a second time during a recording!");
|
||||
mDisplayList = new DisplayList();
|
||||
|
||||
mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
|
||||
mState.initializeRecordingSaveStack(width, height);
|
||||
|
||||
mDeferredBarrierType = DeferredBarrierType::InOrder;
|
||||
mState.setDirtyClip(false);
|
||||
@@ -155,6 +155,8 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
|
||||
return saveValue;
|
||||
}
|
||||
|
||||
auto previousClip = getRecordedClip(); // note: done while snapshot == previous
|
||||
|
||||
snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
|
||||
snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
|
||||
snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
|
||||
@@ -167,7 +169,7 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
|
||||
addOp(new (alloc()) BeginLayerOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*previous.transform, // transform to *draw* with
|
||||
previous.getRenderTargetClip(), // clip to *draw* with
|
||||
previousClip, // clip to *draw* with
|
||||
refPaint(paint)));
|
||||
|
||||
return saveValue;
|
||||
@@ -229,11 +231,10 @@ void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) {
|
||||
}
|
||||
|
||||
void RecordingCanvas::drawPaint(const SkPaint& paint) {
|
||||
// TODO: more efficient recording?
|
||||
addOp(new (alloc()) RectOp(
|
||||
mState.getRenderTargetClipBounds(),
|
||||
mState.getRenderTargetClipBounds(), // OK, since we've not passed transform
|
||||
Matrix4::identity(),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint)));
|
||||
}
|
||||
|
||||
@@ -253,7 +254,7 @@ void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPa
|
||||
addOp(new (alloc()) PointsOp(
|
||||
calcBoundsOfPoints(points, floatCount),
|
||||
*mState.currentSnapshot()->transform,
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
|
||||
}
|
||||
|
||||
@@ -264,7 +265,7 @@ void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPai
|
||||
addOp(new (alloc()) LinesOp(
|
||||
calcBoundsOfPoints(points, floatCount),
|
||||
*mState.currentSnapshot()->transform,
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
|
||||
}
|
||||
|
||||
@@ -272,7 +273,7 @@ void RecordingCanvas::drawRect(float left, float top, float right, float bottom,
|
||||
addOp(new (alloc()) RectOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint)));
|
||||
}
|
||||
|
||||
@@ -305,7 +306,7 @@ void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const
|
||||
addOp(new (alloc()) SimpleRectsOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(paint), rectData, vertexCount));
|
||||
}
|
||||
|
||||
@@ -339,7 +340,7 @@ void RecordingCanvas::drawRoundRect(float left, float top, float right, float bo
|
||||
addOp(new (alloc()) RoundRectOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint), rx, ry));
|
||||
}
|
||||
|
||||
@@ -358,7 +359,7 @@ void RecordingCanvas::drawRoundRect(
|
||||
refBitmapsInShader(paint->value.getShader());
|
||||
addOp(new (alloc()) RoundRectPropsOp(
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
&paint->value,
|
||||
&left->value, &top->value, &right->value, &bottom->value,
|
||||
&rx->value, &ry->value));
|
||||
@@ -380,7 +381,7 @@ void RecordingCanvas::drawCircle(
|
||||
refBitmapsInShader(paint->value.getShader());
|
||||
addOp(new (alloc()) CirclePropsOp(
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
&paint->value,
|
||||
&x->value, &y->value, &radius->value));
|
||||
}
|
||||
@@ -390,7 +391,7 @@ void RecordingCanvas::drawOval(float left, float top, float right, float bottom,
|
||||
addOp(new (alloc()) OvalOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint)));
|
||||
}
|
||||
|
||||
@@ -399,7 +400,7 @@ void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
|
||||
addOp(new (alloc()) ArcOp(
|
||||
Rect(left, top, right, bottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint),
|
||||
startAngle, sweepAngle, useCenter));
|
||||
}
|
||||
@@ -408,7 +409,7 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
|
||||
addOp(new (alloc()) PathOp(
|
||||
Rect(path.getBounds()),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint), refPath(&path)));
|
||||
}
|
||||
|
||||
@@ -459,7 +460,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float sr
|
||||
addOp(new (alloc()) BitmapRectOp(
|
||||
Rect(dstLeft, dstTop, dstRight, dstBottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(paint), refBitmap(bitmap),
|
||||
Rect(srcLeft, srcTop, srcRight, srcBottom)));
|
||||
}
|
||||
@@ -471,7 +472,7 @@ void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int
|
||||
addOp(new (alloc()) BitmapMeshOp(
|
||||
calcBoundsOfPoints(vertices, vertexCount * 2),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
|
||||
refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
|
||||
refBuffer<int>(colors, vertexCount))); // 1 color per vertex
|
||||
@@ -483,7 +484,7 @@ void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_p
|
||||
addOp(new (alloc()) PatchOp(
|
||||
Rect(dstLeft, dstTop, dstRight, dstBottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
|
||||
}
|
||||
|
||||
@@ -499,7 +500,7 @@ void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, i
|
||||
addOp(new (alloc()) TextOp(
|
||||
Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint), glyphs, positions, glyphCount, x, y));
|
||||
drawTextDecorations(x, y, totalAdvance, paint);
|
||||
}
|
||||
@@ -509,9 +510,9 @@ void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int glyphCount, con
|
||||
if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
|
||||
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
|
||||
addOp(new (alloc()) TextOnPathOp(
|
||||
mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
|
||||
mState.getLocalClipBounds(), // TODO: explicitly define bounds
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset));
|
||||
}
|
||||
|
||||
@@ -519,7 +520,7 @@ void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
|
||||
addOp(new (alloc()) BitmapOp(
|
||||
Rect(bitmap->width(), bitmap->height()),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
refPaint(paint), refBitmap(*bitmap)));
|
||||
}
|
||||
|
||||
@@ -528,7 +529,7 @@ void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
|
||||
RenderNodeOp* op = new (alloc()) RenderNodeOp(
|
||||
Rect(stagingProps.getWidth(), stagingProps.getHeight()),
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
renderNode);
|
||||
int opIndex = addOp(op);
|
||||
int childIndex = mDisplayList->addChild(op);
|
||||
@@ -554,16 +555,16 @@ void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) {
|
||||
addOp(new (alloc()) TextureLayerOp(
|
||||
Rect(layer->getWidth(), layer->getHeight()),
|
||||
totalTransform,
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
layer));
|
||||
}
|
||||
|
||||
void RecordingCanvas::callDrawGLFunction(Functor* functor) {
|
||||
mDisplayList->functors.push_back(functor);
|
||||
addOp(new (alloc()) FunctorOp(
|
||||
mState.getRenderTargetClipBounds(), // TODO: explicitly define bounds
|
||||
mState.getLocalClipBounds(), // TODO: explicitly define bounds
|
||||
*(mState.currentSnapshot()->transform),
|
||||
mState.getRenderTargetClipBounds(),
|
||||
getRecordedClip(),
|
||||
functor));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
struct ClipBase;
|
||||
class DeferredLayerUpdater;
|
||||
struct RecordedOp;
|
||||
|
||||
@@ -199,6 +200,9 @@ public:
|
||||
virtual bool drawTextAbsolutePos() const override { return false; }
|
||||
|
||||
private:
|
||||
const ClipBase* getRecordedClip() {
|
||||
return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc());
|
||||
}
|
||||
|
||||
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
|
||||
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
|
||||
|
||||
@@ -167,6 +167,7 @@ public:
|
||||
const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
|
||||
bool clipIsSimple() const { return mClipArea->isSimple(); }
|
||||
const ClipArea& getClipArea() const { return *mClipArea; }
|
||||
ClipArea& mutateClipArea() { return *mClipArea; }
|
||||
|
||||
/**
|
||||
* Resets the clip to the specified rect.
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
#include "renderstate/Scissor.h"
|
||||
|
||||
#include "Rect.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace android {
|
||||
@@ -71,6 +73,26 @@ bool Scissor::set(GLint x, GLint y, GLint width, GLint height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Scissor::set(int viewportHeight, const Rect& clip) {
|
||||
// transform to Y-flipped GL space, and prevent negatives
|
||||
GLint x = std::max(0, (int)clip.left);
|
||||
GLint y = std::max(0, viewportHeight - (int)clip.bottom);
|
||||
GLint width = std::max(0, ((int)clip.right) - x);
|
||||
GLint height = std::max(0, (viewportHeight - (int)clip.top) - y);
|
||||
|
||||
if (x != mScissorX
|
||||
|| y != mScissorY
|
||||
|| width != mScissorWidth
|
||||
|| height != mScissorHeight) {
|
||||
glScissor(x, y, width, height);
|
||||
|
||||
mScissorX = x;
|
||||
mScissorY = y;
|
||||
mScissorWidth = width;
|
||||
mScissorHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
void Scissor::reset() {
|
||||
mScissorX = mScissorY = mScissorWidth = mScissorHeight = 0;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class Rect;
|
||||
|
||||
class Scissor {
|
||||
friend class RenderState;
|
||||
public:
|
||||
bool setEnabled(bool enabled);
|
||||
bool set(GLint x, GLint y, GLint width, GLint height);
|
||||
void set(int viewportHeight, const Rect& clip);
|
||||
void reset();
|
||||
bool isEnabled() { return mEnabled; }
|
||||
void dump();
|
||||
|
||||
67
libs/hwui/tests/common/scenes/ClippingAnimation.cpp
Normal file
67
libs/hwui/tests/common/scenes/ClippingAnimation.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 "TestSceneBase.h"
|
||||
|
||||
class ClippingAnimation;
|
||||
|
||||
static TestScene::Registrar _RectGrid(TestScene::Info{
|
||||
"clip",
|
||||
"Complex clip cases"
|
||||
"Low CPU/GPU load.",
|
||||
TestScene::simpleCreateScene<ClippingAnimation>
|
||||
});
|
||||
|
||||
class ClippingAnimation : public TestScene {
|
||||
public:
|
||||
sp<RenderNode> card;
|
||||
void createContent(int width, int height, TestCanvas& canvas) override {
|
||||
canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode);
|
||||
canvas.insertReorderBarrier(true);
|
||||
|
||||
card = TestUtils::createNode(0, 0, 200, 400,
|
||||
[](RenderProperties& props, TestCanvas& canvas) {
|
||||
canvas.save(SkCanvas::kMatrixClip_SaveFlag);
|
||||
{
|
||||
canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
|
||||
canvas.translate(100, 100);
|
||||
canvas.rotate(45);
|
||||
canvas.translate(-100, -100);
|
||||
canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op);
|
||||
canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode);
|
||||
}
|
||||
canvas.restore();
|
||||
|
||||
canvas.save(SkCanvas::kMatrixClip_SaveFlag);
|
||||
{
|
||||
SkPath clipCircle;
|
||||
clipCircle.addCircle(100, 300, 100);
|
||||
canvas.clipPath(&clipCircle, SkRegion::kIntersect_Op);
|
||||
canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode);
|
||||
}
|
||||
canvas.restore();
|
||||
});
|
||||
canvas.drawRenderNode(card.get());
|
||||
|
||||
canvas.insertReorderBarrier(false);
|
||||
}
|
||||
void doFrame(int frameNr) override {
|
||||
int curFrame = frameNr % 150;
|
||||
card->mutateStagingProperties().setTranslationX(curFrame);
|
||||
card->mutateStagingProperties().setTranslationY(curFrame);
|
||||
card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
|
||||
}
|
||||
};
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "tests/common/TestContext.h"
|
||||
#include "tests/common/TestScene.h"
|
||||
#include "tests/common/TestUtils.h"
|
||||
#include "utils/Color.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <BakedOpState.h>
|
||||
#include <ClipArea.h>
|
||||
#include <RecordedOp.h>
|
||||
#include <tests/common/TestUtils.h>
|
||||
|
||||
@@ -24,31 +25,33 @@ namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
TEST(ResolvedRenderState, construct) {
|
||||
LinearAllocator allocator;
|
||||
Matrix4 translate10x20;
|
||||
translate10x20.loadTranslate(10, 20, 0);
|
||||
|
||||
SkPaint paint;
|
||||
RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(100, 200), &paint);
|
||||
ClipRect clip(Rect(100, 200));
|
||||
RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint);
|
||||
{
|
||||
// recorded with transform, no parent transform
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
|
||||
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
|
||||
EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20);
|
||||
EXPECT_EQ(Rect(100, 200), state.clipRect);
|
||||
EXPECT_EQ(Rect(100, 200), state.clipRect());
|
||||
EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped
|
||||
EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags);
|
||||
}
|
||||
{
|
||||
// recorded with transform and parent transform
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
|
||||
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
|
||||
|
||||
Matrix4 expectedTranslate;
|
||||
expectedTranslate.loadTranslate(20, 40, 0);
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform);
|
||||
|
||||
// intersection of parent & transformed child clip
|
||||
EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect);
|
||||
EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect());
|
||||
|
||||
// translated and also clipped
|
||||
EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds);
|
||||
@@ -57,22 +60,24 @@ TEST(ResolvedRenderState, construct) {
|
||||
}
|
||||
|
||||
TEST(ResolvedRenderState, computeLocalSpaceClip) {
|
||||
LinearAllocator allocator;
|
||||
Matrix4 translate10x20;
|
||||
translate10x20.loadTranslate(10, 20, 0);
|
||||
|
||||
SkPaint paint;
|
||||
RectOp recordedOp(Rect(1000, 1000), translate10x20, Rect(100, 200), &paint);
|
||||
ClipRect clip(Rect(100, 200));
|
||||
RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint);
|
||||
{
|
||||
// recorded with transform, no parent transform
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
|
||||
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
|
||||
EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip())
|
||||
<< "Local clip rect should be 100x200, offset by -10,-20";
|
||||
}
|
||||
{
|
||||
// recorded with transform + parent transform
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200));
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp, false);
|
||||
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false);
|
||||
EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip())
|
||||
<< "Local clip rect should be 90x190, offset by -10,-20";
|
||||
}
|
||||
@@ -149,6 +154,7 @@ const static StrokeTestCase sStrokeTestCases[] = {
|
||||
};
|
||||
|
||||
TEST(ResolvedRenderState, construct_expandForStroke) {
|
||||
LinearAllocator allocator;
|
||||
// Loop over table of test cases and verify different combinations of stroke width and transform
|
||||
for (auto&& testCase : sStrokeTestCases) {
|
||||
SkPaint strokedPaint;
|
||||
@@ -156,14 +162,15 @@ TEST(ResolvedRenderState, construct_expandForStroke) {
|
||||
strokedPaint.setStyle(SkPaint::kStroke_Style);
|
||||
strokedPaint.setStrokeWidth(testCase.strokeWidth);
|
||||
|
||||
ClipRect clip(Rect(200, 200));
|
||||
RectOp recordedOp(Rect(50, 50, 150, 150),
|
||||
Matrix4::identity(), Rect(200, 200), &strokedPaint);
|
||||
Matrix4::identity(), &clip, &strokedPaint);
|
||||
|
||||
Matrix4 snapshotMatrix;
|
||||
snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1);
|
||||
auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200));
|
||||
|
||||
ResolvedRenderState state(*parentSnapshot, recordedOp, true);
|
||||
ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true);
|
||||
testCase.validator(state);
|
||||
}
|
||||
}
|
||||
@@ -175,8 +182,9 @@ TEST(BakedOpState, tryConstruct) {
|
||||
translate100x0.loadTranslate(100, 0, 0);
|
||||
|
||||
SkPaint paint;
|
||||
ClipRect clip(Rect(100, 200));
|
||||
{
|
||||
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(100, 200), &paint);
|
||||
RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
|
||||
BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
|
||||
|
||||
@@ -184,7 +192,7 @@ TEST(BakedOpState, tryConstruct) {
|
||||
EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op
|
||||
}
|
||||
{
|
||||
RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), Rect(100, 200), &paint);
|
||||
RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
|
||||
BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
|
||||
|
||||
@@ -218,7 +226,8 @@ TEST(BakedOpState, tryStrokeableOpConstruct) {
|
||||
SkPaint paint;
|
||||
paint.setStyle(SkPaint::kStrokeAndFill_Style);
|
||||
paint.setStrokeWidth(0.0f);
|
||||
RectOp rejectOp(Rect(100, 200), Matrix4::identity(), Rect(100, 200), &paint);
|
||||
ClipRect clip(Rect(100, 200));
|
||||
RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
|
||||
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
|
||||
BakedOpState::StrokeBehavior::StyleDefined);
|
||||
@@ -231,7 +240,8 @@ TEST(BakedOpState, tryStrokeableOpConstruct) {
|
||||
SkPaint paint;
|
||||
paint.setStyle(SkPaint::kStrokeAndFill_Style);
|
||||
paint.setStrokeWidth(10.0f);
|
||||
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint);
|
||||
ClipRect clip(Rect(200, 200));
|
||||
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
|
||||
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
|
||||
BakedOpState::StrokeBehavior::StyleDefined);
|
||||
@@ -245,7 +255,8 @@ TEST(BakedOpState, tryStrokeableOpConstruct) {
|
||||
SkPaint paint;
|
||||
paint.setStyle(SkPaint::kFill_Style);
|
||||
paint.setStrokeWidth(10.0f);
|
||||
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), Rect(200, 200), &paint);
|
||||
ClipRect clip(Rect(200, 200));
|
||||
RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint);
|
||||
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200));
|
||||
auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp,
|
||||
BakedOpState::StrokeBehavior::Forced);
|
||||
|
||||
@@ -119,5 +119,122 @@ TEST(ClipArea, replaceNegative) {
|
||||
EXPECT_EQ(expected, area.getClipRect());
|
||||
}
|
||||
|
||||
TEST(ClipArea, serializeClip) {
|
||||
ClipArea area(createClipArea());
|
||||
LinearAllocator allocator;
|
||||
|
||||
// unset clip
|
||||
EXPECT_EQ(nullptr, area.serializeClip(allocator));
|
||||
|
||||
// rect clip
|
||||
area.setClip(0, 0, 200, 200);
|
||||
{
|
||||
auto serializedClip = area.serializeClip(allocator);
|
||||
ASSERT_NE(nullptr, serializedClip);
|
||||
ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode);
|
||||
auto clipRect = reinterpret_cast<const ClipRect*>(serializedClip);
|
||||
ASSERT_EQ(Rect(200, 200), clipRect->rect);
|
||||
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
|
||||
<< "Requery of clip on unmodified ClipArea must return same pointer.";
|
||||
}
|
||||
|
||||
// rect list
|
||||
Matrix4 rotate;
|
||||
rotate.loadRotate(2.0f);
|
||||
area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
|
||||
{
|
||||
auto serializedClip = area.serializeClip(allocator);
|
||||
ASSERT_NE(nullptr, serializedClip);
|
||||
ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode);
|
||||
auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip);
|
||||
ASSERT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
|
||||
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
|
||||
<< "Requery of clip on unmodified ClipArea must return same pointer.";
|
||||
}
|
||||
|
||||
// region
|
||||
SkPath circlePath;
|
||||
circlePath.addCircle(100, 100, 100);
|
||||
area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
|
||||
{
|
||||
auto serializedClip = area.serializeClip(allocator);
|
||||
ASSERT_NE(nullptr, serializedClip);
|
||||
ASSERT_EQ(ClipMode::Region, serializedClip->mode);
|
||||
auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip);
|
||||
ASSERT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
|
||||
<< "Clip region should be 200x200";
|
||||
EXPECT_EQ(serializedClip, area.serializeClip(allocator))
|
||||
<< "Requery of clip on unmodified ClipArea must return same pointer.";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClipArea, serializeIntersectedClip) {
|
||||
ClipArea area(createClipArea());
|
||||
LinearAllocator allocator;
|
||||
|
||||
// simple state;
|
||||
EXPECT_EQ(nullptr, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
|
||||
area.setClip(0, 0, 200, 200);
|
||||
{
|
||||
auto origRectClip = area.serializeClip(allocator);
|
||||
ASSERT_NE(nullptr, origRectClip);
|
||||
EXPECT_EQ(origRectClip, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()));
|
||||
}
|
||||
|
||||
// rect
|
||||
{
|
||||
ClipRect recordedClip(Rect(100, 100));
|
||||
Matrix4 translateScale;
|
||||
translateScale.loadTranslate(100, 100, 0);
|
||||
translateScale.scale(2, 3, 1);
|
||||
auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale);
|
||||
ASSERT_NE(nullptr, resolvedClip);
|
||||
ASSERT_EQ(ClipMode::Rectangle, resolvedClip->mode);
|
||||
EXPECT_EQ(Rect(100, 100, 200, 200),
|
||||
reinterpret_cast<const ClipRect*>(resolvedClip)->rect);
|
||||
|
||||
EXPECT_EQ(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip, translateScale))
|
||||
<< "Must return previous serialization, since input is same";
|
||||
|
||||
ClipRect recordedClip2(Rect(100, 100));
|
||||
EXPECT_NE(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip2, translateScale))
|
||||
<< "Shouldn't return previous serialization, since matrix location is different";
|
||||
}
|
||||
|
||||
// rect list
|
||||
Matrix4 rotate;
|
||||
rotate.loadRotate(2.0f);
|
||||
area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op);
|
||||
{
|
||||
ClipRect recordedClip(Rect(100, 100));
|
||||
auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, Matrix4::identity());
|
||||
ASSERT_NE(nullptr, resolvedClip);
|
||||
ASSERT_EQ(ClipMode::RectangleList, resolvedClip->mode);
|
||||
auto clipRectList = reinterpret_cast<const ClipRectList*>(resolvedClip);
|
||||
EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
|
||||
}
|
||||
|
||||
// region
|
||||
SkPath circlePath;
|
||||
circlePath.addCircle(100, 100, 100);
|
||||
area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op);
|
||||
{
|
||||
SkPath ovalPath;
|
||||
ovalPath.addOval(SkRect::MakeLTRB(50, 0, 150, 200));
|
||||
|
||||
ClipRegion recordedClip;
|
||||
recordedClip.region.setPath(ovalPath, SkRegion(SkIRect::MakeWH(200, 200)));
|
||||
|
||||
Matrix4 translate10x20;
|
||||
translate10x20.loadTranslate(10, 20, 0);
|
||||
auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip,
|
||||
translate10x20); // Note: only translate for now, others not handled correctly
|
||||
ASSERT_NE(nullptr, resolvedClip);
|
||||
ASSERT_EQ(ClipMode::Region, resolvedClip->mode);
|
||||
auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip);
|
||||
EXPECT_EQ(SkIRect::MakeLTRB(60, 20, 160, 200), clipRegion->region.getBounds());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace uirenderer
|
||||
} // namespace android
|
||||
|
||||
@@ -27,7 +27,7 @@ struct SimplePair {
|
||||
int two = 2;
|
||||
};
|
||||
|
||||
TEST(LinearAllocator, alloc) {
|
||||
TEST(LinearAllocator, create) {
|
||||
LinearAllocator la;
|
||||
EXPECT_EQ(0u, la.usedSize());
|
||||
la.alloc(64);
|
||||
@@ -35,7 +35,7 @@ TEST(LinearAllocator, alloc) {
|
||||
// so the usedSize isn't strictly defined
|
||||
EXPECT_LE(64u, la.usedSize());
|
||||
EXPECT_GT(80u, la.usedSize());
|
||||
auto pair = la.alloc<SimplePair>();
|
||||
auto pair = la.create<SimplePair>();
|
||||
EXPECT_LE(64u + sizeof(SimplePair), la.usedSize());
|
||||
EXPECT_GT(80u + sizeof(SimplePair), la.usedSize());
|
||||
EXPECT_EQ(1, pair->one);
|
||||
@@ -47,8 +47,8 @@ TEST(LinearAllocator, dtor) {
|
||||
{
|
||||
LinearAllocator la;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
la.alloc<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
|
||||
la.alloc<SimplePair>();
|
||||
la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i);
|
||||
la.create<SimplePair>();
|
||||
}
|
||||
la.alloc(100);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
@@ -75,7 +75,7 @@ TEST(LinearAllocator, rewind) {
|
||||
la.rewindIfLastAlloc(addr, 100);
|
||||
EXPECT_GT(16u, la.usedSize());
|
||||
size_t emptySize = la.usedSize();
|
||||
auto sigdtor = la.alloc<TestUtils::SignalingDtor>();
|
||||
auto sigdtor = la.create<TestUtils::SignalingDtor>();
|
||||
sigdtor->setSignal(&destroyed);
|
||||
EXPECT_EQ(0, destroyed);
|
||||
EXPECT_LE(emptySize, la.usedSize());
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
ADD_FAILURE() << "Layer updates not expected in this test";
|
||||
}
|
||||
virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {}
|
||||
virtual void endFrame() {}
|
||||
virtual void endFrame(const Rect& repaintRect) {}
|
||||
|
||||
// define virtual defaults for single draw methods
|
||||
#define X(Type) \
|
||||
@@ -127,7 +127,7 @@ TEST(OpReorderer, simple) {
|
||||
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(2, mIndex++);
|
||||
}
|
||||
void endFrame() override {
|
||||
void endFrame(const Rect& repaintRect) override {
|
||||
EXPECT_EQ(3, mIndex++);
|
||||
}
|
||||
};
|
||||
@@ -327,7 +327,7 @@ RENDERTHREAD_TEST(OpReorderer, textureLayer) {
|
||||
public:
|
||||
void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(0, mIndex++);
|
||||
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect);
|
||||
EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect());
|
||||
EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds);
|
||||
|
||||
Matrix4 expected;
|
||||
@@ -405,7 +405,7 @@ TEST(OpReorderer, clipped) {
|
||||
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(0, mIndex++);
|
||||
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
|
||||
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
|
||||
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect());
|
||||
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||
}
|
||||
};
|
||||
@@ -439,7 +439,7 @@ TEST(OpReorderer, saveLayerSimple) {
|
||||
EXPECT_EQ(1, mIndex++);
|
||||
EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
|
||||
EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds);
|
||||
EXPECT_EQ(Rect(180, 180), state.computedState.clipRect);
|
||||
EXPECT_EQ(Rect(180, 180), state.computedState.clipRect());
|
||||
|
||||
Matrix4 expectedTransform;
|
||||
expectedTransform.loadTranslate(-10, -10, 0);
|
||||
@@ -448,7 +448,7 @@ TEST(OpReorderer, saveLayerSimple) {
|
||||
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(3, mIndex++);
|
||||
EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
|
||||
EXPECT_EQ(Rect(200, 200), state.computedState.clipRect);
|
||||
EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
|
||||
EXPECT_TRUE(state.computedState.transform.isIdentity());
|
||||
}
|
||||
};
|
||||
@@ -494,7 +494,7 @@ TEST(OpReorderer, saveLayerNested) {
|
||||
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
|
||||
EXPECT_EQ(7, mIndex++);
|
||||
}
|
||||
void endFrame() override {
|
||||
void endFrame(const Rect& repaintRect) override {
|
||||
EXPECT_EQ(9, mIndex++);
|
||||
}
|
||||
void onRectOp(const RectOp& op, const BakedOpState& state) override {
|
||||
@@ -574,7 +574,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
|
||||
EXPECT_TRUE(state.computedState.transform.isIdentity())
|
||||
<< "Transform should be reset within layer";
|
||||
|
||||
EXPECT_EQ(state.computedState.clipRect, Rect(25, 25, 75, 75))
|
||||
EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect())
|
||||
<< "Damage rect should be used to clip layer content";
|
||||
}
|
||||
void endLayer() override {
|
||||
@@ -586,7 +586,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
|
||||
void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
|
||||
EXPECT_EQ(4, mIndex++);
|
||||
}
|
||||
void endFrame() override {
|
||||
void endFrame(const Rect& repaintRect) override {
|
||||
EXPECT_EQ(5, mIndex++);
|
||||
}
|
||||
};
|
||||
@@ -675,7 +675,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
|
||||
EXPECT_EQ(200u, layer->viewportHeight);
|
||||
} else { ADD_FAILURE(); }
|
||||
}
|
||||
void endFrame() override {
|
||||
void endFrame(const Rect& repaintRect) override {
|
||||
EXPECT_EQ(12, mIndex++);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,6 +33,14 @@ static void playbackOps(const DisplayList& displayList,
|
||||
}
|
||||
}
|
||||
|
||||
#define EXPECT_CLIP_RECT(expRect, clipStatePtr) \
|
||||
EXPECT_NE(nullptr, (clipStatePtr)) << "Op is unclipped"; \
|
||||
if ((clipStatePtr)->mode == ClipMode::Rectangle) { \
|
||||
EXPECT_EQ((expRect), reinterpret_cast<const ClipRect*>(clipStatePtr)->rect); \
|
||||
} else { \
|
||||
ADD_FAILURE() << "ClipState not a rect"; \
|
||||
}
|
||||
|
||||
TEST(RecordingCanvas, emptyPlayback) {
|
||||
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
|
||||
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
|
||||
@@ -41,6 +49,22 @@ TEST(RecordingCanvas, emptyPlayback) {
|
||||
playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
|
||||
}
|
||||
|
||||
TEST(RecordingCanvas, clipRect) {
|
||||
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) {
|
||||
canvas.save(SkCanvas::kMatrixClip_SaveFlag);
|
||||
canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op);
|
||||
canvas.drawRect(0, 0, 50, 50, SkPaint());
|
||||
canvas.drawRect(50, 50, 100, 100, SkPaint());
|
||||
canvas.restore();
|
||||
});
|
||||
|
||||
ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops";
|
||||
EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip);
|
||||
EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip);
|
||||
EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip)
|
||||
<< "Clip should be serialized once";
|
||||
}
|
||||
|
||||
TEST(RecordingCanvas, drawLines) {
|
||||
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
|
||||
SkPaint paint;
|
||||
@@ -66,7 +90,7 @@ TEST(RecordingCanvas, drawRect) {
|
||||
ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
|
||||
auto op = *(dl->getOps()[0]);
|
||||
ASSERT_EQ(RecordedOpId::RectOp, op.opId);
|
||||
EXPECT_EQ(Rect(100, 200), op.localClipRect);
|
||||
EXPECT_EQ(nullptr, op.localClip);
|
||||
EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
|
||||
}
|
||||
|
||||
@@ -83,7 +107,7 @@ TEST(RecordingCanvas, drawText) {
|
||||
playbackOps(*dl, [&count](const RecordedOp& op) {
|
||||
count++;
|
||||
ASSERT_EQ(RecordedOpId::TextOp, op.opId);
|
||||
EXPECT_EQ(Rect(200, 200), op.localClipRect);
|
||||
EXPECT_EQ(nullptr, op.localClip);
|
||||
EXPECT_TRUE(op.localMatrix.isIdentity());
|
||||
EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
|
||||
<< "Op expected to be 25+ pixels wide, 10+ pixels tall";
|
||||
@@ -185,7 +209,7 @@ TEST(RecordingCanvas, backgroundAndImage) {
|
||||
ASSERT_NE(nullptr, op.paint);
|
||||
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
|
||||
EXPECT_EQ(Rect(100, 200), op.unmappedBounds);
|
||||
EXPECT_EQ(Rect(100, 200), op.localClipRect);
|
||||
EXPECT_EQ(nullptr, op.localClip);
|
||||
|
||||
Matrix4 expectedMatrix;
|
||||
expectedMatrix.loadIdentity();
|
||||
@@ -194,7 +218,7 @@ TEST(RecordingCanvas, backgroundAndImage) {
|
||||
ASSERT_EQ(RecordedOpId::BitmapOp, op.opId);
|
||||
EXPECT_EQ(nullptr, op.paint);
|
||||
EXPECT_EQ(Rect(25, 25), op.unmappedBounds);
|
||||
EXPECT_EQ(Rect(100, 200), op.localClipRect);
|
||||
EXPECT_EQ(nullptr, op.localClip);
|
||||
|
||||
Matrix4 expectedMatrix;
|
||||
expectedMatrix.loadTranslate(25, 25, 0);
|
||||
@@ -219,12 +243,12 @@ TEST(RecordingCanvas, saveLayer_simple) {
|
||||
case 0:
|
||||
EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
|
||||
EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
|
||||
EXPECT_EQ(Rect(200, 200), op.localClipRect);
|
||||
EXPECT_EQ(nullptr, op.localClip);
|
||||
EXPECT_TRUE(op.localMatrix.isIdentity());
|
||||
break;
|
||||
case 1:
|
||||
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
|
||||
EXPECT_EQ(Rect(180, 160), op.localClipRect);
|
||||
EXPECT_CLIP_RECT(Rect(180, 160), op.localClip);
|
||||
EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
|
||||
expectedMatrix.loadTranslate(-10, -20, 0);
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
|
||||
@@ -254,8 +278,8 @@ TEST(RecordingCanvas, saveLayer_viewportCrop) {
|
||||
if (count++ == 1) {
|
||||
Matrix4 expectedMatrix;
|
||||
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
|
||||
EXPECT_EQ(Rect(100, 100), op.localClipRect) << "Recorded clip rect should be"
|
||||
" intersection of viewport and saveLayer bounds, in layer space";
|
||||
EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be
|
||||
// intersection of viewport and saveLayer bounds, in layer space;
|
||||
EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
|
||||
expectedMatrix.loadTranslate(-100, -100, 0);
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
|
||||
@@ -281,7 +305,7 @@ TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
|
||||
playbackOps(*dl, [&count](const RecordedOp& op) {
|
||||
if (count++ == 1) {
|
||||
EXPECT_EQ(RecordedOpId::RectOp, op.opId);
|
||||
EXPECT_EQ(Rect(100, 100), op.localClipRect);
|
||||
EXPECT_CLIP_RECT(Rect(100, 100), op.localClip);
|
||||
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
|
||||
EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix)
|
||||
<< "Recorded op shouldn't see any canvas transform before the saveLayer";
|
||||
@@ -312,7 +336,16 @@ TEST(RecordingCanvas, saveLayer_rotateClipped) {
|
||||
|
||||
// ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
|
||||
// the parent 200x200 viewport, but prior to rotation
|
||||
EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect);
|
||||
ASSERT_NE(nullptr, op.localClip);
|
||||
ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode);
|
||||
// NOTE: this check relies on saveLayer altering the clip post-viewport init. This
|
||||
// causes the clip to be recorded by contained draw commands, though it's not necessary
|
||||
// since the same clip will be computed at draw time. If such a change is made, this
|
||||
// check could be done at record time by querying the clip, or the clip could be altered
|
||||
// slightly so that it is serialized.
|
||||
EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136),
|
||||
(reinterpret_cast<const ClipRect*>(op.localClip))->rect);
|
||||
|
||||
EXPECT_EQ(Rect(400, 400), op.unmappedBounds);
|
||||
expectedMatrix.loadIdentity();
|
||||
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
|
||||
|
||||
@@ -56,12 +56,12 @@ public:
|
||||
void* alloc(size_t size);
|
||||
|
||||
/**
|
||||
* Allocates an instance of the template type with the default constructor
|
||||
* Allocates an instance of the template type with the given construction parameters
|
||||
* and adds it to the automatic destruction list.
|
||||
*/
|
||||
template<class T>
|
||||
T* alloc() {
|
||||
T* ret = new (*this) T;
|
||||
template<class T, typename... Params>
|
||||
T* create(Params... params) {
|
||||
T* ret = new (*this) T(params...);
|
||||
autoDestroy(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user