/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can
* be found in the LICENSE file.
*/
//
// C++
//
#include "SkDevice_Compute.h"
//
//
//
#if SK_SUPPORT_GPU_COMPUTE
//
// C++
//
#include "SkImageInfo.h"
#include "SkDraw.h"
#include "SkMatrix.h"
#include "SkPath.h"
//
// C
//
#ifdef __cplusplus
extern "C" {
#endif
#include "../spinel/spinel/color.h"
#include "../compute/skc/skc.h"
#ifdef __cplusplus
}
#endif
//
//
//
SkDevice_Compute::SkDevice_Compute(sk_sp<SkContext_Compute> compute, int w, int h)
: SkClipStackDevice(SkImageInfo::MakeN32Premul(w,h), SkSurfaceProps(0,kUnknown_SkPixelGeometry))
, fCompute(std::move(compute))
{
fTopCTM = this->ctm();
fTransformWeakref = SKC_WEAKREF_INVALID;
fClipWeakref = SKC_WEAKREF_INVALID;
skc_err err;
//
// create a composition
//
#define LAYER_COUNT (1<<14)
err = skc_composition_create(fCompute->context, &fComposition);
SKC_ERR_CHECK(err);
// Is this correct?
int clipRect[] = { 0, 0, w - 1, h - 1 };
err = skc_composition_clip_set(fComposition, clipRect);
SKC_ERR_CHECK(err);
//
// create styling
//
err = skc_styling_create(fCompute->context,
LAYER_COUNT,
10,
2 * 1024 * 1024,
&fStyling);
//
// create a path builder
//
err = skc_path_builder_create(fCompute->context, &fPB);
SKC_ERR_CHECK(err);
//
// create a raster builder
//
err = skc_raster_builder_create(fCompute->context, &fRB);
SKC_ERR_CHECK(err);
//
// create the simplest styling group that encloses all layers
//
styling_group_init();
}
//
//
//
SkDevice_Compute::~SkDevice_Compute() {
skc_err err;
err = skc_raster_builder_release(fRB);
SKC_ERR_CHECK(err);
err = skc_path_builder_release(fPB);
SKC_ERR_CHECK(err);
err = skc_styling_dispose(fStyling);
SKC_ERR_CHECK(err);
err = skc_composition_dispose(fComposition);
SKC_ERR_CHECK(err);
}
//
//
//
void SkDevice_Compute::flush() {
//
// seal the styling and composition objects
//
skc_err err;
err = skc_composition_seal(fComposition);
SKC_ERR_CHECK(err);
err = skc_styling_seal(fStyling);
SKC_ERR_CHECK(err);
//
// we're going to block here -- API mismatch
//
//
// render to surface
//
// note this implicitly seals composition and styling
//
err = skc_surface_render(fCompute->surface, fComposition, fStyling);
SKC_ERR_CHECK(err);
//
// kick off pipeline and wait here -- not needed since skc_surface_reset() blocks
//
err = skc_surface_wait(fCompute->surface);
SKC_ERR_CHECK(err);
//
// reset the surface -- implicitly waits for render to finish -- FIXME -- composition might be released too early
//
err = skc_surface_reset(fCompute->surface);
SKC_ERR_CHECK(err);
//
// reset composition and styling
//
err = skc_composition_reset(fComposition);
SKC_ERR_CHECK(err);
err = skc_styling_reset(fStyling);
SKC_ERR_CHECK(err);
//
//
//
styling_group_init();
}
//
//
//
#define SKC_STYLING_CMDS(...) SK_ARRAY_COUNT(__VA_ARGS__),__VA_ARGS__
#define SKC_GROUP_IDS(...) SK_ARRAY_COUNT(__VA_ARGS__),__VA_ARGS__
void SkDevice_Compute::styling_group_init() {
skc_styling_group_alloc(fStyling, &fGroupID);
fParents.push_back(fGroupID);
// ENTER
skc_styling_cmd_t const styling_cmds_enter[] = {
SKC_STYLING_CMD_OP_COVER_ZERO_ACC,
SKC_STYLING_CMD_OP_COLOR_ZERO_ACC | SKC_STYLING_CMD_OP_IS_FINAL
};
skc_styling_group_enter(fStyling, fGroupID, SKC_STYLING_CMDS(styling_cmds_enter));
skc_group_id const group_id_parents[] = { fGroupID };
skc_styling_group_parents(fStyling, fGroupID, SKC_GROUP_IDS(group_id_parents));
// RANGE
skc_styling_group_range_lo(fStyling, fGroupID, 0);
skc_styling_group_range_hi(fStyling, fGroupID, LAYER_COUNT-1);
// LEAVE
skc_styling_cmd_t const styling_cmds_leave[] = {
SKC_STYLING_CMD_OP_SURFACE_COMPOSITE | SKC_STYLING_CMD_OP_IS_FINAL
};
skc_styling_group_leave(fStyling, fGroupID, SKC_STYLING_CMDS(styling_cmds_leave));
// START
fGroupLayerID = LAYER_COUNT-1;
}
//
//
//
#define SK_SCALE_F32 (1.0f/255.0f)
#define SK_TO_RGBA_F32(c) { SK_SCALE_F32 * SkColorGetR(c), \
SK_SCALE_F32 * SkColorGetG(c), \
SK_SCALE_F32 * SkColorGetB(c), \
SK_SCALE_F32 * SkColorGetA(c) }
//
//
//
void SkDevice_Compute::path_rasterize_and_place(const SkPaint& paint,
const skc_path_t path,
const SkMatrix* prePathMatrix) {
float transform[9];
const SkMatrix& ctm = fTopCTM;
SkMatrix tmp;
if (prePathMatrix) {
tmp.setConcat(ctm, *prePathMatrix);
}
transform[0] = tmp.get(SkMatrix::kMScaleX);
transform[1] = tmp.get(SkMatrix::kMSkewX );
transform[2] = tmp.get(SkMatrix::kMTransX);
transform[3] = tmp.get(SkMatrix::kMSkewY );
transform[4] = tmp.get(SkMatrix::kMScaleY);
transform[5] = tmp.get(SkMatrix::kMTransY);
transform[6] = tmp.get(SkMatrix::kMPersp0);
transform[7] = tmp.get(SkMatrix::kMPersp1);
transform[8] = tmp.get(SkMatrix::kMPersp2);
skc_transform_weakref_t& transform_weakref = fTransformWeakref;
//
// always invalid for now
//
skc_raster_clip_weakref_t clip_weakref = fClipWeakref;
// TODO Support arbitrary path clip?
SkRect devClip = SkRect::Make(this->devClipBounds());
const float clip[] = { devClip.fLeft, devClip.fTop, devClip.fRight, devClip.fBottom };
//
//
//
skc_err err;
skc_raster_t raster;
err = skc_raster_begin(fRB);
err = skc_raster_add_filled(fRB, path, &transform_weakref, transform, &clip_weakref, clip);
err = skc_raster_end(fRB, &raster);
//
// can release path handle now because it is being referenced by raster
//
err = skc_path_release(fCompute->context, path);
//
// style the path
//
skc_styling_cmd_t cmds[1 + 3 + 1];
cmds[0] = SKC_STYLING_CMD_OP_COVER_NONZERO;
cmds[SK_ARRAY_COUNT(cmds)-1] = SKC_STYLING_CMD_OP_BLEND_OVER | SKC_STYLING_CMD_OP_IS_FINAL;
{
SkColor4f rgba = paint.getColor4f().premul();
skc_styling_layer_fill_solid_encoder(cmds+1, rgba.vec());
skc_styling_group_layer(fStyling, fGroupID, fGroupLayerID, SKC_STYLING_CMDS(cmds));
}
err = skc_composition_place(fComposition, fGroupLayerID, raster, 0, 0);
//
// can release raster handle now because it is being referenced by composition
//
err = skc_raster_release(fCompute->context, raster);
SkASSERT(err == SKC_ERR_SUCCESS);
fGroupLayerID -= 1;
}
//
//
//
void SkDevice_Compute::path_add(const SkPaint& paint,
const SkPath& path,
const SkMatrix* prePathMatrix) {
skc_err err;
err = skc_path_begin(fPB);
#if 0
SkPath::Iter pi(path,false);
#else
SkPath::RawIter pi(path); // this seems to work fine for now
#endif
SkPoint xy0;
//
// build path
//
while (true)
{
SkPoint pts[4];
SkPath::Verb const verb = pi.next(pts);
switch (verb)
{
case SkPath::kMove_Verb:
xy0 = pts[0];
err = skc_path_move_to(fPB,
pts[0].x(),pts[0].y());
continue;
case SkPath::kLine_Verb:
err = skc_path_line_to(fPB,
pts[1].x(),pts[1].y());
continue;
case SkPath::kQuad_Verb:
err = skc_path_quad_to(fPB,
pts[1].x(),pts[1].y(),
pts[2].x(),pts[2].y());
continue;
case SkPath::kConic_Verb: // <--------------------- FIXME
err = skc_path_line_to(fPB,
pts[2].x(),pts[2].y());
continue;
case SkPath::kCubic_Verb:
err = skc_path_cubic_to(fPB,
pts[1].x(),pts[1].y(),
pts[2].x(),pts[2].y(),
pts[3].x(),pts[3].y());
continue;
case SkPath::kClose_Verb:
err = skc_path_line_to(fPB,xy0.x(),xy0.y());
continue;
case SkPath::kDone_Verb:
break;
}
//
// otherwise, kDone_Verb breaks out of while loop
//
break;
}
//
// seal the path
//
skc_path_t skc_path;
err = skc_path_end(fPB,&skc_path);
//
// rasterize the path and place it in a composition
//
path_rasterize_and_place(paint,skc_path,prePathMatrix);
SkASSERT(err == SKC_ERR_SUCCESS);
}
//
//
//
void
SkDevice_Compute::circles_add(
const SkPaint & paint,
const SkPoint points[],
int32_t const count,
SkScalar const radius)
{
#define CIRCLE_KAPPA 0.55228474983079339840f // moar digits!
#define CIRCLE_RADIUS_X radius
#define CIRCLE_RADIUS_Y radius
#define CIRCLE_KAPPA_X (CIRCLE_RADIUS_X * CIRCLE_KAPPA)
#define CIRCLE_KAPPA_Y (CIRCLE_RADIUS_Y * CIRCLE_KAPPA)
//
// use a 4 Bezier approximation
//
float const circle[] =
{
0.0f, +CIRCLE_RADIUS_Y, // move_to
+CIRCLE_KAPPA_X, +CIRCLE_RADIUS_Y, // cubic_to
+CIRCLE_RADIUS_X, +CIRCLE_KAPPA_Y,
+CIRCLE_RADIUS_X, 0.0f,
+CIRCLE_RADIUS_X, -CIRCLE_KAPPA_Y, // cubic_to
+CIRCLE_KAPPA_X, -CIRCLE_RADIUS_Y,
0.0f, -CIRCLE_RADIUS_Y,
-CIRCLE_KAPPA_X, -CIRCLE_RADIUS_Y, // cubic_to
-CIRCLE_RADIUS_X, -CIRCLE_KAPPA_Y,
-CIRCLE_RADIUS_X, 0.0f,
-CIRCLE_RADIUS_X, +CIRCLE_KAPPA_Y, // cubic_to
-CIRCLE_KAPPA_X, +CIRCLE_RADIUS_Y,
0.0f, +CIRCLE_RADIUS_Y
};
#define CXLAT(x,y,t) circle[x]+t.fX,circle[y]+t.fY
//
//
//
skc_err err;
err = skc_path_begin(fPB);
//
//
//
for (int32_t ii=0; ii<count; ii++)
{
SkPoint const p = points[ii];
err = skc_path_move_to(fPB,
CXLAT(0,1,p));
err = skc_path_cubic_to(fPB,
CXLAT(2,3,p),
CXLAT(4,5,p),
CXLAT(6,7,p));
err = skc_path_cubic_to(fPB,
CXLAT(8, 9,p),
CXLAT(10,11,p),
CXLAT(12,13,p));
err = skc_path_cubic_to(fPB,
CXLAT(14,15,p),
CXLAT(16,17,p),
CXLAT(18,19,p));
err = skc_path_cubic_to(fPB,
CXLAT(20,21,p),
CXLAT(22,23,p),
CXLAT(24,25,p));
}
//
// seal the path
//
skc_path_t skc_path;
err = skc_path_end(fPB,&skc_path);
//
// rasterize the path and place it in a composition
//
path_rasterize_and_place(paint,skc_path,NULL);
SkASSERT(err == SKC_ERR_SUCCESS);
}
//
//
//
void
SkDevice_Compute::squares_add(
const SkPaint & paint,
const SkPoint points[],
int32_t const count,
SkScalar const radius)
{
float const square[] =
{
-radius,+radius, // move_to
+radius,+radius, // line_to
+radius,-radius, // line_to
-radius,-radius, // line_to
-radius,+radius // line_to
};
#define SXLAT(x,y,t) square[x]+t.fX,square[y]+t.fY
//
//
//
skc_err err;
err = skc_path_begin(fPB);
//
//
//
for (int32_t ii=0; ii<count; ii++)
{
SkPoint const p = points[ii];
err = skc_path_move_to(fPB,SXLAT(0,1,p));
err = skc_path_line_to(fPB,SXLAT(2,3,p));
err = skc_path_line_to(fPB,SXLAT(4,5,p));
err = skc_path_line_to(fPB,SXLAT(6,7,p));
err = skc_path_line_to(fPB,SXLAT(8,9,p));
}
//
// seal the path
//
skc_path_t skc_path;
err = skc_path_end(fPB,&skc_path);
//
// rasterize the path and place it in a composition
//
path_rasterize_and_place(paint,skc_path,NULL);
SkASSERT(err == SKC_ERR_SUCCESS);
}
//
// FIXME -- THIS IS NOT CORRECT
//
// Need to implement butt, round, square caps
//
void
SkDevice_Compute::line_stroked_butt(SkPoint const xy0,
SkPoint const xy1,
SkScalar const radius)
{
float const dx = xy1.fX - xy0.fX;
float const dy = xy1.fY - xy0.fY;
float const hypot = hypotf(dx,dy);
// FIXME -- what's practical here?
if (hypot == 0.0f)
return;
float const scale = radius / hypot;
float const rx = dy * scale;
float const ry = dx * scale;
skc_err err;
err = skc_path_move_to(fPB,xy0.fX-rx,xy0.fY+ry);
err = skc_path_line_to(fPB,xy1.fX-rx,xy1.fY+ry);
err = skc_path_line_to(fPB,xy1.fX+rx,xy1.fY-ry);
err = skc_path_line_to(fPB,xy0.fX+rx,xy0.fY-ry);
err = skc_path_line_to(fPB,xy0.fX-rx,xy0.fY+ry);
SkASSERT(err == SKC_ERR_SUCCESS);
}
void
SkDevice_Compute::lines_stroked_add(
const SkPaint & paint,
const SkPoint points[],
int32_t const count,
SkScalar const radius)
{
skc_err err;
err = skc_path_begin(fPB);
//
//
//
for (int32_t ii=0; ii<count; ii+=2)
line_stroked_butt(points[ii],points[ii+1],radius);
//
// seal the path
//
skc_path_t skc_path;
err = skc_path_end(fPB,&skc_path);
//
// rasterize the path and place it in a composition
//
path_rasterize_and_place(paint,skc_path,NULL);
SkASSERT(err == SKC_ERR_SUCCESS);
}
//
//
//
// drawPaint is really just a short-cut for drawRect(wide_open, paint)
// so we have to respect everything (but stroking and maskfilter) in the paint
// - color | shader
// - colorFilter
// - blendmode
// - etc.
void SkDevice_Compute::drawPaint(const SkPaint& paint) {
//
// clear the surface -- will be postponed until render is complete
//
SkColor const c = paint.getColor();
float rgba[4] = SK_TO_RGBA_F32(c);
skc_surface_clear(fCompute->surface,rgba);
}
void SkDevice_Compute::drawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint points[],
const SkPaint& paint) {
if (count == 0) {
return;
}
const SkScalar radius = paint.getStrokeWidth() * 0.5f;
/*
* drawPoints draws each element (point, line) separately. This means our bulk-adding into the
* same raster is not valid for most blendmodes.
*/
switch (mode) {
case SkCanvas::kPoints_PointMode: {
if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
circles_add(paint, points, (int32_t)count, radius);
} else {
squares_add(paint, points,(int32_t)count, radius);
}
} break;
case SkCanvas::kLines_PointMode: {
if (count <= 1) {
return;
}
lines_stroked_add(paint, points, (int32_t)count & ~1, radius);
} break;
case SkCanvas::kPolygon_PointMode: {
SkPoint xy0 = points[0];
skc_err err = skc_path_begin(fPB);
for (size_t i = 0; i < count; ++i) {
const SkPoint xy1 = points[i];
line_stroked_butt(xy0, xy1, radius);
xy0 = xy1;
}
//
// seal the path
//
skc_path_t skc_path;
err = skc_path_end(fPB, &skc_path);
//
// rasterize the path and place it in a composition
//
path_rasterize_and_place(paint, skc_path, nullptr);
SkASSERT(err == SKC_ERR_SUCCESS);
} break;
default:
break;
}
}
void SkDevice_Compute::drawRect(const SkRect& rect, const SkPaint& paint) {
SkPath path;
path.addRect(rect);
this->drawPath(path, paint, nullptr, true);
}
void SkDevice_Compute::drawOval(const SkRect& oval, const SkPaint& paint) {
SkPath path;
path.addOval(oval);
this->drawPath(path, paint, nullptr, true);
}
void SkDevice_Compute::drawRRect(const SkRRect& rrect, const SkPaint& paint) {
SkPath path;
path.addRRect(rrect);
this->drawPath(path, paint, nullptr, true);
}
void SkDevice_Compute::drawPath(const SkPath& path, const SkPaint& paint,
const SkMatrix* prePathMatrix, bool pathIsMutable) {
if (paint.getStyle() == SkPaint::kFill_Style) {
path_add(paint,path,prePathMatrix);
} else {
SkPath stroked;
#define SK_MAGIC_RES_SCALE 1024
paint.getFillPath(path, &stroked, nullptr, SK_MAGIC_RES_SCALE);
this->path_add(paint, stroked, prePathMatrix);
}
}
void SkDevice_Compute::drawText(const void* text,
size_t length,
SkScalar x,
SkScalar y,
const SkPaint& paint) {
SkPath outline;
paint.getTextPath(text,length,x,y,&outline);
this->drawPath(outline, paint, nullptr, true);
}
void
SkDevice_Compute::drawPosText(const void * text,
size_t length,
const SkScalar pos[],
int scalarsPerPos,
const SkPoint & offset,
const SkPaint & paint)
{
#if 0
draw.drawPosText_asPaths((const char *)text,length,
pos,scalarsPerPos,offset,paint);
#endif
}
SkBaseDevice* SkDevice_Compute::onCreateDevice(const CreateInfo& cinfo, const SkPaint* paint) {
#ifdef SK_USE_COMPUTE_LAYER_GROUP
return this->createLayerGroup(cinfo, paint);
#else
// TODO return a new SkDevice_Compute when SkDevice_ComputeLayerGroup doesn't work
return nullptr;
#endif
}
void SkDevice_Compute::drawDevice(SkBaseDevice* device, int left, int top, const SkPaint& paint) {
// It seems that we won't support image filter until snapSpecial and drawSpecial are implemented
// (SkCanvas.cpp will call drawSpecial when the paint has an image filter).
SkASSERT(!paint.getImageFilter());
#ifdef SK_USE_COMPUTE_LAYER_GROUP
// In case of SkDevice_ComputeLayerGroup, we close the group
SkDevice_ComputeLayerGroup* layerDevice = static_cast<SkDevice_ComputeLayerGroup*>(device);
SkASSERT(layerDevice->fRoot == this); // the layerDevice should belong to this root device
SkASSERT(layerDevice->fGroupID == fGroupID); // the layerDevice should be the top device
// left, top should be the same as the origin,
// and we can ignore them because we have no offscreen buffer.
SkASSERT(SkIPoint::Make(left, top) == device->getOrigin());
// close the group and pop the top device
skc_styling_group_range_lo(fStyling, fGroupID, fGroupLayerID + 1);
fGroupID = fParents.back();
fParents.pop_back();
#else
// TODO handle the case where the device is a SkDevice_Compute rather than
// SkDevice_ComputeLayerGroup (in which case an offscreen buffer is created).
#endif
}
#ifdef SK_USE_COMPUTE_LAYER_GROUP
SkDevice_ComputeLayerGroup* SkDevice_Compute::createLayerGroup(const CreateInfo& cinfo,
const SkPaint* paint) {
return new SkDevice_ComputeLayerGroup(this, cinfo, paint);
}
void SkDevice_Compute::onCtmChanged() {
fTopCTM = this->ctm();
fTransformWeakref = SKC_WEAKREF_INVALID;
}
SkDevice_ComputeLayerGroup::SkDevice_ComputeLayerGroup(SkDevice_Compute* root,
const CreateInfo& cinfo, const SkPaint* paint)
: SkBaseDevice(SkImageInfo::MakeN32Premul(cinfo.fInfo.width(), cinfo.fInfo.height()),
SkSurfaceProps(0,kUnknown_SkPixelGeometry)) {
// TODO clip the group using cinfo; handle the paint's alpha and maybe color filter?
// Create a new group. We'll restore the previous group during onRestore.
skc_styling_group_alloc(fRoot->fStyling, &fRoot->fGroupID);
fRoot->fParents.push_back(fRoot->fGroupID);
fGroupID = fRoot->fGroupID;
// ENTER
skc_styling_cmd_t const styling_cmds_enter[] = {
SKC_STYLING_CMD_OP_COVER_ZERO_ACC,
SKC_STYLING_CMD_OP_COLOR_ZERO_ACC
};
skc_styling_group_enter(fRoot->fStyling, fRoot->fGroupID, SKC_STYLING_CMDS(styling_cmds_enter));
skc_styling_group_parents(fRoot->fStyling, fRoot->fGroupID, fRoot->fParents.count(),
fRoot->fParents.begin());
// RANGE
// We'll set range_lo at restore
skc_styling_group_range_hi(fRoot->fStyling, fRoot->fGroupID, fRoot->fGroupLayerID);
// LEAVE
skc_styling_cmd_t const styling_cmds_leave[] = {
SKC_STYLING_CMD_OP_SURFACE_COMPOSITE
};
skc_styling_group_leave(fRoot->fStyling, fRoot->fGroupID, SKC_STYLING_CMDS(styling_cmds_leave));
}
void SkDevice_ComputeLayerGroup::drawDevice(SkBaseDevice* device, int left, int top,
const SkPaint& paint) {
fRoot->drawDevice(device, left, top, paint); // the root will properly close the group
}
SkBaseDevice* SkDevice_ComputeLayerGroup::onCreateDevice(const CreateInfo& cinfo,
const SkPaint* paint) {
return fRoot->createLayerGroup(cinfo, paint);
}
void SkDevice_ComputeLayerGroup::onCtmChanged() {
this->sanityCheck();
// Cancels the translation as we're not using an offscreen buffer
const SkIPoint& origin = this->getOrigin();
fRoot->fTopCTM = this->ctm();
fRoot->fTopCTM.postTranslate(SkIntToScalar(origin.fX), SkIntToScalar(origin.fY));
fRoot->fTransformWeakref = SKC_WEAKREF_INVALID;
}
void SkDevice_ComputeLayerGroup::onSave() {
this->sanityCheck();
fRoot->onSave();
}
void SkDevice_ComputeLayerGroup::onRestore() {
this->sanityCheck();
fRoot->onRestore();
}
void SkDevice_ComputeLayerGroup::onClipRect(const SkRect& rect, SkClipOp op, bool aa) {
this->sanityCheck();
fRoot->fClipStack.clipRect(rect, fRoot->fTopCTM, op, aa);
fRoot->fClipWeakref = SKC_WEAKREF_INVALID;
}
void SkDevice_ComputeLayerGroup::onClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
this->sanityCheck();
fRoot->fClipStack.clipRRect(rrect, fRoot->fTopCTM, op, aa);
fRoot->fClipWeakref = SKC_WEAKREF_INVALID;
}
void SkDevice_ComputeLayerGroup::onClipPath(const SkPath& path, SkClipOp op, bool aa) {
this->sanityCheck();
fRoot->fClipStack.clipPath(path, fRoot->fTopCTM, op, aa);
fRoot->fClipWeakref = SKC_WEAKREF_INVALID;
}
void SkDevice_ComputeLayerGroup::onClipRegion(const SkRegion& deviceRgn, SkClipOp op) {
this->sanityCheck();
fRoot->onClipRegion(deviceRgn, op);
}
void SkDevice_ComputeLayerGroup::onSetDeviceClipRestriction(SkIRect* mutableClipRestriction) {
this->sanityCheck();
fRoot->onSetDeviceClipRestriction(mutableClipRestriction);
}
#endif // SK_USE_COMPUTE_LAYER_GROUP
#endif