Files
Luke Street f49d3c5f58 Fix typo
2026-06-14 20:56:21 -06:00

902 lines
27 KiB
C++

#include "pipeline.hpp"
#include "../internal.hpp"
#include "../webgpu/gpu.hpp"
#include <algorithm>
#include <array>
#include <string_view>
#include <RmlUi/Core/Vertex.h>
#include <tracy/Tracy.hpp>
namespace aurora::rmlui {
namespace {
using namespace std::string_view_literals;
wgpu::BindGroupLayout g_commonBindGroupLayout;
wgpu::BindGroupLayout g_imageBindGroupLayout;
wgpu::BindGroupLayout g_uniformBindGroupLayout;
wgpu::Sampler g_sampler;
constexpr uint32_t DynamicGroup1 = 1u << 1u;
constexpr uint32_t DynamicGroup2 = 1u << 2u;
constexpr uint64_t CommonUniformBindingSize = AURORA_ALIGN(sizeof(UniformBlock), 16);
constexpr uint64_t ExtraUniformBindingSize =
AURORA_ALIGN(std::max({sizeof(BlurUniformBlock), sizeof(DropShadowUniformBlock), sizeof(SimpleFilterUniformBlock),
sizeof(GradientUniformBlock), sizeof(SeedResampleUniformBlock)}),
16);
constexpr std::string_view vertexSource = R"(
struct VertexInput {
@location(0) position: vec2<f32>,
@location(1) uv: vec2<f32>,
@location(2) color: vec4<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: vec2<f32>,
};
struct Uniforms {
mvp: mat4x4<f32>,
translation: vec4<f32>,
gamma: f32,
};
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@vertex
fn main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
var translatedPos = uniforms.translation.xy + in.position;
out.position = uniforms.mvp * vec4<f32>(translatedPos, 0.0, 1.0);
out.color = in.color;
out.uv = in.uv;
return out;
}
)"sv;
constexpr std::string_view fragmentSource = R"(
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: vec2<f32>,
};
struct Uniforms {
mvp: mat4x4<f32>,
translation: vec4<f32>,
gamma: f32,
};
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@fragment
fn main(in: VertexOutput) -> @location(0) vec4<f32> {
let color = in.color * textureSample(t, s, in.uv);
if (uniforms.gamma == 1.0) {
return color;
}
let corrected_color = pow(color.rgb, vec3<f32>(uniforms.gamma));
return vec4<f32>(corrected_color, color.a);
}
)"sv;
constexpr std::string_view gradientFragmentSource = R"(
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: vec2<f32>,
};
struct Uniforms {
mvp: mat4x4<f32>,
translation: vec4<f32>,
gamma: f32,
};
struct GradientUniforms {
function: i32,
num_stops: i32,
p: vec2<f32>,
v: vec2<f32>,
padding: vec2<f32>,
stop_colors: array<vec4<f32>, 16>,
stop_positions: array<vec4<f32>, 4>,
};
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@group(1) @binding(0) var<uniform> gradient: GradientUniforms;
const LINEAR: i32 = 0;
const RADIAL: i32 = 1;
const CONIC: i32 = 2;
const REPEATING_LINEAR: i32 = 3;
const REPEATING_RADIAL: i32 = 4;
const REPEATING_CONIC: i32 = 5;
const PI: f32 = 3.14159265;
fn bayer_dither(position: vec4<f32>) -> f32 {
let bayer = array<u32, 64>(
0u, 32u, 8u, 40u, 2u, 34u, 10u, 42u,
48u, 16u, 56u, 24u, 50u, 18u, 58u, 26u,
12u, 44u, 4u, 36u, 14u, 46u, 6u, 38u,
60u, 28u, 52u, 20u, 62u, 30u, 54u, 22u,
3u, 35u, 11u, 43u, 1u, 33u, 9u, 41u,
51u, 19u, 59u, 27u, 49u, 17u, 57u, 25u,
15u, 47u, 7u, 39u, 13u, 45u, 5u, 37u,
63u, 31u, 55u, 23u, 61u, 29u, 53u, 21u
);
let x = u32(position.x) % 8u;
let y = u32(position.y) % 8u;
return (f32(bayer[x + y * 8u]) / 64.0 - 0.5) / 255.0;
}
fn stop_position(index: i32) -> f32 {
let stop_index = u32(index);
let group_index = stop_index / 4u;
let component_index = stop_index % 4u;
return gradient.stop_positions[group_index][component_index];
}
fn stop_color_mix(t: f32) -> vec4<f32> {
var color = gradient.stop_colors[0];
for (var i = 1; i < 16; i = i + 1) {
if (i < gradient.num_stops) {
color = mix(color, gradient.stop_colors[u32(i)], smoothstep(stop_position(i - 1), stop_position(i), t));
}
}
return color;
}
@fragment
fn main(in: VertexOutput) -> @location(0) vec4<f32> {
var t = 0.0;
if (gradient.function == LINEAR || gradient.function == REPEATING_LINEAR) {
let dist_square = dot(gradient.v, gradient.v);
let v = in.uv - gradient.p;
t = dot(gradient.v, v) / dist_square;
} else if (gradient.function == RADIAL || gradient.function == REPEATING_RADIAL) {
let v = in.uv - gradient.p;
t = length(gradient.v * v);
} else if (gradient.function == CONIC || gradient.function == REPEATING_CONIC) {
let v = in.uv - gradient.p;
let rotated = vec2<f32>(
gradient.v.x * v.x + gradient.v.y * v.y,
-gradient.v.y * v.x + gradient.v.x * v.y
);
t = 0.5 + atan2(-rotated.x, rotated.y) / (2.0 * PI);
}
if (gradient.function == REPEATING_LINEAR ||
gradient.function == REPEATING_RADIAL ||
gradient.function == REPEATING_CONIC) {
let t0 = stop_position(0);
let t1 = stop_position(gradient.num_stops - 1);
let span = t1 - t0;
t = t0 + (t - t0) - span * floor((t - t0) / span);
}
let color = in.color * stop_color_mix(t);
if (uniforms.gamma == 1.0) {
return color;
}
let corrected_color = pow(color.rgb, vec3<f32>(uniforms.gamma));
let dithered_color = clamp(corrected_color + vec3<f32>(bayer_dither(in.position)), vec3<f32>(0.0), vec3<f32>(1.0));
return vec4<f32>(dithered_color, color.a);
}
)"sv;
constexpr std::string_view fullscreenVertexSource = R"(
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
};
var<private> pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(-1.0, 1.0),
vec2(-1.0, -3.0),
vec2(3.0, 1.0),
);
var<private> uvs: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(0.0, 0.0),
vec2(0.0, 2.0),
vec2(2.0, 0.0),
);
@vertex
fn main(@builtin(vertex_index) vtxIdx: u32) -> VertexOutput {
var out: VertexOutput;
out.position = vec4<f32>(pos[vtxIdx], 0.0, 1.0);
out.uv = uvs[vtxIdx];
return out;
}
)"sv;
constexpr std::string_view blurVertexSource = R"(
struct BlurUniforms {
texel_offset: vec2<f32>,
radius: f32,
padding: f32,
tex_coord_min: vec2<f32>,
tex_coord_max: vec2<f32>,
weights: vec4<f32>,
};
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv0: vec2<f32>,
@location(1) uv1: vec2<f32>,
@location(2) uv2: vec2<f32>,
@location(3) uv3: vec2<f32>,
@location(4) uv4: vec2<f32>,
@location(5) uv5: vec2<f32>,
@location(6) uv6: vec2<f32>,
};
@group(2) @binding(0) var<uniform> blur: BlurUniforms;
const BLUR_NUM_WEIGHTS: i32 = 4;
var<private> pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(-1.0, 1.0),
vec2(-1.0, -3.0),
vec2(3.0, 1.0),
);
var<private> uvs: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
vec2(0.0, 0.0),
vec2(0.0, 2.0),
vec2(2.0, 0.0),
);
fn blur_uv(uv: vec2<f32>, index: i32) -> vec2<f32> {
return uv - f32(index - BLUR_NUM_WEIGHTS + 1) * blur.texel_offset;
}
@vertex
fn main(@builtin(vertex_index) vtxIdx: u32) -> VertexOutput {
let uv = uvs[vtxIdx];
var out: VertexOutput;
out.position = vec4<f32>(pos[vtxIdx], 0.0, 1.0);
out.uv0 = blur_uv(uv, 0);
out.uv1 = blur_uv(uv, 1);
out.uv2 = blur_uv(uv, 2);
out.uv3 = blur_uv(uv, 3);
out.uv4 = blur_uv(uv, 4);
out.uv5 = blur_uv(uv, 5);
out.uv6 = blur_uv(uv, 6);
return out;
}
)"sv;
constexpr std::string_view blitFragmentSource = R"(
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
return textureSample(t, s, uv);
}
)"sv;
constexpr std::string_view opaqueBlitFragmentSource = R"(
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let color = textureSample(t, s, uv);
return vec4<f32>(color.rgb, 1.0);
}
)"sv;
constexpr std::string_view seedResampleFragmentSource = R"(
struct SeedUniforms {
sampler_mode: u32,
frame_width: f32,
frame_height: f32,
pad: u32,
};
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@group(2) @binding(0) var<uniform> uniforms: SeedUniforms;
fn sample_by_pixel(pixel: vec2<i32>) -> vec4<f32> {
let source_dims = textureDimensions(t);
let max_coord = vec2<i32>(source_dims) - vec2<i32>(1, 1);
let coord = clamp(pixel, vec2<i32>(0, 0), max_coord);
return textureLoad(t, coord, 0);
}
fn sample_area(frag_position: vec4<f32>) -> vec4<f32> {
let source_size = vec2<f32>(textureDimensions(t));
let target_size = max(vec2<f32>(uniforms.frame_width, uniforms.frame_height), vec2<f32>(1.0, 1.0));
let source_min = clamp((frag_position.xy - vec2<f32>(0.5, 0.5)) / target_size,
vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 1.0)) * source_size;
let source_max = clamp((frag_position.xy + vec2<f32>(0.5, 0.5)) / target_size,
vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 1.0)) * source_size;
let first_pixel = vec2<i32>(floor(source_min));
let last_pixel = vec2<i32>(ceil(source_max));
let max_iterations: i32 = 16;
var avg_color = vec4<f32>(0.0, 0.0, 0.0, 0.0);
var total_weight = 0.0;
for (var iy: i32 = 0; iy < max_iterations; iy = iy + 1) {
let source_y = first_pixel.y + iy;
if (source_y < last_pixel.y) {
let y0 = f32(source_y);
let weight_y = max(min(source_max.y, y0 + 1.0) - max(source_min.y, y0), 0.0);
for (var ix: i32 = 0; ix < max_iterations; ix = ix + 1) {
let source_x = first_pixel.x + ix;
if (source_x < last_pixel.x) {
let x0 = f32(source_x);
let weight_x = max(min(source_max.x, x0 + 1.0) - max(source_min.x, x0), 0.0);
let weight = weight_x * weight_y;
avg_color += weight * sample_by_pixel(vec2<i32>(source_x, source_y));
total_weight += weight;
}
}
}
}
return avg_color / max(total_weight, 0.000001);
}
@fragment
fn main(@builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
var color = textureSample(t, s, uv);
if (uniforms.sampler_mode == 1u) {
color = sample_area(position);
}
return vec4(color.rgb, 1.0);
}
)"sv;
constexpr std::string_view simpleFilterFragmentSource = R"(
struct SimpleFilterUniforms {
matrix: mat4x4<f32>,
opacity: vec4<f32>,
};
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@group(2) @binding(0) var<uniform> simple_filter: SimpleFilterUniforms;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let tex_color = textureSample(t, s, uv);
let transformed_color = simple_filter.matrix * tex_color;
return vec4<f32>(transformed_color.rgb, tex_color.a) * simple_filter.opacity.x;
}
)"sv;
constexpr std::string_view maskImageFragmentSource = R"(
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@group(2) @binding(0) var mask_t: texture_2d<f32>;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let tex_color = textureSample(t, s, uv);
let mask_alpha = textureSample(mask_t, s, uv).a;
return tex_color * mask_alpha;
}
)"sv;
constexpr std::string_view blurFragmentSource = R"(
struct BlurUniforms {
texel_offset: vec2<f32>,
radius: f32,
padding: f32,
tex_coord_min: vec2<f32>,
tex_coord_max: vec2<f32>,
weights: vec4<f32>,
};
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@group(2) @binding(0) var<uniform> blur: BlurUniforms;
fn get_weight(index: i32) -> f32 {
return blur.weights[u32(abs(index))];
}
fn sample_blur(sample_uv: vec2<f32>, offset_index: i32) -> vec4<f32> {
let in_region = step(blur.tex_coord_min, sample_uv) * step(sample_uv, blur.tex_coord_max);
return textureSample(t, s, sample_uv) * get_weight(offset_index) * in_region.x * in_region.y;
}
@fragment
fn main(@location(0) uv0: vec2<f32>, @location(1) uv1: vec2<f32>, @location(2) uv2: vec2<f32>,
@location(3) uv3: vec2<f32>, @location(4) uv4: vec2<f32>, @location(5) uv5: vec2<f32>,
@location(6) uv6: vec2<f32>) -> @location(0) vec4<f32> {
var color = sample_blur(uv0, -3);
color += sample_blur(uv1, -2);
color += sample_blur(uv2, -1);
color += sample_blur(uv3, 0);
color += sample_blur(uv4, 1);
color += sample_blur(uv5, 2);
color += sample_blur(uv6, 3);
return color;
}
)"sv;
constexpr std::string_view regionBlitFragmentSource = R"(
struct BlurUniforms {
texel_offset: vec2<f32>,
radius: f32,
padding: f32,
tex_coord_min: vec2<f32>,
tex_coord_max: vec2<f32>,
weights: vec4<f32>,
};
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@group(2) @binding(0) var<uniform> blur: BlurUniforms;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let sample_uv = mix(blur.tex_coord_min, blur.tex_coord_max, uv);
return textureSample(t, s, sample_uv);
}
)"sv;
constexpr std::string_view dropShadowFragmentSource = R"(
struct DropShadowUniforms {
color: vec4<f32>,
uv_offset: vec2<f32>,
tex_coord_min: vec2<f32>,
tex_coord_max: vec2<f32>,
};
@group(0) @binding(1) var s: sampler;
@group(1) @binding(0) var t: texture_2d<f32>;
@group(2) @binding(0) var<uniform> shadow: DropShadowUniforms;
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let sample_uv = uv - shadow.uv_offset;
let in_region = step(shadow.tex_coord_min, sample_uv) * step(sample_uv, shadow.tex_coord_max);
let alpha = textureSample(t, s, sample_uv).a * in_region.x * in_region.y;
return shadow.color * alpha;
}
)"sv;
wgpu::ComputeState compile_shader(std::string_view wgslSource, std::string_view label) {
const wgpu::ShaderSourceWGSL source{
wgpu::ShaderSourceWGSL::Init{
.nextInChain = nullptr,
.code = wgslSource,
},
};
const wgpu::ShaderModuleDescriptor desc{
.nextInChain = &source,
.label = label,
};
return {
.module = webgpu::g_device.CreateShaderModule(&desc),
.entryPoint = "main",
};
}
wgpu::BlendState blend_state(BlendMode mode) {
switch (mode) {
case BlendMode::Premultiplied:
return {
.color =
{
.operation = wgpu::BlendOperation::Add,
.srcFactor = wgpu::BlendFactor::One,
.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha,
},
.alpha =
{
.operation = wgpu::BlendOperation::Add,
.srcFactor = wgpu::BlendFactor::One,
.dstFactor = wgpu::BlendFactor::OneMinusSrcAlpha,
},
};
case BlendMode::None:
default:
return {};
}
}
const wgpu::PipelineLayout create_pipeline_layout(PipelineKind kind) {
std::array<wgpu::BindGroupLayout, 3> layouts{};
uint32_t layoutCount = 0;
layouts[layoutCount++] = g_commonBindGroupLayout;
switch (kind) {
case PipelineKind::Gradient:
layouts[layoutCount++] = g_uniformBindGroupLayout;
break;
case PipelineKind::MaskImage:
layouts[layoutCount++] = g_imageBindGroupLayout;
layouts[layoutCount++] = g_imageBindGroupLayout;
break;
case PipelineKind::Blur:
case PipelineKind::RegionBlit:
case PipelineKind::DropShadow:
case PipelineKind::SimpleFilter:
case PipelineKind::SeedResample:
layouts[layoutCount++] = g_imageBindGroupLayout;
layouts[layoutCount++] = g_uniformBindGroupLayout;
break;
case PipelineKind::Geometry:
case PipelineKind::Blit:
case PipelineKind::OpaqueBlit:
default:
layouts[layoutCount++] = g_imageBindGroupLayout;
break;
}
const wgpu::PipelineLayoutDescriptor layoutDesc{
.bindGroupLayoutCount = layoutCount,
.bindGroupLayouts = layouts.data(),
};
return webgpu::g_device.CreatePipelineLayout(&layoutDesc);
}
const std::string_view fragment_source(PipelineKind kind) {
switch (kind) {
case PipelineKind::Geometry:
return fragmentSource;
case PipelineKind::Gradient:
return gradientFragmentSource;
case PipelineKind::OpaqueBlit:
return opaqueBlitFragmentSource;
case PipelineKind::SeedResample:
return seedResampleFragmentSource;
case PipelineKind::Blur:
return blurFragmentSource;
case PipelineKind::RegionBlit:
return regionBlitFragmentSource;
case PipelineKind::DropShadow:
return dropShadowFragmentSource;
case PipelineKind::SimpleFilter:
return simpleFilterFragmentSource;
case PipelineKind::MaskImage:
return maskImageFragmentSource;
case PipelineKind::Blit:
default:
return blitFragmentSource;
}
}
const std::string_view vertex_source(VertexLayoutKind kind) {
switch (kind) {
case VertexLayoutKind::Geometry:
return vertexSource;
case VertexLayoutKind::BlurFullscreen:
return blurVertexSource;
case VertexLayoutKind::Fullscreen:
default:
return fullscreenVertexSource;
}
}
} // namespace
void initialize_pipeline() {
constexpr std::array commonEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
.buffer =
{
.type = wgpu::BufferBindingType::Uniform,
.hasDynamicOffset = true,
},
},
wgpu::BindGroupLayoutEntry{
.binding = 1,
.visibility = wgpu::ShaderStage::Fragment,
.sampler =
{
.type = wgpu::SamplerBindingType::Filtering,
},
},
};
const wgpu::BindGroupLayoutDescriptor commonDesc{
.entryCount = commonEntries.size(),
.entries = commonEntries.data(),
};
g_commonBindGroupLayout = webgpu::g_device.CreateBindGroupLayout(&commonDesc);
constexpr std::array imageEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Fragment,
.texture =
{
.sampleType = wgpu::TextureSampleType::Float,
.viewDimension = wgpu::TextureViewDimension::e2D,
},
},
};
const wgpu::BindGroupLayoutDescriptor imageDesc{
.entryCount = imageEntries.size(),
.entries = imageEntries.data(),
};
g_imageBindGroupLayout = webgpu::g_device.CreateBindGroupLayout(&imageDesc);
constexpr std::array uniformEntries{
wgpu::BindGroupLayoutEntry{
.binding = 0,
.visibility = wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment,
.buffer =
{
.type = wgpu::BufferBindingType::Uniform,
.hasDynamicOffset = true,
},
},
};
const wgpu::BindGroupLayoutDescriptor uniformDesc{
.entryCount = uniformEntries.size(),
.entries = uniformEntries.data(),
};
g_uniformBindGroupLayout = webgpu::g_device.CreateBindGroupLayout(&uniformDesc);
constexpr wgpu::SamplerDescriptor samplerDesc{
.addressModeU = wgpu::AddressMode::Repeat,
.addressModeV = wgpu::AddressMode::Repeat,
.addressModeW = wgpu::AddressMode::Repeat,
.magFilter = wgpu::FilterMode::Linear,
.minFilter = wgpu::FilterMode::Linear,
.mipmapFilter = wgpu::MipmapFilterMode::Linear,
.maxAnisotropy = 1,
};
g_sampler = webgpu::g_device.CreateSampler(&samplerDesc);
}
void shutdown_pipeline() {
g_commonBindGroupLayout = {};
g_imageBindGroupLayout = {};
g_uniformBindGroupLayout = {};
g_sampler = {};
}
gfx::BindGroupRef texture_bind_group_ref(const wgpu::TextureView& view) {
const std::array entries{
wgpu::BindGroupEntry{
.binding = 0,
.textureView = view,
},
};
const wgpu::BindGroupDescriptor desc{
.layout = g_imageBindGroupLayout,
.entryCount = entries.size(),
.entries = entries.data(),
};
return gfx::bind_group_ref(desc);
}
gfx::BindGroupRef common_bind_group_ref() {
const std::array entries{
wgpu::BindGroupEntry{
.binding = 0,
.buffer = gfx::g_uniformBuffer,
.offset = 0,
.size = CommonUniformBindingSize,
},
wgpu::BindGroupEntry{
.binding = 1,
.sampler = g_sampler,
},
};
const wgpu::BindGroupDescriptor desc{
.layout = g_commonBindGroupLayout,
.entryCount = entries.size(),
.entries = entries.data(),
};
return gfx::bind_group_ref(desc);
}
gfx::BindGroupRef uniform_bind_group_ref() {
const std::array entries{
wgpu::BindGroupEntry{
.binding = 0,
.buffer = gfx::g_uniformBuffer,
.offset = 0,
.size = ExtraUniformBindingSize,
},
};
const wgpu::BindGroupDescriptor desc{
.layout = g_uniformBindGroupLayout,
.entryCount = entries.size(),
.entries = entries.data(),
};
return gfx::bind_group_ref(desc);
}
wgpu::RenderPipeline create_pipeline(const PipelineConfig& config) {
ZoneScoped;
const auto kind = static_cast<PipelineKind>(config.kind);
const auto vertexLayoutKind = static_cast<VertexLayoutKind>(config.vertexLayout);
const auto colorFormat = static_cast<wgpu::TextureFormat>(config.colorFormat);
const auto stencilFormat = static_cast<wgpu::TextureFormat>(config.stencilFormat);
const auto stencilMode = static_cast<StencilMode>(config.stencilMode);
const auto blendMode = static_cast<BlendMode>(config.blendMode);
const auto vertexShader = compile_shader(vertex_source(vertexLayoutKind), "RmlUi Vertex Shader");
const auto fragmentShader = compile_shader(fragment_source(kind), "RmlUi Fragment Shader");
constexpr std::array vertexAttributes{
wgpu::VertexAttribute{
.format = wgpu::VertexFormat::Float32x2,
.offset = offsetof(Rml::Vertex, position),
.shaderLocation = 0,
},
wgpu::VertexAttribute{
.format = wgpu::VertexFormat::Float32x2,
.offset = offsetof(Rml::Vertex, tex_coord),
.shaderLocation = 1,
},
wgpu::VertexAttribute{
.format = wgpu::VertexFormat::Unorm8x4,
.offset = offsetof(Rml::Vertex, colour),
.shaderLocation = 2,
},
};
const std::array vertexBufferLayouts{
wgpu::VertexBufferLayout{
.stepMode = wgpu::VertexStepMode::Vertex,
.arrayStride = sizeof(Rml::Vertex),
.attributeCount = vertexAttributes.size(),
.attributes = vertexAttributes.data(),
},
};
const auto blend = blend_state(blendMode);
const wgpu::ColorTargetState colorState{
.format = colorFormat,
.blend = blendMode == BlendMode::None ? nullptr : &blend,
.writeMask = static_cast<wgpu::ColorWriteMask>(config.colorWriteMask),
};
const wgpu::FragmentState fragmentState{
.module = fragmentShader.module,
.entryPoint = fragmentShader.entryPoint,
.targetCount = 1,
.targets = &colorState,
};
wgpu::DepthStencilState depthStencilState{};
const wgpu::DepthStencilState* depthStencil = nullptr;
if (stencilMode != StencilMode::None) {
wgpu::CompareFunction compare = wgpu::CompareFunction::Always;
wgpu::StencilOperation passOp = wgpu::StencilOperation::Keep;
switch (stencilMode) {
case StencilMode::EqualKeep:
compare = wgpu::CompareFunction::Equal;
break;
case StencilMode::ClipReplace:
passOp = wgpu::StencilOperation::Replace;
break;
case StencilMode::ClipIntersect:
compare = wgpu::CompareFunction::Equal;
passOp = wgpu::StencilOperation::IncrementClamp;
break;
case StencilMode::AlwaysKeep:
case StencilMode::None:
default:
break;
}
const wgpu::StencilFaceState face{
.compare = compare,
.failOp = wgpu::StencilOperation::Keep,
.depthFailOp = wgpu::StencilOperation::Keep,
.passOp = passOp,
};
depthStencilState = {
.format = stencilFormat,
.stencilFront = face,
.stencilBack = face,
.stencilReadMask = 0xFF,
.stencilWriteMask = 0xFF,
};
depthStencil = &depthStencilState;
}
const bool hasVertexBuffer = vertexLayoutKind == VertexLayoutKind::Geometry;
const auto pipelineLayout = create_pipeline_layout(kind);
const auto label = fmt::format("RmlUi Pipeline {}", config.kind);
const wgpu::RenderPipelineDescriptor pipelineDesc{
.label = label.c_str(),
.layout = pipelineLayout,
.vertex =
{
.module = vertexShader.module,
.entryPoint = vertexShader.entryPoint,
.bufferCount = hasVertexBuffer ? vertexBufferLayouts.size() : 0,
.buffers = hasVertexBuffer ? vertexBufferLayouts.data() : nullptr,
},
.primitive =
{
.topology = wgpu::PrimitiveTopology::TriangleList,
.stripIndexFormat = wgpu::IndexFormat::Undefined,
.frontFace = wgpu::FrontFace::CW,
.cullMode = wgpu::CullMode::None,
},
.depthStencil = depthStencil,
.multisample =
{
.count = config.sampleCount,
},
.fragment = &fragmentState,
};
return webgpu::g_device.CreateRenderPipeline(&pipelineDesc);
}
void render(const DrawData& data, const wgpu::RenderPassEncoder& pass) {
if (!gfx::bind_pipeline(data.pipeline, pass)) {
return;
}
const auto commonBindGroup = gfx::find_bind_group(common_bind_group_ref());
const std::array commonOffsets{data.uniformRange.offset};
pass.SetBindGroup(0, commonBindGroup, commonOffsets.size(), commonOffsets.data());
if (data.bindGroup1 != 0) {
const auto bindGroup = gfx::find_bind_group(data.bindGroup1);
if ((data.dynamicBindGroupMask & DynamicGroup1) != 0) {
const std::array offsets{data.bindGroup1DynamicOffset};
pass.SetBindGroup(1, bindGroup, offsets.size(), offsets.data());
} else {
pass.SetBindGroup(1, bindGroup);
}
}
if (data.bindGroup2 != 0) {
const auto bindGroup = gfx::find_bind_group(data.bindGroup2);
if ((data.dynamicBindGroupMask & DynamicGroup2) != 0) {
const std::array offsets{data.bindGroup2DynamicOffset};
pass.SetBindGroup(2, bindGroup, offsets.size(), offsets.data());
} else {
pass.SetBindGroup(2, bindGroup);
}
}
if (data.hasBlendConstant != 0) {
const wgpu::Color color{data.blendConstant[0], data.blendConstant[1], data.blendConstant[2], data.blendConstant[3]};
pass.SetBlendConstant(&color);
}
pass.SetStencilReference(data.stencilRef);
if (static_cast<DrawKind>(data.drawKind) == DrawKind::Geometry) {
pass.SetVertexBuffer(0, gfx::g_vertexBuffer, data.vertexRange.offset, data.vertexRange.size);
pass.SetIndexBuffer(gfx::g_indexBuffer, wgpu::IndexFormat::Uint32, data.indexRange.offset, data.indexRange.size);
pass.DrawIndexed(data.indexCount);
} else {
pass.Draw(data.vertexCount);
}
}
uint32_t sampler_mode() noexcept {
switch (webgpu::get_resampler()) {
case SAMPLER_AREA:
return 1;
case SAMPLER_BILINEAR:
default:
return 0;
}
}
} // namespace aurora::rmlui