forked from google/skia
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[experimental] Add WebGPU demo (built with Bazel)
This uses the Bazel rule wasm_cc_binary, which is defined in @emsdk [1] Note that wasm_cc_binary does not have a linkopts argument defined, so we instead put any emcc options in the cc_binary target. This works around a few bugs in the emsdk Bazel rules: - emscripten-core/emsdk#907 - emscripten-core/emsdk#807 Prior to PS 5, this CL tried a different way to bring in the toolchain, a more manual way outlined in [2]. A similar approach (modifying the .bazelrc and specifying the toolchain directly) might be necessary at some point, but can probably still be done using the @emsdk Bazel rules and --config=wasm. To update the version of emscripten used, we just need to update the parameter in the WORKSPACE call to emsdk_emscripten_deps(). The example/index.html file in this CL does exactly the same as [3], except the WebGPU calls are made from C++ via WASM. I made heavy use of these examples [4], [5] while exploring APIs. What was also useful was looking at the emscripten source headers [6], [7], [8], [9]. I also learned a lot about WebGPU from [10]. [1] https://github.com/emscripten-core/emsdk/blob/3891e7b04bf8cbb3bc62758e9c575ae096a9a518/bazel/emscripten_toolchain/wasm_cc_binary.bzl [2] https://hackernoon.com/c-to-webassembly-using-bazel-and-emscripten-4him3ymc [3] https://github.com/google/skia/blob/206c1f3f7e01b6d7fd2d3aab13ed719ff39d02e4/demos.skia.org/demos/webgpu/index.html [4] https://github.com/kainino0x/webgpu-cross-platform-demo [5] https://github.com/Twinklebear/wgpu-cpp-starter [6] https://github.com/emscripten-core/emscripten/blob/5e6c74153b85fdb3ee3bf1f339d620f4e7ddf705/system/include/emscripten/html5_webgpu.h [7] https://github.com/emscripten-core/emscripten/blob/5e6c74153b85fdb3ee3bf1f339d620f4e7ddf705/system/include/webgpu/webgpu.h [8] https://github.com/emscripten-core/emscripten/blob/5e6c74153b85fdb3ee3bf1f339d620f4e7ddf705/system/include/webgpu/webgpu_cpp.h [9] https://github.com/emscripten-core/emscripten/blob/5e6c74153b85fdb3ee3bf1f339d620f4e7ddf705/src/library_html5_webgpu.js#L24 [10] https://alain.xyz/blog/raw-webgpu Change-Id: Iff33b72e7265200b2caacbc03e5fcc06a650b56b Reviewed-on: https://skia-review.googlesource.com/c/skia/+/457396 Reviewed-by: Leandro Lovisolo <[email protected]> Reviewed-by: Brian Salomon <[email protected]>
- Loading branch information
Showing
6 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
bazel-bazel-webgpu | ||
bazel-bin | ||
bazel-out | ||
bazel-testlogs | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
release: | ||
bazel build //src:hello-world-wasm --compilation_mode opt | ||
- rm -rf build/ | ||
mkdir build | ||
cp bazel-bin/src/hello-world-wasm/hello-world.js build/hello-world.js | ||
cp bazel-bin/src/hello-world-wasm/hello-world.wasm build/hello-world.wasm | ||
|
||
debug: | ||
bazel build //src:hello-world-wasm --compilation_mode dbg | ||
- rm -rf build/ | ||
mkdir build | ||
cp bazel-bin/src/hello-world-wasm/hello-world.js build/hello-world.js | ||
cp bazel-bin/src/hello-world-wasm/hello-world.wasm build/hello-world.wasm | ||
|
||
serve: | ||
python3 ../../tools/serve_wasm.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
workspace(name = "bazel_webgpu_example") | ||
|
||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") | ||
|
||
# Loading in the emscripten toolchain is documented at https://github.com/emscripten-core/emsdk/tree/3891e7b04bf8cbb3bc62758e9c575ae096a9a518/bazel | ||
# The hash in the http_archive URL corresponds to https://github.com/emscripten-core/emsdk/commit/3891e7b04bf8cbb3bc62758e9c575ae096a9a518 | ||
# AKA the 2.0.31 release. The sha256 sum came from a manual inspection of that archive. | ||
http_archive( | ||
name = "emsdk", | ||
sha256 = "d55e3c73fc4f8d1fecb7aabe548de86bdb55080fe6b12ce593d63b8bade54567", | ||
strip_prefix = "emsdk-3891e7b04bf8cbb3bc62758e9c575ae096a9a518/bazel", | ||
url = "https://github.com/emscripten-core/emsdk/archive/3891e7b04bf8cbb3bc62758e9c575ae096a9a518.tar.gz", | ||
) | ||
|
||
# Working around https://github.com/emscripten-core/emsdk/issues/907 | ||
http_archive( | ||
name = "build_bazel_rules_nodejs", | ||
sha256 = "3635797a96c7bfcd0d265dacd722a07335e64d6ded9834af8d3f1b7ba5a25bba", | ||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/4.3.0/rules_nodejs-4.3.0.tar.gz"], | ||
) | ||
# Once the workaround is no longer needed, we should be able to uncomment below | ||
# load("@emsdk//:deps.bzl", emsdk_deps = "deps") | ||
# emsdk_deps() | ||
|
||
load("@emsdk//:emscripten_deps.bzl", emsdk_emscripten_deps = "emscripten_deps") | ||
|
||
emsdk_emscripten_deps(emscripten_version = "2.0.31") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
<!DOCTYPE html> | ||
<title>Testing WebGPU compiled with Bazel</title> | ||
<meta charset="utf-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
|
||
<script type="text/javascript" src="/build/hello-world.js"></script> | ||
|
||
<p id="log"></p> | ||
|
||
<canvas id="webgpu-demo-canvas" width=500 height=500></canvas> | ||
|
||
<script type="text/javascript" charset="utf-8"> | ||
if ("gpu" in navigator) { | ||
log("WebGPU detected") | ||
WebGPUDemo(); | ||
} else { | ||
log("No WebGPU support.") | ||
} | ||
|
||
function log(s) { | ||
document.getElementById("log").innerText = s; | ||
} | ||
|
||
async function WebGPUDemo() { | ||
const adapter = await navigator.gpu.requestAdapter(); | ||
if (!adapter) { | ||
log("Could not load an adapter. For Chrome, try running with --enable-features=Vulkan --enable-unsafe-webgpu"); | ||
return; | ||
} | ||
const device = await adapter.requestDevice(); | ||
console.log(adapter, device); | ||
|
||
const wk = await WebGPUKitInit({locateFile: (file) => '/build/'+file}); | ||
// https://github.com/emscripten-core/emscripten/issues/12750#issuecomment-725001907 | ||
wk.preinitializedWebGPUDevice = device; | ||
|
||
const surface = new wk.WebGPUSurface("#webgpu-demo-canvas", 500, 500); | ||
|
||
const triangleVertexShader = surface.MakeShader(`[[stage(vertex)]] | ||
fn main([[builtin(vertex_index)]] VertexIndex : u32) | ||
-> [[builtin(position)]] vec4<f32> { | ||
var pos = array<vec2<f32>, 3>( | ||
vec2<f32>(0.0, 0.5), | ||
vec2<f32>(-0.5, -0.5), | ||
vec2<f32>(0.5, -0.5)); | ||
return vec4<f32>(pos[VertexIndex], 0.0, 1.0); | ||
}`); | ||
|
||
const redFragmentShader = surface.MakeShader(`[[stage(fragment)]] | ||
fn main() -> [[location(0)]] vec4<f32> { | ||
return vec4<f32>(1.0, 0.0, 0.0, 1.0); | ||
}`); | ||
|
||
const pipeline = surface.MakeRenderPipeline(triangleVertexShader, redFragmentShader); | ||
|
||
const startTime = Date.now(); | ||
function frame() { | ||
const now = Date.now(); | ||
surface.drawPipeline(pipeline, | ||
Math.abs(Math.sin((startTime - now) / 500)), // red | ||
Math.abs(Math.sin((startTime - now) / 600)), // green | ||
Math.abs(Math.sin((startTime - now) / 700)), // blue | ||
1.0); | ||
requestAnimationFrame(frame); | ||
} | ||
requestAnimationFrame(frame); | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
load("@rules_cc//cc:defs.bzl", "cc_binary") | ||
load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary") | ||
|
||
BASE_LINKOPTS = [ | ||
#"-flto", # https://github.com/emscripten-core/emsdk/issues/807 | ||
"--bind", # Compiles the source code using the Embind bindings to connect C/C++ and JavaScript | ||
"-s ALLOW_MEMORY_GROWTH=1", | ||
"-s USE_PTHREADS=0", # Disable pthreads | ||
"-s ASSERTIONS=0", # Turn off assertions | ||
"-s MODULARIZE=1", | ||
"-s EXPORT_NAME=WebGPUKitInit", | ||
"-s DISABLE_EXCEPTION_CATCHING=1", # Disable all exception catching | ||
"-s NODEJS_CATCH_EXIT=0", # We don't have a 'main' so disable exit() catching | ||
"-s WASM=1", | ||
"-s USE_WEBGPU=1", | ||
] | ||
|
||
RELEASE_OPTS = [ | ||
"--closure 1", # Run the closure compiler | ||
] | ||
|
||
DEBUG_OPTS = [ | ||
"--closure 0", # Do not use closure | ||
] | ||
|
||
config_setting( | ||
name = "release_opts", | ||
values = {"compilation_mode": "opt"}, | ||
) | ||
|
||
config_setting( | ||
name = "debug_opts", | ||
values = {"compilation_mode": "dbg"}, | ||
) | ||
|
||
cc_binary( | ||
name = "hello-world", | ||
srcs = ["bindings.cpp"], | ||
linkopts = select({ | ||
":debug_opts": BASE_LINKOPTS + DEBUG_OPTS, | ||
":release_opts": BASE_LINKOPTS + RELEASE_OPTS, | ||
"//conditions:default": BASE_LINKOPTS + RELEASE_OPTS, | ||
}), | ||
# This target won't build successfully on its own because of missing emscripten | ||
# headers etc. Therefore, we hide it from wildcards. | ||
tags = ["manual"], | ||
) | ||
|
||
wasm_cc_binary( | ||
name = "hello-world-wasm", | ||
cc_target = ":hello-world", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
* Copyright 2021 Google LLC | ||
* | ||
* Use of this source code is governed by a BSD-style license that can be | ||
* found in the LICENSE file. | ||
*/ | ||
#include <emscripten/bind.h> | ||
#include <emscripten/emscripten.h> | ||
#include <emscripten/html5.h> | ||
// https://github.com/emscripten-core/emscripten/blob/main/system/include/emscripten/html5_webgpu.h | ||
// The import/export functions defined here should allow us to fetch a handle to a given JS | ||
// Texture/Sampler/Device etc if needed. | ||
#include <emscripten/html5_webgpu.h> | ||
// https://github.com/emscripten-core/emscripten/blob/main/system/include/webgpu/webgpu.h | ||
// This defines WebGPU constants and such. It also includes a lot of typedefs that make something | ||
// like WGPUDevice defined as a pointer to something external. These "pointers" are actually just | ||
// a small integer that refers to an array index of JS objects being held by a "manager" | ||
// https://github.com/emscripten-core/emscripten/blob/f47bef371f3464471c6d30b631cffcdd06ced004/src/library_webgpu.js#L192 | ||
#include <webgpu/webgpu.h> | ||
// https://github.com/emscripten-core/emscripten/blob/main/system/include/webgpu/webgpu_cpp.h | ||
// This defines the C++ equivalents to the JS WebGPU API. | ||
#include <webgpu/webgpu_cpp.h> | ||
|
||
using namespace emscripten; | ||
|
||
wgpu::ShaderModule createShaderModule(wgpu::Device device, const char* source) { | ||
// https://github.com/emscripten-core/emscripten/blob/da842597941f425e92df0b902d3af53f1bcc2713/system/include/webgpu/webgpu_cpp.h#L1415 | ||
wgpu::ShaderModuleWGSLDescriptor wDesc; | ||
wDesc.source = source; | ||
wgpu::ShaderModuleDescriptor desc = {.nextInChain = &wDesc}; | ||
return device.CreateShaderModule(&desc); | ||
} | ||
|
||
wgpu::RenderPipeline createRenderPipeline(wgpu::Device device, wgpu::ShaderModule vertexShader, | ||
wgpu::ShaderModule fragmentShader) { | ||
wgpu::ColorTargetState colorTargetState{}; | ||
colorTargetState.format = wgpu::TextureFormat::BGRA8Unorm; | ||
|
||
wgpu::FragmentState fragmentState{}; | ||
fragmentState.module = fragmentShader; | ||
fragmentState.entryPoint = "main"; // assumes main() is defined in fragment shader code | ||
fragmentState.targetCount = 1; | ||
fragmentState.targets = &colorTargetState; | ||
|
||
wgpu::PipelineLayoutDescriptor pl{}; | ||
|
||
// Inspired by https://github.com/kainino0x/webgpu-cross-platform-demo/blob/4061dd13096580eb5525619714145087b0d5acf6/main.cpp#L129 | ||
wgpu::RenderPipelineDescriptor pipelineDescriptor{}; | ||
pipelineDescriptor.layout = device.CreatePipelineLayout(&pl); | ||
pipelineDescriptor.vertex.module = vertexShader; | ||
pipelineDescriptor.vertex.entryPoint = "main"; // assumes main() is defined in vertex code | ||
pipelineDescriptor.fragment = &fragmentState; | ||
pipelineDescriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList; | ||
return device.CreateRenderPipeline(&pipelineDescriptor); | ||
} | ||
|
||
wgpu::SwapChain getSwapChainForCanvas(wgpu::Device device, std::string canvasSelector, int width, int height) { | ||
wgpu::SurfaceDescriptorFromCanvasHTMLSelector surfaceSelector; | ||
surfaceSelector.selector = canvasSelector.c_str(); | ||
|
||
wgpu::SurfaceDescriptor surface_desc; | ||
surface_desc.nextInChain = &surfaceSelector; | ||
wgpu::Instance instance; | ||
wgpu::Surface surface = instance.CreateSurface(&surface_desc); | ||
|
||
wgpu::SwapChainDescriptor swap_chain_desc; | ||
swap_chain_desc.format = wgpu::TextureFormat::BGRA8Unorm; | ||
swap_chain_desc.usage = wgpu::TextureUsage::RenderAttachment; | ||
swap_chain_desc.presentMode = wgpu::PresentMode::Fifo; | ||
swap_chain_desc.width = width; | ||
swap_chain_desc.height = height; | ||
return device.CreateSwapChain(surface, &swap_chain_desc); | ||
} | ||
|
||
void drawPipeline(wgpu::Device device, wgpu::TextureView view, wgpu::RenderPipeline pipeline, | ||
wgpu::Color clearColor) { | ||
wgpu::RenderPassColorAttachment attachment{}; | ||
attachment.view = view; | ||
attachment.loadOp = wgpu::LoadOp::Clear; | ||
attachment.storeOp = wgpu::StoreOp::Store; | ||
attachment.clearColor = clearColor; | ||
|
||
wgpu::RenderPassDescriptor renderpass{}; | ||
renderpass.colorAttachmentCount = 1; | ||
renderpass.colorAttachments = &attachment; | ||
wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | ||
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass); | ||
pass.SetPipeline(pipeline); | ||
pass.Draw(3, // vertexCount | ||
1, // instanceCount | ||
0, // firstIndex | ||
0 // firstInstance | ||
); | ||
pass.EndPass(); | ||
wgpu::CommandBuffer commands = encoder.Finish(); | ||
device.GetQueue().Submit(1, &commands); | ||
} | ||
|
||
class WebGPUSurface { | ||
public: | ||
WebGPUSurface(std::string canvasSelector, int width, int height) { | ||
fDevice = wgpu::Device::Acquire(emscripten_webgpu_get_device()); | ||
fCanvasSwap = getSwapChainForCanvas(fDevice, canvasSelector, width, height); | ||
} | ||
|
||
wgpu::ShaderModule makeShader(std::string source) { | ||
return createShaderModule(fDevice, source.c_str()); | ||
} | ||
|
||
wgpu::RenderPipeline makeRenderPipeline(wgpu::ShaderModule vertexShader, | ||
wgpu::ShaderModule fragmentShader) { | ||
return createRenderPipeline(fDevice, vertexShader, fragmentShader); | ||
} | ||
|
||
void drawPipeline(wgpu::RenderPipeline pipeline, float r, float g, float b, float a) { | ||
// We cannot cache the TextureView because it will be destroyed after use. | ||
::drawPipeline(fDevice, fCanvasSwap.GetCurrentTextureView(), pipeline, {r, g, b, a}); | ||
} | ||
|
||
private: | ||
wgpu::Device fDevice; | ||
wgpu::SwapChain fCanvasSwap; | ||
}; | ||
|
||
EMSCRIPTEN_BINDINGS(Skia) { | ||
class_<WebGPUSurface>("WebGPUSurface") | ||
.constructor<std::string, int, int>() | ||
.function("MakeShader", &WebGPUSurface::makeShader) | ||
.function("MakeRenderPipeline", &WebGPUSurface::makeRenderPipeline) | ||
.function("drawPipeline", &WebGPUSurface::drawPipeline); | ||
|
||
class_<wgpu::ShaderModule>("ShaderModule"); | ||
class_<wgpu::RenderPipeline>("RenderPipeline"); | ||
} |