Merge "Implement Skia pipelines for OpenGL and Vulkan."

This commit is contained in:
TreeHugger Robot
2016-11-03 19:59:41 +00:00
committed by Android (Google) Code Review
19 changed files with 1114 additions and 93 deletions

View File

@@ -23,7 +23,10 @@ hwui_src_files := \
pipeline/skia/RenderNodeDrawable.cpp \
pipeline/skia/ReorderBarrierDrawables.cpp \
pipeline/skia/SkiaDisplayList.cpp \
pipeline/skia/SkiaOpenGLPipeline.cpp \
pipeline/skia/SkiaPipeline.cpp \
pipeline/skia/SkiaRecordingCanvas.cpp \
pipeline/skia/SkiaVulkanPipeline.cpp \
renderstate/Blend.cpp \
renderstate/MeshState.cpp \
renderstate/OffscreenBufferPool.cpp \
@@ -296,6 +299,7 @@ LOCAL_SRC_FILES += \
tests/unit/RenderPropertiesTests.cpp \
tests/unit/SkiaBehaviorTests.cpp \
tests/unit/SkiaDisplayListTests.cpp \
tests/unit/SkiaPipelineTests.cpp \
tests/unit/SkiaCanvasTests.cpp \
tests/unit/SnapshotTests.cpp \
tests/unit/StringUtilsTests.cpp \

View File

@@ -41,6 +41,13 @@ void DeviceInfo::initialize() {
});
}
void DeviceInfo::initialize(int maxTextureSize) {
std::call_once(sInitializedFlag, [maxTextureSize]() {
sDeviceInfo = new DeviceInfo();
sDeviceInfo->mMaxTextureSize = maxTextureSize;
});
}
void DeviceInfo::load() {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
}

View File

@@ -32,6 +32,7 @@ public:
// only call this after GL has been initialized, or at any point if compiled
// with HWUI_NULL_GPU
static void initialize();
static void initialize(int maxTextureSize);
int maxTextureSize() const { return mMaxTextureSize; }

View File

@@ -33,6 +33,7 @@
#include "Matrix.h"
#include "RenderProperties.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaLayer.h"
#include <vector>
@@ -60,10 +61,6 @@ namespace proto {
class RenderNode;
}
namespace skiapipeline {
class SkiaDisplayList;
}
/**
* Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
*
@@ -312,14 +309,23 @@ public:
* Returns true if an offscreen layer from any renderPipeline is attached
* to this node.
*/
bool hasLayer() const { return mLayer || mLayerSurface.get(); }
bool hasLayer() const { return mLayer || mSkiaLayer.get(); }
/**
* Used by the RenderPipeline to attach an offscreen surface to the RenderNode.
* The surface is then will be used to store the contents of a layer.
*/
void setLayerSurface(sk_sp<SkSurface> layer) { mLayerSurface = layer; }
void setLayerSurface(sk_sp<SkSurface> layer) {
if (layer.get()) {
if (!mSkiaLayer.get()) {
mSkiaLayer = std::make_unique<skiapipeline::SkiaLayer>();
}
mSkiaLayer->layerSurface = std::move(layer);
mSkiaLayer->inverseTransformInWindow.loadIdentity();
} else {
mSkiaLayer.reset();
}
}
/**
* If the RenderNode is of type LayerType::RenderLayer then this method will
@@ -330,7 +336,13 @@ public:
* NOTE: this function is only guaranteed to return accurate results after
* prepareTree has been run for this RenderNode
*/
SkSurface* getLayerSurface() const { return mLayerSurface.get(); }
SkSurface* getLayerSurface() const {
return mSkiaLayer.get() ? mSkiaLayer->layerSurface.get() : nullptr;
}
skiapipeline::SkiaLayer* getSkiaLayer() const {
return mSkiaLayer.get();
}
private:
/**
@@ -346,7 +358,7 @@ private:
* An offscreen rendering target used to contain the contents this RenderNode
* when it has been set to draw as a LayerType::RenderLayer.
*/
sk_sp<SkSurface> mLayerSurface;
std::unique_ptr<skiapipeline::SkiaLayer> mSkiaLayer;
}; // class RenderNode
} /* namespace uirenderer */

View File

@@ -20,6 +20,8 @@
#include "RenderNode.h"
#include "MinikinUtils.h"
#include "Paint.h"
#include "Properties.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "Typeface.h"
#include <SkDrawFilter.h>
@@ -27,6 +29,9 @@
namespace android {
Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
if (uirenderer::Properties::isSkiaEnabled()) {
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}
return new uirenderer::RecordingCanvas(width, height);
}

View File

@@ -17,7 +17,7 @@
#include "RenderNodeDrawable.h"
#include "RenderNode.h"
#include "SkiaDisplayList.h"
#include "SkiaFrameRenderer.h"
#include "SkiaPipeline.h"
#include "utils/TraceUtils.h"
namespace android {
@@ -57,7 +57,7 @@ void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
RenderNode* renderNode = mRenderNode.get();
if (SkiaFrameRenderer::skpCaptureEnabled()) {
if (SkiaPipeline::skpCaptureEnabled()) {
SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
}

View File

@@ -17,7 +17,7 @@
#include "ReorderBarrierDrawables.h"
#include "RenderNode.h"
#include "SkiaDisplayList.h"
#include "SkiaFrameRenderer.h"
#include "SkiaPipeline.h"
#include <SkBlurMask.h>
#include <SkBlurMaskFilter.h>
@@ -161,7 +161,7 @@ static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float ca
return;
}
const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
const Vector3 lightPos = SkiaPipeline::getLightCenter();
float zRatio = casterZValue / (lightPos.z - casterZValue);
// clamp
if (zRatio < 0.0f) {
@@ -170,7 +170,7 @@ static void DrawSpotShadowGeneral(SkCanvas* canvas, const Shape& shape, float ca
zRatio = 0.95f;
}
float blurRadius = SkiaFrameRenderer::getLightRadius()*zRatio;
float blurRadius = SkiaPipeline::getLightRadius()*zRatio;
SkAutoCanvasRestore acr(canvas, true);
@@ -276,7 +276,7 @@ static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadi
}
if (spotAlpha > 0.0f) {
const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
const Vector3 lightPos = SkiaPipeline::getLightCenter();
float zRatio = casterZValue / (lightPos.z - casterZValue);
// clamp
if (zRatio < 0.0f) {
@@ -285,7 +285,7 @@ static void DrawRRectShadows(const SkRect& casterRect, SkScalar casterCornerRadi
zRatio = 0.95f;
}
const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
const SkScalar lightWidth = SkiaPipeline::getLightRadius();
SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
// the device-space radius sent to the blur shader must fit in 14.2 fixed point
if (srcSpaceSpotRadius*scaleFactor > MAX_BLUR_RADIUS) {
@@ -439,7 +439,7 @@ static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCo
}
if (spotAlpha > 0.0f) {
const Vector3 lightPos = SkiaFrameRenderer::getLightCenter();
const Vector3 lightPos = SkiaPipeline::getLightCenter();
float zRatio = casterZValue / (lightPos.z - casterZValue);
// clamp
if (zRatio < 0.0f) {
@@ -448,7 +448,7 @@ static void DrawRRectShadowsWithClip(const SkRect& casterRect, SkScalar casterCo
zRatio = 0.95f;
}
const SkScalar lightWidth = SkiaFrameRenderer::getLightRadius();
const SkScalar lightWidth = SkiaPipeline::getLightRadius();
const SkScalar srcSpaceSpotRadius = 2.0f * lightWidth * zRatio;
const SkScalar devSpaceSpotRadius = srcSpaceSpotRadius * scaleFactor;
@@ -626,8 +626,8 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable*
return;
}
float ambientAlpha = SkiaFrameRenderer::getAmbientShadowAlpha()*casterAlpha;
float spotAlpha = SkiaFrameRenderer::getSpotShadowAlpha()*casterAlpha;
float ambientAlpha = SkiaPipeline::getAmbientShadowAlpha()*casterAlpha;
float spotAlpha = SkiaPipeline::getSpotShadowAlpha()*casterAlpha;
const float casterZValue = casterProperties.getZ();
const RevealClip& revealClip = casterProperties.getRevealClip();

View File

@@ -16,39 +16,23 @@
#pragma once
#include <SkSurface.h>
#include "Matrix.h"
namespace android {
namespace uirenderer {
namespace skiapipeline {
/**
* TODO: this is a stub that will be added in a subsquent CL
* An offscreen rendering target used to contain the contents a RenderNode.
*/
class SkiaFrameRenderer {
public:
static bool skpCaptureEnabled() { return false; }
// TODO avoids unused compile error but we need to pass this to the reorder drawables!
static float getLightRadius() {
return 1.0f;
}
static uint8_t getAmbientShadowAlpha() {
return 1;
}
static uint8_t getSpotShadowAlpha() {
return 1;
}
static Vector3 getLightCenter() {
Vector3 result;
result.x = result.y = result.z = 1.0f;
return result;
}
struct SkiaLayer
{
sk_sp<SkSurface> layerSurface;
Matrix4 inverseTransformInWindow;
};
}; // namespace skiapipeline
}; // namespace uirenderer
}; // namespace android
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,170 @@
/*
* Copyright (C) 2016 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 "SkiaOpenGLPipeline.h"
#include "DeferredLayerUpdater.h"
#include "renderthread/EglManager.h"
#include "renderstate/RenderState.h"
#include "Readback.h"
#include "utils/TraceUtils.h"
#include <android/native_window.h>
#include <cutils/properties.h>
#include <strings.h>
using namespace android::uirenderer::renderthread;
namespace android {
namespace uirenderer {
namespace skiapipeline {
SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
: SkiaPipeline(thread)
, mEglManager(thread.eglManager()) {
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
// TODO: Figure out why this workaround is needed, see b/13913604
// In the meantime this matches the behavior of GLRenderer, so it is not a regression
EGLint error = 0;
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}
return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}
Frame SkiaOpenGLPipeline::getFrame() {
LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
"drawRenderNode called on a context with no surface!");
return mEglManager.beginFrame(mEglSurface);
}
bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty,
const SkRect& dirty,
const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) {
mEglManager.damageFrame(frame, dirty);
// setup surface for fbo0
GrBackendRenderTargetDesc renderTargetDesc;
renderTargetDesc.fWidth = frame.width();
renderTargetDesc.fHeight = frame.height();
renderTargetDesc.fConfig = kRGBA_8888_GrPixelConfig;
renderTargetDesc.fOrigin = kBottomLeft_GrSurfaceOrigin;
renderTargetDesc.fSampleCnt = 0;
renderTargetDesc.fStencilBits = STENCIL_BUFFER_SIZE;
renderTargetDesc.fRenderTargetHandle = 0;
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(
mRenderThread.getGrContext(), renderTargetDesc, &props));
SkiaPipeline::updateLighting(lightGeometry, lightInfo);
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
layerUpdateQueue->clear();
return true;
}
bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew,
const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) {
GL_CHECKPOINT(LOW);
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
*requireSwap = drew || mEglManager.damageRequiresSwap();
if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
return false;
}
return *requireSwap;
}
bool SkiaOpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
layer->apply();
return Readback::copyTextureLayerInto(mRenderThread, *(layer->backingLayer()), bitmap)
== CopyResult::Success;
}
DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() {
mEglManager.initialize();
Layer* layer = new Layer(mRenderThread.renderState(), 0, 0);
layer->generateTexture();
return new DeferredLayerUpdater(layer);
}
void SkiaOpenGLPipeline::onStop() {
if (mEglManager.isCurrent(mEglSurface)) {
mEglManager.makeCurrent(EGL_NO_SURFACE);
}
}
bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
if (mEglSurface != EGL_NO_SURFACE) {
mEglManager.destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
}
if (surface) {
mEglSurface = mEglManager.createSurface(surface);
}
if (mEglSurface != EGL_NO_SURFACE) {
const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
return true;
}
return false;
}
bool SkiaOpenGLPipeline::isSurfaceReady() {
return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE);
}
bool SkiaOpenGLPipeline::isContextReady() {
return CC_LIKELY(mEglManager.hasEglContext());
}
void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) {
DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
if (thread.eglManager().hasEglContext()) {
mode = DrawGlInfo::kModeProcess;
}
(*functor)(mode, nullptr);
// If there's no context we don't need to reset as there's no gl state to save/restore
if (mode != DrawGlInfo::kModeProcessNoContext) {
thread.getGrContext()->resetContext();
}
}
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 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.
*/
#pragma once
#include "SkiaPipeline.h"
namespace android {
namespace uirenderer {
namespace skiapipeline {
class SkiaOpenGLPipeline : public SkiaPipeline {
public:
SkiaOpenGLPipeline(renderthread::RenderThread& thread);
virtual ~SkiaOpenGLPipeline() {}
renderthread::MakeCurrentResult makeCurrent() override;
renderthread::Frame getFrame() override;
bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo,
const std::vector< sp<RenderNode> >& renderNodes,
FrameInfoVisualizer* profiler) override;
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
DeferredLayerUpdater* createTextureLayer() override;
bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
private:
renderthread::EglManager& mEglManager;
EGLSurface mEglSurface = EGL_NO_SURFACE;
bool mBufferPreserved = false;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,258 @@
/*
* Copyright (C) 2016 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 "SkiaPipeline.h"
#include "utils/TraceUtils.h"
#include <SkOSFile.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <SkPixelSerializer.h>
#include <SkStream.h>
using namespace android::uirenderer::renderthread;
namespace android {
namespace uirenderer {
namespace skiapipeline {
float SkiaPipeline::mLightRadius = 0;
uint8_t SkiaPipeline::mAmbientShadowAlpha = 0;
uint8_t SkiaPipeline::mSpotShadowAlpha = 0;
Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN};
SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { }
TaskManager* SkiaPipeline::getTaskManager() {
return &mTaskManager;
}
void SkiaPipeline::onDestroyHardwareResources() {
// No need to flush the caches here. There is a timer
// which will flush temporary resources over time.
}
void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo) {
updateLighting(lightGeometry, lightInfo);
ATRACE_NAME("draw layers");
renderLayersImpl(*layerUpdateQueue, opaque);
layerUpdateQueue->clear();
}
void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
// Render all layers that need to be updated, in order.
for (size_t i = 0; i < layers.entries().size(); i++) {
RenderNode* layerNode = layers.entries()[i].renderNode;
// only schedule repaint if node still on layer - possible it may have been
// removed during a dropped frame, but layers may still remain scheduled so
// as not to lose info on what portion is damaged
if (CC_LIKELY(layerNode->getLayerSurface() != nullptr)) {
SkASSERT(layerNode->getLayerSurface());
SkASSERT(layerNode->getDisplayList()->isSkiaDL());
SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList();
if (!displayList || displayList->isEmpty()) {
SkDEBUGF(("%p drawLayers(%s) : missing drawable", this, layerNode->getName()));
return;
}
const Rect& layerDamage = layers.entries()[i].damage;
SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
int saveCount = layerCanvas->save();
SkASSERT(saveCount == 1);
layerCanvas->clipRect(layerDamage.toSkRect(), SkRegion::kReplace_Op);
auto savedLightCenter = mLightCenter;
// map current light center into RenderNode's coordinate space
layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(mLightCenter);
const RenderProperties& properties = layerNode->properties();
const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
return;
}
layerCanvas->clear(SK_ColorTRANSPARENT);
RenderNodeDrawable root(layerNode, layerCanvas, false);
root.forceDraw(layerCanvas);
layerCanvas->restoreToCount(saveCount);
layerCanvas->flush();
mLightCenter = savedLightCenter;
}
}
}
bool SkiaPipeline::createOrUpdateLayer(RenderNode* node,
const DamageAccumulator& damageAccumulator) {
SkSurface* layer = node->getLayerSurface();
if (!layer || layer->width() != node->getWidth() || layer->height() != node->getHeight()) {
SkImageInfo info = SkImageInfo::MakeN32Premul(node->getWidth(), node->getHeight());
SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
node->setLayerSurface(
SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes,
info, 0, &props));
if (node->getLayerSurface()) {
// update the transform in window of the layer to reset its origin wrt light source
// position
Matrix4 windowTransform;
damageAccumulator.computeCurrentTransform(&windowTransform);
node->getSkiaLayer()->inverseTransformInWindow = windowTransform;
}
return true;
}
return false;
}
void SkiaPipeline::destroyLayer(RenderNode* node) {
node->setLayerSurface(nullptr);
}
void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
GrContext* context = thread.getGrContext();
if (context) {
ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
SkBitmap skiaBitmap;
bitmap->getSkBitmap(&skiaBitmap);
sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
SkImage_pinAsTexture(image.get(), context);
SkImage_unpinAsTexture(image.get(), context);
}
}
// Encodes to PNG, unless there is already encoded data, in which case that gets
// used.
class PngPixelSerializer : public SkPixelSerializer {
public:
bool onUseEncodedData(const void*, size_t) override { return true; }
SkData* onEncode(const SkPixmap& pixmap) override {
return SkImageEncoder::EncodeData(pixmap.info(), pixmap.addr(), pixmap.rowBytes(),
SkImageEncoder::kPNG_Type, 100);
}
};
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
sk_sp<SkSurface> surface) {
// unpin all mutable images that were attached to nodes deleted while on the UI thread
SkiaDisplayList::cleanupImages(surface->getCanvas()->getGrContext());
// draw all layers up front
renderLayersImpl(layers, opaque);
// initialize the canvas for the current frame
SkCanvas* canvas = surface->getCanvas();
std::unique_ptr<SkPictureRecorder> recorder;
bool recordingPicture = false;
char prop[PROPERTY_VALUE_MAX];
if (skpCaptureEnabled()) {
property_get("debug.hwui.capture_frame_as_skp", prop, "0");
recordingPicture = prop[0] != '0' && !sk_exists(prop);
if (recordingPicture) {
recorder.reset(new SkPictureRecorder());
canvas = recorder->beginRecording(surface->width(), surface->height(),
nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
}
}
canvas->clipRect(clip, SkRegion::kReplace_Op);
if (!opaque) {
canvas->clear(SK_ColorTRANSPARENT);
}
// If there are multiple render nodes, they are laid out as follows:
// #0 - backdrop (content + caption)
// #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds)
// #2 - additional overlay nodes
// Usually the backdrop cannot be seen since it will be entirely covered by the content. While
// resizing however it might become partially visible. The following render loop will crop the
// backdrop against the content and draw the remaining part of it. It will then draw the content
// cropped to the backdrop (since that indicates a shrinking of the window).
//
// Additional nodes will be drawn on top with no particular clipping semantics.
// The bounds of the backdrop against which the content should be clipped.
Rect backdropBounds = contentDrawBounds;
// Usually the contents bounds should be mContentDrawBounds - however - we will
// move it towards the fixed edge to give it a more stable appearance (for the moment).
// If there is no content bounds we ignore the layering as stated above and start with 2.
int layer = (contentDrawBounds.isEmpty() || nodes.size() == 1) ? 2 : 0;
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
SkASSERT(node->getDisplayList()->isSkiaDL());
int count = canvas->save();
if (layer == 0) {
const RenderProperties& properties = node->properties();
Rect targetBounds(properties.getLeft(), properties.getTop(),
properties.getRight(), properties.getBottom());
// Move the content bounds towards the fixed corner of the backdrop.
const int x = targetBounds.left;
const int y = targetBounds.top;
// Remember the intersection of the target bounds and the intersection bounds against
// which we have to crop the content.
backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
backdropBounds.doIntersect(targetBounds);
} else if (layer == 1) {
// We shift and clip the content to match its final location in the window.
const SkRect clip = SkRect::MakeXYWH(contentDrawBounds.left, contentDrawBounds.top,
backdropBounds.getWidth(), backdropBounds.getHeight());
const float dx = backdropBounds.left - contentDrawBounds.left;
const float dy = backdropBounds.top - contentDrawBounds.top;
canvas->translate(dx, dy);
// It gets cropped against the bounds of the backdrop to stay inside.
canvas->clipRect(clip, SkRegion::kIntersect_Op);
}
RenderNodeDrawable root(node.get(), canvas);
root.draw(canvas);
canvas->restoreToCount(count);
layer++;
}
if (skpCaptureEnabled() && recordingPicture) {
sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
if (picture->approximateOpCount() > 0) {
SkFILEWStream stream(prop);
if (stream.isValid()) {
PngPixelSerializer serializer;
picture->serialize(&stream, &serializer);
stream.flush();
SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop);
}
}
surface->getCanvas()->drawPicture(picture);
}
ATRACE_NAME("flush commands");
canvas->flush();
}
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2016 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.
*/
#pragma once
#include "renderthread/CanvasContext.h"
#include "FrameBuilder.h"
#include "renderthread/IRenderPipeline.h"
#include <SkSurface.h>
namespace android {
namespace uirenderer {
namespace skiapipeline {
class SkiaPipeline : public renderthread::IRenderPipeline {
public:
SkiaPipeline(renderthread::RenderThread& thread);
virtual ~SkiaPipeline() {}
TaskManager* getTaskManager() override;
void onDestroyHardwareResources() override;
void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo) override;
bool createOrUpdateLayer(RenderNode* node,
const DamageAccumulator& damageAccumulator) override;
void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector< sp<RenderNode> >& nodes, bool opaque, const Rect &contentDrawBounds,
sk_sp<SkSurface> surface);
static void destroyLayer(RenderNode* node);
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
static bool skpCaptureEnabled() { return false; }
static float getLightRadius() {
if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) {
return Properties::overrideLightRadius;
}
return mLightRadius;
}
static uint8_t getAmbientShadowAlpha() {
if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) {
return Properties::overrideAmbientShadowStrength;
}
return mAmbientShadowAlpha;
}
static uint8_t getSpotShadowAlpha() {
if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) {
return Properties::overrideSpotShadowStrength;
}
return mSpotShadowAlpha;
}
static Vector3 getLightCenter() {
if (CC_UNLIKELY(Properties::overrideLightPosY > 0 || Properties::overrideLightPosZ > 0)) {
Vector3 adjustedLightCenter = mLightCenter;
if (CC_UNLIKELY(Properties::overrideLightPosY > 0)) {
// negated since this shifts up
adjustedLightCenter.y = - Properties::overrideLightPosY;
}
if (CC_UNLIKELY(Properties::overrideLightPosZ > 0)) {
adjustedLightCenter.z = Properties::overrideLightPosZ;
}
return adjustedLightCenter;
}
return mLightCenter;
}
static void updateLighting(const FrameBuilder::LightGeometry& lightGeometry,
const BakedOpRenderer::LightInfo& lightInfo) {
mLightRadius = lightGeometry.radius;
mAmbientShadowAlpha = lightInfo.ambientShadowAlpha;
mSpotShadowAlpha = lightInfo.spotShadowAlpha;
mLightCenter = lightGeometry.center;
}
protected:
renderthread::RenderThread& mRenderThread;
private:
TaskManager mTaskManager;
static float mLightRadius;
static uint8_t mAmbientShadowAlpha;
static uint8_t mSpotShadowAlpha;
static Vector3 mLightCenter;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2016 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 "SkiaVulkanPipeline.h"
#include "DeferredLayerUpdater.h"
#include "renderthread/EglManager.h" // needed for Frame
#include "Readback.h"
#include "renderstate/RenderState.h"
#include <SkTypes.h>
#include <WindowContextFactory_android.h>
#include <VulkanWindowContext.h>
#include <android/native_window.h>
#include <cutils/properties.h>
#include <strings.h>
using namespace android::uirenderer::renderthread;
using namespace sk_app;
namespace android {
namespace uirenderer {
namespace skiapipeline {
MakeCurrentResult SkiaVulkanPipeline::makeCurrent() {
return (mWindowContext != nullptr) ?
MakeCurrentResult::AlreadyCurrent : MakeCurrentResult::Failed;
}
Frame SkiaVulkanPipeline::getFrame() {
LOG_ALWAYS_FATAL_IF(mWindowContext == nullptr, "Tried to draw into null vulkan context!");
mBackbuffer = mWindowContext->getBackbufferSurface();
if (mBackbuffer.get() == nullptr) {
// try recreating the context?
SkDebugf("failed to get backbuffer");
return Frame(-1, -1, 0);
}
// TODO: support buffer age if Vulkan API can do it
Frame frame(mBackbuffer->width(), mBackbuffer->height(), 0);
return frame;
}
bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty,
const SkRect& dirty,
const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler) {
if (mBackbuffer.get() == nullptr) {
return false;
}
renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mBackbuffer);
layerUpdateQueue->clear();
return true;
}
bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew,
const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) {
*requireSwap = drew;
// Even if we decided to cancel the frame, from the perspective of jank
// metrics the frame was swapped at this point
currentFrameInfo->markSwapBuffers();
if (*requireSwap) {
mWindowContext->swapBuffers();
}
mBackbuffer.reset();
return *requireSwap;
}
bool SkiaVulkanPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
// TODO: implement copyLayerInto for vulkan.
return false;
}
DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() {
Layer* layer = new Layer(mRenderThread.renderState(), 0, 0);
return new DeferredLayerUpdater(layer);
}
void SkiaVulkanPipeline::onStop() {
}
bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
if (mWindowContext) {
delete mWindowContext;
mWindowContext = nullptr;
}
if (surface) {
DisplayParams displayParams;
mWindowContext = window_context_factory::NewVulkanForAndroid(surface, displayParams);
if (mWindowContext) {
DeviceInfo::initialize(mWindowContext->getGrContext()->caps()->maxRenderTargetSize());
}
}
// this doesn't work for if there is more than one CanvasContext available at one time!
mRenderThread.setGrContext(mWindowContext ? mWindowContext->getGrContext() : nullptr);
return mWindowContext != nullptr;
}
bool SkiaVulkanPipeline::isSurfaceReady() {
return CC_LIKELY(mWindowContext != nullptr) && mWindowContext->isValid();
}
bool SkiaVulkanPipeline::isContextReady() {
return CC_LIKELY(mWindowContext != nullptr);
}
void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) {
// TODO: we currently don't support OpenGL WebView's
DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
(*functor)(mode, nullptr);
}
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2016 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.
*/
#pragma once
#include "SkiaPipeline.h"
#include <SkSurface.h>
namespace sk_app {
class WindowContext;
}
namespace android {
namespace uirenderer {
namespace skiapipeline {
class SkiaVulkanPipeline : public SkiaPipeline {
public:
SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
virtual ~SkiaVulkanPipeline() {}
renderthread::MakeCurrentResult makeCurrent() override;
renderthread::Frame getFrame() override;
bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
const FrameBuilder::LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque,
const BakedOpRenderer::LightInfo& lightInfo,
const std::vector< sp<RenderNode> >& renderNodes,
FrameInfoVisualizer* profiler) override;
bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
FrameInfo* currentFrameInfo, bool* requireSwap) override;
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
DeferredLayerUpdater* createTextureLayer() override;
bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior) override;
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
private:
sk_app::WindowContext* mWindowContext = nullptr;
sk_sp<SkSurface> mBackbuffer;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -28,6 +28,9 @@
#include "renderstate/Stencil.h"
#include "protos/hwui.pb.h"
#include "OpenGLPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "utils/GLUtils.h"
#include "utils/TimeUtils.h"
@@ -69,13 +72,11 @@ CanvasContext* CanvasContext::create(RenderThread& thread,
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<OpenGLPipeline>(thread));
case RenderPipelineType::SkiaGL:
//TODO: implement SKIA GL
LOG_ALWAYS_FATAL("skiaGL canvas type not implemented.");
break;
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
case RenderPipelineType::SkiaVulkan:
//TODO: implement Vulkan
LOG_ALWAYS_FATAL("Vulkan canvas type not implemented.");
break;
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
break;
@@ -89,6 +90,10 @@ void CanvasContext::destroyLayer(RenderNode* node) {
case RenderPipelineType::OpenGL:
OpenGLPipeline::destroyLayer(node);
break;
case RenderPipelineType::SkiaGL:
case RenderPipelineType::SkiaVulkan:
skiapipeline::SkiaPipeline::destroyLayer(node);
break;
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
break;
@@ -102,6 +107,12 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor)
case RenderPipelineType::OpenGL:
OpenGLPipeline::invokeFunctor(thread, functor);
break;
case RenderPipelineType::SkiaGL:
skiapipeline::SkiaOpenGLPipeline::invokeFunctor(thread, functor);
break;
case RenderPipelineType::SkiaVulkan:
skiapipeline::SkiaVulkanPipeline::invokeFunctor(thread, functor);
break;
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
break;
@@ -114,6 +125,10 @@ void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
case RenderPipelineType::OpenGL:
OpenGLPipeline::prepareToDraw(thread, bitmap);
break;
case RenderPipelineType::SkiaGL:
case RenderPipelineType::SkiaVulkan:
skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
break;
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
break;

View File

@@ -31,6 +31,11 @@ class EglManager;
class Frame {
public:
Frame(EGLint width, EGLint height, EGLint bufferAge)
: mWidth(width)
, mHeight(height)
, mBufferAge(bufferAge) { }
EGLint width() const { return mWidth; }
EGLint height() const { return mHeight; }
@@ -39,6 +44,7 @@ public:
EGLint bufferAge() const { return mBufferAge; }
private:
Frame() {}
friend class EglManager;
EGLSurface mSurface;

View File

@@ -22,6 +22,7 @@
#include <Rect.h>
#include <RenderNode.h>
#include <hwui/Bitmap.h>
#include <pipeline/skia/SkiaRecordingCanvas.h>
#include <renderstate/RenderState.h>
#include <renderthread/RenderThread.h>
#include <Snapshot.h>
@@ -196,6 +197,35 @@ public:
node.setStagingDisplayList(canvas->finishRecording(), nullptr);
}
static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom,
std::function<void(RenderProperties& props, skiapipeline::SkiaRecordingCanvas& canvas)> setup,
const char* name = nullptr, skiapipeline::SkiaDisplayList* displayList = nullptr) {
#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
DeviceInfo::initialize();
#endif
sp<RenderNode> node = new RenderNode();
if (name) {
node->setName(name);
}
RenderProperties& props = node->mutateStagingProperties();
props.setLeftTopRightBottom(left, top, right, bottom);
if (displayList) {
node->setStagingDisplayList(displayList, nullptr);
}
if (setup) {
std::unique_ptr<skiapipeline::SkiaRecordingCanvas> canvas(
new skiapipeline::SkiaRecordingCanvas(nullptr,
props.getWidth(), props.getHeight()));
setup(props, *canvas.get());
node->setStagingDisplayList(canvas->finishRecording(), nullptr);
}
node->setPropertyFieldsDirty(0xFFFFFFFF);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
return node;
}
/**
* Forces a sync of a tree of RenderNode, such that every descendant will have its staging
* properties and DisplayList moved to the render copies.

View File

@@ -34,34 +34,6 @@ using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
static sp<RenderNode> createSkiaNode(int left, int top, int right, int bottom,
std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup,
const char* name = nullptr, SkiaDisplayList* displayList = nullptr) {
#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
DeviceInfo::initialize();
#endif
sp<RenderNode> node = new RenderNode();
if (name) {
node->setName(name);
}
RenderProperties& props = node->mutateStagingProperties();
props.setLeftTopRightBottom(left, top, right, bottom);
if (displayList) {
node->setStagingDisplayList(displayList, nullptr);
}
if (setup) {
std::unique_ptr<SkiaRecordingCanvas> canvas(new SkiaRecordingCanvas(nullptr,
props.getWidth(), props.getHeight()));
setup(props, *canvas.get());
node->setStagingDisplayList(canvas->finishRecording(), nullptr);
}
node->setPropertyFieldsDirty(0xFFFFFFFF);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
return node;
}
TEST(RenderNodeDrawable, create) {
auto rootNode = TestUtils::createNode(0, 0, 200, 400,
[](RenderProperties& props, Canvas& canvas) {
@@ -86,7 +58,7 @@ TEST(RenderNodeDrawable, drawContent) {
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
//create a RenderNodeDrawable backed by a RenderNode backed by a SkLiteRecorder
auto rootNode = createSkiaNode(0, 0, 1, 1,
auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& recorder) {
recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
@@ -118,14 +90,14 @@ TEST(RenderNodeDrawable, drawAndReorder) {
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
//-z draws to all 4 pixels (RED)
auto redNode = createSkiaNode(0, 0, 4, 4,
auto redNode = TestUtils::createSkiaNode(0, 0, 4, 4,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
props.setElevation(-10.0f);
}, "redNode");
//0z draws to bottom 2 pixels (GREEN)
auto bottomHalfGreenNode = createSkiaNode(0, 0, 4, 4,
auto bottomHalfGreenNode = TestUtils::createSkiaNode(0, 0, 4, 4,
[](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
SkPaint greenPaint;
greenPaint.setColor(SK_ColorGREEN);
@@ -135,7 +107,7 @@ TEST(RenderNodeDrawable, drawAndReorder) {
}, "bottomHalfGreenNode");
//+z draws to right 2 pixels (BLUE)
auto rightHalfBlueNode = createSkiaNode(0, 0, 4, 4,
auto rightHalfBlueNode = TestUtils::createSkiaNode(0, 0, 4, 4,
[](RenderProperties& props, SkiaRecordingCanvas& rightHalfBlueCanvas) {
SkPaint bluePaint;
bluePaint.setColor(SK_ColorBLUE);
@@ -144,7 +116,7 @@ TEST(RenderNodeDrawable, drawAndReorder) {
props.setElevation(10.0f);
}, "rightHalfBlueNode");
auto rootNode = createSkiaNode(0, 0, 4, 4,
auto rootNode = TestUtils::createSkiaNode(0, 0, 4, 4,
[&](RenderProperties& props, SkiaRecordingCanvas& rootRecorder) {
rootRecorder.insertReorderBarrier(true);
//draw in reverse Z order, so Z alters draw order
@@ -167,7 +139,7 @@ TEST(RenderNodeDrawable, composeOnLayer)
canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
auto rootNode = createSkiaNode(0, 0, 1, 1,
auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& recorder) {
recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
@@ -176,7 +148,7 @@ TEST(RenderNodeDrawable, composeOnLayer)
auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
auto canvas2 = surfaceLayer->getCanvas();
canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
rootNode->setLayerSurface( surfaceLayer );
rootNode->setLayerSurface(surfaceLayer);
RenderNodeDrawable drawable1(rootNode.get(), &canvas, false);
canvas.drawDrawable(&drawable1);
@@ -190,7 +162,7 @@ TEST(RenderNodeDrawable, composeOnLayer)
canvas.drawDrawable(&drawable3);
ASSERT_EQ(SK_ColorRED, TestUtils::getColor(surface, 0, 0));
rootNode->setLayerSurface( sk_sp<SkSurface>() );
rootNode->setLayerSurface(sk_sp<SkSurface>());
}
//TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder
@@ -203,18 +175,18 @@ TEST(RenderNodeDrawable, projectDraw) {
canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
auto redNode = createSkiaNode(0, 0, 1, 1,
auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
}, "redNode");
auto greenNodeWithRedChild = createSkiaNode(0, 0, 1, 1,
auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1,
[&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) {
greenCanvasWithRedChild.drawRenderNode(redNode.get());
greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
}, "greenNodeWithRedChild");
auto rootNode = createSkiaNode(0, 0, 1, 1,
auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) {
rootCanvas.drawRenderNode(greenNodeWithRedChild.get());
}, "rootNode");

View File

@@ -0,0 +1,181 @@
/*
* Copyright (C) 2016 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 <gtest/gtest.h>
#include <VectorDrawable.h>
#include "AnimationContext.h"
#include "DamageAccumulator.h"
#include "IContextFactory.h"
#include "pipeline/skia/SkiaDisplayList.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "renderthread/CanvasContext.h"
#include "tests/common/TestUtils.h"
#include "SkiaCanvas.h"
#include <SkLiteRecorder.h>
#include <string.h>
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurface::MakeRasterN32Premul(1, 1);
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckBounds) {
auto backdropRedNode = TestUtils::createSkiaNode(1, 1, 4, 4,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
auto contentGreenNode = TestUtils::createSkiaNode(2, 2, 5, 5,
[](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
blueCanvas.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(backdropRedNode); //first node is backdrop
renderNodes.push_back(contentGreenNode); //second node is content drawn on top of the backdrop
android::uirenderer::Rect contentDrawBounds(1, 1, 3, 3);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurface::MakeRasterN32Premul(5, 5);
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
//backdropBounds is (1, 1, 3, 3), content clip is (1, 1, 3, 3), content translate is (0, 0)
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorGREEN);
ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE);
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
contentDrawBounds.set(0, 0, 5, 5);
//backdropBounds is (1, 1, 4, 4), content clip is (0, 0, 3, 3), content translate is (1, 1)
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surface, 2, 2), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorGREEN);
ASSERT_EQ(TestUtils::getColor(surface, 4, 4), SK_ColorBLUE);
}
RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2,
[](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
SkPaint greenPaint;
greenPaint.setColor(SK_ColorGREEN);
greenPaint.setStyle(SkPaint::kFill_Style);
bottomHalfGreenCanvas.drawRect(0, 1, 2, 2, greenPaint);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(halfGreenNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurface::MakeRasterN32Premul(2, 2);
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT);
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
}
RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
auto redNode = TestUtils::createSkiaNode(0, 0, 2, 2,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeXYWH(0, 1, 2, 1);
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(redNode);
android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
auto surface = SkSurface::MakeRasterN32Premul(2, 2);
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
}
RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
[](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1);
surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
redNode->setLayerSurface(surfaceLayer1);
//create a 2nd 2x2 layer and add it to the queue as well.
//make the layer's dirty area one half of the layer and verify only the dirty half is updated.
auto blueNode = TestUtils::createSkiaNode(0, 0, 2, 2,
[](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
});
auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2);
surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
blueNode->setLayerSurface(surfaceLayer2);
//attach both layers to the update queue
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRect::MakeLargest();
layerUpdateQueue.enqueueLayerWithDamage(redNode.get(), dirty);
layerUpdateQueue.enqueueLayerWithDamage(blueNode.get(), SkRect::MakeWH(2, 1));
ASSERT_EQ(layerUpdateQueue.entries().size(), 2UL);
bool opaque = true;
FrameBuilder::LightGeometry lightGeometry;
lightGeometry.radius = 1.0f;
lightGeometry.center = { 0.0f, 0.0f, 0.0f };
BakedOpRenderer::LightInfo lightInfo;
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo);
ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE);
ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE);
ASSERT_TRUE(layerUpdateQueue.entries().empty());
redNode->setLayerSurface(sk_sp<SkSurface>());
blueNode->setLayerSurface(sk_sp<SkSurface>());
}