blob: 114259e6f287b2a910d0d5bd33061ea553c3b079 [file] [log] [blame]
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "DMSrcSink.h"
#include "DMSrcSinkAndroid.h"
#include "AnimationContext.h"
#include "DisplayListCanvas.h"
#include "IContextFactory.h"
#include "RenderNode.h"
#include "SkCanvas.h"
#include "SkiaCanvasProxy.h"
#include "SkTLazy.h"
#include "SkMaskFilter.h"
#include "SkPictureRecorder.h"
#include "SkStream.h"
#include "android/rect.h"
#include "android/native_window.h"
#include "gui/BufferQueue.h"
#include "gui/CpuConsumer.h"
#include "gui/IGraphicBufferConsumer.h"
#include "gui/IGraphicBufferProducer.h"
#include "gui/Surface.h"
#include "renderthread/RenderProxy.h"
#include "renderthread/TimeLord.h"
/* These functions are only compiled in the Android Framework. */
namespace {
/** Discard SkShaders not exposed by the Android Java API. */
void CheckShader(SkPaint* paint) {
SkShader* shader = paint->getShader();
if (!shader) {
return;
}
if (shader->asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType) {
return;
}
if (shader->asACompose(NULL)) {
return;
}
SkShader::GradientType gtype = shader->asAGradient(NULL);
if (gtype == SkShader::kLinear_GradientType ||
gtype == SkShader::kRadial_GradientType ||
gtype == SkShader::kSweep_GradientType) {
return;
}
paint->setShader(NULL);
}
/** Simplify a paint. */
void Filter(SkPaint* paint) {
uint32_t flags = paint->getFlags();
flags &= ~SkPaint::kLCDRenderText_Flag;
paint->setFlags(flags);
// Android doesn't support Xfermodes above kLighten_Mode
SkXfermode::Mode mode;
SkXfermode::AsMode(paint->getXfermode(), &mode);
if (mode > SkXfermode::kLighten_Mode) {
paint->setXfermode(NULL);
}
// Force bilinear scaling or none
if (paint->getFilterQuality() != kNone_SkFilterQuality) {
paint->setFilterQuality(kLow_SkFilterQuality);
}
CheckShader(paint);
// Android SDK only supports mode & matrix color filters
// (and, again, no modes above kLighten_Mode).
SkColorFilter* cf = paint->getColorFilter();
if (cf) {
SkColor color;
SkXfermode::Mode mode;
SkScalar srcColorMatrix[20];
bool isMode = cf->asColorMode(&color, &mode);
if (isMode && mode > SkXfermode::kLighten_Mode) {
paint->setColorFilter(
SkColorFilter::CreateModeFilter(color, SkXfermode::kSrcOver_Mode));
} else if (!isMode && !cf->asColorMatrix(srcColorMatrix)) {
paint->setColorFilter(NULL);
}
}
SkPathEffect* pe = paint->getPathEffect();
if (pe && !pe->exposedInAndroidJavaAPI()) {
paint->setPathEffect(NULL);
}
// TODO: Android doesn't support all the flags that can be passed to
// blur filters; we need plumbing to get them out.
paint->setImageFilter(NULL);
paint->setLooper(NULL);
};
/** SkDrawFilter is likely to be deprecated; this is a proxy
canvas that does the same thing: alter SkPaint fields.
onDraw*() functions may have their SkPaint modified, and are then
passed on to the same function on proxyTarget.
This still suffers one of the same architectural flaws as SkDrawFilter:
TextBlob paints are incomplete when filter is called.
*/
#define FILTER(p) \
SkPaint filteredPaint(p); \
Filter(&filteredPaint);
#define FILTER_PTR(p) \
SkTLazy<SkPaint> lazyPaint; \
SkPaint* filteredPaint = (SkPaint*) p; \
if (p) { \
filteredPaint = lazyPaint.set(*p); \
Filter(filteredPaint); \
}
class FilteringCanvas : public SkCanvas {
public:
FilteringCanvas(SkCanvas* proxyTarget) : fProxyTarget(proxyTarget) { }
protected:
void onDrawPaint(const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawPaint(filteredPaint);
}
void onDrawPoints(PointMode pMode, size_t count, const SkPoint pts[],
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawPoints(pMode, count, pts, filteredPaint);
}
void onDrawOval(const SkRect& r, const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawOval(r, filteredPaint);
}
void onDrawRect(const SkRect& r, const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawRect(r, filteredPaint);
}
void onDrawRRect(const SkRRect& r, const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawRRect(r, filteredPaint);
}
void onDrawPath(const SkPath& path, const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawPath(path, filteredPaint);
}
void onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
const SkPaint* paint) SK_OVERRIDE {
FILTER_PTR(paint);
fProxyTarget->drawBitmap(bitmap, left, top, filteredPaint);
}
void onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* src, const SkRect& dst,
const SkPaint* paint, DrawBitmapRectFlags flags) SK_OVERRIDE {
FILTER_PTR(paint);
fProxyTarget->drawBitmapRectToRect(bitmap, src, dst, filteredPaint, flags);
}
void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
const SkRect& dst, const SkPaint* paint) SK_OVERRIDE {
FILTER_PTR(paint);
fProxyTarget->drawBitmapNine(bitmap, center, dst, filteredPaint);
}
void onDrawSprite(const SkBitmap& bitmap, int left, int top,
const SkPaint* paint) SK_OVERRIDE {
FILTER_PTR(paint);
fProxyTarget->drawSprite(bitmap, left, top, filteredPaint);
}
void onDrawVertices(VertexMode vMode, int vertexCount, const SkPoint vertices[],
const SkPoint texs[], const SkColor colors[], SkXfermode* xMode,
const uint16_t indices[], int indexCount,
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawVertices(vMode, vertexCount, vertices, texs, colors,
xMode, indices, indexCount, filteredPaint);
}
void onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawDRRect(outer, inner, filteredPaint);
}
void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawText(text, byteLength, x, y, filteredPaint);
}
void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawPosText(text, byteLength, pos, filteredPaint);
}
void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
SkScalar constY, const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawPosTextH(text, byteLength, xpos, constY, filteredPaint);
}
void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
const SkMatrix* matrix, const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawTextOnPath(text, byteLength, path, matrix, filteredPaint);
}
void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawTextBlob(blob, x, y, filteredPaint);
}
void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
const SkPoint texCoords[4], SkXfermode* xmode,
const SkPaint& paint) SK_OVERRIDE {
FILTER(paint);
fProxyTarget->drawPatch(cubics, colors, texCoords, xmode, filteredPaint);
}
protected:
SkCanvas* fProxyTarget;
};
/**
* Helper class for setting up android::uirenderer::renderthread::RenderProxy.
*/
class ContextFactory : public android::uirenderer::IContextFactory {
public:
android::uirenderer::AnimationContext* createAnimationContext
(android::uirenderer::renderthread::TimeLord& clock) SK_OVERRIDE {
return new android::uirenderer::AnimationContext(clock);
}
};
} // namespace
namespace DM {
Error HWUISink::draw(const Src& src, SkBitmap* dst, SkWStream*, SkString*) const {
// Do all setup in this function because we don't know the size
// for the RenderNode and RenderProxy during the constructor.
// In practice this doesn't seem too expensive.
const SkISize size = src.size();
// Based on android::SurfaceTexture_init()
android::sp<android::IGraphicBufferProducer> producer;
android::sp<android::IGraphicBufferConsumer> consumer;
android::BufferQueue::createBufferQueue(&producer, &consumer);
// Consumer setup
android::sp<android::CpuConsumer> cpuConsumer =
new android::CpuConsumer(consumer, 1);
cpuConsumer->setName(android::String8("SkiaTestClient"));
cpuConsumer->setDefaultBufferSize(size.width(), size.height());
// Producer setup
android::sp<android::Surface> surface = new android::Surface(producer);
native_window_set_buffers_dimensions(surface.get(), size.width(), size.height());
native_window_set_buffers_format(surface.get(), android::PIXEL_FORMAT_RGBA_8888);
native_window_set_usage(surface.get(), GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_SW_WRITE_NEVER |
GRALLOC_USAGE_HW_RENDER);
// RenderNode setup based on hwui/tests/main.cpp:TreeContentAnimation
SkAutoTDelete<android::uirenderer::RenderNode> rootNode
(new android::uirenderer::RenderNode());
rootNode->incStrong(nullptr);
// Values set here won't be applied until the framework has called
// RenderNode::pushStagingPropertiesChanges() during RenderProxy::syncAndDrawFrame().
rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, size.width(), size.height());
rootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::X |
android::uirenderer::RenderNode::Y);
rootNode->mutateStagingProperties().setClipToBounds(false);
rootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC);
// RenderProxy setup based on hwui/tests/main.cpp:TreeContentAnimation
ContextFactory factory;
SkAutoTDelete<android::uirenderer::renderthread::RenderProxy> proxy
(new android::uirenderer::renderthread::RenderProxy(false, rootNode, &factory));
proxy->loadSystemProperties();
proxy->initialize(surface.get());
float lightX = size.width() / 2.0f;
android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f };
proxy->setup(size.width(), size.height(), 800.0f, 255 * 0.075f, 255 * 0.15f);
proxy->setLightCenter(lightVector);
// Do the draw
SkAutoTDelete<android::uirenderer::DisplayListCanvas> canvas
(new android::uirenderer::DisplayListCanvas());
canvas->setViewport(size.width(), size.height());
canvas->prepare();
canvas->clipRect(0, 0, size.width(), size.height(), SkRegion::Op::kReplace_Op);
Error err = src.draw(canvas->asSkCanvas());
if (!err.isEmpty()) {
return err;
}
canvas->finish();
rootNode->setStagingDisplayList(canvas->finishRecording());
proxy->syncAndDrawFrame();
proxy->fence();
// Capture pixels
SkImageInfo destinationConfig =
SkImageInfo::Make(size.width(), size.height(),
kRGBA_8888_SkColorType, kPremul_SkAlphaType);
dst->allocPixels(destinationConfig);
sk_memset32((uint32_t*) dst->getPixels(), SK_ColorRED, size.width() * size.height());
android::CpuConsumer::LockedBuffer nativeBuffer;
android::status_t retval = cpuConsumer->lockNextBuffer(&nativeBuffer);
if (retval == android::BAD_VALUE) {
SkDebugf("HWUISink::draw() got no buffer; returning transparent");
// No buffer ready to read - commonly triggered by dm sending us
// a no-op source, or calling code that doesn't do anything on this
// backend.
dst->eraseColor(SK_ColorTRANSPARENT);
return "";
} else if (retval) {
return SkStringPrintf("Failed to lock buffer to read pixels: %d.", retval);
}
// Move the pixels into the destination SkBitmap
SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 &&
"Native buffer not RGBA!");
SkImageInfo nativeConfig =
SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
kRGBA_8888_SkColorType, kPremul_SkAlphaType);
// Android stride is in pixels, Skia stride is in bytes
SkBitmap nativeWrapper;
bool success =
nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4);
if (!success) {
return "Failed to wrap HWUI buffer in a SkBitmap";
}
SK_ALWAYSBREAK(dst->colorType() == kRGBA_8888_SkColorType &&
"Destination buffer not RGBA!");
success =
nativeWrapper.readPixels(destinationConfig, dst->getPixels(), dst->rowBytes(), 0, 0);
if (!success) {
return "Failed to extract pixels from HWUI buffer";
}
cpuConsumer->unlockBuffer(nativeBuffer);
return "";
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
ViaAndroidSDK::ViaAndroidSDK(Sink* sink) : fSink(sink) { }
Error ViaAndroidSDK::draw(const Src& src,
SkBitmap* bitmap,
SkWStream* stream,
SkString* log) const {
struct ProxySrc : public Src {
const Src& fSrc;
ProxySrc(const Src& src)
: fSrc(src) {}
Error draw(SkCanvas* canvas) const SK_OVERRIDE {
// Pass through HWUI's upper layers to get operational transforms
SkAutoTDelete<android::Canvas> ac (android::Canvas::create_canvas(canvas));
SkAutoTUnref<android::uirenderer::SkiaCanvasProxy> scProxy
(new android::uirenderer::SkiaCanvasProxy(ac));
// Pass through another proxy to get paint transforms
FilteringCanvas fc(scProxy);
fSrc.draw(&fc);
return "";
}
SkISize size() const SK_OVERRIDE { return fSrc.size(); }
Name name() const SK_OVERRIDE { sk_throw(); return ""; }
} proxy(src);
return fSink->draw(proxy, bitmap, stream, log);
}
} // namespace DM