blob: fee65c3a34fec8c8d8ea73260d6aded12936c362 [file] [log] [blame]
/*
* Copyright 2023 Rive
*/
#ifdef RIVE_WEBGPU
#include "pls_render_context_webgpu_vulkan.hpp"
#include "shaders/constants.glsl"
#include <webgpu/webgpu_cpp.h>
#include <emscripten.h>
#include <emscripten/html5_webgpu.h>
namespace rive::pls
{
// Create a group for binding PLS textures as Vulkan input attachments. The "inputTexture" property
// is nonstandard WebGPU.
EM_JS(int, make_pls_input_attachment_texture_bind_group, (int device), {
device = JsValStore.get(device);
const plsBindBroupLayout = device.createBindGroupLayout({
entries : [
{
binding : 0,
visibility : GPUShaderStage.FRAGMENT,
inputTexture : {viewDimension : "2d"},
},
{
binding : 1,
visibility : GPUShaderStage.FRAGMENT,
inputTexture : {viewDimension : "2d"},
},
{
binding : 2,
visibility : GPUShaderStage.FRAGMENT,
inputTexture : {viewDimension : "2d"},
},
{
binding : 3,
visibility : GPUShaderStage.FRAGMENT,
inputTexture : {viewDimension : "2d"},
},
]
});
return JsValStore.add(plsBindBroupLayout);
});
wgpu::BindGroupLayout PLSRenderContextWebGPUVulkan::initPLSTextureBindGroup()
{
m_plsTextureBindGroupJSHandle = EmJsHandle(make_pls_input_attachment_texture_bind_group(
emscripten_webgpu_export_device(device().Get())));
return wgpu::BindGroupLayout::Acquire(
emscripten_webgpu_import_bind_group_layout(m_plsTextureBindGroupJSHandle.get()));
}
// The "TRANSIENT_ATTACHMENT" and "INPUT_ATTACHMENT" flags are nonstandard WebGPU.
EM_JS(int, texture_usage_transient_input_attachment, (), {
return GPUTextureUsage.TRANSIENT_ATTACHMENT | GPUTextureUsage.INPUT_ATTACHMENT;
});
rcp<PLSRenderTargetWebGPU> PLSRenderContextWebGPUVulkan::makeRenderTarget(
wgpu::TextureFormat framebufferFormat,
uint32_t width,
uint32_t height)
{
return rcp(new PLSRenderTargetWebGPU(
device(),
framebufferFormat,
width,
height,
static_cast<wgpu::TextureUsage>(texture_usage_transient_input_attachment())));
}
// Create a standard PLS "draw" pipeline that uses Vulkan input attachments and
// VK_EXT_rasterization_order_attachment_access for pixel local storage. The "inputs" and "features"
// properties on GPUFragmentState, and the "usedAsInput" property on GPUColorTargetState are
// nonstandard WebGPU.
EM_JS(int,
make_pls_draw_pipeline,
(int device,
rive::pls::DrawType drawType,
wgpu::TextureFormat framebufferFormat,
int vertexShader,
int fragmentShader,
int pipelineLayout,
bool clockwiseFrontFace),
{
device = JsValStore.get(device);
vertexShader = JsValStore.get(vertexShader);
fragmentShader = JsValStore.get(fragmentShader);
pipelineLayout = JsValStore.get(pipelineLayout);
const RGBA8_UNORM = 0x00000012;
const BGRA8_UNORM = 0x00000017;
switch (framebufferFormat)
{
case RGBA8_UNORM:
framebufferFormat = "rgba8unorm";
break;
case BGRA8_UNORM:
framebufferFormat = "bgra8unorm";
break;
default:
throw "unsupported framebuffer format " + framebufferFormat;
}
let vertexBufferLayout;
switch (drawType)
{
case 0:
case 1:
attrs = [
{
format : "float32x4",
offset : 0,
shaderLocation : 0,
},
{
format : "float32x4",
offset : 4 * 4,
shaderLocation : 1,
},
];
vertexBufferLayouts = [
{
arrayStride : 4 * 8,
stepMode : "vertex",
attributes : attrs,
},
];
break;
case 2:
attrs = [
{
format : "float32x3",
offset : 0,
shaderLocation : 0,
},
];
vertexBufferLayouts = [
{
arrayStride : 4 * 3,
stepMode : "vertex",
attributes : attrs,
},
];
break;
case 3:
attrs = [
{
format : "float32x2",
offset : 0,
shaderLocation : 0,
},
{
format : "float32x2",
offset : 0,
shaderLocation : 1,
},
];
vertexBufferLayouts = [
{
arrayStride : 4 * 2,
stepMode : "vertex",
attributes : [attrs[0]],
},
{
arrayStride : 4 * 2,
stepMode : "vertex",
attributes : [attrs[1]],
},
];
break;
}
const pipelineDesc = {
vertex : {
module : vertexShader,
entryPoint : "main",
buffers : vertexBufferLayouts,
},
fragment : {
module : fragmentShader,
entryPoint : "main",
inputs : [
{format : framebufferFormat, usedAsColor : true},
{format : "r32uint", usedAsColor : true},
{format : "r32uint", usedAsColor : true},
{format : framebufferFormat, usedAsColor : true},
],
targets : [
{format : framebufferFormat, usedAsInput : true},
{format : "r32uint", usedAsInput : true},
{format : "r32uint", usedAsInput : true},
{format : framebufferFormat, usedAsInput : true},
],
features : GPUFragmentStateFeatures.RASTERIZATION_ORDER_ATTACHMENT_ACCESS,
},
primitive : {
topology : "triangle-list",
frontFace : clockwiseFrontFace ? "cw" : "ccw",
cullMode : drawType != 3 ? "back" : "none"
},
layout : pipelineLayout
};
const pipeline = device.createRenderPipeline(pipelineDesc);
return JsValStore.add(pipeline);
});
wgpu::RenderPipeline PLSRenderContextWebGPUVulkan::makePLSDrawPipeline(
rive::pls::DrawType drawType,
wgpu::TextureFormat framebufferFormat,
wgpu::ShaderModule vertexShader,
wgpu::ShaderModule fragmentShader,
EmJsHandle* pipelineJSHandleIfNeeded)
{
*pipelineJSHandleIfNeeded = EmJsHandle(
make_pls_draw_pipeline(emscripten_webgpu_export_device(device().Get()),
drawType,
framebufferFormat,
emscripten_webgpu_export_shader_module(vertexShader.Get()),
emscripten_webgpu_export_shader_module(fragmentShader.Get()),
emscripten_webgpu_export_pipeline_layout(drawPipelineLayout().Get()),
frontFaceForOnScreenDraws() == wgpu::FrontFace::CW));
return wgpu::RenderPipeline::Acquire(
emscripten_webgpu_import_render_pipeline(pipelineJSHandleIfNeeded->get()));
}
// Create a standard PLS "draw" render pass that uses Vulkan input attachments for pixel local
// storage. The "inputAttachments" property on GPURenderPassDescriptor is nonstandard WebGPU.
EM_JS(int,
make_pls_render_pass,
(int encoder,
int tex0,
int tex1,
int tex2,
int tex3,
wgpu::LoadOp loadOp,
double r,
double g,
double b,
double a),
{
encoder = JsValStore.get(encoder);
tex0 = JsValStore.get(tex0);
tex1 = JsValStore.get(tex1);
tex2 = JsValStore.get(tex2);
tex3 = JsValStore.get(tex3);
const CLEAR = 1;
const LOAD = 2;
switch (loadOp)
{
case CLEAR:
loadOp = "clear";
break;
case LOAD:
loadOp = "load";
break;
default:
throw "unsupported framebuffer format " + framebufferFormat;
}
const zero = {r : 0, g : 0, b : 0, a : 0};
const plsAttachments = [
{
view : tex0,
loadOp : loadOp,
storeOp : "store",
clearValue : {r : r, g : g, b : b, a : a},
},
{
view : tex1,
loadOp : "clear",
storeOp : "discard",
clearValue : zero,
},
{
view : tex2,
loadOp : "clear",
storeOp : "discard",
clearValue : zero,
},
{
view : tex3,
loadOp : "clear",
storeOp : "discard",
clearValue : zero,
},
];
const renderPass = encoder.beginRenderPass({
inputAttachments : plsAttachments,
colorAttachments : plsAttachments,
});
return JsValStore.add(renderPass);
});
wgpu::RenderPassEncoder PLSRenderContextWebGPUVulkan::makePLSRenderPass(
wgpu::CommandEncoder encoder,
const PLSRenderTargetWebGPU* renderTarget,
wgpu::LoadOp loadOp,
const wgpu::Color& clearColor,
EmJsHandle* renderPassJSHandleIfNeeded)
{
*renderPassJSHandleIfNeeded = EmJsHandle(make_pls_render_pass(
emscripten_webgpu_export_command_encoder(encoder.Get()),
emscripten_webgpu_export_texture_view(renderTarget->m_targetTextureView.Get()),
emscripten_webgpu_export_texture_view(renderTarget->m_coverageTextureView.Get()),
emscripten_webgpu_export_texture_view(renderTarget->m_clipTextureView.Get()),
emscripten_webgpu_export_texture_view(renderTarget->m_originalDstColorTextureView.Get()),
loadOp,
clearColor.r,
clearColor.g,
clearColor.b,
clearColor.a));
return wgpu::RenderPassEncoder::Acquire(
emscripten_webgpu_import_render_pass_encoder(renderPassJSHandleIfNeeded->get()));
}
} // namespace rive::pls
#endif