/* * 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