| /* |
| * Copyright (C) 2010 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 "OpenGLRenderer.h" |
| |
| #include "DeferredDisplayList.h" |
| #include "GammaFontRenderer.h" |
| #include "Glop.h" |
| #include "GlopBuilder.h" |
| #include "Patch.h" |
| #include "PathTessellator.h" |
| #include "Properties.h" |
| #include "RenderNode.h" |
| #include "renderstate/MeshState.h" |
| #include "renderstate/RenderState.h" |
| #include "ShadowTessellator.h" |
| #include "SkiaShader.h" |
| #include "Vector.h" |
| #include "VertexBuffer.h" |
| #include "utils/GLUtils.h" |
| #include "utils/PaintUtils.h" |
| #include "utils/TraceUtils.h" |
| |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <sys/types.h> |
| |
| #include <SkCanvas.h> |
| #include <SkColor.h> |
| #include <SkPathOps.h> |
| #include <SkShader.h> |
| #include <SkTypeface.h> |
| |
| #include <utils/Log.h> |
| #include <utils/StopWatch.h> |
| |
| #include <private/hwui/DrawGlInfo.h> |
| |
| #include <ui/Rect.h> |
| |
| #if DEBUG_DETAILED_EVENTS |
| #define EVENT_LOGD(...) eventMarkDEBUG(__VA_ARGS__) |
| #else |
| #define EVENT_LOGD(...) |
| #endif |
| |
| namespace android { |
| namespace uirenderer { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Constructors/destructor |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| OpenGLRenderer::OpenGLRenderer(RenderState& renderState) |
| : mState(*this) |
| , mCaches(Caches::getInstance()) |
| , mRenderState(renderState) |
| , mFrameStarted(false) |
| , mScissorOptimizationDisabled(false) |
| , mSuppressTiling(false) |
| , mFirstFrameAfterResize(true) |
| , mDirty(false) |
| , mLightCenter((Vector3){FLT_MIN, FLT_MIN, FLT_MIN}) |
| , mLightRadius(FLT_MIN) |
| , mAmbientShadowAlpha(0) |
| , mSpotShadowAlpha(0) { |
| } |
| |
| OpenGLRenderer::~OpenGLRenderer() { |
| // The context has already been destroyed at this point, do not call |
| // GL APIs. All GL state should be kept in Caches.h |
| } |
| |
| void OpenGLRenderer::initProperties() { |
| char property[PROPERTY_VALUE_MAX]; |
| if (property_get(PROPERTY_DISABLE_SCISSOR_OPTIMIZATION, property, "false")) { |
| mScissorOptimizationDisabled = !strcasecmp(property, "true"); |
| INIT_LOGD(" Scissor optimization %s", |
| mScissorOptimizationDisabled ? "disabled" : "enabled"); |
| } else { |
| INIT_LOGD(" Scissor optimization enabled"); |
| } |
| } |
| |
| void OpenGLRenderer::initLight(float lightRadius, uint8_t ambientShadowAlpha, |
| uint8_t spotShadowAlpha) { |
| mLightRadius = lightRadius; |
| mAmbientShadowAlpha = ambientShadowAlpha; |
| mSpotShadowAlpha = spotShadowAlpha; |
| } |
| |
| void OpenGLRenderer::setLightCenter(const Vector3& lightCenter) { |
| mLightCenter = lightCenter; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Setup |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void OpenGLRenderer::onViewportInitialized() { |
| glDisable(GL_DITHER); |
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); |
| mFirstFrameAfterResize = true; |
| } |
| |
| void OpenGLRenderer::setupFrameState(float left, float top, |
| float right, float bottom, bool opaque) { |
| mCaches.clearGarbage(); |
| mState.initializeSaveStack(left, top, right, bottom, mLightCenter); |
| mOpaque = opaque; |
| mTilingClip.set(left, top, right, bottom); |
| } |
| |
| void OpenGLRenderer::startFrame() { |
| if (mFrameStarted) return; |
| mFrameStarted = true; |
| |
| mState.setDirtyClip(true); |
| |
| discardFramebuffer(mTilingClip.left, mTilingClip.top, mTilingClip.right, mTilingClip.bottom); |
| |
| mRenderState.setViewport(mState.getWidth(), mState.getHeight()); |
| |
| // Functors break the tiling extension in pretty spectacular ways |
| // This ensures we don't use tiling when a functor is going to be |
| // invoked during the frame |
| mSuppressTiling = mCaches.hasRegisteredFunctors() |
| || mFirstFrameAfterResize; |
| mFirstFrameAfterResize = false; |
| |
| startTilingCurrentClip(true); |
| |
| debugOverdraw(true, true); |
| |
| clear(mTilingClip.left, mTilingClip.top, |
| mTilingClip.right, mTilingClip.bottom, mOpaque); |
| } |
| |
| void OpenGLRenderer::prepareDirty(float left, float top, |
| float right, float bottom, bool opaque) { |
| |
| setupFrameState(left, top, right, bottom, opaque); |
| |
| // Layer renderers will start the frame immediately |
| // The framebuffer renderer will first defer the display list |
| // for each layer and wait until the first drawing command |
| // to start the frame |
| if (currentSnapshot()->fbo == 0) { |
| mRenderState.blend().syncEnabled(); |
| updateLayers(); |
| } else { |
| startFrame(); |
| } |
| } |
| |
| void OpenGLRenderer::discardFramebuffer(float left, float top, float right, float bottom) { |
| // If we know that we are going to redraw the entire framebuffer, |
| // perform a discard to let the driver know we don't need to preserve |
| // the back buffer for this frame. |
| if (mCaches.extensions().hasDiscardFramebuffer() && |
| left <= 0.0f && top <= 0.0f && right >= mState.getWidth() && bottom >= mState.getHeight()) { |
| const bool isFbo = getTargetFbo() == 0; |
| const GLenum attachments[] = { |
| isFbo ? (const GLenum) GL_COLOR_EXT : (const GLenum) GL_COLOR_ATTACHMENT0, |
| isFbo ? (const GLenum) GL_STENCIL_EXT : (const GLenum) GL_STENCIL_ATTACHMENT }; |
| glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments); |
| } |
| } |
| |
| void OpenGLRenderer::clear(float left, float top, float right, float bottom, bool opaque) { |
| if (!opaque) { |
| mRenderState.scissor().setEnabled(true); |
| mRenderState.scissor().set(left, getViewportHeight() - bottom, right - left, bottom - top); |
| glClear(GL_COLOR_BUFFER_BIT); |
| mDirty = true; |
| return; |
| } |
| |
| mRenderState.scissor().reset(); |
| } |
| |
| void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) { |
| if (!mSuppressTiling) { |
| const Snapshot* snapshot = currentSnapshot(); |
| |
| const Rect* clip = &mTilingClip; |
| if (snapshot->flags & Snapshot::kFlagFboTarget) { |
| clip = &(snapshot->layer->clipRect); |
| } |
| |
| startTiling(*clip, getViewportHeight(), opaque, expand); |
| } |
| } |
| |
| void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) { |
| if (!mSuppressTiling) { |
| if(expand) { |
| // Expand the startTiling region by 1 |
| int leftNotZero = (clip.left > 0) ? 1 : 0; |
| int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0; |
| |
| mCaches.startTiling( |
| clip.left - leftNotZero, |
| windowHeight - clip.bottom - topNotZero, |
| clip.right - clip.left + leftNotZero + 1, |
| clip.bottom - clip.top + topNotZero + 1, |
| opaque); |
| } else { |
| mCaches.startTiling(clip.left, windowHeight - clip.bottom, |
| clip.right - clip.left, clip.bottom - clip.top, opaque); |
| } |
| } |
| } |
| |
| void OpenGLRenderer::endTiling() { |
| if (!mSuppressTiling) mCaches.endTiling(); |
| } |
| |
| bool OpenGLRenderer::finish() { |
| renderOverdraw(); |
| endTiling(); |
| mTempPaths.clear(); |
| |
| // When finish() is invoked on FBO 0 we've reached the end |
| // of the current frame |
| if (getTargetFbo() == 0) { |
| mCaches.pathCache.trim(); |
| mCaches.tessellationCache.trim(); |
| } |
| |
| if (!suppressErrorChecks()) { |
| #if DEBUG_OPENGL |
| GLUtils::dumpGLErrors(); |
| #endif |
| |
| #if DEBUG_MEMORY_USAGE |
| mCaches.dumpMemoryUsage(); |
| #else |
| if (Properties::debugLevel & kDebugMemory) { |
| mCaches.dumpMemoryUsage(); |
| } |
| #endif |
| } |
| |
| mFrameStarted = false; |
| |
| return reportAndClearDirty(); |
| } |
| |
| void OpenGLRenderer::resumeAfterLayer() { |
| mRenderState.setViewport(getViewportWidth(), getViewportHeight()); |
| mRenderState.bindFramebuffer(currentSnapshot()->fbo); |
| debugOverdraw(true, false); |
| |
| mRenderState.scissor().reset(); |
| dirtyClip(); |
| } |
| |
| void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { |
| if (mState.currentlyIgnored()) return; |
| |
| Rect clip(mState.currentClipRect()); |
| clip.snapToPixelBoundaries(); |
| |
| // Since we don't know what the functor will draw, let's dirty |
| // the entire clip region |
| if (hasLayer()) { |
| dirtyLayerUnchecked(clip, getRegion()); |
| } |
| |
| DrawGlInfo info; |
| info.clipLeft = clip.left; |
| info.clipTop = clip.top; |
| info.clipRight = clip.right; |
| info.clipBottom = clip.bottom; |
| info.isLayer = hasLayer(); |
| info.width = getViewportWidth(); |
| info.height = getViewportHeight(); |
| currentTransform()->copyTo(&info.transform[0]); |
| |
| bool prevDirtyClip = mState.getDirtyClip(); |
| // setup GL state for functor |
| if (mState.getDirtyClip()) { |
| setStencilFromClip(); // can issue draws, so must precede enableScissor()/interrupt() |
| } |
| if (mRenderState.scissor().setEnabled(true) || prevDirtyClip) { |
| setScissorFromClip(); |
| } |
| |
| mRenderState.invokeFunctor(functor, DrawGlInfo::kModeDraw, &info); |
| // Scissor may have been modified, reset dirty clip |
| dirtyClip(); |
| |
| mDirty = true; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Debug |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void OpenGLRenderer::eventMarkDEBUG(const char* fmt, ...) const { |
| #if DEBUG_DETAILED_EVENTS |
| const int BUFFER_SIZE = 256; |
| va_list ap; |
| char buf[BUFFER_SIZE]; |
| |
| va_start(ap, fmt); |
| vsnprintf(buf, BUFFER_SIZE, fmt, ap); |
| va_end(ap); |
| |
| eventMark(buf); |
| #endif |
| } |
| |
| |
| void OpenGLRenderer::eventMark(const char* name) const { |
| mCaches.eventMark(0, name); |
| } |
| |
| void OpenGLRenderer::startMark(const char* name) const { |
| mCaches.startMark(0, name); |
| } |
| |
| void OpenGLRenderer::endMark() const { |
| mCaches.endMark(); |
| } |
| |
| void OpenGLRenderer::debugOverdraw(bool enable, bool clear) { |
| mRenderState.debugOverdraw(enable, clear); |
| } |
| |
| void OpenGLRenderer::renderOverdraw() { |
| if (Properties::debugOverdraw && getTargetFbo() == 0) { |
| const Rect* clip = &mTilingClip; |
| |
| mRenderState.scissor().setEnabled(true); |
| mRenderState.scissor().set(clip->left, |
| mState.firstSnapshot()->getViewportHeight() - clip->bottom, |
| clip->right - clip->left, |
| clip->bottom - clip->top); |
| |
| // 1x overdraw |
| mRenderState.stencil().enableDebugTest(2); |
| drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode); |
| |
| // 2x overdraw |
| mRenderState.stencil().enableDebugTest(3); |
| drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode); |
| |
| // 3x overdraw |
| mRenderState.stencil().enableDebugTest(4); |
| drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode); |
| |
| // 4x overdraw and higher |
| mRenderState.stencil().enableDebugTest(4, true); |
| drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode); |
| |
| mRenderState.stencil().disable(); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Layers |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { |
| if (layer->deferredUpdateScheduled && layer->renderer |
| && layer->renderNode.get() && layer->renderNode->isRenderable()) { |
| |
| if (inFrame) { |
| endTiling(); |
| debugOverdraw(false, false); |
| } |
| |
| if (CC_UNLIKELY(inFrame || Properties::drawDeferDisabled)) { |
| layer->render(*this); |
| } else { |
| layer->defer(*this); |
| } |
| |
| if (inFrame) { |
| resumeAfterLayer(); |
| startTilingCurrentClip(); |
| } |
| |
| layer->debugDrawUpdate = Properties::debugLayersUpdates; |
| layer->hasDrawnSinceUpdate = false; |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void OpenGLRenderer::updateLayers() { |
| // If draw deferring is enabled this method will simply defer |
| // the display list of each individual layer. The layers remain |
| // in the layer updates list which will be cleared by flushLayers(). |
| int count = mLayerUpdates.size(); |
| if (count > 0) { |
| if (CC_UNLIKELY(Properties::drawDeferDisabled)) { |
| startMark("Layer Updates"); |
| } else { |
| startMark("Defer Layer Updates"); |
| } |
| |
| // Note: it is very important to update the layers in order |
| for (int i = 0; i < count; i++) { |
| Layer* layer = mLayerUpdates.itemAt(i).get(); |
| updateLayer(layer, false); |
| } |
| |
| if (CC_UNLIKELY(Properties::drawDeferDisabled)) { |
| mLayerUpdates.clear(); |
| mRenderState.bindFramebuffer(getTargetFbo()); |
| } |
| endMark(); |
| } |
| } |
| |
| void OpenGLRenderer::flushLayers() { |
| int count = mLayerUpdates.size(); |
| if (count > 0) { |
| startMark("Apply Layer Updates"); |
| |
| // Note: it is very important to update the layers in order |
| for (int i = 0; i < count; i++) { |
| mLayerUpdates.itemAt(i)->flush(); |
| } |
| |
| mLayerUpdates.clear(); |
| mRenderState.bindFramebuffer(getTargetFbo()); |
| |
| endMark(); |
| } |
| } |
| |
| void OpenGLRenderer::pushLayerUpdate(Layer* layer) { |
| if (layer) { |
| // Make sure we don't introduce duplicates. |
| // SortedVector would do this automatically but we need to respect |
| // the insertion order. The linear search is not an issue since |
| // this list is usually very short (typically one item, at most a few) |
| for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { |
| if (mLayerUpdates.itemAt(i) == layer) { |
| return; |
| } |
| } |
| mLayerUpdates.push_back(layer); |
| } |
| } |
| |
| void OpenGLRenderer::cancelLayerUpdate(Layer* layer) { |
| if (layer) { |
| for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { |
| if (mLayerUpdates.itemAt(i) == layer) { |
| mLayerUpdates.removeAt(i); |
| break; |
| } |
| } |
| } |
| } |
| |
| void OpenGLRenderer::flushLayerUpdates() { |
| ATRACE_NAME("Update HW Layers"); |
| mRenderState.blend().syncEnabled(); |
| updateLayers(); |
| flushLayers(); |
| // Wait for all the layer updates to be executed |
| glFinish(); |
| } |
| |
| void OpenGLRenderer::markLayersAsBuildLayers() { |
| for (size_t i = 0; i < mLayerUpdates.size(); i++) { |
| mLayerUpdates[i]->wasBuildLayered = true; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // State management |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void OpenGLRenderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { |
| bool restoreViewport = removed.flags & Snapshot::kFlagIsFboLayer; |
| bool restoreClip = removed.flags & Snapshot::kFlagClipSet; |
| bool restoreLayer = removed.flags & Snapshot::kFlagIsLayer; |
| |
| if (restoreViewport) { |
| mRenderState.setViewport(getViewportWidth(), getViewportHeight()); |
| } |
| |
| if (restoreClip) { |
| dirtyClip(); |
| } |
| |
| if (restoreLayer) { |
| endMark(); // Savelayer |
| ATRACE_END(); // SaveLayer |
| startMark("ComposeLayer"); |
| composeLayer(removed, restored); |
| endMark(); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Layers |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, |
| const SkPaint* paint, int flags, const SkPath* convexMask) { |
| // force matrix/clip isolation for layer |
| flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag; |
| |
| const int count = mState.saveSnapshot(flags); |
| |
| if (!mState.currentlyIgnored()) { |
| createLayer(left, top, right, bottom, paint, flags, convexMask); |
| } |
| |
| return count; |
| } |
| |
| void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer) { |
| const Rect untransformedBounds(bounds); |
| |
| currentTransform()->mapRect(bounds); |
| |
| // Layers only make sense if they are in the framebuffer's bounds |
| if (bounds.intersect(mState.currentClipRect())) { |
| // We cannot work with sub-pixels in this case |
| bounds.snapToPixelBoundaries(); |
| |
| // When the layer is not an FBO, we may use glCopyTexImage so we |
| // need to make sure the layer does not extend outside the bounds |
| // of the framebuffer |
| const Snapshot& previous = *(currentSnapshot()->previous); |
| Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight()); |
| if (!bounds.intersect(previousViewport)) { |
| bounds.setEmpty(); |
| } else if (fboLayer) { |
| clip.set(bounds); |
| mat4 inverse; |
| inverse.loadInverse(*currentTransform()); |
| inverse.mapRect(clip); |
| clip.snapToPixelBoundaries(); |
| if (clip.intersect(untransformedBounds)) { |
| clip.translate(-untransformedBounds.left, -untransformedBounds.top); |
| bounds.set(untransformedBounds); |
| } else { |
| clip.setEmpty(); |
| } |
| } |
| } else { |
| bounds.setEmpty(); |
| } |
| } |
| |
| void OpenGLRenderer::updateSnapshotIgnoreForLayer(const Rect& bounds, const Rect& clip, |
| bool fboLayer, int alpha) { |
| if (bounds.isEmpty() || bounds.getWidth() > mCaches.maxTextureSize || |
| bounds.getHeight() > mCaches.maxTextureSize || |
| (fboLayer && clip.isEmpty())) { |
| writableSnapshot()->empty = fboLayer; |
| } else { |
| writableSnapshot()->invisible = writableSnapshot()->invisible || (alpha <= 0 && fboLayer); |
| } |
| } |
| |
| int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float bottom, |
| const SkPaint* paint, int flags) { |
| const int count = mState.saveSnapshot(flags); |
| |
| if (!mState.currentlyIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) { |
| // initialize the snapshot as though it almost represents an FBO layer so deferred draw |
| // operations will be able to store and restore the current clip and transform info, and |
| // quick rejection will be correct (for display lists) |
| |
| Rect bounds(left, top, right, bottom); |
| Rect clip; |
| calculateLayerBoundsAndClip(bounds, clip, true); |
| updateSnapshotIgnoreForLayer(bounds, clip, true, getAlphaDirect(paint)); |
| |
| if (!mState.currentlyIgnored()) { |
| writableSnapshot()->resetTransform(-bounds.left, -bounds.top, 0.0f); |
| writableSnapshot()->resetClip(clip.left, clip.top, clip.right, clip.bottom); |
| writableSnapshot()->initializeViewport(bounds.getWidth(), bounds.getHeight()); |
| writableSnapshot()->roundRectClipState = nullptr; |
| } |
| } |
| |
| return count; |
| } |
| |
| /** |
| * Layers are viewed by Skia are slightly different than layers in image editing |
| * programs (for instance.) When a layer is created, previously created layers |
| * and the frame buffer still receive every drawing command. For instance, if a |
| * layer is created and a shape intersecting the bounds of the layers and the |
| * framebuffer is draw, the shape will be drawn on both (unless the layer was |
| * created with the SkCanvas::kClipToLayer_SaveFlag flag.) |
| * |
| * A way to implement layers is to create an FBO for each layer, backed by an RGBA |
| * texture. Unfortunately, this is inefficient as it requires every primitive to |
| * be drawn n + 1 times, where n is the number of active layers. In practice this |
| * means, for every primitive: |
| * - Switch active frame buffer |
| * - Change viewport, clip and projection matrix |
| * - Issue the drawing |
| * |
| * Switching rendering target n + 1 times per drawn primitive is extremely costly. |
| * To avoid this, layers are implemented in a different way here, at least in the |
| * general case. FBOs are used, as an optimization, when the "clip to layer" flag |
| * is set. When this flag is set we can redirect all drawing operations into a |
| * single FBO. |
| * |
| * This implementation relies on the frame buffer being at least RGBA 8888. When |
| * a layer is created, only a texture is created, not an FBO. The content of the |
| * frame buffer contained within the layer's bounds is copied into this texture |
| * using glCopyTexImage2D(). The layer's region is then cleared(1) in the frame |
| * buffer and drawing continues as normal. This technique therefore treats the |
| * frame buffer as a scratch buffer for the layers. |
| * |
| * To compose the layers back onto the frame buffer, each layer texture |
| * (containing the original frame buffer data) is drawn as a simple quad over |
| * the frame buffer. The trick is that the quad is set as the composition |
| * destination in the blending equation, and the frame buffer becomes the source |
| * of the composition. |
| * |
| * Drawing layers with an alpha value requires an extra step before composition. |
| * An empty quad is drawn over the layer's region in the frame buffer. This quad |
| * is drawn with the rgba color (0,0,0,alpha). The alpha value offered by the |
| * quad is used to multiply the colors in the frame buffer. This is achieved by |
| * changing the GL blend functions for the GL_FUNC_ADD blend equation to |
| * GL_ZERO, GL_SRC_ALPHA. |
| * |
| * Because glCopyTexImage2D() can be slow, an alternative implementation might |
| * be use to draw a single clipped layer. The implementation described above |
| * is correct in every case. |
| * |
| * (1) The frame buffer is actually not cleared right away. To allow the GPU |
| * to potentially optimize series of calls to glCopyTexImage2D, the frame |
| * buffer is left untouched until the first drawing operation. Only when |
| * something actually gets drawn are the layers regions cleared. |
| */ |
| bool OpenGLRenderer::createLayer(float left, float top, float right, float bottom, |
| const SkPaint* paint, int flags, const SkPath* convexMask) { |
| LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top); |
| LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize()); |
| |
| const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag; |
| |
| // Window coordinates of the layer |
| Rect clip; |
| Rect bounds(left, top, right, bottom); |
| calculateLayerBoundsAndClip(bounds, clip, fboLayer); |
| updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, getAlphaDirect(paint)); |
| |
| // Bail out if we won't draw in this snapshot |
| if (mState.currentlyIgnored()) { |
| return false; |
| } |
| |
| mCaches.textureState().activateTexture(0); |
| Layer* layer = mCaches.layerCache.get(mRenderState, bounds.getWidth(), bounds.getHeight()); |
| if (!layer) { |
| return false; |
| } |
| |
| layer->setPaint(paint); |
| layer->layer.set(bounds); |
| layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()), |
| bounds.getWidth() / float(layer->getWidth()), 0.0f); |
| |
| layer->setBlend(true); |
| layer->setDirty(false); |
| layer->setConvexMask(convexMask); // note: the mask must be cleared before returning to the cache |
| |
| // Save the layer in the snapshot |
| writableSnapshot()->flags |= Snapshot::kFlagIsLayer; |
| writableSnapshot()->layer = layer; |
| |
| ATRACE_FORMAT_BEGIN("%ssaveLayer %ux%u", |
| fboLayer ? "" : "unclipped ", |
| layer->getWidth(), layer->getHeight()); |
| startMark("SaveLayer"); |
| if (fboLayer) { |
| return createFboLayer(layer, bounds, clip); |
| } else { |
| // Copy the framebuffer into the layer |
| layer->bindTexture(); |
| if (!bounds.isEmpty()) { |
| if (layer->isEmpty()) { |
| // Workaround for some GL drivers. When reading pixels lying outside |
| // of the window we should get undefined values for those pixels. |
| // Unfortunately some drivers will turn the entire target texture black |
| // when reading outside of the window. |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, layer->getWidth(), layer->getHeight(), |
| 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); |
| layer->setEmpty(false); |
| } |
| |
| glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, |
| bounds.left, getViewportHeight() - bounds.bottom, |
| bounds.getWidth(), bounds.getHeight()); |
| |
| // Enqueue the buffer coordinates to clear the corresponding region later |
| mLayers.push_back(Rect(bounds)); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { |
| layer->clipRect.set(clip); |
| layer->setFbo(mCaches.fboCache.get()); |
| |
| writableSnapshot()->region = &writableSnapshot()->layer->region; |
| writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; |
| writableSnapshot()->fbo = layer->getFbo(); |
| writableSnapshot()->resetTransform(-bounds.left, -bounds.top, 0.0f); |
| writableSnapshot()->resetClip(clip.left, clip.top, clip.right, clip.bottom); |
| writableSnapshot()->initializeViewport(bounds.getWidth(), bounds.getHeight()); |
| writableSnapshot()->roundRectClipState = nullptr; |
| |
| endTiling(); |
| debugOverdraw(false, false); |
| // Bind texture to FBO |
| mRenderState.bindFramebuffer(layer->getFbo()); |
| layer->bindTexture(); |
| |
| // Initialize the texture if needed |
| if (layer->isEmpty()) { |
| layer->allocateTexture(); |
| layer->setEmpty(false); |
| } |
| |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| layer->getTextureId(), 0); |
| |
| // Expand the startTiling region by 1 |
| startTilingCurrentClip(true, true); |
| |
| // Clear the FBO, expand the clear region by 1 to get nice bilinear filtering |
| mRenderState.scissor().setEnabled(true); |
| mRenderState.scissor().set(clip.left - 1.0f, bounds.getHeight() - clip.bottom - 1.0f, |
| clip.getWidth() + 2.0f, clip.getHeight() + 2.0f); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| dirtyClip(); |
| |
| // Change the ortho projection |
| mRenderState.setViewport(bounds.getWidth(), bounds.getHeight()); |
| return true; |
| } |
| |
| /** |
| * Read the documentation of createLayer() before doing anything in this method. |
| */ |
| void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& restored) { |
| if (!removed.layer) { |
| ALOGE("Attempting to compose a layer that does not exist"); |
| return; |
| } |
| |
| Layer* layer = removed.layer; |
| const Rect& rect = layer->layer; |
| const bool fboLayer = removed.flags & Snapshot::kFlagIsFboLayer; |
| |
| bool clipRequired = false; |
| mState.calculateQuickRejectForScissor(rect.left, rect.top, rect.right, rect.bottom, |
| &clipRequired, nullptr, false); // safely ignore return, should never be rejected |
| mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired); |
| |
| if (fboLayer) { |
| endTiling(); |
| |
| // Detach the texture from the FBO |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); |
| |
| layer->removeFbo(false); |
| |
| // Unbind current FBO and restore previous one |
| mRenderState.bindFramebuffer(restored.fbo); |
| debugOverdraw(true, false); |
| |
| startTilingCurrentClip(); |
| } |
| |
| if (!fboLayer && layer->getAlpha() < 255) { |
| SkPaint layerPaint; |
| layerPaint.setAlpha(layer->getAlpha()); |
| layerPaint.setXfermodeMode(SkXfermode::kDstIn_Mode); |
| layerPaint.setColorFilter(layer->getColorFilter()); |
| |
| drawColorRect(rect.left, rect.top, rect.right, rect.bottom, &layerPaint, true); |
| // Required below, composeLayerRect() will divide by 255 |
| layer->setAlpha(255); |
| } |
| |
| mRenderState.meshState().unbindMeshBuffer(); |
| |
| mCaches.textureState().activateTexture(0); |
| |
| // When the layer is stored in an FBO, we can save a bit of fillrate by |
| // drawing only the dirty region |
| if (fboLayer) { |
| dirtyLayer(rect.left, rect.top, rect.right, rect.bottom, *restored.transform); |
| composeLayerRegion(layer, rect); |
| } else if (!rect.isEmpty()) { |
| dirtyLayer(rect.left, rect.top, rect.right, rect.bottom); |
| |
| save(0); |
| // the layer contains screen buffer content that shouldn't be alpha modulated |
| // (and any necessary alpha modulation was handled drawing into the layer) |
| writableSnapshot()->alpha = 1.0f; |
| composeLayerRectSwapped(layer, rect); |
| restore(); |
| } |
| |
| dirtyClip(); |
| |
| // Failing to add the layer to the cache should happen only if the layer is too large |
| layer->setConvexMask(nullptr); |
| if (!mCaches.layerCache.put(layer)) { |
| LAYER_LOGD("Deleting layer"); |
| layer->decStrong(nullptr); |
| } |
| } |
| |
| void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { |
| const bool tryToSnap = !layer->getForceFilter() |
| && layer->getWidth() == (uint32_t) rect.getWidth() |
| && layer->getHeight() == (uint32_t) rect.getHeight(); |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO |
| .setFillTextureLayer(*layer, getLayerAlpha(layer)) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewMapUnitToRectOptionalSnap(tryToSnap, rect) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::composeLayerRectSwapped(Layer* layer, const Rect& rect) { |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUvQuad(nullptr, layer->texCoords) |
| .setFillLayer(layer->getTexture(), layer->getColorFilter(), |
| getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::Swap) |
| .setTransform(*currentSnapshot(), TransformFlags::MeshIgnoresCanvasTransform) |
| .setModelViewMapUnitToRect(rect) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect) { |
| if (layer->isTextureLayer()) { |
| EVENT_LOGD("composeTextureLayerRect"); |
| drawTextureLayer(layer, rect); |
| } else { |
| EVENT_LOGD("composeHardwareLayerRect"); |
| |
| const bool tryToSnap = layer->getWidth() == static_cast<uint32_t>(rect.getWidth()) |
| && layer->getHeight() == static_cast<uint32_t>(rect.getHeight()); |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUvQuad(nullptr, layer->texCoords) |
| .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewMapUnitToRectOptionalSnap(tryToSnap, rect) |
| .build(); |
| renderGlop(glop); |
| } |
| } |
| |
| /** |
| * Issues the command X, and if we're composing a save layer to the fbo or drawing a newly updated |
| * hardware layer with overdraw debug on, draws again to the stencil only, so that these draw |
| * operations are correctly counted twice for overdraw. NOTE: assumes composeLayerRegion only used |
| * by saveLayer's restore |
| */ |
| #define DRAW_DOUBLE_STENCIL_IF(COND, DRAW_COMMAND) { \ |
| DRAW_COMMAND; \ |
| if (CC_UNLIKELY(Properties::debugOverdraw && getTargetFbo() == 0 && COND)) { \ |
| glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); \ |
| DRAW_COMMAND; \ |
| glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); \ |
| } \ |
| } |
| |
| #define DRAW_DOUBLE_STENCIL(DRAW_COMMAND) DRAW_DOUBLE_STENCIL_IF(true, DRAW_COMMAND) |
| |
| // This class is purely for inspection. It inherits from SkShader, but Skia does not know how to |
| // use it. The OpenGLRenderer will look at it to find its Layer and whether it is opaque. |
| class LayerShader : public SkShader { |
| public: |
| LayerShader(Layer* layer, const SkMatrix* localMatrix) |
| : INHERITED(localMatrix) |
| , mLayer(layer) { |
| } |
| |
| virtual bool asACustomShader(void** data) const override { |
| if (data) { |
| *data = static_cast<void*>(mLayer); |
| } |
| return true; |
| } |
| |
| virtual bool isOpaque() const override { |
| return !mLayer->isBlend(); |
| } |
| |
| protected: |
| virtual void shadeSpan(int x, int y, SkPMColor[], int count) { |
| LOG_ALWAYS_FATAL("LayerShader should never be drawn with raster backend."); |
| } |
| |
| virtual void flatten(SkWriteBuffer&) const override { |
| LOG_ALWAYS_FATAL("LayerShader should never be flattened."); |
| } |
| |
| virtual Factory getFactory() const override { |
| LOG_ALWAYS_FATAL("LayerShader should never be created from a stream."); |
| return nullptr; |
| } |
| private: |
| // Unowned. |
| Layer* mLayer; |
| typedef SkShader INHERITED; |
| }; |
| |
| void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { |
| if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw |
| |
| if (layer->getConvexMask()) { |
| save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); |
| |
| // clip to the area of the layer the mask can be larger |
| clipRect(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kIntersect_Op); |
| |
| SkPaint paint; |
| paint.setAntiAlias(true); |
| paint.setColor(SkColorSetARGB(int(getLayerAlpha(layer) * 255), 0, 0, 0)); |
| |
| // create LayerShader to map SaveLayer content into subsequent draw |
| SkMatrix shaderMatrix; |
| shaderMatrix.setTranslate(rect.left, rect.bottom); |
| shaderMatrix.preScale(1, -1); |
| LayerShader layerShader(layer, &shaderMatrix); |
| paint.setShader(&layerShader); |
| |
| // Since the drawing primitive is defined in local drawing space, |
| // we don't need to modify the draw matrix |
| const SkPath* maskPath = layer->getConvexMask(); |
| DRAW_DOUBLE_STENCIL(drawConvexPath(*maskPath, &paint)); |
| |
| paint.setShader(nullptr); |
| restore(); |
| |
| return; |
| } |
| |
| if (layer->region.isRect()) { |
| layer->setRegionAsRect(); |
| |
| DRAW_DOUBLE_STENCIL(composeLayerRect(layer, layer->regionRect)); |
| |
| layer->region.clear(); |
| return; |
| } |
| |
| EVENT_LOGD("composeLayerRegion"); |
| // standard Region based draw |
| size_t count; |
| const android::Rect* rects; |
| Region safeRegion; |
| if (CC_LIKELY(hasRectToRectTransform())) { |
| rects = layer->region.getArray(&count); |
| } else { |
| safeRegion = Region::createTJunctionFreeRegion(layer->region); |
| rects = safeRegion.getArray(&count); |
| } |
| |
| const float texX = 1.0f / float(layer->getWidth()); |
| const float texY = 1.0f / float(layer->getHeight()); |
| const float height = rect.getHeight(); |
| |
| TextureVertex quadVertices[count * 4]; |
| TextureVertex* mesh = &quadVertices[0]; |
| for (size_t i = 0; i < count; i++) { |
| const android::Rect* r = &rects[i]; |
| |
| const float u1 = r->left * texX; |
| const float v1 = (height - r->top) * texY; |
| const float u2 = r->right * texX; |
| const float v2 = (height - r->bottom) * texY; |
| |
| // TODO: Reject quads outside of the clip |
| TextureVertex::set(mesh++, r->left, r->top, u1, v1); |
| TextureVertex::set(mesh++, r->right, r->top, u2, v1); |
| TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); |
| TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); |
| } |
| Rect modelRect = Rect(rect.getWidth(), rect.getHeight()); |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedIndexedQuads(&quadVertices[0], count * 6) |
| .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewOffsetRectSnap(rect.left, rect.top, modelRect) |
| .build(); |
| DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop)); |
| |
| #if DEBUG_LAYERS_AS_REGIONS |
| drawRegionRectsDebug(layer->region); |
| #endif |
| |
| layer->region.clear(); |
| } |
| |
| #if DEBUG_LAYERS_AS_REGIONS |
| void OpenGLRenderer::drawRegionRectsDebug(const Region& region) { |
| size_t count; |
| const android::Rect* rects = region.getArray(&count); |
| |
| uint32_t colors[] = { |
| 0x7fff0000, 0x7f00ff00, |
| 0x7f0000ff, 0x7fff00ff, |
| }; |
| |
| int offset = 0; |
| int32_t top = rects[0].top; |
| |
| for (size_t i = 0; i < count; i++) { |
| if (top != rects[i].top) { |
| offset ^= 0x2; |
| top = rects[i].top; |
| } |
| |
| SkPaint paint; |
| paint.setColor(colors[offset + (i & 0x1)]); |
| Rect r(rects[i].left, rects[i].top, rects[i].right, rects[i].bottom); |
| drawColorRect(r.left, r.top, r.right, r.bottom, paint); |
| } |
| } |
| #endif |
| |
| void OpenGLRenderer::drawRegionRects(const SkRegion& region, const SkPaint& paint, bool dirty) { |
| Vector<float> rects; |
| |
| SkRegion::Iterator it(region); |
| while (!it.done()) { |
| const SkIRect& r = it.rect(); |
| rects.push(r.fLeft); |
| rects.push(r.fTop); |
| rects.push(r.fRight); |
| rects.push(r.fBottom); |
| it.next(); |
| } |
| |
| drawColorRects(rects.array(), rects.size(), &paint, true, dirty, false); |
| } |
| |
| void OpenGLRenderer::dirtyLayer(const float left, const float top, |
| const float right, const float bottom, const Matrix4& transform) { |
| if (hasLayer()) { |
| Rect bounds(left, top, right, bottom); |
| transform.mapRect(bounds); |
| dirtyLayerUnchecked(bounds, getRegion()); |
| } |
| } |
| |
| void OpenGLRenderer::dirtyLayer(const float left, const float top, |
| const float right, const float bottom) { |
| if (hasLayer()) { |
| Rect bounds(left, top, right, bottom); |
| dirtyLayerUnchecked(bounds, getRegion()); |
| } |
| } |
| |
| void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { |
| if (CC_LIKELY(!bounds.isEmpty() && bounds.intersect(mState.currentClipRect()))) { |
| bounds.snapToPixelBoundaries(); |
| android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom); |
| if (!dirty.isEmpty()) { |
| region->orSelf(dirty); |
| } |
| } |
| } |
| |
| void OpenGLRenderer::clearLayerRegions() { |
| const size_t quadCount = mLayers.size(); |
| if (quadCount == 0) return; |
| |
| if (!mState.currentlyIgnored()) { |
| EVENT_LOGD("clearLayerRegions"); |
| // Doing several glScissor/glClear here can negatively impact |
| // GPUs with a tiler architecture, instead we draw quads with |
| // the Clear blending mode |
| |
| // The list contains bounds that have already been clipped |
| // against their initial clip rect, and the current clip |
| // is likely different so we need to disable clipping here |
| bool scissorChanged = mRenderState.scissor().setEnabled(false); |
| |
| Vertex mesh[quadCount * 4]; |
| Vertex* vertex = mesh; |
| |
| for (uint32_t i = 0; i < quadCount; i++) { |
| const Rect& bounds = mLayers[i]; |
| |
| Vertex::set(vertex++, bounds.left, bounds.top); |
| Vertex::set(vertex++, bounds.right, bounds.top); |
| Vertex::set(vertex++, bounds.left, bounds.bottom); |
| Vertex::set(vertex++, bounds.right, bounds.bottom); |
| } |
| // We must clear the list of dirty rects before we |
| // call clearLayerRegions() in renderGlop to prevent |
| // stencil setup from doing the same thing again |
| mLayers.clear(); |
| |
| const int transformFlags = TransformFlags::MeshIgnoresCanvasTransform; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(nullptr) // clear ignores clip state |
| .setMeshIndexedQuads(&mesh[0], quadCount) |
| .setFillClear() |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect())) |
| .build(); |
| renderGlop(glop, GlopRenderType::LayerClear); |
| |
| if (scissorChanged) mRenderState.scissor().setEnabled(true); |
| } else { |
| mLayers.clear(); |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // State Deferral |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) { |
| const Rect& currentClip = mState.currentClipRect(); |
| const mat4* currentMatrix = currentTransform(); |
| |
| if (stateDeferFlags & kStateDeferFlag_Draw) { |
| // state has bounds initialized in local coordinates |
| if (!state.mBounds.isEmpty()) { |
| currentMatrix->mapRect(state.mBounds); |
| Rect clippedBounds(state.mBounds); |
| // NOTE: if we ever want to use this clipping info to drive whether the scissor |
| // is used, it should more closely duplicate the quickReject logic (in how it uses |
| // snapToPixelBoundaries) |
| |
| if (!clippedBounds.intersect(currentClip)) { |
| // quick rejected |
| return true; |
| } |
| |
| state.mClipSideFlags = kClipSide_None; |
| if (!currentClip.contains(state.mBounds)) { |
| int& flags = state.mClipSideFlags; |
| // op partially clipped, so record which sides are clipped for clip-aware merging |
| if (currentClip.left > state.mBounds.left) flags |= kClipSide_Left; |
| if (currentClip.top > state.mBounds.top) flags |= kClipSide_Top; |
| if (currentClip.right < state.mBounds.right) flags |= kClipSide_Right; |
| if (currentClip.bottom < state.mBounds.bottom) flags |= kClipSide_Bottom; |
| } |
| state.mBounds.set(clippedBounds); |
| } else { |
| // Empty bounds implies size unknown. Label op as conservatively clipped to disable |
| // overdraw avoidance (since we don't know what it overlaps) |
| state.mClipSideFlags = kClipSide_ConservativeFull; |
| state.mBounds.set(currentClip); |
| } |
| } |
| |
| state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip); |
| if (state.mClipValid) { |
| state.mClip.set(currentClip); |
| } |
| |
| // Transform and alpha always deferred, since they are used by state operations |
| // (Note: saveLayer/restore use colorFilter and alpha, so we just save restore everything) |
| state.mMatrix.load(*currentMatrix); |
| state.mAlpha = currentSnapshot()->alpha; |
| |
| // always store/restore, since these are just pointers |
| state.mRoundRectClipState = currentSnapshot()->roundRectClipState; |
| state.mProjectionPathMask = currentSnapshot()->projectionPathMask; |
| return false; |
| } |
| |
| void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) { |
| setMatrix(state.mMatrix); |
| writableSnapshot()->alpha = state.mAlpha; |
| writableSnapshot()->roundRectClipState = state.mRoundRectClipState; |
| writableSnapshot()->projectionPathMask = state.mProjectionPathMask; |
| |
| if (state.mClipValid && !skipClipRestore) { |
| writableSnapshot()->setClip(state.mClip.left, state.mClip.top, |
| state.mClip.right, state.mClip.bottom); |
| dirtyClip(); |
| } |
| } |
| |
| /** |
| * Merged multidraw (such as in drawText and drawBitmaps rely on the fact that no clipping is done |
| * in the draw path. Instead, clipping is done ahead of time - either as a single clip rect (when at |
| * least one op is clipped), or disabled entirely (because no merged op is clipped) |
| * |
| * This method should be called when restoreDisplayState() won't be restoring the clip |
| */ |
| void OpenGLRenderer::setupMergedMultiDraw(const Rect* clipRect) { |
| if (clipRect != nullptr) { |
| writableSnapshot()->setClip(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom); |
| } else { |
| writableSnapshot()->setClip(0, 0, mState.getWidth(), mState.getHeight()); |
| } |
| dirtyClip(); |
| bool enableScissor = (clipRect != nullptr) || mScissorOptimizationDisabled; |
| mRenderState.scissor().setEnabled(enableScissor); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Clipping |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void OpenGLRenderer::setScissorFromClip() { |
| Rect clip(mState.currentClipRect()); |
| clip.snapToPixelBoundaries(); |
| |
| if (mRenderState.scissor().set(clip.left, getViewportHeight() - clip.bottom, |
| clip.getWidth(), clip.getHeight())) { |
| mState.setDirtyClip(false); |
| } |
| } |
| |
| void OpenGLRenderer::ensureStencilBuffer() { |
| // Thanks to the mismatch between EGL and OpenGL ES FBO we |
| // cannot attach a stencil buffer to fbo0 dynamically. Let's |
| // just hope we have one when hasLayer() returns false. |
| if (hasLayer()) { |
| attachStencilBufferToLayer(currentSnapshot()->layer); |
| } |
| } |
| |
| void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) { |
| // The layer's FBO is already bound when we reach this stage |
| if (!layer->getStencilRenderBuffer()) { |
| // GL_QCOM_tiled_rendering doesn't like it if a renderbuffer |
| // is attached after we initiated tiling. We must turn it off, |
| // attach the new render buffer then turn tiling back on |
| endTiling(); |
| |
| RenderBuffer* buffer = mCaches.renderBufferCache.get( |
| Stencil::getLayerStencilFormat(), |
| layer->getWidth(), layer->getHeight()); |
| layer->setStencilRenderBuffer(buffer); |
| |
| startTiling(layer->clipRect, layer->layer.getHeight()); |
| } |
| } |
| |
| static void handlePoint(std::vector<Vertex>& rectangleVertices, const Matrix4& transform, |
| float x, float y) { |
| Vertex v; |
| v.x = x; |
| v.y = y; |
| transform.mapPoint(v.x, v.y); |
| rectangleVertices.push_back(v); |
| } |
| |
| static void handlePointNoTransform(std::vector<Vertex>& rectangleVertices, float x, float y) { |
| Vertex v; |
| v.x = x; |
| v.y = y; |
| rectangleVertices.push_back(v); |
| } |
| |
| void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) { |
| int quadCount = rectangleList.getTransformedRectanglesCount(); |
| std::vector<Vertex> rectangleVertices(quadCount * 4); |
| Rect scissorBox = rectangleList.calculateBounds(); |
| scissorBox.snapToPixelBoundaries(); |
| for (int i = 0; i < quadCount; ++i) { |
| const TransformedRectangle& tr(rectangleList.getTransformedRectangle(i)); |
| const Matrix4& transform = tr.getTransform(); |
| Rect bounds = tr.getBounds(); |
| if (transform.rectToRect()) { |
| transform.mapRect(bounds); |
| if (!bounds.intersect(scissorBox)) { |
| bounds.setEmpty(); |
| } else { |
| handlePointNoTransform(rectangleVertices, bounds.left, bounds.top); |
| handlePointNoTransform(rectangleVertices, bounds.right, bounds.top); |
| handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom); |
| handlePointNoTransform(rectangleVertices, bounds.right, bounds.bottom); |
| } |
| } else { |
| handlePoint(rectangleVertices, transform, bounds.left, bounds.top); |
| handlePoint(rectangleVertices, transform, bounds.right, bounds.top); |
| handlePoint(rectangleVertices, transform, bounds.left, bounds.bottom); |
| handlePoint(rectangleVertices, transform, bounds.right, bounds.bottom); |
| } |
| } |
| |
| mRenderState.scissor().set(scissorBox.left, getViewportHeight() - scissorBox.bottom, |
| scissorBox.getWidth(), scissorBox.getHeight()); |
| const int transformFlags = TransformFlags::MeshIgnoresCanvasTransform; |
| Glop glop; |
| Vertex* vertices = &rectangleVertices[0]; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshIndexedQuads(vertices, rectangleVertices.size() / 4) |
| .setFillBlack() |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewOffsetRect(0, 0, scissorBox) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::setStencilFromClip() { |
| if (!Properties::debugOverdraw) { |
| if (!currentSnapshot()->clipIsSimple()) { |
| int incrementThreshold; |
| EVENT_LOGD("setStencilFromClip - enabling"); |
| |
| // NOTE: The order here is important, we must set dirtyClip to false |
| // before any draw call to avoid calling back into this method |
| mState.setDirtyClip(false); |
| |
| ensureStencilBuffer(); |
| |
| const ClipArea& clipArea = currentSnapshot()->getClipArea(); |
| |
| bool isRectangleList = clipArea.isRectangleList(); |
| if (isRectangleList) { |
| incrementThreshold = clipArea.getRectangleList().getTransformedRectanglesCount(); |
| } else { |
| incrementThreshold = 0; |
| } |
| |
| mRenderState.stencil().enableWrite(incrementThreshold); |
| |
| // Clean and update the stencil, but first make sure we restrict drawing |
| // to the region's bounds |
| bool resetScissor = mRenderState.scissor().setEnabled(true); |
| if (resetScissor) { |
| // The scissor was not set so we now need to update it |
| setScissorFromClip(); |
| } |
| |
| mRenderState.stencil().clear(); |
| |
| // stash and disable the outline clip state, since stencil doesn't account for outline |
| bool storedSkipOutlineClip = mSkipOutlineClip; |
| mSkipOutlineClip = true; |
| |
| SkPaint paint; |
| paint.setColor(SK_ColorBLACK); |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| |
| if (isRectangleList) { |
| drawRectangleList(clipArea.getRectangleList()); |
| } else { |
| // NOTE: We could use the region contour path to generate a smaller mesh |
| // Since we are using the stencil we could use the red book path |
| // drawing technique. It might increase bandwidth usage though. |
| |
| // The last parameter is important: we are not drawing in the color buffer |
| // so we don't want to dirty the current layer, if any |
| drawRegionRects(clipArea.getClipRegion(), paint, false); |
| } |
| if (resetScissor) mRenderState.scissor().setEnabled(false); |
| mSkipOutlineClip = storedSkipOutlineClip; |
| |
| mRenderState.stencil().enableTest(incrementThreshold); |
| |
| // Draw the region used to generate the stencil if the appropriate debug |
| // mode is enabled |
| // TODO: Implement for rectangle list clip areas |
| if (Properties::debugStencilClip == StencilClipDebug::ShowRegion |
| && !clipArea.isRectangleList()) { |
| paint.setColor(0x7f0000ff); |
| paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); |
| drawRegionRects(currentSnapshot()->getClipRegion(), paint); |
| } |
| } else { |
| EVENT_LOGD("setStencilFromClip - disabling"); |
| mRenderState.stencil().disable(); |
| } |
| } |
| } |
| |
| /** |
| * Returns false and sets scissor enable based upon bounds if drawing won't be clipped out. |
| * |
| * @param paint if not null, the bounds will be expanded to account for stroke depending on paint |
| * style, and tessellated AA ramp |
| */ |
| bool OpenGLRenderer::quickRejectSetupScissor(float left, float top, float right, float bottom, |
| const SkPaint* paint) { |
| bool snapOut = paint && paint->isAntiAlias(); |
| |
| if (paint && paint->getStyle() != SkPaint::kFill_Style) { |
| float outset = paint->getStrokeWidth() * 0.5f; |
| left -= outset; |
| top -= outset; |
| right += outset; |
| bottom += outset; |
| } |
| |
| bool clipRequired = false; |
| bool roundRectClipRequired = false; |
| if (mState.calculateQuickRejectForScissor(left, top, right, bottom, |
| &clipRequired, &roundRectClipRequired, snapOut)) { |
| return true; |
| } |
| |
| // not quick rejected, so enable the scissor if clipRequired |
| mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired); |
| mSkipOutlineClip = !roundRectClipRequired; |
| return false; |
| } |
| |
| void OpenGLRenderer::debugClip() { |
| #if DEBUG_CLIP_REGIONS |
| if (!currentSnapshot()->clipRegion->isEmpty()) { |
| SkPaint paint; |
| paint.setColor(0x7f00ff00); |
| drawRegionRects(*(currentSnapshot()->clipRegion, paint); |
| |
| } |
| #endif |
| } |
| |
| void OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type) { |
| // TODO: It would be best if we could do this before quickRejectSetupScissor() |
| // changes the scissor test state |
| if (type != GlopRenderType::LayerClear) { |
| // Regular draws need to clear the dirty area on the layer before they start drawing on top |
| // of it. If this draw *is* a layer clear, it skips the clear step (since it would |
| // infinitely recurse) |
| clearLayerRegions(); |
| } |
| |
| if (mState.getDirtyClip()) { |
| if (mRenderState.scissor().isEnabled()) { |
| setScissorFromClip(); |
| } |
| |
| setStencilFromClip(); |
| } |
| mRenderState.render(glop); |
| if (type == GlopRenderType::Standard && !mRenderState.stencil().isWriteEnabled()) { |
| // TODO: specify more clearly when a draw should dirty the layer. |
| // is writing to the stencil the only time we should ignore this? |
| dirtyLayer(glop.bounds.left, glop.bounds.top, glop.bounds.right, glop.bounds.bottom); |
| mDirty = true; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Drawing |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) { |
| // All the usual checks and setup operations (quickReject, setupDraw, etc.) |
| // will be performed by the display list itself |
| if (renderNode && renderNode->isRenderable()) { |
| // compute 3d ordering |
| renderNode->computeOrdering(); |
| if (CC_UNLIKELY(Properties::drawDeferDisabled)) { |
| startFrame(); |
| ReplayStateStruct replayStruct(*this, dirty, replayFlags); |
| renderNode->replay(replayStruct, 0); |
| return; |
| } |
| |
| // Don't avoid overdraw when visualizing, since that makes it harder to |
| // debug where it's coming from, and when the problem occurs. |
| bool avoidOverdraw = !Properties::debugOverdraw; |
| DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw); |
| DeferStateStruct deferStruct(deferredList, *this, replayFlags); |
| renderNode->defer(deferStruct, 0); |
| |
| flushLayers(); |
| startFrame(); |
| |
| deferredList.flush(*this, dirty); |
| } else { |
| // Even if there is no drawing command(Ex: invisible), |
| // it still needs startFrame to clear buffer and start tiling. |
| startFrame(); |
| } |
| } |
| |
| /** |
| * Important note: this method is intended to draw batches of bitmaps and |
| * will not set the scissor enable or dirty the current layer, if any. |
| * The caller is responsible for properly dirtying the current layer. |
| */ |
| void OpenGLRenderer::drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, |
| int bitmapCount, TextureVertex* vertices, bool pureTranslate, |
| const Rect& bounds, const SkPaint* paint) { |
| Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); |
| if (!texture) return; |
| |
| const AutoTexture autoCleanup(texture); |
| |
| // TODO: remove layer dirty in multi-draw callers |
| // TODO: snap doesn't need to touch transform, only texture filter. |
| bool snap = pureTranslate; |
| const float x = floorf(bounds.left + 0.5f); |
| const float y = floorf(bounds.top + 0.5f); |
| |
| const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) |
| ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; |
| const int transformFlags = TransformFlags::MeshIgnoresCanvasTransform; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedMesh(vertices, bitmapCount * 6) |
| .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewOffsetRectOptionalSnap(snap, x, y, Rect(0, 0, bounds.getWidth(), bounds.getHeight())) |
| .build(); |
| renderGlop(glop, GlopRenderType::Multi); |
| } |
| |
| void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { |
| if (quickRejectSetupScissor(0, 0, bitmap->width(), bitmap->height())) { |
| return; |
| } |
| |
| mCaches.textureState().activateTexture(0); |
| Texture* texture = getTexture(bitmap); |
| if (!texture) return; |
| const AutoTexture autoCleanup(texture); |
| |
| const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) |
| ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUnitQuad(texture->uvMapper) |
| .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height)) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int meshHeight, |
| const float* vertices, const int* colors, const SkPaint* paint) { |
| if (!vertices || mState.currentlyIgnored()) { |
| return; |
| } |
| |
| float left = FLT_MAX; |
| float top = FLT_MAX; |
| float right = FLT_MIN; |
| float bottom = FLT_MIN; |
| |
| const uint32_t elementCount = meshWidth * meshHeight * 6; |
| |
| std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]); |
| ColorTextureVertex* vertex = &mesh[0]; |
| |
| std::unique_ptr<int[]> tempColors; |
| if (!colors) { |
| uint32_t colorsCount = (meshWidth + 1) * (meshHeight + 1); |
| tempColors.reset(new int[colorsCount]); |
| memset(tempColors.get(), 0xff, colorsCount * sizeof(int)); |
| colors = tempColors.get(); |
| } |
| |
| Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); |
| const UvMapper& mapper(getMapper(texture)); |
| |
| for (int32_t y = 0; y < meshHeight; y++) { |
| for (int32_t x = 0; x < meshWidth; x++) { |
| uint32_t i = (y * (meshWidth + 1) + x) * 2; |
| |
| float u1 = float(x) / meshWidth; |
| float u2 = float(x + 1) / meshWidth; |
| float v1 = float(y) / meshHeight; |
| float v2 = float(y + 1) / meshHeight; |
| |
| mapper.map(u1, v1, u2, v2); |
| |
| int ax = i + (meshWidth + 1) * 2; |
| int ay = ax + 1; |
| int bx = i; |
| int by = bx + 1; |
| int cx = i + 2; |
| int cy = cx + 1; |
| int dx = i + (meshWidth + 1) * 2 + 2; |
| int dy = dx + 1; |
| |
| ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]); |
| ColorTextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2, colors[ax / 2]); |
| ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]); |
| |
| ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]); |
| ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]); |
| ColorTextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1, colors[cx / 2]); |
| |
| left = std::min(left, std::min(vertices[ax], std::min(vertices[bx], vertices[cx]))); |
| top = std::min(top, std::min(vertices[ay], std::min(vertices[by], vertices[cy]))); |
| right = std::max(right, std::max(vertices[ax], std::max(vertices[bx], vertices[cx]))); |
| bottom = std::max(bottom, std::max(vertices[ay], std::max(vertices[by], vertices[cy]))); |
| } |
| } |
| |
| if (quickRejectSetupScissor(left, top, right, bottom)) { |
| return; |
| } |
| |
| if (!texture) { |
| texture = mCaches.textureCache.get(bitmap); |
| if (!texture) { |
| return; |
| } |
| } |
| const AutoTexture autoCleanup(texture); |
| |
| /* |
| * TODO: handle alpha_8 textures correctly by applying paint color, but *not* |
| * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh. |
| */ |
| const int textureFillFlags = TextureFillFlags::None; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshColoredTexturedMesh(mesh.get(), elementCount) |
| .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewOffsetRect(0, 0, Rect(left, top, right, bottom)) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, Rect src, Rect dst, const SkPaint* paint) { |
| if (quickRejectSetupScissor(dst)) { |
| return; |
| } |
| |
| Texture* texture = getTexture(bitmap); |
| if (!texture) return; |
| const AutoTexture autoCleanup(texture); |
| |
| Rect uv(std::max(0.0f, src.left / texture->width), |
| std::max(0.0f, src.top / texture->height), |
| std::min(1.0f, src.right / texture->width), |
| std::min(1.0f, src.bottom / texture->height)); |
| |
| const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) |
| ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; |
| const bool tryToSnap = MathUtils::areEqual(src.getWidth(), dst.getWidth()) |
| && MathUtils::areEqual(src.getHeight(), dst.getHeight()); |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUvQuad(texture->uvMapper, uv) |
| .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewMapUnitToRectOptionalSnap(tryToSnap, dst) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh, |
| AssetAtlas::Entry* entry, float left, float top, float right, float bottom, |
| const SkPaint* paint) { |
| if (!mesh || !mesh->verticesCount || quickRejectSetupScissor(left, top, right, bottom)) { |
| return; |
| } |
| |
| Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); |
| if (!texture) return; |
| |
| // 9 patches are built for stretching - always filter |
| int textureFillFlags = TextureFillFlags::ForceFilter; |
| if (bitmap->colorType() == kAlpha_8_SkColorType) { |
| textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; |
| } |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshPatchQuads(*mesh) |
| .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewOffsetRectSnap(left, top, Rect(0, 0, right - left, bottom - top)) // TODO: get minimal bounds from patch |
| .build(); |
| renderGlop(glop); |
| } |
| |
| /** |
| * Important note: this method is intended to draw batches of 9-patch objects and |
| * will not set the scissor enable or dirty the current layer, if any. |
| * The caller is responsible for properly dirtying the current layer. |
| */ |
| void OpenGLRenderer::drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry* entry, |
| TextureVertex* vertices, uint32_t elementCount, const SkPaint* paint) { |
| mCaches.textureState().activateTexture(0); |
| Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); |
| if (!texture) return; |
| const AutoTexture autoCleanup(texture); |
| |
| // TODO: get correct bounds from caller |
| const int transformFlags = TransformFlags::MeshIgnoresCanvasTransform; |
| // 9 patches are built for stretching - always filter |
| int textureFillFlags = TextureFillFlags::ForceFilter; |
| if (bitmap->colorType() == kAlpha_8_SkColorType) { |
| textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; |
| } |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedIndexedQuads(vertices, elementCount) |
| .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0)) |
| .build(); |
| renderGlop(glop, GlopRenderType::Multi); |
| } |
| |
| void OpenGLRenderer::drawVertexBuffer(float translateX, float translateY, |
| const VertexBuffer& vertexBuffer, const SkPaint* paint, int displayFlags) { |
| // not missing call to quickReject/dirtyLayer, always done at a higher level |
| if (!vertexBuffer.getVertexCount()) { |
| // no vertices to draw |
| return; |
| } |
| |
| bool shadowInterp = displayFlags & kVertexBuffer_ShadowInterp; |
| const int transformFlags = TransformFlags::OffsetByFudgeFactor; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshVertexBuffer(vertexBuffer, shadowInterp) |
| .setFillPaint(*paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds()) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| /** |
| * Renders a convex path via tessellation. For AA paths, this function uses a similar approach to |
| * that of AA lines in the drawLines() function. We expand the convex path by a half pixel in |
| * screen space in all directions. However, instead of using a fragment shader to compute the |
| * translucency of the color from its position, we simply use a varying parameter to define how far |
| * a given pixel is from the edge. For non-AA paths, the expansion and alpha varying are not used. |
| * |
| * Doesn't yet support joins, caps, or path effects. |
| */ |
| void OpenGLRenderer::drawConvexPath(const SkPath& path, const SkPaint* paint) { |
| VertexBuffer vertexBuffer; |
| // TODO: try clipping large paths to viewport |
| |
| PathTessellator::tessellatePath(path, paint, *currentTransform(), vertexBuffer); |
| drawVertexBuffer(vertexBuffer, paint); |
| } |
| |
| /** |
| * We create tristrips for the lines much like shape stroke tessellation, using a per-vertex alpha |
| * and additional geometry for defining an alpha slope perimeter. |
| * |
| * Using GL_LINES can be difficult because the rasterization rules for those lines produces some |
| * unexpected results, and may vary between hardware devices. Previously we used a varying-base |
| * in-shader alpha region, but found it to be taxing on some GPUs. |
| * |
| * TODO: try using a fixed input buffer for non-capped lines as in text rendering. this may reduce |
| * memory transfer by removing need for degenerate vertices. |
| */ |
| void OpenGLRenderer::drawLines(const float* points, int count, const SkPaint* paint) { |
| if (mState.currentlyIgnored() || count < 4) return; |
| |
| count &= ~0x3; // round down to nearest four |
| |
| VertexBuffer buffer; |
| PathTessellator::tessellateLines(points, count, paint, *currentTransform(), buffer); |
| const Rect& bounds = buffer.getBounds(); |
| |
| if (quickRejectSetupScissor(bounds.left, bounds.top, bounds.right, bounds.bottom)) { |
| return; |
| } |
| |
| int displayFlags = paint->isAntiAlias() ? 0 : kVertexBuffer_Offset; |
| drawVertexBuffer(buffer, paint, displayFlags); |
| } |
| |
| void OpenGLRenderer::drawPoints(const float* points, int count, const SkPaint* paint) { |
| if (mState.currentlyIgnored() || count < 2) return; |
| |
| count &= ~0x1; // round down to nearest two |
| |
| VertexBuffer buffer; |
| PathTessellator::tessellatePoints(points, count, paint, *currentTransform(), buffer); |
| |
| const Rect& bounds = buffer.getBounds(); |
| if (quickRejectSetupScissor(bounds.left, bounds.top, bounds.right, bounds.bottom)) { |
| return; |
| } |
| |
| int displayFlags = paint->isAntiAlias() ? 0 : kVertexBuffer_Offset; |
| drawVertexBuffer(buffer, paint, displayFlags); |
| |
| mDirty = true; |
| } |
| |
| void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) { |
| // No need to check against the clip, we fill the clip region |
| if (mState.currentlyIgnored()) return; |
| |
| Rect clip(mState.currentClipRect()); |
| clip.snapToPixelBoundaries(); |
| |
| SkPaint paint; |
| paint.setColor(color); |
| paint.setXfermodeMode(mode); |
| |
| drawColorRect(clip.left, clip.top, clip.right, clip.bottom, &paint, true); |
| |
| mDirty = true; |
| } |
| |
| void OpenGLRenderer::drawShape(float left, float top, PathTexture* texture, |
| const SkPaint* paint) { |
| if (!texture) return; |
| const AutoTexture autoCleanup(texture); |
| |
| const float x = left + texture->left - texture->offset; |
| const float y = top + texture->top - texture->offset; |
| |
| drawPathTexture(texture, x, y, paint); |
| |
| mDirty = true; |
| } |
| |
| void OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom, |
| float rx, float ry, const SkPaint* p) { |
| if (mState.currentlyIgnored() |
| || quickRejectSetupScissor(left, top, right, bottom, p) |
| || PaintUtils::paintWillNotDraw(*p)) { |
| return; |
| } |
| |
| if (p->getPathEffect() != nullptr) { |
| mCaches.textureState().activateTexture(0); |
| PathTexture* texture = mCaches.pathCache.getRoundRect( |
| right - left, bottom - top, rx, ry, p); |
| drawShape(left, top, texture, p); |
| } else { |
| const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect( |
| *currentTransform(), *p, right - left, bottom - top, rx, ry); |
| drawVertexBuffer(left, top, *vertexBuffer, p); |
| } |
| } |
| |
| void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p) { |
| if (mState.currentlyIgnored() |
| || quickRejectSetupScissor(x - radius, y - radius, x + radius, y + radius, p) |
| || PaintUtils::paintWillNotDraw(*p)) { |
| return; |
| } |
| |
| if (p->getPathEffect() != nullptr) { |
| mCaches.textureState().activateTexture(0); |
| PathTexture* texture = mCaches.pathCache.getCircle(radius, p); |
| drawShape(x - radius, y - radius, texture, p); |
| return; |
| } |
| |
| SkPath path; |
| if (p->getStyle() == SkPaint::kStrokeAndFill_Style) { |
| path.addCircle(x, y, radius + p->getStrokeWidth() / 2); |
| } else { |
| path.addCircle(x, y, radius); |
| } |
| |
| if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) { |
| // mask ripples with projection mask |
| SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask); |
| |
| Matrix4 screenSpaceTransform; |
| currentSnapshot()->buildScreenSpaceTransform(&screenSpaceTransform); |
| |
| Matrix4 totalTransform; |
| totalTransform.loadInverse(screenSpaceTransform); |
| totalTransform.multiply(currentSnapshot()->projectionPathMask->projectionMaskTransform); |
| |
| SkMatrix skTotalTransform; |
| totalTransform.copyTo(skTotalTransform); |
| maskPath.transform(skTotalTransform); |
| |
| // Mask the ripple path by the projection mask, now that it's |
| // in local space. Note that this can create CCW paths. |
| Op(path, maskPath, kIntersect_PathOp, &path); |
| } |
| drawConvexPath(path, p); |
| } |
| |
| void OpenGLRenderer::drawOval(float left, float top, float right, float bottom, |
| const SkPaint* p) { |
| if (mState.currentlyIgnored() |
| || quickRejectSetupScissor(left, top, right, bottom, p) |
| || PaintUtils::paintWillNotDraw(*p)) { |
| return; |
| } |
| |
| if (p->getPathEffect() != nullptr) { |
| mCaches.textureState().activateTexture(0); |
| PathTexture* texture = mCaches.pathCache.getOval(right - left, bottom - top, p); |
| drawShape(left, top, texture, p); |
| } else { |
| SkPath path; |
| SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); |
| if (p->getStyle() == SkPaint::kStrokeAndFill_Style) { |
| rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2); |
| } |
| path.addOval(rect); |
| drawConvexPath(path, p); |
| } |
| } |
| |
| void OpenGLRenderer::drawArc(float left, float top, float right, float bottom, |
| float startAngle, float sweepAngle, bool useCenter, const SkPaint* p) { |
| if (mState.currentlyIgnored() |
| || quickRejectSetupScissor(left, top, right, bottom, p) |
| || PaintUtils::paintWillNotDraw(*p)) { |
| return; |
| } |
| |
| // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) |
| if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != nullptr || useCenter) { |
| mCaches.textureState().activateTexture(0); |
| PathTexture* texture = mCaches.pathCache.getArc(right - left, bottom - top, |
| startAngle, sweepAngle, useCenter, p); |
| drawShape(left, top, texture, p); |
| return; |
| } |
| SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); |
| if (p->getStyle() == SkPaint::kStrokeAndFill_Style) { |
| rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2); |
| } |
| |
| SkPath path; |
| if (useCenter) { |
| path.moveTo(rect.centerX(), rect.centerY()); |
| } |
| path.arcTo(rect, startAngle, sweepAngle, !useCenter); |
| if (useCenter) { |
| path.close(); |
| } |
| drawConvexPath(path, p); |
| } |
| |
| // See SkPaintDefaults.h |
| #define SkPaintDefaults_MiterLimit SkIntToScalar(4) |
| |
| void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, |
| const SkPaint* p) { |
| if (mState.currentlyIgnored() |
| || quickRejectSetupScissor(left, top, right, bottom, p) |
| || PaintUtils::paintWillNotDraw(*p)) { |
| return; |
| } |
| |
| if (p->getStyle() != SkPaint::kFill_Style) { |
| // only fill style is supported by drawConvexPath, since others have to handle joins |
| if (p->getPathEffect() != nullptr || p->getStrokeJoin() != SkPaint::kMiter_Join || |
| p->getStrokeMiter() != SkPaintDefaults_MiterLimit) { |
| mCaches.textureState().activateTexture(0); |
| PathTexture* texture = |
| mCaches.pathCache.getRect(right - left, bottom - top, p); |
| drawShape(left, top, texture, p); |
| } else { |
| SkPath path; |
| SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); |
| if (p->getStyle() == SkPaint::kStrokeAndFill_Style) { |
| rect.outset(p->getStrokeWidth() / 2, p->getStrokeWidth() / 2); |
| } |
| path.addRect(rect); |
| drawConvexPath(path, p); |
| } |
| } else { |
| if (p->isAntiAlias() && !currentTransform()->isSimple()) { |
| SkPath path; |
| path.addRect(left, top, right, bottom); |
| drawConvexPath(path, p); |
| } else { |
| drawColorRect(left, top, right, bottom, p); |
| |
| mDirty = true; |
| } |
| } |
| } |
| |
| void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, |
| int bytesCount, int count, const float* positions, |
| FontRenderer& fontRenderer, int alpha, float x, float y) { |
| mCaches.textureState().activateTexture(0); |
| |
| TextShadow textShadow; |
| if (!getTextShadow(paint, &textShadow)) { |
| LOG_ALWAYS_FATAL("failed to query shadow attributes"); |
| } |
| |
| // NOTE: The drop shadow will not perform gamma correction |
| // if shader-based correction is enabled |
| mCaches.dropShadowCache.setFontRenderer(fontRenderer); |
| ShadowTexture* texture = mCaches.dropShadowCache.get( |
| paint, text, bytesCount, count, textShadow.radius, positions); |
| // If the drop shadow exceeds the max texture size or couldn't be |
| // allocated, skip drawing |
| if (!texture) return; |
| const AutoTexture autoCleanup(texture); |
| |
| const float sx = x - texture->left + textShadow.dx; |
| const float sy = y - texture->top + textShadow.dy; |
| |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUnitQuad(nullptr) |
| .setFillShadowTexturePaint(*texture, textShadow.color, *paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height)) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| bool OpenGLRenderer::canSkipText(const SkPaint* paint) const { |
| float alpha = (hasTextShadow(paint) ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha; |
| return MathUtils::isZero(alpha) |
| && PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode; |
| } |
| |
| void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, |
| const float* positions, const SkPaint* paint) { |
| if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) { |
| return; |
| } |
| |
| // NOTE: Skia does not support perspective transform on drawPosText yet |
| if (!currentTransform()->isSimple()) { |
| return; |
| } |
| |
| mRenderState.scissor().setEnabled(true); |
| |
| float x = 0.0f; |
| float y = 0.0f; |
| const bool pureTranslate = currentTransform()->isPureTranslate(); |
| if (pureTranslate) { |
| x = floorf(x + currentTransform()->getTranslateX() + 0.5f); |
| y = floorf(y + currentTransform()->getTranslateY() + 0.5f); |
| } |
| |
| FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); |
| fontRenderer.setFont(paint, SkMatrix::I()); |
| |
| int alpha; |
| SkXfermode::Mode mode; |
| getAlphaAndMode(paint, &alpha, &mode); |
| |
| if (CC_UNLIKELY(hasTextShadow(paint))) { |
| drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, |
| alpha, 0.0f, 0.0f); |
| } |
| |
| // Pick the appropriate texture filtering |
| bool linearFilter = currentTransform()->changesBounds(); |
| if (pureTranslate && !linearFilter) { |
| linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f; |
| } |
| fontRenderer.setTextureFiltering(linearFilter); |
| |
| const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip()); |
| Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); |
| |
| TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); |
| if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y, |
| positions, hasLayer() ? &bounds : nullptr, &functor)) { |
| dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); |
| mDirty = true; |
| } |
| |
| } |
| |
| bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const { |
| if (CC_LIKELY(transform.isPureTranslate())) { |
| outMatrix->setIdentity(); |
| return false; |
| } else if (CC_UNLIKELY(transform.isPerspective())) { |
| outMatrix->setIdentity(); |
| return true; |
| } |
| |
| /** |
| * Input is a non-perspective, scaling transform. Generate a scale-only transform, |
| * with values rounded to the nearest int. |
| */ |
| float sx, sy; |
| transform.decomposeScale(sx, sy); |
| outMatrix->setScale( |
| roundf(std::max(1.0f, sx)), |
| roundf(std::max(1.0f, sy))); |
| return true; |
| } |
| |
| int OpenGLRenderer::getSaveCount() const { |
| return mState.getSaveCount(); |
| } |
| |
| int OpenGLRenderer::save(int flags) { |
| return mState.save(flags); |
| } |
| |
| void OpenGLRenderer::restore() { |
| mState.restore(); |
| } |
| |
| void OpenGLRenderer::restoreToCount(int saveCount) { |
| mState.restoreToCount(saveCount); |
| } |
| |
| |
| void OpenGLRenderer::translate(float dx, float dy, float dz) { |
| mState.translate(dx, dy, dz); |
| } |
| |
| void OpenGLRenderer::rotate(float degrees) { |
| mState.rotate(degrees); |
| } |
| |
| void OpenGLRenderer::scale(float sx, float sy) { |
| mState.scale(sx, sy); |
| } |
| |
| void OpenGLRenderer::skew(float sx, float sy) { |
| mState.skew(sx, sy); |
| } |
| |
| void OpenGLRenderer::setMatrix(const Matrix4& matrix) { |
| mState.setMatrix(matrix); |
| } |
| |
| void OpenGLRenderer::setLocalMatrix(const SkMatrix& matrix) { |
| mState.setMatrix(mBaseTransform); |
| mState.concatMatrix(matrix); |
| } |
| |
| void OpenGLRenderer::concatMatrix(const Matrix4& matrix) { |
| mState.concatMatrix(matrix); |
| } |
| |
| bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { |
| return mState.clipRect(left, top, right, bottom, op); |
| } |
| |
| bool OpenGLRenderer::clipPath(const SkPath* path, SkRegion::Op op) { |
| return mState.clipPath(path, op); |
| } |
| |
| bool OpenGLRenderer::clipRegion(const SkRegion* region, SkRegion::Op op) { |
| return mState.clipRegion(region, op); |
| } |
| |
| void OpenGLRenderer::setClippingOutline(LinearAllocator& allocator, const Outline* outline) { |
| mState.setClippingOutline(allocator, outline); |
| } |
| |
| void OpenGLRenderer::setClippingRoundRect(LinearAllocator& allocator, |
| const Rect& rect, float radius, bool highPriority) { |
| mState.setClippingRoundRect(allocator, rect, radius, highPriority); |
| } |
| |
| void OpenGLRenderer::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) { |
| mState.setProjectionPathMask(allocator, path); |
| } |
| |
| void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, |
| const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds, |
| DrawOpMode drawOpMode) { |
| |
| if (drawOpMode == DrawOpMode::kImmediate) { |
| // The checks for corner-case ignorable text and quick rejection is only done for immediate |
| // drawing as ops from DeferredDisplayList are already filtered for these |
| if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint) || |
| quickRejectSetupScissor(bounds)) { |
| return; |
| } |
| } |
| |
| const float oldX = x; |
| const float oldY = y; |
| |
| const mat4& transform = *currentTransform(); |
| const bool pureTranslate = transform.isPureTranslate(); |
| |
| if (CC_LIKELY(pureTranslate)) { |
| x = floorf(x + transform.getTranslateX() + 0.5f); |
| y = floorf(y + transform.getTranslateY() + 0.5f); |
| } |
| |
| int alpha; |
| SkXfermode::Mode mode; |
| getAlphaAndMode(paint, &alpha, &mode); |
| |
| FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); |
| |
| if (CC_UNLIKELY(hasTextShadow(paint))) { |
| fontRenderer.setFont(paint, SkMatrix::I()); |
| drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, |
| alpha, oldX, oldY); |
| } |
| |
| const bool hasActiveLayer = hasLayer(); |
| |
| // We only pass a partial transform to the font renderer. That partial |
| // matrix defines how glyphs are rasterized. Typically we want glyphs |
| // to be rasterized at their final size on screen, which means the partial |
| // matrix needs to take the scale factor into account. |
| // When a partial matrix is used to transform glyphs during rasterization, |
| // the mesh is generated with the inverse transform (in the case of scale, |
| // the mesh is generated at 1.0 / scale for instance.) This allows us to |
| // apply the full transform matrix at draw time in the vertex shader. |
| // Applying the full matrix in the shader is the easiest way to handle |
| // rotation and perspective and allows us to always generated quads in the |
| // font renderer which greatly simplifies the code, clipping in particular. |
| SkMatrix fontTransform; |
| bool linearFilter = findBestFontTransform(transform, &fontTransform) |
| || fabs(y - (int) y) > 0.0f |
| || fabs(x - (int) x) > 0.0f; |
| fontRenderer.setFont(paint, fontTransform); |
| fontRenderer.setTextureFiltering(linearFilter); |
| |
| // TODO: Implement better clipping for scaled/rotated text |
| const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect(); |
| Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); |
| |
| bool status; |
| TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); |
| |
| // don't call issuedrawcommand, do it at end of batch |
| bool forceFinish = (drawOpMode != DrawOpMode::kDefer); |
| if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) { |
| SkPaint paintCopy(*paint); |
| paintCopy.setTextAlign(SkPaint::kLeft_Align); |
| status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y, |
| positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); |
| } else { |
| status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, |
| positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); |
| } |
| |
| if ((status || drawOpMode != DrawOpMode::kImmediate) && hasActiveLayer) { |
| if (!pureTranslate) { |
| transform.mapRect(layerBounds); |
| } |
| dirtyLayerUnchecked(layerBounds, getRegion()); |
| } |
| |
| drawTextDecorations(totalAdvance, oldX, oldY, paint); |
| |
| mDirty = true; |
| } |
| |
| void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, |
| const SkPath* path, float hOffset, float vOffset, const SkPaint* paint) { |
| if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) { |
| return; |
| } |
| |
| // TODO: avoid scissor by calculating maximum bounds using path bounds + font metrics |
| mRenderState.scissor().setEnabled(true); |
| |
| FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); |
| fontRenderer.setFont(paint, SkMatrix::I()); |
| fontRenderer.setTextureFiltering(true); |
| |
| int alpha; |
| SkXfermode::Mode mode; |
| getAlphaAndMode(paint, &alpha, &mode); |
| TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); |
| |
| const Rect* clip = &writableSnapshot()->getLocalClip(); |
| Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); |
| |
| if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path, |
| hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) { |
| dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); |
| mDirty = true; |
| } |
| } |
| |
| void OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) { |
| if (mState.currentlyIgnored()) return; |
| |
| mCaches.textureState().activateTexture(0); |
| |
| PathTexture* texture = mCaches.pathCache.get(path, paint); |
| if (!texture) return; |
| const AutoTexture autoCleanup(texture); |
| |
| const float x = texture->left - texture->offset; |
| const float y = texture->top - texture->offset; |
| |
| drawPathTexture(texture, x, y, paint); |
| mDirty = true; |
| } |
| |
| void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { |
| if (!layer) { |
| return; |
| } |
| |
| mat4* transform = nullptr; |
| if (layer->isTextureLayer()) { |
| transform = &layer->getTransform(); |
| if (!transform->isIdentity()) { |
| save(SkCanvas::kMatrix_SaveFlag); |
| concatMatrix(*transform); |
| } |
| } |
| |
| bool clipRequired = false; |
| const bool rejected = mState.calculateQuickRejectForScissor( |
| x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), |
| &clipRequired, nullptr, false); |
| |
| if (rejected) { |
| if (transform && !transform->isIdentity()) { |
| restore(); |
| } |
| return; |
| } |
| |
| EVENT_LOGD("drawLayer," RECT_STRING ", clipRequired %d", x, y, |
| x + layer->layer.getWidth(), y + layer->layer.getHeight(), clipRequired); |
| |
| updateLayer(layer, true); |
| |
| mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired); |
| mCaches.textureState().activateTexture(0); |
| |
| if (CC_LIKELY(!layer->region.isEmpty())) { |
| if (layer->region.isRect()) { |
| DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, |
| composeLayerRect(layer, layer->regionRect)); |
| } else if (layer->mesh) { |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedIndexedQuads(layer->mesh, layer->meshElementCount) |
| .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewOffsetRectSnap(x, y, Rect(0, 0, layer->layer.getWidth(), layer->layer.getHeight())) |
| .build(); |
| DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop)); |
| #if DEBUG_LAYERS_AS_REGIONS |
| drawRegionRectsDebug(layer->region); |
| #endif |
| } |
| |
| if (layer->debugDrawUpdate) { |
| layer->debugDrawUpdate = false; |
| |
| SkPaint paint; |
| paint.setColor(0x7f00ff00); |
| drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), &paint); |
| } |
| } |
| layer->hasDrawnSinceUpdate = true; |
| |
| if (transform && !transform->isIdentity()) { |
| restore(); |
| } |
| |
| mDirty = true; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Draw filters |
| /////////////////////////////////////////////////////////////////////////////// |
| void OpenGLRenderer::setDrawFilter(SkDrawFilter* filter) { |
| // We should never get here since we apply the draw filter when stashing |
| // the paints in the DisplayList. |
| LOG_ALWAYS_FATAL("OpenGLRenderer does not directly support DrawFilters"); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // Drawing implementation |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) { |
| Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); |
| if (!texture) { |
| return mCaches.textureCache.get(bitmap); |
| } |
| return texture; |
| } |
| |
| void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y, |
| const SkPaint* paint) { |
| if (quickRejectSetupScissor(x, y, x + texture->width, y + texture->height)) { |
| return; |
| } |
| |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshTexturedUnitQuad(nullptr) |
| .setFillPathTexturePaint(*texture, *paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), TransformFlags::None) |
| .setModelViewMapUnitToRect(Rect(x, y, x + texture->width, y + texture->height)) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| // Same values used by Skia |
| #define kStdStrikeThru_Offset (-6.0f / 21.0f) |
| #define kStdUnderline_Offset (1.0f / 9.0f) |
| #define kStdUnderline_Thickness (1.0f / 18.0f) |
| |
| void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y, |
| const SkPaint* paint) { |
| // Handle underline and strike-through |
| uint32_t flags = paint->getFlags(); |
| if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { |
| SkPaint paintCopy(*paint); |
| |
| if (CC_LIKELY(underlineWidth > 0.0f)) { |
| const float textSize = paintCopy.getTextSize(); |
| const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f); |
| |
| const float left = x; |
| float top = 0.0f; |
| |
| int linesCount = 0; |
| if (flags & SkPaint::kUnderlineText_Flag) linesCount++; |
| if (flags & SkPaint::kStrikeThruText_Flag) linesCount++; |
| |
| const int pointsCount = 4 * linesCount; |
| float points[pointsCount]; |
| int currentPoint = 0; |
| |
| if (flags & SkPaint::kUnderlineText_Flag) { |
| top = y + textSize * kStdUnderline_Offset; |
| points[currentPoint++] = left; |
| points[currentPoint++] = top; |
| points[currentPoint++] = left + underlineWidth; |
| points[currentPoint++] = top; |
| } |
| |
| if (flags & SkPaint::kStrikeThruText_Flag) { |
| top = y + textSize * kStdStrikeThru_Offset; |
| points[currentPoint++] = left; |
| points[currentPoint++] = top; |
| points[currentPoint++] = left + underlineWidth; |
| points[currentPoint++] = top; |
| } |
| |
| paintCopy.setStrokeWidth(strokeWidth); |
| |
| drawLines(&points[0], pointsCount, &paintCopy); |
| } |
| } |
| } |
| |
| void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { |
| if (mState.currentlyIgnored()) { |
| return; |
| } |
| |
| drawColorRects(rects, count, paint, false, true, true); |
| } |
| |
| void OpenGLRenderer::drawShadow(float casterAlpha, |
| const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) { |
| if (mState.currentlyIgnored()) return; |
| |
| // TODO: use quickRejectWithScissor. For now, always force enable scissor. |
| mRenderState.scissor().setEnabled(true); |
| |
| SkPaint paint; |
| paint.setAntiAlias(true); // want to use AlphaVertex |
| |
| // The caller has made sure casterAlpha > 0. |
| float ambientShadowAlpha = mAmbientShadowAlpha; |
| if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) { |
| ambientShadowAlpha = Properties::overrideAmbientShadowStrength; |
| } |
| if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) { |
| paint.setARGB(casterAlpha * ambientShadowAlpha, 0, 0, 0); |
| drawVertexBuffer(*ambientShadowVertexBuffer, &paint, kVertexBuffer_ShadowInterp); |
| } |
| |
| float spotShadowAlpha = mSpotShadowAlpha; |
| if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) { |
| spotShadowAlpha = Properties::overrideSpotShadowStrength; |
| } |
| if (spotShadowVertexBuffer && spotShadowAlpha > 0) { |
| paint.setARGB(casterAlpha * spotShadowAlpha, 0, 0, 0); |
| drawVertexBuffer(*spotShadowVertexBuffer, &paint, kVertexBuffer_ShadowInterp); |
| } |
| |
| mDirty=true; |
| } |
| |
| void OpenGLRenderer::drawColorRects(const float* rects, int count, const SkPaint* paint, |
| bool ignoreTransform, bool dirty, bool clip) { |
| if (count == 0) { |
| return; |
| } |
| |
| float left = FLT_MAX; |
| float top = FLT_MAX; |
| float right = FLT_MIN; |
| float bottom = FLT_MIN; |
| |
| Vertex mesh[count]; |
| Vertex* vertex = mesh; |
| |
| for (int index = 0; index < count; index += 4) { |
| float l = rects[index + 0]; |
| float t = rects[index + 1]; |
| float r = rects[index + 2]; |
| float b = rects[index + 3]; |
| |
| Vertex::set(vertex++, l, t); |
| Vertex::set(vertex++, r, t); |
| Vertex::set(vertex++, l, b); |
| Vertex::set(vertex++, r, b); |
| |
| left = std::min(left, l); |
| top = std::min(top, t); |
| right = std::max(right, r); |
| bottom = std::max(bottom, b); |
| } |
| |
| if (clip && quickRejectSetupScissor(left, top, right, bottom)) { |
| return; |
| } |
| |
| const int transformFlags = ignoreTransform |
| ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshIndexedQuads(&mesh[0], count / 4) |
| .setFillPaint(*paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewOffsetRect(0, 0, Rect(left, top, right, bottom)) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom, |
| const SkPaint* paint, bool ignoreTransform) { |
| const int transformFlags = ignoreTransform |
| ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; |
| Glop glop; |
| GlopBuilder(mRenderState, mCaches, &glop) |
| .setRoundRectClipState(currentSnapshot()->roundRectClipState) |
| .setMeshUnitQuad() |
| .setFillPaint(*paint, currentSnapshot()->alpha) |
| .setTransform(*currentSnapshot(), transformFlags) |
| .setModelViewMapUnitToRect(Rect(left, top, right, bottom)) |
| .build(); |
| renderGlop(glop); |
| } |
| |
| void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, |
| SkXfermode::Mode* mode) const { |
| getAlphaAndModeDirect(paint, alpha, mode); |
| *alpha *= currentSnapshot()->alpha; |
| } |
| |
| float OpenGLRenderer::getLayerAlpha(const Layer* layer) const { |
| return (layer->getAlpha() / 255.0f) * currentSnapshot()->alpha; |
| } |
| |
| }; // namespace uirenderer |
| }; // namespace android |