利用wasm绘制图的一些参考:
fhtr.org/gravityring/sprites.html
用Canvas + WASM画一个迷宫 - 知乎 (zhihu.com)
WebGL 重置画布尺寸 (webglfundamentals.org)
canvaskit demo
src\third_party\skia\demos.skia.org\Makefile
根据它可以本地启动示例:python -m SimpleHTTPServer 8123
访问:http://localhost:8123/demos/hello_world/index.html
如果需要下载的js访问不到,替换成本地的: <script type="text/javascript" src="https://unpkg.com/canvaskit-wasm@latest/bin/full/canvaskit.js"></script>
skp 在线的背后源码:
E:\dev\chromium96\src\third_party\skia\modules\canvaskit\wasm_tools
目录:
E:\dev\chromium96\src\third_party\skia\experimental\wasm-skp-debugger
E:\dev\chromium96\src\third_party\skia\tools\debugger
关联:
E:\dev\chromium96\src\third_party\skia\experimental\wasm-skp-debugger\debugger_bindings.cpp
#include "tools/debugger/DebugCanvas.h"
#include "tools/debugger/DebugLayerManager.h"
debuggerz网站源码???:
third_party/skia/modules/canvaskit/debugger_bindings.cpp
这里封装了 供js去调用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class SkpDebugPlayer { public: SkpDebugPlayer() : udm(UrlDataManager(SkString("/data"))){} 。。。。。。 }
类里面的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* loadSkp deserializes a skp file that has been copied into the shared WASM memory. * cptr - a pointer to the data to deserialize. * length - length of the data in bytes. * The caller must allocate the memory with M._malloc where M is the wasm module in javascript * and copy the data into M.buffer at the pointer returned by malloc. * * uintptr_t is used here because emscripten will not allow binding of functions with pointers * to primitive types. We can instead pass a number and cast it to whatever kind of * pointer we're expecting. * * Returns an error string which is populated in the case that the file cannot be read. */ std::string loadSkp(uintptr_t cptr, int length) { const uint8_t* data = reinterpret_cast<const uint8_t*>(cptr); // Both traditional and multi-frame skp files have a magic word SkMemoryStream stream(data, length); SkDebugf("make stream at %p, with %d bytes\n",data, length); const bool isMulti = memcmp(data, kMultiMagic, sizeof(kMultiMagic) - 1) == 0; if (isMulti) { SkDebugf("Try reading as a multi-frame skp\n"); const auto& error = loadMultiFrame(&stream); if (!error.empty()) { return error; } } else { SkDebugf("Try reading as single-frame skp\n"); // TODO(nifong): Rely on SkPicture's return errors once it provides some. frames.push_back(loadSingleFrame(&stream)); } return ""; }
js的调用:src\third_party\skia\experimental\wasm-skp-debugger\tests\startup.spec.js
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
it('can load and draw a skp file on a Web GL canvas', function(done) { LoadDebugger.then(catchException(done, () => { const surface = Debugger.MakeWebGLCanvasSurface( document.getElementById('debugger_view')); fetch('/debugger/sample.skp').then(function(response) { // Load test file if (!response.ok) { throw new Error("HTTP error, status = " + response.status); } response.arrayBuffer().then(function(buffer) { const fileContents = new Uint8Array(buffer); console.log('fetched /debugger/sample.skp'); const player = Debugger.SkpFilePlayer(fileContents); // Draw picture player.drawTo(surface, 789); // number of commands in sample file surface.flush(); console.log('drew picture to canvas element'); surface.dispose(); done(); }); }); })); });
third_party/skia/tools/debugger/DebugCanvas.h
画多个skp,背景透明问题
debugger_bindings.cpp中的代码:
/* drawTo asks the debug canvas to draw from the beginning of the picture * to the given command and flush the canvas. */ void drawTo(SkSurface* surface, int32_t index) { // Set the command within the frame or layer event being drawn. if (fInspectedLayer >= 0) { fLayerManager->setCommand(fInspectedLayer, fp, index); } else { index = constrainFrameCommand(index); } auto* canvas = surface->getCanvas(); canvas->clear(SK_ColorTRANSPARENT); if (fInspectedLayer >= 0) { // when it's a layer event we're viewing, we use the layer manager to render it. fLayerManager->drawLayerEventTo(surface, fInspectedLayer, fp); } else { // otherwise, its a frame at the top level. frames[fp]->drawTo(surface->getCanvas(), index); } surface->flush(); } // Draws to the end of the current frame. void draw(SkSurface* surface) { auto* canvas = surface->getCanvas(); canvas->clear(SK_ColorTRANSPARENT); frames[fp]->draw(surface->getCanvas()); surface->getCanvas()->flush(); }
/** Executes all draw calls to the canvas. @param canvas The canvas being drawn to */ void draw(SkCanvas* canvas); /** Executes the draw calls up to the specified index. Does not clear the canvas to transparent black first, if needed, caller should do that first. @param canvas The canvas being drawn to @param index The index of the final command being executed @param m an optional Mth gpu op to highlight, or -1 */ void drawTo(SkCanvas* canvas, int index, int m = -1);
对上面头文件的实现:third_party/skia/tools/debugger/DebugCanvas.cpp
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void DebugCanvas::drawTo(SkCanvas* originalCanvas, int index, int m) { SkASSERT(!fCommandVector.isEmpty()); SkASSERT(index < fCommandVector.count()); int saveCount = originalCanvas->save(); originalCanvas->resetMatrix(); SkCanvasPriv::ResetClip(originalCanvas); DebugPaintFilterCanvas filterCanvas(originalCanvas); SkCanvas* finalCanvas = fOverdrawViz ? &filterCanvas : originalCanvas; #if SK_GPU_V1 auto dContext = GrAsDirectContext(finalCanvas->recordingContext()); // If we have a GPU backend we can also visualize the op information GrAuditTrail* at = nullptr; if (fDrawGpuOpBounds || m != -1) { // The audit trail must be obtained from the original canvas. at = this->getAuditTrail(originalCanvas); } #endif for (int i = 0; i <= index; i++) { #if SK_GPU_V1 GrAuditTrail::AutoCollectOps* acb = nullptr; if (at) { // We need to flush any pending operations, or they might combine with commands below. // Previous operations were not registered with the audit trail when they were // created, so if we allow them to combine, the audit trail will fail to find them. if (dContext) { dContext->flush(); } acb = new GrAuditTrail::AutoCollectOps(at, i); } #endif if (fCommandVector[i]->isVisible()) { fCommandVector[i]->execute(finalCanvas); } #if SK_GPU_V1 if (at && acb) { delete acb; } #endif } if (SkColorGetA(fClipVizColor) != 0) { finalCanvas->save(); SkPaint clipPaint; clipPaint.setColor(fClipVizColor); finalCanvas->drawPaint(clipPaint); finalCanvas->restore(); } fMatrix = finalCanvas->getLocalToDevice(); fClip = finalCanvas->getDeviceClipBounds(); if (fShowOrigin) { const SkPaint originXPaint = SkPaint({1.0, 0, 0, 1.0}); const SkPaint originYPaint = SkPaint({0, 1.0, 0, 1.0}); // Draw an origin cross at the origin before restoring to assist in visualizing the // current matrix. drawArrow(finalCanvas, {-50, 0}, {50, 0}, originXPaint); drawArrow(finalCanvas, {0, -50}, {0, 50}, originYPaint); } finalCanvas->restoreToCount(saveCount); if (fShowAndroidClip) { // Draw visualization of android device clip restriction SkPaint androidClipPaint; androidClipPaint.setARGB(80, 255, 100, 0); finalCanvas->drawRect(fAndroidClip, androidClipPaint); } #if SK_GPU_V1 // draw any ops if required and issue a full reset onto GrAuditTrail if (at) { // just in case there is global reordering, we flush the canvas before querying // GrAuditTrail GrAuditTrail::AutoEnable ae(at); if (dContext) { dContext->flush(); } // we pick three colorblind-safe colors, 75% alpha static const SkColor kTotalBounds = SkColorSetARGB(0xC0, 0x6A, 0x3D, 0x9A); static const SkColor kCommandOpBounds = SkColorSetARGB(0xC0, 0xE3, 0x1A, 0x1C); static const SkColor kOtherOpBounds = SkColorSetARGB(0xC0, 0xFF, 0x7F, 0x00); // get the render target of the top device (from the original canvas) so we can ignore ops // drawn offscreen GrRenderTargetProxy* rtp = SkCanvasPriv::TopDeviceTargetProxy(originalCanvas); GrSurfaceProxy::UniqueID proxyID = rtp->uniqueID(); // get the bounding boxes to draw SkTArray<GrAuditTrail::OpInfo> childrenBounds; if (m == -1) { at->getBoundsByClientID(&childrenBounds, index); } else { // the client wants us to draw the mth op at->getBoundsByOpsTaskID(&childrenBounds.push_back(), m); } // Shift the rects half a pixel, so they appear as exactly 1px thick lines. finalCanvas->save(); finalCanvas->translate(0.5, -0.5); SkPaint paint; paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(1); for (int i = 0; i < childrenBounds.count(); i++) { if (childrenBounds[i].fProxyUniqueID != proxyID) { // offscreen draw, ignore for now continue; } paint.setColor(kTotalBounds); finalCanvas->drawRect(childrenBounds[i].fBounds, paint); for (int j = 0; j < childrenBounds[i].fOps.count(); j++) { const GrAuditTrail::OpInfo::Op& op = childrenBounds[i].fOps[j]; if (op.fClientID != index) { paint.setColor(kOtherOpBounds); } else { paint.setColor(kCommandOpBounds); } finalCanvas->drawRect(op.fBounds, paint); } } finalCanvas->restore(); this->cleanupAuditTrail(at); } #endif }
third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * Options for configuring a WebGL context. If an option is omitted, a sensible default will * be used. These are defined by the WebGL standards. */ export interface WebGLOptions { alpha?: number; antialias?: number; depth?: number; enableExtensionsByDefault?: number; explicitSwapControl?: number; failIfMajorPerformanceCaveat?: number; majorVersion?: number; minorVersion?: number; preferLowPowerToHighPerformance?: number; premultipliedAlpha?: number; preserveDrawingBuffer?: number; renderViaOffscreenBackBuffer?: number; stencil?: number; }
surface 与 canvas示例:
C:\dev\skia_source\modules\canvaskit\npm_build\multicanvas.html
C:\dev\skia_source\modules\canvaskit\npm_build\types\canvaskit-wasm-tests.ts
一个 surfaceTests js代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
function surfaceTests(CK: CanvasKit, gl?: WebGLRenderingContext) { if (!gl) { return; } const canvasEl = document.querySelector('canvas') as HTMLCanvasElement; const surfaceOne = CK.MakeCanvasSurface(canvasEl)!; // $ExpectType Surface const surfaceTwo = CK.MakeCanvasSurface('my_canvas')!; const surfaceThree = CK.MakeSWCanvasSurface(canvasEl)!; // $ExpectType Surface const surfaceFour = CK.MakeSWCanvasSurface('my_canvas')!; const surfaceFive = CK.MakeWebGLCanvasSurface(canvasEl, // $ExpectType Surface CK.ColorSpace.SRGB, { majorVersion: 2, preferLowPowerToHighPerformance: 1, })!; const surfaceSix = CK.MakeWebGLCanvasSurface('my_canvas', CK.ColorSpace.DISPLAY_P3, { enableExtensionsByDefault: 2, })!; const surfaceSeven = CK.MakeSurface(200, 200)!; // $ExpectType Surface const m = CK.Malloc(Uint8Array, 5 * 5 * 4); const surfaceEight = CK.MakeRasterDirectSurface({ 5, height: 5, colorType: CK.ColorType.RGBA_8888, alphaType: CK.AlphaType.Premul, colorSpace: CK.ColorSpace.SRGB, }, m, 20); surfaceOne.flush(); const canvas = surfaceTwo.getCanvas(); // $ExpectType Canvas const ii = surfaceThree.imageInfo(); // $ExpectType ImageInfo const h = surfaceFour.height(); // $ExpectType number const w = surfaceFive.width(); // $ExpectType number const subsurface = surfaceOne.makeSurface(ii); // $ExpectType Surface const isGPU = subsurface.reportBackendTypeIsGPU(); // $ExpectType boolean const count = surfaceThree.sampleCnt(); // $ExpectType number const img = surfaceFour.makeImageSnapshot([0, 3, 2, 5]); // $ExpectType Image const img2 = surfaceSix.makeImageSnapshot(); // $ExpectType Image const img3 = surfaceFour.makeImageFromTexture(gl.createTexture()!, { height: 40, 80, colorType: CK.ColorType.RGBA_8888, alphaType: CK.AlphaType.Unpremul, colorSpace: CK.ColorSpace.SRGB, }); const img4 = surfaceFour.makeImageFromTextureSource(new Image()); // $ExpectType Image | null const videoEle = document.createElement('video'); const img5 = surfaceFour.makeImageFromTextureSource(videoEle, { height: 40, 80, colorType: CK.ColorType.RGBA_8888, alphaType: CK.AlphaType.Unpremul, }); const img6 = surfaceFour.makeImageFromTextureSource(new ImageData(40, 80)); // $ExpectType Image | null surfaceSeven.delete(); const ctx = CK.GetWebGLContext(canvasEl); // $ExpectType number CK.deleteContext(ctx); const grCtx = CK.MakeGrContext(ctx); const surfaceNine = CK.MakeOnScreenGLSurface(grCtx!, 100, 400, // $ExpectType Surface CK.ColorSpace.ADOBE_RGB)!; const rt = CK.MakeRenderTarget(grCtx!, 100, 200); // $ExpectType Surface | null const rt2 = CK.MakeRenderTarget(grCtx!, { // $ExpectType Surface | null 79, height: 205, colorType: CK.ColorType.RGBA_8888, alphaType: CK.AlphaType.Premul, colorSpace: CK.ColorSpace.SRGB, }); const drawFrame = (canvas: Canvas) => { canvas.clear([0, 0, 0, 0]); }; surfaceFour.requestAnimationFrame(drawFrame); surfaceFour.drawOnce(drawFrame); }
示例,在canvas上做个imageInfo,获取到改变尺寸的surface,看看它是不是gpu。c++
void draw(SkCanvas* canvas) { sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(5, 6); SkCanvas* smallCanvas = surface->getCanvas(); SkImageInfo imageInfo = SkImageInfo::MakeN32Premul(10, 14); sk_sp<SkSurface> compatible = smallCanvas->makeSurface(imageInfo); SkDebugf("compatible %c= nullptr\n", compatible == nullptr ? '=' : '!'); SkDebugf("size = %d, %d\n", compatible->width(), compatible->height());
???js: const isGPU = subsurface.reportBackendTypeIsGPU(); // $ExpectType boolean
}
一个给canvaskit 报告的bug 写的测试示例,这个bug已经关闭:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
let htmlCanvas; let skCanvas; let skSurface; const paint = new CanvasKit.Paint(); function getCanvasLayer(w,h) { htmlCanvas = document.getElementById("canvas"); console.log("Canvas class: %s", htmlCanvas.constructor.name); htmlCanvas.height = h; htmlCanvas.width = w; } function prepareSurface(w, h) { if (skSurface && !skSurface.isDeleted()) { skSurface.dispose(); console.log('Disposed surface'); } const context = htmlCanvas.getContext("2d"); skSurface = CanvasKit.MakeWebGLCanvasSurface(htmlCanvas); if (!skSurface) { console.log('Failed to make surface'); } } function drawOffscreenCanvas(skps, w,h) { let picture = CanvasKit.MakePicture(skps); skCanvas = skSurface.getCanvas(); skCanvas.save(); skCanvas.drawPicture(picture); skCanvas.restore(); picture.delete(); } function flushOffscreenCanvas(w,h) { skSurface.flush(); // Here is something interesting, remove line 19 and call line 20, after MakeWebGLCanvasSurface(htmlCanvas) // htmlCanvas.getContext("2d") returns null context. // htmlCanvas.getContext("webgl") returns null context. // htmlCanvas.getContext("webgl2") return a valid WebGL2RenderingContext. // Now if we move this getContext before MakeWebGLCanvasSurface, all 3 // context "2d", "webgl" and "webgl2" return a valid context but // MakeWebGLCanvasSurface will throw an error: // Uncaught (in promise) TypeError: Cannot read property 'version' of undefined //const context = htmlCanvas.getContext("webgl"); //console.log("Context class: %s", context.constructor.name); } function drawFrame(skps) { const canvasLayerWidth = 3000; const canvasLayerHeight = 3000; const w = 1000; const h = 1000; getCanvasLayer(canvasLayerWidth,canvasLayerHeight); prepareSurface(w,h); drawOffscreenCanvas(skps,w,h); flushOffscreenCanvas(w,h); } fetch('url', { 'mode': 'cors' }) .then(response => response.blob()) .then(blob => blob.arrayBuffer()) .then(skpic => { console.log(skpic) drawFrame(skpic); });
js canvas 动画示例:都是官方网站的:
Canvas api:包括transform,video:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Manipulating_video_using_canvas
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations
3d迷宫移动:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/A_basic_ray-caster
带球:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Advanced_animations