diff options
Diffstat (limited to 'libs')
32 files changed, 1090 insertions, 351 deletions
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 8a03b94492d8..6913f43a87c3 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -176,7 +176,8 @@ AssetManager::~AssetManager(void) delete[] mVendor; } -bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAsLib) +bool AssetManager::addAssetPath( + const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset) { AutoMutex _l(mLock); @@ -222,6 +223,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAs } delete manifestAsset; + ap.isSystemAsset = isSystemAsset; mAssetPaths.add(ap); // new paths are always added at the end @@ -233,6 +235,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie, bool appAs // Load overlays, if any asset_path oap; for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) { + oap.isSystemAsset = isSystemAsset; mAssetPaths.add(oap); } #endif @@ -340,7 +343,7 @@ bool AssetManager::addDefaultAssets() String8 path(root); path.appendPath(kSystemAssets); - return addAssetPath(path, NULL); + return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */); } int32_t AssetManager::nextAssetPath(const int32_t cookie) const @@ -682,10 +685,10 @@ bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) con ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); - mResources->add(sharedRes); + mResources->add(sharedRes, ap.isSystemAsset); } else { ALOGV("Parsing resources for %s", ap.path.string()); - mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib); + mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); } onlyEmptyResources = false; @@ -831,11 +834,11 @@ bool AssetManager::isUpToDate() return mZipSet.isUpToDate(); } -void AssetManager::getLocales(Vector<String8>* locales) const +void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { ResTable* res = mResources; if (res != NULL) { - res->getLocales(locales); + res->getLocales(locales, includeSystemLocales); } const size_t numLocales = locales->size(); diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 21b543eefa01..44f92c7bf3d6 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -3080,13 +3080,16 @@ struct ResTable::Package // table that defined the package); the ones after are skins on top of it. struct ResTable::PackageGroup { - PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id, bool appAsLib) + PackageGroup( + ResTable* _owner, const String16& _name, uint32_t _id, + bool appAsLib, bool _isSystemAsset) : owner(_owner) , name(_name) , id(_id) , largestTypeId(0) , bags(NULL) , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib) + , isSystemAsset(_isSystemAsset) { } ~PackageGroup() { @@ -3178,6 +3181,10 @@ struct ResTable::PackageGroup // by having these tables in a per-package scope rather than // per-package-group. DynamicRefTable dynamicRefTable; + + // If the package group comes from a system asset. Used in + // determining non-system locales. + const bool isSystemAsset; }; struct ResTable::bag_set @@ -3572,8 +3579,9 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { copyData); } -status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, - bool appAsLib) { +status_t ResTable::add( + Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, + bool appAsLib, bool isSystemAsset) { const void* data = asset->getBuffer(true); if (data == NULL) { ALOGW("Unable to get buffer of resource asset file"); @@ -3592,20 +3600,21 @@ status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bo } return addInternal(data, static_cast<size_t>(asset->getLength()), - idmapData, idmapSize, appAsLib, cookie, copyData); + idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset); } -status_t ResTable::add(ResTable* src) +status_t ResTable::add(ResTable* src, bool isSystemAsset) { mError = src->mError; - for (size_t i=0; i<src->mHeaders.size(); i++) { + for (size_t i=0; i < src->mHeaders.size(); i++) { mHeaders.add(src->mHeaders[i]); } - for (size_t i=0; i<src->mPackageGroups.size(); i++) { + for (size_t i=0; i < src->mPackageGroups.size(); i++) { PackageGroup* srcPg = src->mPackageGroups[i]; - PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, false); + PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, + false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset); for (size_t j=0; j<srcPg->packages.size(); j++) { pg->packages.add(srcPg->packages[j]); } @@ -3646,7 +3655,7 @@ status_t ResTable::addEmpty(const int32_t cookie) { } status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize, - bool appAsLib, const int32_t cookie, bool copyData) + bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset) { if (!data) { return NO_ERROR; @@ -3749,7 +3758,8 @@ status_t ResTable::addInternal(const void* data, size_t dataSize, const void* id return (mError=BAD_TYPE); } - if (parsePackage((ResTable_package*)chunk, header, appAsLib) != NO_ERROR) { + if (parsePackage( + (ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) { return mError; } curPackage++; @@ -5663,7 +5673,7 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con } void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap, - bool ignoreAndroidPackage) const { + bool ignoreAndroidPackage, bool includeSystemConfigs) const { const size_t packageCount = mPackageGroups.size(); String16 android("android"); for (size_t i = 0; i < packageCount; i++) { @@ -5671,6 +5681,9 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi if (ignoreAndroidPackage && android == packageGroup->name) { continue; } + if (!includeSystemConfigs && packageGroup->isSystemAsset) { + continue; + } const size_t typeCount = packageGroup->types.size(); for (size_t j = 0; j < typeCount; j++) { const TypeList& typeList = packageGroup->types[j]; @@ -5707,11 +5720,14 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi } } -void ResTable::getLocales(Vector<String8>* locales) const +void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { Vector<ResTable_config> configs; ALOGV("calling getConfigurations"); - getConfigurations(&configs); + getConfigurations(&configs, + false /* ignoreMipmap */, + false /* ignoreAndroidPackage */, + includeSystemLocales /* includeSystemConfigs */); ALOGV("called getConfigurations size=%d", (int)configs.size()); const size_t I = configs.size(); @@ -5937,7 +5953,7 @@ status_t ResTable::getEntry( } status_t ResTable::parsePackage(const ResTable_package* const pkg, - const Header* const header, bool appAsLib) + const Header* const header, bool appAsLib, bool isSystemAsset) { const uint8_t* base = (const uint8_t*)pkg; status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset), @@ -5985,8 +6001,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (id >= 256) { LOG_ALWAYS_FATAL("Package id out of range"); return NO_ERROR; - } else if (id == 0 || appAsLib) { - // This is a library so assign an ID + } else if (id == 0 || appAsLib || isSystemAsset) { + // This is a library or a system asset, so assign an ID id = mNextPackageId++; } @@ -6018,7 +6034,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])]; strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0])); - group = new PackageGroup(this, String16(tmpName), id, appAsLib); + group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset); if (group == NULL) { delete package; return (mError=NO_MEMORY); diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index f48c509755e3..11056d4fac97 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -108,6 +108,7 @@ ifeq (true, $(HWUI_NEW_OPS)) hwui_src_files += \ BakedOpDispatcher.cpp \ BakedOpRenderer.cpp \ + BakedOpState.cpp \ OpReorderer.cpp \ RecordingCanvas.cpp diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 0f0768f08a5e..097675a5f520 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.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)); @@ -297,26 +302,6 @@ void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer, } } -void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) { - LOG_ALWAYS_FATAL("unsupported operation"); -} - -void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer&, const BeginLayerOp&, const BakedOpState&) { - LOG_ALWAYS_FATAL("unsupported operation"); -} - -void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer&, const EndLayerOp&, const BakedOpState&) { - LOG_ALWAYS_FATAL("unsupported operation"); -} - -void BakedOpDispatcher::onCirclePropsOp(BakedOpRenderer&, const CirclePropsOp&, const BakedOpState&) { - LOG_ALWAYS_FATAL("unsupported operation"); -} - -void BakedOpDispatcher::onRoundRectPropsOp(BakedOpRenderer&, const RoundRectPropsOp&, const BakedOpState&) { - LOG_ALWAYS_FATAL("unsupported operation"); -} - namespace VertexBufferRenderFlags { enum { Offset = 0x1, @@ -701,14 +686,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()); diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h index ed34ada90272..4dfdd3ff619a 100644 --- a/libs/hwui/BakedOpDispatcher.h +++ b/libs/hwui/BakedOpDispatcher.h @@ -36,13 +36,13 @@ public: // Declares all "onMergedBitmapOps(...)" style methods for mergeable op types #define X(Type) \ static void onMerged##Type##s(BakedOpRenderer& renderer, const MergedBakedOpList& opList); - MAP_MERGED_OPS(X) + MAP_MERGEABLE_OPS(X) #undef X // Declares all "onBitmapOp(...)" style methods for every op type #define X(Type) \ static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state); - MAP_OPS(X) + MAP_RENDERABLE_OPS(X) #undef X }; diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index f8282dc1073a..a0d5faed270d 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -60,27 +60,67 @@ void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const } void BakedOpRenderer::endLayer() { + if (mRenderTarget.stencil) { + // if stencil was used for clipping, detach it and return it to pool + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "glfbrb endlayer failed"); + mCaches.renderBufferCache.put(mRenderTarget.stencil); + mRenderTarget.stencil = nullptr; + } + mRenderTarget.lastStencilClip = nullptr; + mRenderTarget.offscreenBuffer->updateMeshFromRegion(); - mRenderTarget.offscreenBuffer = nullptr; + mRenderTarget.offscreenBuffer = nullptr; // It's in drawLayerOp's hands now. // 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(); 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.clearGarbage(); mCaches.pathCache.trim(); mCaches.tessellationCache.trim(); @@ -128,12 +168,121 @@ 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 && 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.frameBufferId != 0 && !mRenderTarget.stencil) { + OffscreenBuffer* layer = mRenderTarget.offscreenBuffer; + mRenderTarget.stencil = mCaches.renderBufferCache.get( + Stencil::getLayerStencilFormat(), + layer->texture.width, layer->texture.height); + // stencil is bound + allocated - associate it with current FBO + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, mRenderTarget.stencil->getName()); + } + + if (clip->mode == ClipMode::RectangleList) { + setupStencilRectList(clip); + } else { + setupStencilRegion(clip); + } + } else { + // stencil is up to date - just need to ensure it's enabled (since an unclipped + // or scissor-only clipped op may have been drawn, disabling the stencil) + int incrementThreshold = 0; + if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { + auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList; + incrementThreshold = rectList.getTransformedRectanglesCount(); + } + mRenderState.stencil().enableTest(incrementThreshold); + } + } else { + // either scissor or no clip, so disable stencil test + 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 +291,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; diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h index f158e8bb49c3..65e8b29a9ed2 100644 --- a/libs/hwui/BakedOpRenderer.h +++ b/libs/hwui/BakedOpRenderer.h @@ -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; @@ -92,10 +95,23 @@ private: // render target state - setup by start/end layer/frame // only valid to use in between start/end pairs. struct { + // If not drawing to a layer: fbo = 0, offscreenBuffer = null, + // Otherwise these refer to currently painting layer's state GLuint frameBufferId = 0; OffscreenBuffer* offscreenBuffer = nullptr; + + // Used when drawing to a layer and using stencil clipping. otherwise null. + RenderBuffer* stencil = nullptr; + + // value representing the ClipRectList* or ClipRegion* currently stored in + // the stencil of the current render target + const ClipBase* lastStencilClip = nullptr; + + // Size of renderable region in current render target - for layers, may not match actual + // bounds of FBO texture. offscreenBuffer->texture has this information. uint32_t viewportWidth = 0; uint32_t viewportHeight = 0; + Matrix4 orthoMatrix; } mRenderTarget; diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp new file mode 100644 index 000000000000..e6b943a606d5 --- /dev/null +++ b/libs/hwui/BakedOpState.cpp @@ -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 diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index b12c0c970352..9df4e3aa5442 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -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); - - // 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); - } + ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, + const RecordedOp& recordedOp, bool expandForStroke); - 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) diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index cf76e6be46c4..cf2726b5f530 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -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, diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h index 4709ef41915f..b9e87ae5595d 100644 --- a/libs/hwui/CanvasState.h +++ b/libs/hwui/CanvasState.h @@ -80,6 +80,12 @@ public: * 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. + */ void initializeSaveStack(int viewportWidth, int viewportHeight, float clipLeft, float clipTop, float clipRight, float clipBottom, const Vector3& lightCenter); @@ -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 diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index 5f166cafd01c..160090dcd8cc 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -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 */ diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h index 268301c62fc9..479796db042a 100644 --- a/libs/hwui/ClipArea.h +++ b/libs/hwui/ClipArea.h @@ -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; diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index ff4dc4aef94a..99944985cda8 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -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 diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h index bcf819eb42e0..e72f39621d57 100644 --- a/libs/hwui/Glop.h +++ b/libs/hwui/Glop.h @@ -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 }; }; diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 2507ff377133..45fc16cc3136 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -78,7 +78,7 @@ GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementC mOutGlop->mesh.vertices = { vbo, VertexAttribFlags::TextureCoord, - nullptr, nullptr, nullptr, + nullptr, (const void*) kMeshTextureOffset, nullptr, kTextureVertexStride }; mOutGlop->mesh.elementCount = elementCount; return *this; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index ad9559ffdf9f..3f492d507b0d 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -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); } @@ -652,9 +652,7 @@ void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) { [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.defer##Type(static_cast<const Type&>(op)); }, void OpReorderer::deferNodeOps(const RenderNode& renderNode) { typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op); - static OpDispatcher receivers[] = { - MAP_OPS(OP_RECEIVER) - }; + static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER); // can't be null, since DL=null node rejection happens before deferNodePropsAndOps const DisplayList& displayList = *(renderNode.getDisplayList()); @@ -681,10 +679,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 +704,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 +767,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 +827,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 +951,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); @@ -968,13 +966,5 @@ void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) { } } -void OpReorderer::deferLayerOp(const LayerOp& op) { - LOG_ALWAYS_FATAL("unsupported"); -} - -void OpReorderer::deferShadowOp(const ShadowOp& op) { - LOG_ALWAYS_FATAL("unsupported"); -} - } // namespace uirenderer } // namespace android diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h index dbbce8b15170..429913f6c82d 100644 --- a/libs/hwui/OpReorderer.h +++ b/libs/hwui/OpReorderer.h @@ -138,7 +138,7 @@ public: template <typename StaticDispatcher, typename Renderer> void replayBakedOps(Renderer& renderer) { /** - * defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to + * Defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to * dispatch the op via a method on a static dispatcher when the op is replayed. * * For example a BitmapOp would resolve, via the lambda lookup, to calling: @@ -149,29 +149,19 @@ public: [](void* renderer, const BakedOpState& state) { \ StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), static_cast<const Type&>(*(state.op)), state); \ }, - static BakedOpReceiver unmergedReceivers[] = { - MAP_OPS(X) - }; + static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); #undef X /** - * defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a - * static dispatcher when the group of merged ops is replayed. Unmergeable ops trigger - * a LOG_ALWAYS_FATAL(). + * Defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a + * static dispatcher when the group of merged ops is replayed. */ #define X(Type) \ [](void* renderer, const MergedBakedOpList& opList) { \ - LOG_ALWAYS_FATAL("op type %d does not support merging", opList.states[0]->op->opId); \ - }, - #define Y(Type) \ - [](void* renderer, const MergedBakedOpList& opList) { \ StaticDispatcher::onMerged##Type##s(*(static_cast<Renderer*>(renderer)), opList); \ }, - static MergedOpReceiver mergedReceivers[] = { - MAP_OPS_BASED_ON_MERGEABILITY(X, Y) - }; + static MergedOpReceiver mergedReceivers[] = BUILD_MERGEABLE_OP_LUT(X); #undef X - #undef Y // Relay through layers in reverse order, since layers // later in the list will be drawn by earlier ones @@ -192,7 +182,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 +213,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 @@ -256,9 +246,9 @@ private: * These private methods are called from within deferImpl to defer each individual op * type differently. */ -#define INTERNAL_OP_HANDLER(Type) \ - void defer##Type(const Type& op); - MAP_OPS(INTERNAL_OP_HANDLER) +#define X(Type) void defer##Type(const Type& op); + MAP_DEFERRABLE_OPS(X) +#undef X std::vector<std::unique_ptr<SkPath> > mFrameAllocatedPaths; diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index cfdd0d211dac..b243f990d27d 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -33,68 +33,96 @@ class SkPaint; namespace android { namespace uirenderer { +struct ClipBase; class OffscreenBuffer; class RenderNode; struct Vertex; /** - * On of the provided macros is executed for each op type in order. The first will be used for ops - * that cannot merge, and the second for those that can. + * Authoritative op list, used for generating the op ID enum, ID based LUTS, and + * the functions to which they dispatch. Parameter macros are executed for each op, + * in order, based on the op's type. * - * This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs. + * There are 4 types of op: + * + * Pre render - not directly consumed by renderer, reorder stage resolves this into renderable type + * Render only - generated renderable ops - never passed to a reorderer + * Unmergeable - reorderable, renderable (but not mergeable) + * Mergeable - reorderable, renderable (and mergeable) */ -#define MAP_OPS_BASED_ON_MERGEABILITY(U_OP_FN, M_OP_FN) \ - U_OP_FN(ArcOp) \ - M_OP_FN(BitmapOp) \ - U_OP_FN(BitmapMeshOp) \ - U_OP_FN(BitmapRectOp) \ - U_OP_FN(CirclePropsOp) \ - U_OP_FN(FunctorOp) \ - U_OP_FN(LinesOp) \ - U_OP_FN(OvalOp) \ - M_OP_FN(PatchOp) \ - U_OP_FN(PathOp) \ - U_OP_FN(PointsOp) \ - U_OP_FN(RectOp) \ - U_OP_FN(RenderNodeOp) \ - U_OP_FN(RoundRectOp) \ - U_OP_FN(RoundRectPropsOp) \ - U_OP_FN(ShadowOp) \ - U_OP_FN(SimpleRectsOp) \ - M_OP_FN(TextOp) \ - U_OP_FN(TextOnPathOp) \ - U_OP_FN(TextureLayerOp) \ - U_OP_FN(BeginLayerOp) \ - U_OP_FN(EndLayerOp) \ - U_OP_FN(LayerOp) +#define MAP_OPS_BASED_ON_TYPE(PRE_RENDER_OP_FN, RENDER_ONLY_OP_FN, UNMERGEABLE_OP_FN, MERGEABLE_OP_FN) \ + PRE_RENDER_OP_FN(RenderNodeOp) \ + PRE_RENDER_OP_FN(CirclePropsOp) \ + PRE_RENDER_OP_FN(RoundRectPropsOp) \ + PRE_RENDER_OP_FN(BeginLayerOp) \ + PRE_RENDER_OP_FN(EndLayerOp) \ + \ + RENDER_ONLY_OP_FN(ShadowOp) \ + RENDER_ONLY_OP_FN(LayerOp) \ + \ + UNMERGEABLE_OP_FN(ArcOp) \ + UNMERGEABLE_OP_FN(BitmapMeshOp) \ + UNMERGEABLE_OP_FN(BitmapRectOp) \ + UNMERGEABLE_OP_FN(FunctorOp) \ + UNMERGEABLE_OP_FN(LinesOp) \ + UNMERGEABLE_OP_FN(OvalOp) \ + UNMERGEABLE_OP_FN(PathOp) \ + UNMERGEABLE_OP_FN(PointsOp) \ + UNMERGEABLE_OP_FN(RectOp) \ + UNMERGEABLE_OP_FN(RoundRectOp) \ + UNMERGEABLE_OP_FN(SimpleRectsOp) \ + UNMERGEABLE_OP_FN(TextOnPathOp) \ + UNMERGEABLE_OP_FN(TextureLayerOp) \ + \ + MERGEABLE_OP_FN(BitmapOp) \ + MERGEABLE_OP_FN(PatchOp) \ + MERGEABLE_OP_FN(TextOp) /** - * The provided macro is executed for each op type in order. This is used in cases where - * merge-ability of ops doesn't matter. + * LUT generators, which will insert nullptr for unsupported ops */ -#define MAP_OPS(OP_FN) \ - MAP_OPS_BASED_ON_MERGEABILITY(OP_FN, OP_FN) +#define NULLPTR_OP_FN(Type) nullptr, + +#define BUILD_DEFERRABLE_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(OP_FN, NULLPTR_OP_FN, OP_FN, OP_FN) } + +#define BUILD_MERGEABLE_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, NULLPTR_OP_FN, NULLPTR_OP_FN, OP_FN) } + +#define BUILD_RENDERABLE_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, OP_FN, OP_FN, OP_FN) } +/** + * Op mapping functions, which skip unsupported ops. + * + * Note: Do not use for LUTS, since these do not preserve ID order. + */ #define NULL_OP_FN(Type) -#define MAP_MERGED_OPS(OP_FN) \ - MAP_OPS_BASED_ON_MERGEABILITY(NULL_OP_FN, OP_FN) +#define MAP_MERGEABLE_OPS(OP_FN) \ + MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, NULL_OP_FN, NULL_OP_FN, OP_FN) + +#define MAP_RENDERABLE_OPS(OP_FN) \ + MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, OP_FN, OP_FN, OP_FN) + +#define MAP_DEFERRABLE_OPS(OP_FN) \ + MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN) // Generate OpId enum #define IDENTITY_FN(Type) Type, namespace RecordedOpId { enum { - MAP_OPS(IDENTITY_FN) + MAP_OPS_BASED_ON_TYPE(IDENTITY_FN, IDENTITY_FN, IDENTITY_FN, IDENTITY_FN) Count, }; } -static_assert(RecordedOpId::ArcOp == 0, +static_assert(RecordedOpId::RenderNodeOp == 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 +134,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 +144,7 @@ protected: : opId(opId) , unmappedBounds(unmappedBounds) , localMatrix(localMatrix) - , localClipRect(localClipRect) + , localClip(localClip) , paint(paint) {} }; @@ -187,9 +215,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 +287,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 +314,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 +330,7 @@ struct ShadowOp : RecordedOp { Matrix4 shadowMatrixZ; const float casterAlpha; const SkPath* casterPath; + const Rect localClipRect; const Vector3 lightCenter; }; @@ -374,7 +404,7 @@ struct BeginLayerOp : RecordedOp { */ struct EndLayerOp : RecordedOp { EndLayerOp() - : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), Rect(), nullptr) {} + : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {} }; /** @@ -388,13 +418,13 @@ struct LayerOp : RecordedOp { LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle) : SUPER_PAINTLESS(LayerOp) , layerHandle(layerHandle) - , alpha(paint->getAlpha() / 255.0f) + , alpha(paint ? paint->getAlpha() / 255.0f : 1.0f) , mode(PaintUtils::getXfermodeDirect(paint)) - , colorFilter(paint->getColorFilter()) + , colorFilter(paint ? paint->getColorFilter() : nullptr) , 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()) diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index f75d8d4f6479..f7f6caff22b6 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -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)); } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 470f9ecb5024..1a2ac97f5364 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -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); diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 194aa5735a7e..5fac3a1497c0 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -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. diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp index 95dcd18867d9..61dd8c3200a4 100644 --- a/libs/hwui/renderstate/Scissor.cpp +++ b/libs/hwui/renderstate/Scissor.cpp @@ -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; } diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h index b37ec583686f..f30224470059 100644 --- a/libs/hwui/renderstate/Scissor.h +++ b/libs/hwui/renderstate/Scissor.h @@ -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(); diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp new file mode 100644 index 000000000000..db6402ce136f --- /dev/null +++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp @@ -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); + 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(); + + // put on a layer, to test stencil attachment + props.mutateLayerProperties().setType(LayerType::RenderLayer); + props.setAlpha(0.9f); + }); + canvas.drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/TestSceneBase.h b/libs/hwui/tests/common/scenes/TestSceneBase.h index ac781243c25e..935ddcf9212d 100644 --- a/libs/hwui/tests/common/scenes/TestSceneBase.h +++ b/libs/hwui/tests/common/scenes/TestSceneBase.h @@ -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> diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp index f9f5316a58df..3fd822d71310 100644 --- a/libs/hwui/tests/unit/BakedOpStateTests.cpp +++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp @@ -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); diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp index c4d305e5de18..4cae737ab295 100644 --- a/libs/hwui/tests/unit/ClipAreaTests.cpp +++ b/libs/hwui/tests/unit/ClipAreaTests.cpp @@ -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 diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp index 78d65ddaa19d..5c442901045e 100644 --- a/libs/hwui/tests/unit/LinearAllocatorTests.cpp +++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp @@ -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()); diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp index b28e4361bc4a..66dccb4b0d4f 100644 --- a/libs/hwui/tests/unit/OpReordererTests.cpp +++ b/libs/hwui/tests/unit/OpReordererTests.cpp @@ -64,14 +64,14 @@ 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) \ virtual void on##Type(const Type&, const BakedOpState&) { \ ADD_FAILURE() << #Type " not expected in this test"; \ } - MAP_OPS(X) + MAP_RENDERABLE_OPS(X) #undef X // define virtual defaults for merged draw methods @@ -79,7 +79,7 @@ public: virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \ ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \ } - MAP_MERGED_OPS(X) + MAP_MERGEABLE_OPS(X) #undef X int getIndex() { return mIndex; } @@ -99,7 +99,7 @@ public: static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \ renderer.on##Type(op, state); \ } - MAP_OPS(X); + MAP_RENDERABLE_OPS(X); #undef X // define merged op methods, which redirect to TestRendererBase @@ -107,7 +107,7 @@ public: static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \ renderer.onMerged##Type##s(opList); \ } - MAP_MERGED_OPS(X); + MAP_MERGEABLE_OPS(X); #undef X }; @@ -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++); } }; diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 08f927ce00fd..a63cb18d7c4b 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -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); diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h index e1c6f6c70428..dcbc0dda951a 100644 --- a/libs/hwui/utils/LinearAllocator.h +++ b/libs/hwui/utils/LinearAllocator.h @@ -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; } |