blob: f2ee61c38c6616fa8e805381f142a67891ca9708 [file] [log] [blame]
// Copyright 2021 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.
// Based on https://github.com/kainino0x/webgpu-cross-platform-demo
#include <webgpu/webgpu_cpp.h>
#undef NDEBUG
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/html5_webgpu.h>
static const wgpu::Instance instance = wgpuCreateInstance(nullptr);
void GetDevice(void (*callback)(wgpu::Device)) {
instance.RequestAdapter(nullptr, [](WGPURequestAdapterStatus status, WGPUAdapter cAdapter, const char* message, void* userdata) {
if (message) {
printf("RequestAdapter: %s\n", message);
}
assert(status == WGPURequestAdapterStatus_Success);
wgpu::Adapter adapter = wgpu::Adapter::Acquire(cAdapter);
adapter.RequestDevice(nullptr, [](WGPURequestDeviceStatus status, WGPUDevice cDevice, const char* message, void* userdata) {
if (message) {
printf("RequestDevice: %s\n", message);
}
assert(status == WGPURequestDeviceStatus_Success);
wgpu::Device device = wgpu::Device::Acquire(cDevice);
reinterpret_cast<void (*)(wgpu::Device)>(userdata)(device);
}, userdata);
}, reinterpret_cast<void*>(callback));
}
static const char shaderCode[] = R"(
@vertex
fn main_v(@builtin(vertex_index) idx: 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[idx], 0.0, 1.0);
}
@fragment
fn main_f() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 0.502, 1.0, 1.0); // 0x80/0xff ~= 0.502
}
)";
static wgpu::Device device;
static wgpu::Queue queue;
static wgpu::Buffer readbackBuffer;
static wgpu::RenderPipeline pipeline;
static int testsCompleted = 0;
void init() {
device.SetUncapturedErrorCallback(
[](WGPUErrorType errorType, const char* message, void*) {
printf("%d: %s\n", errorType, message);
}, nullptr);
queue = device.GetQueue();
wgpu::ShaderModule shaderModule{};
{
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
wgslDesc.code = shaderCode;
wgpu::ShaderModuleDescriptor descriptor{};
descriptor.nextInChain = &wgslDesc;
shaderModule = device.CreateShaderModule(&descriptor);
}
{
wgpu::BindGroupLayoutDescriptor bglDesc{};
auto bgl = device.CreateBindGroupLayout(&bglDesc);
wgpu::BindGroupDescriptor desc{};
desc.layout = bgl;
desc.entryCount = 0;
desc.entries = nullptr;
device.CreateBindGroup(&desc);
}
{
wgpu::PipelineLayoutDescriptor pl{};
pl.bindGroupLayoutCount = 0;
pl.bindGroupLayouts = nullptr;
wgpu::ColorTargetState colorTargetState{};
colorTargetState.format = wgpu::TextureFormat::BGRA8Unorm;
wgpu::FragmentState fragmentState{};
fragmentState.module = shaderModule;
fragmentState.entryPoint = "main_f";
fragmentState.targetCount = 1;
fragmentState.targets = &colorTargetState;
wgpu::DepthStencilState depthStencilState{};
depthStencilState.format = wgpu::TextureFormat::Depth32Float;
depthStencilState.depthCompare = wgpu::CompareFunction::Always;
wgpu::RenderPipelineDescriptor descriptor{};
descriptor.layout = device.CreatePipelineLayout(&pl);
descriptor.vertex.module = shaderModule;
descriptor.vertex.entryPoint = "main_v";
descriptor.fragment = &fragmentState;
descriptor.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
descriptor.depthStencil = &depthStencilState;
pipeline = device.CreateRenderPipeline(&descriptor);
}
}
// The depth stencil attachment isn't really needed to draw the triangle
// and doesn't really affect the render result.
// But having one should give us a slightly better test coverage for the compile of the depth stencil descriptor.
void render(wgpu::TextureView view, wgpu::TextureView depthStencilView) {
wgpu::RenderPassColorAttachment attachment{};
attachment.view = view;
attachment.loadOp = wgpu::LoadOp::Clear;
attachment.storeOp = wgpu::StoreOp::Store;
attachment.clearValue = {0, 0, 0, 1};
wgpu::RenderPassDescriptor renderpass{};
renderpass.colorAttachmentCount = 1;
renderpass.colorAttachments = &attachment;
wgpu::RenderPassDepthStencilAttachment depthStencilAttachment = {};
depthStencilAttachment.view = depthStencilView;
depthStencilAttachment.depthClearValue = 0;
depthStencilAttachment.depthLoadOp = wgpu::LoadOp::Clear;
depthStencilAttachment.depthStoreOp = wgpu::StoreOp::Store;
renderpass.depthStencilAttachment = &depthStencilAttachment;
wgpu::CommandBuffer commands;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderpass);
pass.SetPipeline(pipeline);
pass.Draw(3);
pass.End();
}
commands = encoder.Finish();
}
queue.Submit(1, &commands);
}
void issueContentsCheck(const char* functionName,
wgpu::Buffer readbackBuffer, uint32_t expectData) {
struct UserData {
const char* functionName;
wgpu::Buffer readbackBuffer;
uint32_t expectData;
};
UserData* userdata = new UserData;
userdata->functionName = functionName;
userdata->readbackBuffer = readbackBuffer;
userdata->expectData = expectData;
readbackBuffer.MapAsync(
wgpu::MapMode::Read, 0, 4,
[](WGPUBufferMapAsyncStatus status, void* vp_userdata) {
assert(status == WGPUBufferMapAsyncStatus_Success);
std::unique_ptr<UserData> userdata(reinterpret_cast<UserData*>(vp_userdata));
const void* ptr = userdata->readbackBuffer.GetConstMappedRange();
printf("%s: readback -> %p%s\n", userdata->functionName,
ptr, ptr ? "" : " <------- FAILED");
assert(ptr != nullptr);
uint32_t readback = static_cast<const uint32_t*>(ptr)[0];
userdata->readbackBuffer.Unmap();
printf(" got %08x, expected %08x%s\n",
readback, userdata->expectData,
readback == userdata->expectData ? "" : " <------- FAILED");
testsCompleted++;
}, userdata);
}
void doCopyTestMappedAtCreation(bool useRange) {
static constexpr uint32_t kValue = 0x05060708;
size_t size = useRange ? 12 : 4;
wgpu::Buffer src;
{
wgpu::BufferDescriptor descriptor{};
descriptor.size = size;
descriptor.usage = wgpu::BufferUsage::CopySrc;
descriptor.mappedAtCreation = true;
src = device.CreateBuffer(&descriptor);
}
size_t offset = useRange ? 8 : 0;
uint32_t* ptr = static_cast<uint32_t*>(useRange ?
src.GetMappedRange(offset, 4) :
src.GetMappedRange());
printf("%s: getMappedRange -> %p%s\n", __FUNCTION__,
ptr, ptr ? "" : " <------- FAILED");
assert(ptr != nullptr);
*ptr = kValue;
src.Unmap();
wgpu::Buffer dst;
{
wgpu::BufferDescriptor descriptor{};
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
dst = device.CreateBuffer(&descriptor);
}
wgpu::CommandBuffer commands;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(src, offset, dst, 0, 4);
commands = encoder.Finish();
}
queue.Submit(1, &commands);
issueContentsCheck(__FUNCTION__, dst, kValue);
}
void doCopyTestMapAsync(bool useRange) {
static constexpr uint32_t kValue = 0x01020304;
size_t size = useRange ? 12 : 4;
wgpu::Buffer src;
{
wgpu::BufferDescriptor descriptor{};
descriptor.size = size;
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc;
src = device.CreateBuffer(&descriptor);
}
size_t offset = useRange ? 8 : 0;
struct UserData {
const char* functionName;
bool useRange;
size_t offset;
wgpu::Buffer src;
};
UserData* userdata = new UserData;
userdata->functionName = __FUNCTION__;
userdata->useRange = useRange;
userdata->offset = offset;
userdata->src = src;
src.MapAsync(wgpu::MapMode::Write, offset, 4,
[](WGPUBufferMapAsyncStatus status, void* vp_userdata) {
assert(status == WGPUBufferMapAsyncStatus_Success);
std::unique_ptr<UserData> userdata(reinterpret_cast<UserData*>(vp_userdata));
uint32_t* ptr = static_cast<uint32_t*>(userdata->useRange ?
userdata->src.GetMappedRange(userdata->offset, 4) :
userdata->src.GetMappedRange());
printf("%s: getMappedRange -> %p%s\n", userdata->functionName,
ptr, ptr ? "" : " <------- FAILED");
assert(ptr != nullptr);
*ptr = kValue;
userdata->src.Unmap();
wgpu::Buffer dst;
{
wgpu::BufferDescriptor descriptor{};
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
dst = device.CreateBuffer(&descriptor);
}
wgpu::CommandBuffer commands;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyBufferToBuffer(userdata->src, userdata->offset, dst, 0, 4);
commands = encoder.Finish();
}
queue.Submit(1, &commands);
issueContentsCheck(userdata->functionName, dst, kValue);
}, userdata);
}
void doRenderTest() {
wgpu::Texture readbackTexture;
{
wgpu::TextureDescriptor descriptor{};
descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
descriptor.size = {1, 1, 1};
descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
readbackTexture = device.CreateTexture(&descriptor);
}
wgpu::Texture depthTexture;
{
wgpu::TextureDescriptor descriptor{};
descriptor.usage = wgpu::TextureUsage::RenderAttachment;
descriptor.size = {1, 1, 1};
descriptor.format = wgpu::TextureFormat::Depth32Float;
depthTexture = device.CreateTexture(&descriptor);
}
render(readbackTexture.CreateView(), depthTexture.CreateView());
{
// A little texture.GetFormat test
assert(wgpu::TextureFormat::BGRA8Unorm == readbackTexture.GetFormat());
assert(wgpu::TextureFormat::Depth32Float == depthTexture.GetFormat());
}
{
wgpu::BufferDescriptor descriptor{};
descriptor.size = 4;
descriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
readbackBuffer = device.CreateBuffer(&descriptor);
}
wgpu::CommandBuffer commands;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ImageCopyTexture src{};
src.texture = readbackTexture;
src.origin = {0, 0, 0};
wgpu::ImageCopyBuffer dst{};
dst.buffer = readbackBuffer;
dst.layout.bytesPerRow = 256;
wgpu::Extent3D extent = {1, 1, 1};
encoder.CopyTextureToBuffer(&src, &dst, &extent);
commands = encoder.Finish();
}
queue.Submit(1, &commands);
// Check the color value encoded in the shader makes it out correctly.
static const uint32_t expectData = 0xff0080ff;
issueContentsCheck(__FUNCTION__, readbackBuffer, expectData);
}
wgpu::SwapChain swapChain;
wgpu::TextureView canvasDepthStencilView;
const uint32_t kWidth = 300;
const uint32_t kHeight = 150;
void frame() {
wgpu::TextureView backbuffer = swapChain.GetCurrentTextureView();
render(backbuffer, canvasDepthStencilView);
// TODO: Read back from the canvas with drawImage() (or something) and
// check the result.
emscripten_cancel_main_loop();
// exit(0) (rather than emscripten_force_exit(0)) ensures there is no dangling keepalive.
exit(0);
}
void run() {
init();
doCopyTestMappedAtCreation(false);
doCopyTestMappedAtCreation(true);
doCopyTestMapAsync(false);
doCopyTestMapAsync(true);
doRenderTest();
{
wgpu::SurfaceDescriptorFromCanvasHTMLSelector canvasDesc{};
canvasDesc.selector = "#canvas";
wgpu::SurfaceDescriptor surfDesc{};
surfDesc.nextInChain = &canvasDesc;
wgpu::Surface surface = instance.CreateSurface(&surfDesc);
wgpu::SwapChainDescriptor scDesc{};
scDesc.usage = wgpu::TextureUsage::RenderAttachment;
scDesc.format = wgpu::TextureFormat::BGRA8Unorm;
scDesc.width = kWidth;
scDesc.height = kHeight;
scDesc.presentMode = wgpu::PresentMode::Fifo;
swapChain = device.CreateSwapChain(surface, &scDesc);
{
wgpu::TextureDescriptor descriptor{};
descriptor.usage = wgpu::TextureUsage::RenderAttachment;
descriptor.size = {kWidth, kHeight, 1};
descriptor.format = wgpu::TextureFormat::Depth32Float;
canvasDepthStencilView = device.CreateTexture(&descriptor).CreateView();
}
}
emscripten_set_main_loop(frame, 0, false);
}
int main() {
GetDevice([](wgpu::Device dev) {
device = dev;
run();
});
// The test result will be reported when the main_loop completes.
// emscripten_exit_with_live_runtime isn't needed because the WebGPU
// callbacks should all automatically keep the runtime alive until
// emscripten_set_main_loop, and that should keep it alive until
// emscripten_cancel_main_loop.
//
// This code is returned when the runtime exits unless something else sets
// it, like exit(0).
return 99;
}