Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5854b348 authored by Chris Craik's avatar Chris Craik
Browse files

Rework receiver/dispatcher design slightly, and replace Layer usage.

Switched from 'renderer/info' to 'dispatcher/renderer' to make their
interaction more natural. The new BakedOpRenderer is more similar in
responsibilities to the OpenGLRenderer, as it manages layer and frame
lifecycles, and performs the actual rendering.

However, it's still simpler because the BakedOpDispatcher handles
mapping Canvas drawing ops to Glops, and the OpReorderer handles almost
all canvas state operations.

Also switch BakedOpRenderer to use the new OffscreenBuffer, which
serves as a lightweight Layer replacement, with a much simpler
lifecycle.

Change-Id: Ie0e2e248503400041d49729d813d485d28c76eb3
parent ac7062e7
Loading
Loading
Loading
Loading
+115 −95
Original line number Diff line number Diff line
@@ -25,190 +25,210 @@
namespace android {
namespace uirenderer {

void BakedOpRenderer::Info::setViewport(uint32_t width, uint32_t height) {
    viewportWidth = width;
    viewportHeight = height;
    orthoMatrix.loadOrtho(viewportWidth, viewportHeight);
////////////////////////////////////////////////////////////////////////////////
// OffscreenBuffer
////////////////////////////////////////////////////////////////////////////////

    renderState.setViewport(width, height);
    renderState.blend().syncEnabled();
}
OffscreenBuffer::OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
        uint32_t viewportWidth, uint32_t viewportHeight)
        : texture(caches)
        , texCoords(0, viewportHeight / float(textureHeight), viewportWidth / float(textureWidth), 0) {
    texture.width = textureWidth;
    texture.height = textureHeight;

Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) {
    Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap);
    if (!texture) {
        return caches.textureCache.get(bitmap);
    }
    return texture;
}
    caches.textureState().activateTexture(0);
    glGenTextures(1, &texture.id);
    caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);

void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) {
    bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
    renderState.scissor().setEnabled(useScissor);
    if (useScissor) {
        const Rect& clip = state.computedState.clipRect;
        renderState.scissor().set(clip.left, viewportHeight - clip.bottom,
            clip.getWidth(), clip.getHeight());
    }
    renderState.render(glop, orthoMatrix);
    didDraw = true;
    texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
    // not setting filter on texture, since it's set when drawing, based on transform

    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
            GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}

Layer* BakedOpRenderer::startLayer(Info& info, uint32_t width, uint32_t height) {
    info.caches.textureState().activateTexture(0);
    Layer* layer = info.caches.layerCache.get(info.renderState, width, height);
    LOG_ALWAYS_FATAL_IF(!layer, "need layer...");
////////////////////////////////////////////////////////////////////////////////
// BakedOpRenderer
////////////////////////////////////////////////////////////////////////////////

    info.layer = layer;
    layer->texCoords.set(0.0f, width / float(layer->getHeight()),
            height / float(layer->getWidth()), 0.0f);
OffscreenBuffer* BakedOpRenderer::startLayer(uint32_t width, uint32_t height) {
    LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer...");

    layer->setFbo(info.renderState.genFramebuffer());
    info.renderState.bindFramebuffer(layer->getFbo());
    layer->bindTexture();
    // TODO: really should be caching these!
    OffscreenBuffer* buffer = new OffscreenBuffer(mCaches, width, height, width, height);
    mRenderTarget.offscreenBuffer = buffer;

    // Initialize the texture if needed
    if (layer->isEmpty()) {
        layer->allocateTexture();
        layer->setEmpty(false);
    }
    // create and bind framebuffer
    mRenderTarget.frameBufferId = mRenderState.genFramebuffer();
    mRenderState.bindFramebuffer(mRenderTarget.frameBufferId);

    // attach the texture to the FBO
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
            layer->getTextureId(), 0);
            buffer->texture.id, 0);
    LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
    LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
            "framebuffer incomplete!");

    // Clear the FBO
    info.renderState.scissor().setEnabled(false);
    mRenderState.scissor().setEnabled(false);
    glClear(GL_COLOR_BUFFER_BIT);

    // Change the viewport & ortho projection
    info.setViewport(width, height);
    return layer;
    setViewport(width, height);
    return buffer;
}

void BakedOpRenderer::endLayer(Info& info) {
    Layer* layer = info.layer;
    info.layer = nullptr;
void BakedOpRenderer::endLayer() {
    mRenderTarget.offscreenBuffer = nullptr;

    // Detach the texture from the FBO
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
    LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED");
    layer->removeFbo(false);
    mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId);
    mRenderTarget.frameBufferId = -1;
}

void BakedOpRenderer::startFrame(Info& info, uint32_t width, uint32_t height) {
    info.renderState.bindFramebuffer(0);
    info.setViewport(width, height);
    Caches::getInstance().clearGarbage();
void BakedOpRenderer::startFrame(uint32_t width, uint32_t height) {
    mRenderState.bindFramebuffer(0);
    setViewport(width, height);
    mCaches.clearGarbage();

    if (!info.opaque) {
    if (!mOpaque) {
        // TODO: partial invalidate!
        info.renderState.scissor().setEnabled(false);
        mRenderState.scissor().setEnabled(false);
        glClear(GL_COLOR_BUFFER_BIT);
        info.didDraw = true;
        mHasDrawn = true;
    }
}
void BakedOpRenderer::endFrame(Info& info) {
    info.caches.pathCache.trim();
    info.caches.tessellationCache.trim();

void BakedOpRenderer::endFrame() {
    mCaches.pathCache.trim();
    mCaches.tessellationCache.trim();

#if DEBUG_OPENGL
    GLUtils::dumpGLErrors();
#endif

#if DEBUG_MEMORY_USAGE
    info.caches.dumpMemoryUsage();
    mCaches.dumpMemoryUsage();
#else
    if (Properties::debugLevel & kDebugMemory) {
        info.caches.dumpMemoryUsage();
        mCaches.dumpMemoryUsage();
    }
#endif
}

void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) {
void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) {
    mRenderTarget.viewportWidth = width;
    mRenderTarget.viewportHeight = height;
    mRenderTarget.orthoMatrix.loadOrtho(width, height);

    mRenderState.setViewport(width, height);
    mRenderState.blend().syncEnabled();
}

Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) {
    Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap);
    if (!texture) {
        return mCaches.textureCache.get(bitmap);
    }
    return texture;
}

void BakedOpRenderer::renderGlop(const BakedOpState& state, const Glop& glop) {
    bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None;
    mRenderState.scissor().setEnabled(useScissor);
    if (useScissor) {
        const Rect& clip = state.computedState.clipRect;
        mRenderState.scissor().set(clip.left, mRenderTarget.viewportHeight - clip.bottom,
            clip.getWidth(), clip.getHeight());
    }
    mRenderState.render(glop, mRenderTarget.orthoMatrix);
    mHasDrawn = true;
}

////////////////////////////////////////////////////////////////////////////////
// static BakedOpDispatcher methods
////////////////////////////////////////////////////////////////////////////////

void BakedOpDispatcher::onRenderNodeOp(BakedOpRenderer&, const RenderNodeOp&, const BakedOpState&) {
    LOG_ALWAYS_FATAL("unsupported operation");
}

void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) {
    info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
    Texture* texture = info.getTexture(op.bitmap);
void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) {
    renderer.caches().textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
    Texture* texture = renderer.getTexture(op.bitmap);
    if (!texture) return;
    const AutoTexture autoCleanup(texture);

    const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
            ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
    Glop glop;
    GlopBuilder(info.renderState, info.caches, &glop)
    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
            .setRoundRectClipState(state.roundRectClipState)
            .setMeshTexturedUnitQuad(texture->uvMapper)
            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
            .setTransform(state.computedState.transform, TransformFlags::None)
            .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
            .build();
    info.renderGlop(state, glop);
    renderer.renderGlop(state, glop);
}

void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
    Glop glop;
    GlopBuilder(info.renderState, info.caches, &glop)
    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
            .setRoundRectClipState(state.roundRectClipState)
            .setMeshUnitQuad()
            .setFillPaint(*op.paint, state.alpha)
            .setTransform(state.computedState.transform, TransformFlags::None)
            .setModelViewMapUnitToRect(op.unmappedBounds)
            .build();
    info.renderGlop(state, glop);
    renderer.renderGlop(state, glop);
}

void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) {
    Glop glop;
    GlopBuilder(info.renderState, info.caches, &glop)
    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
            .setRoundRectClipState(state.roundRectClipState)
            .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
            .setFillPaint(*op.paint, state.alpha)
            .setTransform(state.computedState.transform, TransformFlags::None)
            .setModelViewOffsetRect(0, 0, op.unmappedBounds)
            .build();
    info.renderGlop(state, glop);
    renderer.renderGlop(state, glop);
}

void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) {
void BakedOpDispatcher::onBeginLayerOp(BakedOpRenderer& renderer, const BeginLayerOp& op, const BakedOpState& state) {
    LOG_ALWAYS_FATAL("unsupported operation");
}

void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) {
void BakedOpDispatcher::onEndLayerOp(BakedOpRenderer& renderer, const EndLayerOp& op, const BakedOpState& state) {
    LOG_ALWAYS_FATAL("unsupported operation");
}

void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
    Layer* layer = *op.layerHandle;

    // TODO: make this work for HW layers
    layer->setPaint(op.paint);
    layer->setBlend(true);
    float layerAlpha = (layer->getAlpha() / 255.0f) * state.alpha;
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
    OffscreenBuffer* buffer = *op.layerHandle;

    // TODO: extend this to handle HW layers & paint properties which
    // reside in node.properties().layerProperties()
    float layerAlpha = (op.paint->getAlpha() / 255.0f) * state.alpha;
    const bool tryToSnap = state.computedState.transform.isPureTranslate();
    Glop glop;
    GlopBuilder(info.renderState, info.caches, &glop)
    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
            .setRoundRectClipState(state.roundRectClipState)
            .setMeshTexturedUvQuad(nullptr, layer->texCoords)
            .setFillLayer(layer->getTexture(), layer->getColorFilter(), layerAlpha, layer->getMode(), Blend::ModeOrderSwap::NoSwap)
            .setMeshTexturedUvQuad(nullptr, buffer->texCoords)
            .setFillLayer(buffer->texture, op.paint->getColorFilter(), layerAlpha, PaintUtils::getXfermodeDirect(op.paint), Blend::ModeOrderSwap::NoSwap)
            .setTransform(state.computedState.transform, TransformFlags::None)
            .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
            .build();
    info.renderGlop(state, glop);
    renderer.renderGlop(state, glop);

    // return layer to cache, since each clipped savelayer is only drawn once.
    layer->setConvexMask(nullptr);
    if (!info.caches.layerCache.put(layer)) {
        // Failing to add the layer to the cache should happen only if the layer is too large
        LAYER_LOGD("Deleting layer");
        layer->decStrong(nullptr);
    }
    // destroy and delete, since each clipped saveLayer is only drawn once.
    buffer->texture.deleteTexture();

    // TODO: return texture/offscreenbuffer to cache!
    delete buffer;
}

} // namespace uirenderer
+65 −31
Original line number Diff line number Diff line
@@ -28,48 +28,82 @@ struct Glop;
class Layer;
class RenderState;

class BakedOpRenderer {
/**
 * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and
 * encompasses enough information to draw it back on screen (minus paint properties, which are held
 * by LayerOp).
 */
class OffscreenBuffer {
public:
    class Info {
    OffscreenBuffer(Caches& caches, uint32_t textureWidth, uint32_t textureHeight,
            uint32_t viewportWidth, uint32_t viewportHeight);

    Texture texture;
    Rect texCoords;
    Region region;
};

/**
 * Main rendering manager for a collection of work - one frame + any contained FBOs.
 *
 * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only
 * place where FBOs are bound, created, and destroyed.
 *
 * All rendering operations will be sent by the Dispatcher, a collection of static methods,
 * which has intentionally limited access to the renderer functionality.
 */
class BakedOpRenderer {
public:
        Info(Caches& caches, RenderState& renderState, bool opaque)
                : renderState(renderState)
                , caches(caches)
                , opaque(opaque) {
    BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque)
            : mRenderState(renderState)
            , mCaches(caches)
            , mOpaque(opaque) {
    }

        void setViewport(uint32_t width, uint32_t height);
    RenderState& renderState() { return mRenderState; }
    Caches& caches() { return mCaches; }

    void startFrame(uint32_t width, uint32_t height);
    void endFrame();
    OffscreenBuffer* startLayer(uint32_t width, uint32_t height);
    void endLayer();

    Texture* getTexture(const SkBitmap* bitmap);

    void renderGlop(const BakedOpState& state, const Glop& glop);
        RenderState& renderState;
        Caches& caches;

        bool didDraw = false;
    bool didDraw() { return mHasDrawn; }
private:
    void setViewport(uint32_t width, uint32_t height);

        Layer* layer = nullptr;
    RenderState& mRenderState;
    Caches& mCaches;
    bool mOpaque;
    bool mHasDrawn = false;

        // where should these live? layer state object?
        bool opaque;
    // render target state - setup by start/end layer/frame
    // only valid to use in between start/end pairs.
    struct {
        GLuint frameBufferId = 0;
        OffscreenBuffer* offscreenBuffer = nullptr;
        uint32_t viewportWidth = 0;
        uint32_t viewportHeight = 0;
        Matrix4 orthoMatrix;
    } mRenderTarget;
};

    static Layer* startLayer(Info& info, uint32_t width, uint32_t height);
    static void endLayer(Info& info);
    static void startFrame(Info& info, uint32_t width, uint32_t height);
    static void endFrame(Info& info);

/**
     * Declare all "onBitmapOp(...)" style function for every op type.
 * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
 * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.
 *
     * These functions will perform the actual rendering of the individual operations in OpenGL,
     * given the transform/clip and other state built into the BakedOpState object passed in.
 * This dispatcher is separate from the renderer so that the dispatcher / renderer interaction is
 * minimal through public BakedOpRenderer APIs.
 */
    #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state);
    MAP_OPS(BAKED_OP_RENDERER_METHOD);
class BakedOpDispatcher {
public:
    // Declares all "onBitmapOp(...)" style methods for every op type
#define DISPATCH_METHOD(Type) \
        static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state);
    MAP_OPS(DISPATCH_METHOD);
};

}; // namespace uirenderer
+3 −6
Original line number Diff line number Diff line
@@ -277,7 +277,8 @@ void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
    }
}

void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const {
void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const {
    ATRACE_NAME("flush drawing commands");
    for (const BatchBase* batch : mBatches) {
        // TODO: different behavior based on batch->isMerging()
        for (const BakedOpState* op : batch->getOps()) {
@@ -353,10 +354,6 @@ void OpReorderer::deferImpl(const DisplayList& displayList) {
    }
}

void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
    ATRACE_NAME("flush drawing commands");
}

void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
    if (op.renderNode->nothingToDraw()) {
        return;
@@ -435,7 +432,7 @@ void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
            beginLayerOp.localMatrix,
            beginLayerOp.localClipRect,
            beginLayerOp.paint,
            &mLayerReorderers[finishedLayerIndex].layer);
            &mLayerReorderers[finishedLayerIndex].offscreenBuffer);
    BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);

    if (bakedOpState) {
+17 −16
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ namespace uirenderer {
class BakedOpState;
class BatchBase;
class MergingOpBatch;
class OffscreenBuffer;
class OpBatch;
class Rect;

@@ -55,7 +56,7 @@ namespace OpBatchType {
}

class OpReorderer : public CanvasStateClient {
    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
    typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpDispatcher;

    /**
     * Stores the deferred render operations and state used to compute ordering
@@ -79,7 +80,7 @@ class OpReorderer : public CanvasStateClient {
        void deferMergeableOp(LinearAllocator& allocator,
                BakedOpState* op, batchid_t batchId, mergeid_t mergeId);

        void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
        void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers) const;

        bool empty() const {
            return mBatches.empty();
@@ -91,7 +92,7 @@ class OpReorderer : public CanvasStateClient {

        void dump() const;

        Layer* layer = nullptr;
        OffscreenBuffer* offscreenBuffer = nullptr;
        const BeginLayerOp* beginLayerOp = nullptr;
        const uint32_t width;
        const uint32_t height;
@@ -127,15 +128,15 @@ public:
     *
     * For example a BitmapOp would resolve, via the lambda lookup, to calling:
     *
     * StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state);
     * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state);
     */
#define BAKED_OP_RECEIVER(Type) \
    [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
        StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \
    [](void* internalRenderer, const RecordedOp& op, const BakedOpState& state) { \
        StaticDispatcher::on##Type(*(static_cast<Renderer*>(internalRenderer)), static_cast<const Type&>(op), state); \
    },
    template <typename StaticReceiver, typename Arg>
    void replayBakedOps(Arg& arg) {
        static BakedOpReceiver receivers[] = {
    template <typename StaticDispatcher, typename Renderer>
    void replayBakedOps(Renderer& renderer) {
        static BakedOpDispatcher receivers[] = {
            MAP_OPS(BAKED_OP_RECEIVER)
        };

@@ -144,16 +145,16 @@ public:
        for (int i = mLayerReorderers.size() - 1; i >= 1; i--) {
            LayerReorderer& layer = mLayerReorderers[i];
            if (!layer.empty()) {
                layer.layer = StaticReceiver::startLayer(arg, layer.width, layer.height);
                layer.replayBakedOpsImpl((void*)&arg, receivers);
                StaticReceiver::endLayer(arg);
                layer.offscreenBuffer = renderer.startLayer(layer.width, layer.height);
                layer.replayBakedOpsImpl((void*)&renderer, receivers);
                renderer.endLayer();
            }
        }

        const LayerReorderer& fbo0 = mLayerReorderers[0];
        StaticReceiver::startFrame(arg, fbo0.width, fbo0.height);
        fbo0.replayBakedOpsImpl((void*)&arg, receivers);
        StaticReceiver::endFrame(arg);
        renderer.startFrame(fbo0.width, fbo0.height);
        fbo0.replayBakedOpsImpl((void*)&renderer, receivers);
        renderer.endFrame();
    }

    void dump() const {
@@ -178,7 +179,7 @@ private:

    void deferImpl(const DisplayList& displayList);

    void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
    void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);

    /**
     * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
+3 −3
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ class SkPaint;
namespace android {
namespace uirenderer {

class Layer;
class OffscreenBuffer;
class RenderNode;
struct Vertex;

@@ -137,12 +137,12 @@ struct EndLayerOp : RecordedOp {
};

struct LayerOp : RecordedOp {
    LayerOp(BASE_PARAMS, Layer** layerHandle)
    LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle)
            : SUPER(LayerOp)
            , layerHandle(layerHandle) {}
    // Records a handle to the Layer object, since the Layer itself won't be
    // constructed until after this operation is constructed.
    Layer** layerHandle;
    OffscreenBuffer** layerHandle;
};

}; // namespace uirenderer
Loading