From 20d963238d827c7941e65dbafde5e5954cb45668 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Tue, 24 Feb 2026 17:57:55 +0800 Subject: [PATCH 1/3] removed per-item pipeline --- packages/ranim-render/src/graph/mod.rs | 3 +- packages/ranim-render/src/graph/view.rs | 16 +- .../src/graph/view/merged_vitem_color.rs | 68 --- .../src/graph/view/merged_vitem_compute.rs | 47 -- .../src/graph/view/merged_vitem_depth.rs | 64 --- .../src/graph/view/vitem_color.rs | 29 +- .../src/graph/view/vitem_compute.rs | 33 +- .../src/graph/view/vitem_depth.rs | 34 +- packages/ranim-render/src/lib.rs | 46 +- .../src/pipelines/merged_vitem.rs | 188 -------- packages/ranim-render/src/pipelines/mod.rs | 8 +- .../src/pipelines/shaders/merged_vitem.wgsl | 349 --------------- .../shaders/merged_vitem_compute.wgsl | 94 ---- .../src/pipelines/shaders/vitem.wgsl | 236 +++++----- .../src/pipelines/shaders/vitem_compute.wgsl | 87 ++-- packages/ranim-render/src/pipelines/vitem.rs | 386 ++++++---------- .../src/pipelines/vitem_compute.rs | 200 --------- packages/ranim-render/src/primitives.rs | 3 +- packages/ranim-render/src/primitives/vitem.rs | 423 ------------------ .../primitives/{merged_vitem.rs => vitems.rs} | 4 +- 20 files changed, 403 insertions(+), 1915 deletions(-) delete mode 100644 packages/ranim-render/src/graph/view/merged_vitem_color.rs delete mode 100644 packages/ranim-render/src/graph/view/merged_vitem_compute.rs delete mode 100644 packages/ranim-render/src/graph/view/merged_vitem_depth.rs delete mode 100644 packages/ranim-render/src/pipelines/merged_vitem.rs delete mode 100644 packages/ranim-render/src/pipelines/shaders/merged_vitem.wgsl delete mode 100644 packages/ranim-render/src/pipelines/shaders/merged_vitem_compute.wgsl delete mode 100644 packages/ranim-render/src/pipelines/vitem_compute.rs delete mode 100644 packages/ranim-render/src/primitives/vitem.rs rename packages/ranim-render/src/primitives/{merged_vitem.rs => vitems.rs} (99%) diff --git a/packages/ranim-render/src/graph/mod.rs b/packages/ranim-render/src/graph/mod.rs index 6ca707b1..5c6f0074 100644 --- a/packages/ranim-render/src/graph/mod.rs +++ b/packages/ranim-render/src/graph/mod.rs @@ -9,7 +9,7 @@ use variadics_please::all_tuples; use crate::{ RenderContext, - primitives::{viewport::ViewportGpuPacket, vitem::VItemRenderInstance}, + primitives::viewport::ViewportGpuPacket, resource::Handle, utils::collections::{Graph, TypeBinnedVec}, }; @@ -102,7 +102,6 @@ pub trait RenderPacketsQuery { /// A marker trait to make compiler happy. pub trait RenderPacketMark {} -impl RenderPacketMark for VItemRenderInstance {} impl RenderPacketMark for ViewportGpuPacket {} impl RenderPacketsQuery for T { diff --git a/packages/ranim-render/src/graph/view.rs b/packages/ranim-render/src/graph/view.rs index c9aba857..3f5d9347 100644 --- a/packages/ranim-render/src/graph/view.rs +++ b/packages/ranim-render/src/graph/view.rs @@ -1,20 +1,14 @@ -pub mod vitem_color; use std::ops::{Deref, DerefMut}; -pub use vitem_color::*; +pub mod oit_resolve; +pub use oit_resolve::*; + pub mod vitem_compute; pub use vitem_compute::*; pub mod vitem_depth; pub use vitem_depth::*; -pub mod oit_resolve; -pub use oit_resolve::*; - -pub mod merged_vitem_compute; -pub use merged_vitem_compute::*; -pub mod merged_vitem_depth; -pub use merged_vitem_depth::*; -pub mod merged_vitem_color; -pub use merged_vitem_color::*; +pub mod vitem_color; +pub use vitem_color::*; use crate::{ RenderContext, diff --git a/packages/ranim-render/src/graph/view/merged_vitem_color.rs b/packages/ranim-render/src/graph/view/merged_vitem_color.rs deleted file mode 100644 index 382afeb3..00000000 --- a/packages/ranim-render/src/graph/view/merged_vitem_color.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::{ - RenderContext, RenderTextures, - graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, - pipelines::MergedVItemColorPipeline, - primitives::viewport::ViewportGpuPacket, -}; - -pub struct MergedVItemColorNode; - -impl ViewRenderNodeTrait for MergedVItemColorNode { - type Query = (); - - fn run( - &self, - #[cfg(not(feature = "profiling"))] encoder: &mut wgpu::CommandEncoder, - #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, - _packets: ::Output<'_>, - ctx: RenderContext, - viewport: &ViewportGpuPacket, - ) { - let Some(merged) = ctx.merged_buffer else { - return; - }; - if merged.item_count() == 0 { - return; - } - - let RenderTextures { - render_view, - depth_stencil_view, - .. - } = ctx.render_textures; - let rpass_desc = wgpu::RenderPassDescriptor { - label: Some("Merged VItem Color Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: render_view, - resolve_target: None, - depth_slice: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: depth_stencil_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }), - timestamp_writes: None, - occlusion_query_set: None, - }; - #[cfg(feature = "profiling")] - let mut rpass = encoder.scoped_render_pass("Merged VItem Color Render Pass", rpass_desc); - #[cfg(not(feature = "profiling"))] - let mut rpass = encoder.begin_render_pass(&rpass_desc); - rpass.set_pipeline( - &ctx.pipelines - .get_or_init::(ctx.wgpu_ctx), - ); - rpass.set_bind_group(0, &ctx.resolution_info.bind_group, &[]); - rpass.set_bind_group(1, &viewport.uniforms_bind_group.bind_group, &[]); - rpass.set_bind_group(2, merged.render_bind_group.as_ref().unwrap(), &[]); - rpass.draw(0..4, 0..merged.item_count()); - } -} diff --git a/packages/ranim-render/src/graph/view/merged_vitem_compute.rs b/packages/ranim-render/src/graph/view/merged_vitem_compute.rs deleted file mode 100644 index 78de37bd..00000000 --- a/packages/ranim-render/src/graph/view/merged_vitem_compute.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::{ - RenderContext, - graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, - pipelines::MergedVItemComputePipeline, - primitives::viewport::ViewportGpuPacket, -}; - -pub struct MergedVItemComputeNode; - -impl ViewRenderNodeTrait for MergedVItemComputeNode { - type Query = (); - - fn run( - &self, - #[cfg(not(feature = "profiling"))] encoder: &mut wgpu::CommandEncoder, - #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, - _packets: ::Output<'_>, - ctx: RenderContext, - _viewport: &ViewportGpuPacket, - ) { - let Some(merged) = ctx.merged_buffer else { - return; - }; - if merged.item_count() == 0 { - return; - } - - #[cfg(feature = "profiling")] - let mut encoder = encoder.scope("Merged Compute Pass"); - - { - #[cfg(feature = "profiling")] - let mut cpass = encoder.scoped_compute_pass("Merged VItem Map Points Compute Pass"); - #[cfg(not(feature = "profiling"))] - let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Merged VItem Map Points Compute Pass"), - timestamp_writes: None, - }); - cpass.set_pipeline( - &ctx.pipelines - .get_or_init::(ctx.wgpu_ctx), - ); - cpass.set_bind_group(0, merged.compute_bind_group.as_ref().unwrap(), &[]); - cpass.dispatch_workgroups(merged.total_points().div_ceil(256), 1, 1); - } - } -} diff --git a/packages/ranim-render/src/graph/view/merged_vitem_depth.rs b/packages/ranim-render/src/graph/view/merged_vitem_depth.rs deleted file mode 100644 index cb7ca637..00000000 --- a/packages/ranim-render/src/graph/view/merged_vitem_depth.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::{ - RenderContext, RenderTextures, - graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, - pipelines::MergedVItemDepthPipeline, - primitives::viewport::ViewportGpuPacket, -}; - -pub struct MergedVItemDepthNode; - -impl ViewRenderNodeTrait for MergedVItemDepthNode { - type Query = (); - - fn run( - &self, - #[cfg(not(feature = "profiling"))] encoder: &mut wgpu::CommandEncoder, - #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, - _packets: ::Output<'_>, - ctx: RenderContext, - viewport: &ViewportGpuPacket, - ) { - let Some(merged) = ctx.merged_buffer else { - return; - }; - if merged.item_count() == 0 { - return; - } - - #[cfg(feature = "profiling")] - let mut encoder = encoder.scope("Merged Depth Render Pass"); - - { - let RenderTextures { - depth_stencil_view, .. - } = ctx.render_textures; - let rpass_desc = wgpu::RenderPassDescriptor { - label: Some("Merged VItem Depth Render Pass"), - color_attachments: &[], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: depth_stencil_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }), - timestamp_writes: None, - occlusion_query_set: None, - }; - #[cfg(feature = "profiling")] - let mut rpass = - encoder.scoped_render_pass("Merged VItem Depth Render Pass", rpass_desc); - #[cfg(not(feature = "profiling"))] - let mut rpass = encoder.begin_render_pass(&rpass_desc); - rpass.set_pipeline( - &ctx.pipelines - .get_or_init::(ctx.wgpu_ctx), - ); - rpass.set_bind_group(0, &ctx.resolution_info.bind_group, &[]); - rpass.set_bind_group(1, &viewport.uniforms_bind_group.bind_group, &[]); - rpass.set_bind_group(2, merged.render_bind_group.as_ref().unwrap(), &[]); - rpass.draw(0..4, 0..merged.item_count()); - } - } -} diff --git a/packages/ranim-render/src/graph/view/vitem_color.rs b/packages/ranim-render/src/graph/view/vitem_color.rs index 7a26e065..57696900 100644 --- a/packages/ranim-render/src/graph/view/vitem_color.rs +++ b/packages/ranim-render/src/graph/view/vitem_color.rs @@ -2,29 +2,36 @@ use crate::{ RenderContext, RenderTextures, graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, pipelines::VItemColorPipeline, - primitives::{viewport::ViewportGpuPacket, vitem::VItemRenderInstance}, + primitives::viewport::ViewportGpuPacket, }; -pub struct VItemColorNode; +pub struct MergedVItemColorNode; + +impl ViewRenderNodeTrait for MergedVItemColorNode { + type Query = (); -impl ViewRenderNodeTrait for VItemColorNode { - type Query = VItemRenderInstance; fn run( &self, #[cfg(not(feature = "profiling"))] encoder: &mut wgpu::CommandEncoder, #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, - vitem2d_packets: ::Output<'_>, + _packets: ::Output<'_>, ctx: RenderContext, viewport: &ViewportGpuPacket, ) { - // VItem2d Render Pass + let Some(merged) = ctx.merged_buffer else { + return; + }; + if merged.item_count() == 0 { + return; + } + let RenderTextures { render_view, depth_stencil_view, .. } = ctx.render_textures; let rpass_desc = wgpu::RenderPassDescriptor { - label: Some("VItem2d Render Pass"), + label: Some("Merged VItem Color Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: render_view, resolve_target: None, @@ -46,7 +53,7 @@ impl ViewRenderNodeTrait for VItemColorNode { occlusion_query_set: None, }; #[cfg(feature = "profiling")] - let mut rpass = encoder.scoped_render_pass("VItem2d Render Pass", rpass_desc); + let mut rpass = encoder.scoped_render_pass("Merged VItem Color Render Pass", rpass_desc); #[cfg(not(feature = "profiling"))] let mut rpass = encoder.begin_render_pass(&rpass_desc); rpass.set_pipeline( @@ -55,9 +62,7 @@ impl ViewRenderNodeTrait for VItemColorNode { ); rpass.set_bind_group(0, &ctx.resolution_info.bind_group, &[]); rpass.set_bind_group(1, &viewport.uniforms_bind_group.bind_group, &[]); - vitem2d_packets - .iter() - .map(|h| ctx.render_pool.get_packet(h)) - .for_each(|vitem| vitem.encode_render_pass_command(&mut rpass)); + rpass.set_bind_group(2, merged.render_bind_group.as_ref().unwrap(), &[]); + rpass.draw(0..4, 0..merged.item_count()); } } diff --git a/packages/ranim-render/src/graph/view/vitem_compute.rs b/packages/ranim-render/src/graph/view/vitem_compute.rs index bc4e77c0..411aa7bf 100644 --- a/packages/ranim-render/src/graph/view/vitem_compute.rs +++ b/packages/ranim-render/src/graph/view/vitem_compute.rs @@ -2,41 +2,46 @@ use crate::{ RenderContext, graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, pipelines::VItemComputePipeline, - primitives::{viewport::ViewportGpuPacket, vitem::VItemRenderInstance}, + primitives::viewport::ViewportGpuPacket, }; -pub struct VItemComputeNode; -impl ViewRenderNodeTrait for VItemComputeNode { - type Query = VItemRenderInstance; +pub struct MergedVItemComputeNode; + +impl ViewRenderNodeTrait for MergedVItemComputeNode { + type Query = (); fn run( &self, #[cfg(not(feature = "profiling"))] encoder: &mut wgpu::CommandEncoder, #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, - vitem2d_packets: ::Output<'_>, + _packets: ::Output<'_>, ctx: RenderContext, _viewport: &ViewportGpuPacket, ) { + let Some(merged) = ctx.merged_buffer else { + return; + }; + if merged.item_count() == 0 { + return; + } + #[cfg(feature = "profiling")] - let mut encoder = encoder.scope("Compute Pass"); - // VItem2d Compute Pass + let mut encoder = encoder.scope("Merged Compute Pass"); + { #[cfg(feature = "profiling")] - let mut cpass = encoder.scoped_compute_pass("VItem2d Map Points Compute Pass"); + let mut cpass = encoder.scoped_compute_pass("Merged VItem Map Points Compute Pass"); #[cfg(not(feature = "profiling"))] let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("VItem2d Map Points Compute Pass"), + label: Some("Merged VItem Map Points Compute Pass"), timestamp_writes: None, }); cpass.set_pipeline( &ctx.pipelines .get_or_init::(ctx.wgpu_ctx), ); - - vitem2d_packets - .iter() - .map(|h| ctx.render_pool.get_packet(h)) - .for_each(|vitem| vitem.encode_compute_pass_command(&mut cpass)); + cpass.set_bind_group(0, merged.compute_bind_group.as_ref().unwrap(), &[]); + cpass.dispatch_workgroups(merged.total_points().div_ceil(256), 1, 1); } } } diff --git a/packages/ranim-render/src/graph/view/vitem_depth.rs b/packages/ranim-render/src/graph/view/vitem_depth.rs index 5658bae5..7674c651 100644 --- a/packages/ranim-render/src/graph/view/vitem_depth.rs +++ b/packages/ranim-render/src/graph/view/vitem_depth.rs @@ -2,29 +2,38 @@ use crate::{ RenderContext, RenderTextures, graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, pipelines::VItemDepthPipeline, - primitives::{viewport::ViewportGpuPacket, vitem::VItemRenderInstance}, + primitives::viewport::ViewportGpuPacket, }; -pub struct VItemDepthNode; -impl ViewRenderNodeTrait for VItemDepthNode { - type Query = VItemRenderInstance; +pub struct MergedVItemDepthNode; + +impl ViewRenderNodeTrait for MergedVItemDepthNode { + type Query = (); + fn run( &self, #[cfg(not(feature = "profiling"))] encoder: &mut wgpu::CommandEncoder, #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, - vitem2d_packets: ::Output<'_>, + _packets: ::Output<'_>, ctx: RenderContext, viewport: &ViewportGpuPacket, ) { + let Some(merged) = ctx.merged_buffer else { + return; + }; + if merged.item_count() == 0 { + return; + } + #[cfg(feature = "profiling")] - let mut encoder = encoder.scope("Depth Render Pass"); - // VItem2d Depth Render Pass + let mut encoder = encoder.scope("Merged Depth Render Pass"); + { let RenderTextures { depth_stencil_view, .. } = ctx.render_textures; let rpass_desc = wgpu::RenderPassDescriptor { - label: Some("VItem2d Depth Render Pass"), + label: Some("Merged VItem Depth Render Pass"), color_attachments: &[], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { view: depth_stencil_view, @@ -38,7 +47,8 @@ impl ViewRenderNodeTrait for VItemDepthNode { occlusion_query_set: None, }; #[cfg(feature = "profiling")] - let mut rpass = encoder.scoped_render_pass("VItem2d Depth Render Pass", rpass_desc); + let mut rpass = + encoder.scoped_render_pass("Merged VItem Depth Render Pass", rpass_desc); #[cfg(not(feature = "profiling"))] let mut rpass = encoder.begin_render_pass(&rpass_desc); rpass.set_pipeline( @@ -47,10 +57,8 @@ impl ViewRenderNodeTrait for VItemDepthNode { ); rpass.set_bind_group(0, &ctx.resolution_info.bind_group, &[]); rpass.set_bind_group(1, &viewport.uniforms_bind_group.bind_group, &[]); - vitem2d_packets - .iter() - .map(|h| ctx.render_pool.get_packet(h)) - .for_each(|vitem| vitem.encode_depth_render_pass_command(&mut rpass)); + rpass.set_bind_group(2, merged.render_bind_group.as_ref().unwrap(), &[]); + rpass.draw(0..4, 0..merged.item_count()); } } } diff --git a/packages/ranim-render/src/lib.rs b/packages/ranim-render/src/lib.rs index f13595b8..7199969f 100644 --- a/packages/ranim-render/src/lib.rs +++ b/packages/ranim-render/src/lib.rs @@ -20,7 +20,7 @@ use glam::{UVec3, uvec3}; use crate::{ graph::{AnyGlobalRenderNodeTrait, GlobalRenderGraph, RenderPackets}, - primitives::{merged_vitem::MergedVItemBuffer, viewport::ViewportUniform}, + primitives::{viewport::ViewportUniform, vitems::VItemsBuffer}, resource::{PipelinesPool, RenderPool, RenderTextures}, utils::{WgpuBuffer, WgpuVecBuffer}, }; @@ -92,7 +92,7 @@ pub struct RenderContext<'a> { pub resolution_info: &'a ResolutionInfo, pub clear_color: wgpu::Color, /// Present when using the merged rendering path. - pub merged_buffer: Option<&'a MergedVItemBuffer>, + pub merged_buffer: Option<&'a VItemsBuffer>, } // MARK: Renderer @@ -105,7 +105,7 @@ pub struct Renderer { render_graph: GlobalRenderGraph, /// Present when using the merged rendering path (lazily initialized on first use). - merged_buffer: Option, + merged_buffer: Option, #[cfg(feature = "profiling")] pub(crate) profiler: wgpu_profiler::GpuProfiler, @@ -124,29 +124,7 @@ impl Renderer { self.width as f32 / self.height as f32 } - #[allow(unused)] - #[deprecated(note = "will be replaced by the GPU-driven one instead.")] fn build_render_graph() -> GlobalRenderGraph { - use graph::*; - let mut render_graph = GlobalRenderGraph::new(); - let clear = render_graph.insert_node(ClearNode); - let view_render = render_graph.insert_node({ - use graph::view::*; - let mut render_graph = ViewRenderGraph::new(); - let vitem_compute = render_graph.insert_node(VItemComputeNode); - let vitem2d_depth = render_graph.insert_node(VItemDepthNode); - let vitem2d_render = render_graph.insert_node(VItemColorNode); - let oit_resolve = render_graph.insert_node(OITResolveNode); - render_graph.insert_edge(vitem_compute, vitem2d_depth); - render_graph.insert_edge(vitem2d_depth, vitem2d_render); - render_graph.insert_edge(vitem2d_render, oit_resolve); - render_graph - }); - render_graph.insert_edge(clear, view_render); - render_graph - } - - fn build_merged_render_graph() -> GlobalRenderGraph { use graph::*; let mut render_graph = GlobalRenderGraph::new(); let clear = render_graph.insert_node(ClearNode); @@ -167,13 +145,7 @@ impl Renderer { } pub fn new(ctx: &WgpuContext, width: u32, height: u32, oit_layers: usize) -> Self { - Self::new_with_graph( - ctx, - width, - height, - oit_layers, - Self::build_merged_render_graph(), - ) + Self::new_with_graph(ctx, width, height, oit_layers, Self::build_render_graph()) } fn new_with_graph( @@ -223,18 +195,10 @@ impl Renderer { let viewport = ViewportUniform::from_camera_frame(camera_frame, self.width, self.height); self.packets.push(pool.alloc_packet(ctx, &viewport)); - // Per-VItem packets (old path nodes query these; merged nodes ignore them) - self.packets.extend( - store - .vitems - .iter() - .map(|(_id, data)| pool.alloc_packet(ctx, data)), - ); - // Merged buffer (merged nodes read this; old nodes ignore it) let merged = self .merged_buffer - .get_or_insert_with(|| MergedVItemBuffer::new(ctx)); + .get_or_insert_with(|| VItemsBuffer::new(ctx)); merged.update(ctx, &store.vitems); // Encode & submit diff --git a/packages/ranim-render/src/pipelines/merged_vitem.rs b/packages/ranim-render/src/pipelines/merged_vitem.rs deleted file mode 100644 index 6d26503c..00000000 --- a/packages/ranim-render/src/pipelines/merged_vitem.rs +++ /dev/null @@ -1,188 +0,0 @@ -use std::ops::Deref; - -use crate::{ - ResolutionInfo, WgpuContext, - primitives::{merged_vitem::MergedVItemBuffer, viewport::ViewportBindGroup}, - resource::{GpuResource, OUTPUT_TEXTURE_FORMAT}, -}; - -// MARK: Compute pipeline - -pub struct MergedVItemComputePipeline { - pipeline: wgpu::ComputePipeline, -} - -impl Deref for MergedVItemComputePipeline { - type Target = wgpu::ComputePipeline; - fn deref(&self) -> &Self::Target { - &self.pipeline - } -} - -impl GpuResource for MergedVItemComputePipeline { - fn new(ctx: &WgpuContext) -> Self { - let module = &ctx - .device - .create_shader_module(wgpu::include_wgsl!("./shaders/merged_vitem_compute.wgsl")); - let layout = ctx - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Merged VItem Compute Pipeline Layout"), - bind_group_layouts: &[&MergedVItemBuffer::compute_bind_group_layout(ctx)], - push_constant_ranges: &[], - }); - let pipeline = ctx - .device - .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("Merged VItem Compute Pipeline"), - layout: Some(&layout), - module, - entry_point: Some("cs_main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - cache: None, - }); - Self { pipeline } - } -} - -// MARK: Color pipeline - -pub struct MergedVItemColorPipeline { - pipeline: wgpu::RenderPipeline, -} - -impl Deref for MergedVItemColorPipeline { - type Target = wgpu::RenderPipeline; - fn deref(&self) -> &Self::Target { - &self.pipeline - } -} - -impl GpuResource for MergedVItemColorPipeline { - fn new(ctx: &WgpuContext) -> Self { - let module = &ctx - .device - .create_shader_module(wgpu::include_wgsl!("./shaders/merged_vitem.wgsl")); - let layout = ctx - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Merged VItem Color Pipeline Layout"), - bind_group_layouts: &[ - &ResolutionInfo::create_bind_group_layout(ctx), - &ViewportBindGroup::bind_group_layout(ctx), - &MergedVItemBuffer::render_bind_group_layout(ctx), - ], - push_constant_ranges: &[], - }); - let pipeline = ctx - .device - .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Merged VItem Color Pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module, - entry_point: Some("fs_main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - targets: &[Some(wgpu::ColorTargetState { - format: OUTPUT_TEXTURE_FORMAT, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::LessEqual, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - Self { pipeline } - } -} - -// MARK: Depth pipeline - -pub struct MergedVItemDepthPipeline { - pipeline: wgpu::RenderPipeline, -} - -impl Deref for MergedVItemDepthPipeline { - type Target = wgpu::RenderPipeline; - fn deref(&self) -> &Self::Target { - &self.pipeline - } -} - -impl GpuResource for MergedVItemDepthPipeline { - fn new(ctx: &WgpuContext) -> Self { - let module = &ctx - .device - .create_shader_module(wgpu::include_wgsl!("./shaders/merged_vitem.wgsl")); - let layout = ctx - .device - .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Merged VItem Depth Pipeline Layout"), - bind_group_layouts: &[ - &ResolutionInfo::create_bind_group_layout(ctx), - &ViewportBindGroup::bind_group_layout(ctx), - &MergedVItemBuffer::render_bind_group_layout(ctx), - ], - push_constant_ranges: &[], - }); - let pipeline = ctx - .device - .create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Merged VItem Depth Pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module, - entry_point: Some("fs_depth_only"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - targets: &[], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - Self { pipeline } - } -} diff --git a/packages/ranim-render/src/pipelines/mod.rs b/packages/ranim-render/src/pipelines/mod.rs index 51a15584..f8a34797 100644 --- a/packages/ranim-render/src/pipelines/mod.rs +++ b/packages/ranim-render/src/pipelines/mod.rs @@ -1,13 +1,7 @@ //! The pipelines of ranim pub mod debug; -pub mod merged_vitem; pub mod oit_resolve; pub mod vitem; -pub mod vitem_compute; -pub use merged_vitem::{ - MergedVItemColorPipeline, MergedVItemComputePipeline, MergedVItemDepthPipeline, -}; pub use oit_resolve::OITResolvePipeline; -pub use vitem::{VItemColorPipeline, VItemDepthPipeline}; -pub use vitem_compute::VItemComputePipeline; +pub use vitem::{VItemColorPipeline, VItemComputePipeline, VItemDepthPipeline}; diff --git a/packages/ranim-render/src/pipelines/shaders/merged_vitem.wgsl b/packages/ranim-render/src/pipelines/shaders/merged_vitem.wgsl deleted file mode 100644 index fd8c98b2..00000000 --- a/packages/ranim-render/src/pipelines/shaders/merged_vitem.wgsl +++ /dev/null @@ -1,349 +0,0 @@ -// === Shared bindings (group 0: resolution/OIT, group 1: camera) === - -@group(0) @binding(0) var frame: vec3; -@group(0) @binding(1) var pixel_count: array>; -@group(0) @binding(2) var oit_colors: array; -@group(0) @binding(3) var oit_depths: array; - -struct CameraUniforms { - proj_mat: mat4x4, - view_mat: mat4x4, - half_frame_size: vec2, -} -@group(1) @binding(0) var cam_uniforms: CameraUniforms; - -// === Merged VItem data (group 2) === - -struct ItemInfo { - point_offset: u32, - point_count: u32, - attr_offset: u32, - attr_count: u32, -} - -struct PlaneData { - origin: vec4, - basis_u: vec4, - basis_v: vec4, -} - -@group(2) @binding(0) var item_infos: array; -@group(2) @binding(1) var planes: array; -// clip_boxes: 5 i32 per item [min_x, max_x, min_y, max_y, max_w] -@group(2) @binding(2) var clip_boxes: array; -@group(2) @binding(3) var points: array>; -@group(2) @binding(4) var fill_rgbas: array>; -@group(2) @binding(5) var stroke_rgbas: array>; -@group(2) @binding(6) var stroke_widths: array; - -// === Per-instance data passed from vertex to fragment === - -struct VertexOutput { - @builtin(position) frag_pos: vec4, - @location(0) pos: vec2, - @location(1) @interpolate(flat) instance_id: u32, -} - -// === Helper: access item's point/attr data === - -fn item_point(info: ItemInfo, local_idx: u32) -> vec2 { - return points[info.point_offset + local_idx].xy; -} - -fn item_is_closed(info: ItemInfo, local_idx: u32) -> bool { - return bool(points[info.point_offset + local_idx].z); -} - -fn item_fill_rgba(info: ItemInfo, anchor_idx: u32) -> vec4 { - return fill_rgbas[info.attr_offset + anchor_idx]; -} - -fn item_stroke_rgba(info: ItemInfo, anchor_idx: u32) -> vec4 { - return stroke_rgbas[info.attr_offset + anchor_idx]; -} - -fn item_stroke_width(info: ItemInfo, anchor_idx: u32) -> f32 { - return stroke_widths[info.attr_offset + anchor_idx]; -} - -// === SDF math (same as original) === - -fn pack_color(color: vec4) -> u32 { - let c = vec4(color * 255.0); - return (c.r) | (c.g << 8u) | (c.b << 16u) | (c.a << 24u); -} - -fn cross_2d(a: vec2, b: vec2) -> f32 { - return a.x * b.y - a.y * b.x; -} - -fn blend_color(f: vec4, b: vec4) -> vec4 { - let a = f.a + b.a * (1.0 - f.a); - return vec4( - f.r * f.a + b.r * b.a * (1.0 - f.a) / a, - f.g * f.a + b.g * b.a * (1.0 - f.a) / a, - f.b * f.a + b.b * b.a * (1.0 - f.a) / a, - a - ); -} - -fn solve_cubic(a: f32, b: f32, c: f32) -> vec3 { - let p = b - a * a / 3.0; - let p3 = p * p * p; - let q = a * (2.0 * a * a - 9.0 * b) / 27.0 + c; - let d = q * q + 4.0 * p3 / 27.0; - let offset = -a / 3.0; - if (d >= 0.0) { - let z = sqrt(d); - let x = (vec2(z, -z) - q) / 2.0; - let uv = sign(x) * pow(abs(x), vec2(1.0 / 3.0)); - return vec3(offset + uv.x + uv.y); - } - let v = acos(-sqrt(-27.0 / p3) * q / 2.0) / 3.0; - let m = cos(v); - let n = sin(v) * 1.732050808; - return vec3(m + m, -n - m, n - m) * sqrt(-p / 3.0) + offset; -} - -fn distance_bezier(pos: vec2, A: vec2, _B: vec2, C: vec2) -> f32 { - var B = mix(_B + vec2(1e-4), _B, abs(sign(_B * 2.0 - A - C))); - let a = B - A; - let b = A - B * 2.0 + C; - let c = a * 2.0; - let d = A - pos; - let k = vec3(3.0 * dot(a, b), 2.0 * dot(a, a) + dot(d, b), dot(d, a)) / dot(b, b); - let solved = solve_cubic(k.x, k.y, k.z); - let t = vec3( - clamp(solved.x, 0.0, 1.0), - clamp(solved.y, 0.0, 1.0), - clamp(solved.z, 0.0, 1.0), - ); - var ppos = A + (c + b * t.x) * t.x; - var dis = length(ppos - pos); - ppos = A + (c + b * t.y) * t.y; - dis = min(dis, length(ppos - pos)); - ppos = A + (c + b * t.z) * t.z; - dis = min(dis, length(ppos - pos)); - return dis; -} - -fn distance_line(pos: vec2, A: vec2, B: vec2) -> f32 { - let e = B - A; - let w = pos - A; - let b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0); - return length(b); -} - -fn sign_bezier(p: vec2, A: vec2, B: vec2, C: vec2) -> f32 { - let a: vec2 = C - A; - let b: vec2 = B - A; - let c: vec2 = p - A; - let denominator: f32 = a.x * b.y - b.x * a.y; - let bary: vec2 = vec2(cross_2d(c, b), cross_2d(a, c)) / denominator; - let d: vec2 = vec2(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y; - let sign_inside: f32 = select(1.0, sign(d.x * d.x - d.y), d.x > d.y); - let sign_left: f32 = sign_line(p, A, C); - return sign_inside * sign_left; -} - -fn sign_line(p: vec2, A: vec2, B: vec2) -> f32 { - let cond: vec3 = vec3( - (p.y >= A.y), - (p.y < B.y), - (cross_2d(B - A, p - A) > 0.0) - ); - return select(1.0, -1.0, all(cond) || !any(cond)); -} - -// === SDF rendering (adapted for merged buffers) === - -struct SubpathAttr { - end_idx: u32, - nearest_idx: u32, - d: f32, - sgn: f32, -} - -fn get_subpath_attr(pos: vec2, info: ItemInfo, start_local_idx: u32) -> SubpathAttr { - var attr: SubpathAttr; - attr.end_idx = info.point_count; - attr.nearest_idx = 0u; - attr.d = 3.40282346638528859812e38; - attr.sgn = 1.0; - - let n = (info.point_count - 1u) / 2u * 2u; - for (var i = start_local_idx; i < n; i += 2u) { - let a = item_point(info, i); - let b = item_point(info, i + 1u); - let c = item_point(info, i + 2u); - if length(b - a) == 0.0 { - attr.end_idx = i; - break; - } - - let v1 = normalize(b - a); - let v2 = normalize(c - b); - let is_line = abs(cross_2d(v1, v2)) < 0.0001 && dot(v1, v2) > 0.0; - let dist = select(distance_bezier(pos, a, b, c), distance_line(pos, a, c), is_line); - if dist < attr.d { - attr.d = dist; - attr.nearest_idx = i; - } - if item_is_closed(info, i) { - attr.sgn *= select(sign_bezier(pos, a, b, c), sign_line(pos, a, c), is_line); - } - } - - return attr; -} - -fn render(pos: vec2, info: ItemInfo) -> vec4 { - var idx = 0u; - var d = 3.40282346638528859812e38; - var sgn = 1.0; - - var start_idx = 0u; - while start_idx < info.point_count { - let attr = get_subpath_attr(pos, info, start_idx); - if attr.d < d { - idx = attr.nearest_idx; - d = attr.d; - } - sgn *= attr.sgn; - start_idx = attr.end_idx + 2u; - } - - let sgn_d = sgn * d; - - let e = item_point(info, idx + 1u) - item_point(info, idx); - let w = pos - item_point(info, idx); - let ratio = clamp(dot(w, e) / dot(e, e), 0.0, 1.0); - let anchor_index = idx / 2u; - - let antialias_radius = 0.015 / 4.0; - - var fill_rgba: vec4 = select( - vec4(0.0), - mix(item_fill_rgba(info, anchor_index), item_fill_rgba(info, anchor_index + 1u), ratio), - item_is_closed(info, idx) - ); - fill_rgba.a *= smoothstep(1.0, -1.0, (sgn_d) / antialias_radius); - - var stroke_width = mix( - item_stroke_width(info, anchor_index), - item_stroke_width(info, anchor_index + 1u), - ratio - ); - var stroke_rgba: vec4 = mix( - item_stroke_rgba(info, anchor_index), - item_stroke_rgba(info, anchor_index + 1u), - ratio - ); - stroke_rgba.a *= smoothstep(1.0, -1.0, (d - stroke_width) / antialias_radius); - - var f_color = blend_color(stroke_rgba, fill_rgba); - - if (f_color.a < 0.01) { - discard; - } - - return f_color; -} - -// === Fragment shaders === - -struct FragmentOutput { - @location(0) color: vec4, - @builtin(frag_depth) depth: f32, -} - -@fragment -fn fs_main( - @builtin(position) frag_pos: vec4, - @location(0) pos: vec2, - @location(1) @interpolate(flat) instance_id: u32, -) -> FragmentOutput { - var out: FragmentOutput; - let info = item_infos[instance_id]; - let color = render(pos, info); - - if (color.a >= 0.99) { - out.color = color; - out.depth = frag_pos.z; - return out; - } - - let coords = vec2(floor(frag_pos.xy)); - let pixel_idx = coords.y * frame.x + coords.x; - let layer_idx = atomicAdd(&pixel_count[pixel_idx], 1u); - - if (layer_idx < frame.z) { - let buffer_idx = pixel_idx * frame.z + layer_idx; - oit_colors[buffer_idx] = pack_color(color); - oit_depths[buffer_idx] = frag_pos.z; - } - - discard; - out.color = vec4(0.0, 0.0, 0.0, 0.0); - out.depth = 1.0; - return out; -} - -@fragment -fn fs_depth_only( - @builtin(position) frag_pos: vec4, - @location(0) pos: vec2, - @location(1) @interpolate(flat) instance_id: u32, -) -> @builtin(frag_depth) f32 { - let info = item_infos[instance_id]; - let color = render(pos, info); - - if (color.a < 0.99) { - discard; - } - - return frag_pos.z; -} - -// === Vertex shader === - -@vertex -fn vs_main( - @builtin(vertex_index) vertex_index: u32, - @builtin(instance_index) instance_index: u32, -) -> VertexOutput { - var out: VertexOutput; - - let info = item_infos[instance_index]; - let plane = planes[instance_index]; - let clip_base = instance_index * 5u; - - let scale = 1000.0; - let min_x = f32(clip_boxes[clip_base + 0u]) / scale; - let max_x = f32(clip_boxes[clip_base + 1u]) / scale; - let min_y = f32(clip_boxes[clip_base + 2u]) / scale; - let max_y = f32(clip_boxes[clip_base + 3u]) / scale; - let max_w = f32(clip_boxes[clip_base + 4u]) / scale; - - var clip_point: vec2; - clip_point.x = select( - max_x + max_w, - min_x - max_w, - (vertex_index & 2u) == 0u - ); - clip_point.y = select( - max_y + max_w, - min_y - max_w, - (vertex_index & 1u) == 0u - ); - - let u = clip_point.x; - let v = clip_point.y; - - let pos3d = plane.origin.xyz + u * plane.basis_u.xyz + v * plane.basis_v.xyz; - - out.frag_pos = cam_uniforms.proj_mat * cam_uniforms.view_mat * vec4(pos3d, 1.0); - out.pos = clip_point; - out.instance_id = instance_index; - return out; -} diff --git a/packages/ranim-render/src/pipelines/shaders/merged_vitem_compute.wgsl b/packages/ranim-render/src/pipelines/shaders/merged_vitem_compute.wgsl deleted file mode 100644 index 9d4d2ba5..00000000 --- a/packages/ranim-render/src/pipelines/shaders/merged_vitem_compute.wgsl +++ /dev/null @@ -1,94 +0,0 @@ -// Per-item metadata -struct ItemInfo { - point_offset: u32, - point_count: u32, - attr_offset: u32, - attr_count: u32, -} - -struct Plane { - origin: vec3, - basis_u: vec3, - basis_v: vec3, -} - -// Padded version matching the Rust repr -struct PlaneData { - origin: vec4, - basis_u: vec4, - basis_v: vec4, -} - -struct ClipBox { - min_x: atomic, - max_x: atomic, - min_y: atomic, - max_y: atomic, - max_w: atomic, -} - -@group(0) @binding(0) var item_infos: array; -@group(0) @binding(1) var planes: array; -@group(0) @binding(2) var points3d: array>; -@group(0) @binding(3) var stroke_widths: array; -@group(0) @binding(4) var points2d: array>; -// clip_boxes: 5 i32 per item, laid out as [min_x, max_x, min_y, max_y, max_w, ...] -@group(0) @binding(5) var clip_boxes: array>; - -@compute -@workgroup_size(256) -fn cs_main( - @builtin(global_invocation_id) global_invocation_id: vec3, -) { - let total_points = arrayLength(&points3d); - let index = global_invocation_id.x; - if index >= total_points { - return; - } - - // Binary search to find which item this point belongs to - let item_count = arrayLength(&item_infos); - var lo = 0u; - var hi = item_count; - while lo < hi { - let mid = (lo + hi) / 2u; - let info = item_infos[mid]; - if index < info.point_offset { - hi = mid; - } else if index >= info.point_offset + info.point_count { - lo = mid + 1u; - } else { - lo = mid; - break; - } - } - let item_idx = lo; - let info = item_infos[item_idx]; - let plane_data = planes[item_idx]; - - let plane_origin = plane_data.origin.xyz; - let plane_basis_u = plane_data.basis_u.xyz; - let plane_basis_v = plane_data.basis_v.xyz; - - let p_vec = points3d[index]; - let p = p_vec.xyz; - let is_closed = p_vec.w; - let diff = p - plane_origin; - - let x = dot(diff, plane_basis_u); - let y = dot(diff, plane_basis_v); - - // Local index within this item's points - let local_idx = index - info.point_offset; - let w = stroke_widths[info.attr_offset + local_idx / 2u]; - - points2d[index] = vec4(x, y, is_closed, 0.0); - - let scale = 1000.0; - let clip_base = item_idx * 5u; - atomicMin(&clip_boxes[clip_base + 0u], i32(floor(x * scale))); - atomicMax(&clip_boxes[clip_base + 1u], i32(ceil(x * scale))); - atomicMin(&clip_boxes[clip_base + 2u], i32(floor(y * scale))); - atomicMax(&clip_boxes[clip_base + 3u], i32(ceil(y * scale))); - atomicMax(&clip_boxes[clip_base + 4u], i32(ceil(w * scale))); -} diff --git a/packages/ranim-render/src/pipelines/shaders/vitem.wgsl b/packages/ranim-render/src/pipelines/shaders/vitem.wgsl index afd47e2a..fd8c98b2 100644 --- a/packages/ranim-render/src/pipelines/shaders/vitem.wgsl +++ b/packages/ranim-render/src/pipelines/shaders/vitem.wgsl @@ -1,52 +1,76 @@ +// === Shared bindings (group 0: resolution/OIT, group 1: camera) === + @group(0) @binding(0) var frame: vec3; @group(0) @binding(1) var pixel_count: array>; @group(0) @binding(2) var oit_colors: array; @group(0) @binding(3) var oit_depths: array; + struct CameraUniforms { proj_mat: mat4x4, view_mat: mat4x4, half_frame_size: vec2, } -@group(1) @binding(0) var cam_uniforms : CameraUniforms; +@group(1) @binding(0) var cam_uniforms: CameraUniforms; -fn pack_color(color: vec4) -> u32 { - let c = vec4(color * 255.0); - return (c.r) | (c.g << 8u) | (c.b << 16u) | (c.a << 24u); +// === Merged VItem data (group 2) === + +struct ItemInfo { + point_offset: u32, + point_count: u32, + attr_offset: u32, + attr_count: u32, } -@group(2) @binding(0) var points: array>; // x, y, is_closed, padding -@group(2) @binding(1) var fill_rgbas: array>; -@group(2) @binding(2) var stroke_rgbas: array>; -@group(2) @binding(3) var stroke_widths: array; -struct ClipBox { - min_x: i32, - max_x: i32, - min_y: i32, - max_y: i32, - max_w: i32, +struct PlaneData { + origin: vec4, + basis_u: vec4, + basis_v: vec4, } -@group(2) @binding(4) var clip_box: ClipBox; -struct Plane { - origin: vec3, - basis_u: vec3, - basis_v: vec3, +@group(2) @binding(0) var item_infos: array; +@group(2) @binding(1) var planes: array; +// clip_boxes: 5 i32 per item [min_x, max_x, min_y, max_y, max_w] +@group(2) @binding(2) var clip_boxes: array; +@group(2) @binding(3) var points: array>; +@group(2) @binding(4) var fill_rgbas: array>; +@group(2) @binding(5) var stroke_rgbas: array>; +@group(2) @binding(6) var stroke_widths: array; + +// === Per-instance data passed from vertex to fragment === + +struct VertexOutput { + @builtin(position) frag_pos: vec4, + @location(0) pos: vec2, + @location(1) @interpolate(flat) instance_id: u32, } -@group(2) @binding(5) var plane: Plane; -fn point(idx: u32) -> vec2 { - return points[idx].xy; +// === Helper: access item's point/attr data === + +fn item_point(info: ItemInfo, local_idx: u32) -> vec2 { + return points[info.point_offset + local_idx].xy; } -fn is_closed(idx: u32) -> bool { - return bool(points[idx].z); + +fn item_is_closed(info: ItemInfo, local_idx: u32) -> bool { + return bool(points[info.point_offset + local_idx].z); } -struct SubpathAttr { - end_idx: u32, - nearest_idx: u32, - d: f32, // distance - sgn: f32, - debug: vec4, +fn item_fill_rgba(info: ItemInfo, anchor_idx: u32) -> vec4 { + return fill_rgbas[info.attr_offset + anchor_idx]; +} + +fn item_stroke_rgba(info: ItemInfo, anchor_idx: u32) -> vec4 { + return stroke_rgbas[info.attr_offset + anchor_idx]; +} + +fn item_stroke_width(info: ItemInfo, anchor_idx: u32) -> f32 { + return stroke_widths[info.attr_offset + anchor_idx]; +} + +// === SDF math (same as original) === + +fn pack_color(color: vec4) -> u32 { + let c = vec4(color * 255.0); + return (c.r) | (c.g << 8u) | (c.b << 16u) | (c.a << 24u); } fn cross_2d(a: vec2, b: vec2) -> f32 { @@ -66,11 +90,9 @@ fn blend_color(f: vec4, b: vec4) -> vec4 { fn solve_cubic(a: f32, b: f32, c: f32) -> vec3 { let p = b - a * a / 3.0; let p3 = p * p * p; - let q = a * (2.0 * a * a - 9.0 * b) / 27.0 + c; let d = q * q + 4.0 * p3 / 27.0; let offset = -a / 3.0; - if (d >= 0.0) { let z = sqrt(d); let x = (vec2(z, -z) - q) / 2.0; @@ -85,13 +107,10 @@ fn solve_cubic(a: f32, b: f32, c: f32) -> vec3 { fn distance_bezier(pos: vec2, A: vec2, _B: vec2, C: vec2) -> f32 { var B = mix(_B + vec2(1e-4), _B, abs(sign(_B * 2.0 - A - C))); - // var B = _B; - let a = B - A; let b = A - B * 2.0 + C; let c = a * 2.0; let d = A - pos; - let k = vec3(3.0 * dot(a, b), 2.0 * dot(a, a) + dot(d, b), dot(d, a)) / dot(b, b); let solved = solve_cubic(k.x, k.y, k.z); let t = vec3( @@ -99,14 +118,12 @@ fn distance_bezier(pos: vec2, A: vec2, _B: vec2, C: vec2) -> clamp(solved.y, 0.0, 1.0), clamp(solved.z, 0.0, 1.0), ); - var ppos = A + (c + b * t.x) * t.x; var dis = length(ppos - pos); ppos = A + (c + b * t.y) * t.y; dis = min(dis, length(ppos - pos)); ppos = A + (c + b * t.z) * t.z; dis = min(dis, length(ppos - pos)); - return dis; } @@ -121,20 +138,14 @@ fn sign_bezier(p: vec2, A: vec2, B: vec2, C: vec2) -> f32 { let a: vec2 = C - A; let b: vec2 = B - A; let c: vec2 = p - A; - let denominator: f32 = a.x * b.y - b.x * a.y; let bary: vec2 = vec2(cross_2d(c, b), cross_2d(a, c)) / denominator; - let d: vec2 = vec2(bary.y * 0.5, 0.0) + 1.0 - bary.x - bary.y; - let sign_inside: f32 = select(1.0, sign(d.x * d.x - d.y), d.x > d.y); let sign_left: f32 = sign_line(p, A, C); - return sign_inside * sign_left; } -// left -> -1.0 -// right -> 1.0 fn sign_line(p: vec2, A: vec2, B: vec2) -> f32 { let cond: vec3 = vec3( (p.y >= A.y), @@ -144,21 +155,27 @@ fn sign_line(p: vec2, A: vec2, B: vec2) -> f32 { return select(1.0, -1.0, all(cond) || !any(cond)); } -fn get_subpath_attr(pos: vec2, start_idx: u32) -> SubpathAttr { - let points_len = arrayLength(&points); +// === SDF rendering (adapted for merged buffers) === + +struct SubpathAttr { + end_idx: u32, + nearest_idx: u32, + d: f32, + sgn: f32, +} +fn get_subpath_attr(pos: vec2, info: ItemInfo, start_local_idx: u32) -> SubpathAttr { var attr: SubpathAttr; - attr.end_idx = points_len; + attr.end_idx = info.point_count; attr.nearest_idx = 0u; attr.d = 3.40282346638528859812e38; attr.sgn = 1.0; - attr.debug = vec4(1.0, 1.0, 1.0, 1.0); - let n = (points_len - 1) / 2 * 2; - for (var i = start_idx; i < n; i += 2u) { - let a = point(i); - let b = point(i + 1u); - let c = point(i + 2u); + let n = (info.point_count - 1u) / 2u * 2u; + for (var i = start_local_idx; i < n; i += 2u) { + let a = item_point(info, i); + let b = item_point(info, i + 1u); + let c = item_point(info, i + 2u); if length(b - a) == 0.0 { attr.end_idx = i; break; @@ -166,14 +183,13 @@ fn get_subpath_attr(pos: vec2, start_idx: u32) -> SubpathAttr { let v1 = normalize(b - a); let v2 = normalize(c - b); - let is_line = abs(cross_2d(v1, v2)) < 0.0001 && dot(v1, v2) > 0.0; let dist = select(distance_bezier(pos, a, b, c), distance_line(pos, a, c), is_line); if dist < attr.d { attr.d = dist; attr.nearest_idx = i; } - if is_closed(i) { + if item_is_closed(info, i) { attr.sgn *= select(sign_bezier(pos, a, b, c), sign_line(pos, a, c), is_line); } } @@ -181,54 +197,52 @@ fn get_subpath_attr(pos: vec2, start_idx: u32) -> SubpathAttr { return attr; } -fn render(pos: vec2) -> vec4 { - let points_len = arrayLength(&points); - +fn render(pos: vec2, info: ItemInfo) -> vec4 { var idx = 0u; var d = 3.40282346638528859812e38; var sgn = 1.0; var start_idx = 0u; - while start_idx < points_len { - let attr = get_subpath_attr(pos, start_idx); + while start_idx < info.point_count { + let attr = get_subpath_attr(pos, info, start_idx); if attr.d < d { idx = attr.nearest_idx; d = attr.d; } sgn *= attr.sgn; - start_idx = attr.end_idx + 2; + start_idx = attr.end_idx + 2u; } let sgn_d = sgn * d; - let e = point(idx + 1u).xy - point(idx).xy; - let w = pos.xy - point(idx).xy; + let e = item_point(info, idx + 1u) - item_point(info, idx); + let w = pos - item_point(info, idx); let ratio = clamp(dot(w, e) / dot(e, e), 0.0, 1.0); - let anchor_index = idx / 2; - - // TODO: Antialias - this depends on screen space derivative? - // Since we are in local space, we need to know the pixel scale. - // dpdx and dpdy can help. - let antialias_radius = 0.015 / 4.0; // Fixed for now, should use fwidth - // Antialias using screen space derivative of the coordinate system - // We use the gradient of 'pos' instead of 'd' because 'd' has discontinuities - // at voronoi boundaries (subpath joins), causing artifacts/striations when using fwidth(d). - // let dist_grad = max(length(dpdx(pos)), length(dpdy(pos))); - // let dist_grad = length(fwidth(pos)); - // let antialias_radius = dist_grad * 0.75; - - var fill_rgba: vec4 = select(vec4(0.0), mix(fill_rgbas[anchor_index], fill_rgbas[anchor_index + 1], ratio), is_closed(idx)); + let anchor_index = idx / 2u; + + let antialias_radius = 0.015 / 4.0; + + var fill_rgba: vec4 = select( + vec4(0.0), + mix(item_fill_rgba(info, anchor_index), item_fill_rgba(info, anchor_index + 1u), ratio), + item_is_closed(info, idx) + ); fill_rgba.a *= smoothstep(1.0, -1.0, (sgn_d) / antialias_radius); - var stroke_width = mix(stroke_widths[anchor_index], stroke_widths[anchor_index + 1], ratio); - // stroke_width = stroke_width * dist_grad; - // stroke_width = stroke_width * 100.0; - var stroke_rgba: vec4 = mix(stroke_rgbas[anchor_index], stroke_rgbas[anchor_index + 1], ratio); + var stroke_width = mix( + item_stroke_width(info, anchor_index), + item_stroke_width(info, anchor_index + 1u), + ratio + ); + var stroke_rgba: vec4 = mix( + item_stroke_rgba(info, anchor_index), + item_stroke_rgba(info, anchor_index + 1u), + ratio + ); stroke_rgba.a *= smoothstep(1.0, -1.0, (d - stroke_width) / antialias_radius); var f_color = blend_color(stroke_rgba, fill_rgba); - // Discard if fully transparent if (f_color.a < 0.01) { discard; } @@ -236,19 +250,22 @@ fn render(pos: vec2) -> vec4 { return f_color; } +// === Fragment shaders === + struct FragmentOutput { @location(0) color: vec4, @builtin(frag_depth) depth: f32, } -struct DepthOnlyOutput { - @builtin(frag_depth) depth: f32, -} - @fragment -fn fs_main(@builtin(position) frag_pos: vec4, @location(0) pos: vec2) -> FragmentOutput { +fn fs_main( + @builtin(position) frag_pos: vec4, + @location(0) pos: vec2, + @location(1) @interpolate(flat) instance_id: u32, +) -> FragmentOutput { var out: FragmentOutput; - let color = render(pos); + let info = item_infos[instance_id]; + let color = render(pos, info); if (color.a >= 0.99) { out.color = color; @@ -273,10 +290,14 @@ fn fs_main(@builtin(position) frag_pos: vec4, @location(0) pos: vec2) } @fragment -fn fs_depth_only(@builtin(position) frag_pos: vec4, @location(0) pos: vec2) -> @builtin(frag_depth) f32 { - let color = render(pos); +fn fs_depth_only( + @builtin(position) frag_pos: vec4, + @location(0) pos: vec2, + @location(1) @interpolate(flat) instance_id: u32, +) -> @builtin(frag_depth) f32 { + let info = item_infos[instance_id]; + let color = render(pos, info); - // Only write depth for opaque parts. if (color.a < 0.99) { discard; } @@ -284,30 +305,26 @@ fn fs_depth_only(@builtin(position) frag_pos: vec4, @location(0) pos: vec2< return frag_pos.z; } -struct VertexOutput { - @builtin(position) frag_pos: vec4, - @location(0) pos: vec2, -} +// === Vertex shader === -// At here we simply construct two triangles that covers all vitem points. -// -// We'll need the following vertex data: -// - frag_pos: the clip space coordinate. -// - pos: the pos in the plane's coordinate system used to calculate sdf things -// -// Actually, we don't mind which coordinate system `pos` use. As long as it @vertex -fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { +fn vs_main( + @builtin(vertex_index) vertex_index: u32, + @builtin(instance_index) instance_index: u32, +) -> VertexOutput { var out: VertexOutput; + let info = item_infos[instance_index]; + let plane = planes[instance_index]; + let clip_base = instance_index * 5u; + let scale = 1000.0; - let min_x = f32(clip_box.min_x) / scale; - let max_x = f32(clip_box.max_x) / scale; - let min_y = f32(clip_box.min_y) / scale; - let max_y = f32(clip_box.max_y) / scale; - let max_w = f32(clip_box.max_w) / scale; + let min_x = f32(clip_boxes[clip_base + 0u]) / scale; + let max_x = f32(clip_boxes[clip_base + 1u]) / scale; + let min_y = f32(clip_boxes[clip_base + 2u]) / scale; + let max_y = f32(clip_boxes[clip_base + 3u]) / scale; + let max_w = f32(clip_boxes[clip_base + 4u]) / scale; - // Clip point in the plane's coordinate system var clip_point: vec2; clip_point.x = select( max_x + max_w, @@ -323,9 +340,10 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput { let u = clip_point.x; let v = clip_point.y; - let pos3d = plane.origin + u * plane.basis_u + v * plane.basis_v; + let pos3d = plane.origin.xyz + u * plane.basis_u.xyz + v * plane.basis_v.xyz; out.frag_pos = cam_uniforms.proj_mat * cam_uniforms.view_mat * vec4(pos3d, 1.0); out.pos = clip_point; + out.instance_id = instance_index; return out; } diff --git a/packages/ranim-render/src/pipelines/shaders/vitem_compute.wgsl b/packages/ranim-render/src/pipelines/shaders/vitem_compute.wgsl index a1dd6b8b..9d4d2ba5 100644 --- a/packages/ranim-render/src/pipelines/shaders/vitem_compute.wgsl +++ b/packages/ranim-render/src/pipelines/shaders/vitem_compute.wgsl @@ -1,16 +1,24 @@ +// Per-item metadata +struct ItemInfo { + point_offset: u32, + point_count: u32, + attr_offset: u32, + attr_count: u32, +} + struct Plane { origin: vec3, basis_u: vec3, basis_v: vec3, } -@group(0) @binding(0) var plane: Plane; -// (x, y, z, is_closed) -@group(0) @binding(1) var points3d: array>; -// width -@group(0) @binding(2) var stroke_width: array; -// (x, y, is_closed, padding) -@group(0) @binding(3) var points2d: array>; +// Padded version matching the Rust repr +struct PlaneData { + origin: vec4, + basis_u: vec4, + basis_v: vec4, +} + struct ClipBox { min_x: atomic, max_x: atomic, @@ -18,40 +26,69 @@ struct ClipBox { max_y: atomic, max_w: atomic, } -@group(0) @binding(4) var clip_points: ClipBox; -@group(0) @binding(5) var point_cnt: u32; + +@group(0) @binding(0) var item_infos: array; +@group(0) @binding(1) var planes: array; +@group(0) @binding(2) var points3d: array>; +@group(0) @binding(3) var stroke_widths: array; +@group(0) @binding(4) var points2d: array>; +// clip_boxes: 5 i32 per item, laid out as [min_x, max_x, min_y, max_y, max_w, ...] +@group(0) @binding(5) var clip_boxes: array>; @compute @workgroup_size(256) fn cs_main( @builtin(global_invocation_id) global_invocation_id: vec3, - @builtin(num_workgroups) num_workgroups: vec3 ) { + let total_points = arrayLength(&points3d); let index = global_invocation_id.x; - if index >= point_cnt { + if index >= total_points { return; } + // Binary search to find which item this point belongs to + let item_count = arrayLength(&item_infos); + var lo = 0u; + var hi = item_count; + while lo < hi { + let mid = (lo + hi) / 2u; + let info = item_infos[mid]; + if index < info.point_offset { + hi = mid; + } else if index >= info.point_offset + info.point_count { + lo = mid + 1u; + } else { + lo = mid; + break; + } + } + let item_idx = lo; + let info = item_infos[item_idx]; + let plane_data = planes[item_idx]; + + let plane_origin = plane_data.origin.xyz; + let plane_basis_u = plane_data.basis_u.xyz; + let plane_basis_v = plane_data.basis_v.xyz; + let p_vec = points3d[index]; let p = p_vec.xyz; let is_closed = p_vec.w; - let diff = p - plane.origin; + let diff = p - plane_origin; + + let x = dot(diff, plane_basis_u); + let y = dot(diff, plane_basis_v); - // Project diff onto the plane spanned by basis_u and basis_v - // Since basis_u and basis_v are orthogonal and normalized, we can use dot product directly. - let x = dot(diff, plane.basis_u); - let y = dot(diff, plane.basis_v); - let w = stroke_width[index / 2]; + // Local index within this item's points + let local_idx = index - info.point_offset; + let w = stroke_widths[info.attr_offset + local_idx / 2u]; points2d[index] = vec4(x, y, is_closed, 0.0); - // Note that atomicMin/Max can only work on u32 or i32 - // so we turn the float into int by multiplying by a scale factor - // and turn it back in the vertex shader let scale = 1000.0; - atomicMin(&clip_points.min_x, i32(floor(x * scale))); - atomicMax(&clip_points.max_x, i32(ceil(x * scale))); - atomicMin(&clip_points.min_y, i32(floor(y * scale))); - atomicMax(&clip_points.max_y, i32(ceil(y * scale))); - atomicMax(&clip_points.max_w, i32(ceil(w * scale))); + let clip_base = item_idx * 5u; + atomicMin(&clip_boxes[clip_base + 0u], i32(floor(x * scale))); + atomicMax(&clip_boxes[clip_base + 1u], i32(ceil(x * scale))); + atomicMin(&clip_boxes[clip_base + 2u], i32(floor(y * scale))); + atomicMax(&clip_boxes[clip_base + 3u], i32(ceil(y * scale))); + atomicMax(&clip_boxes[clip_base + 4u], i32(ceil(w * scale))); } diff --git a/packages/ranim-render/src/pipelines/vitem.rs b/packages/ranim-render/src/pipelines/vitem.rs index 20c3a811..58bd61e7 100644 --- a/packages/ranim-render/src/pipelines/vitem.rs +++ b/packages/ranim-render/src/pipelines/vitem.rs @@ -2,159 +2,51 @@ use std::ops::Deref; use crate::{ ResolutionInfo, WgpuContext, - primitives::viewport::ViewportBindGroup, + primitives::{viewport::ViewportBindGroup, vitems::VItemsBuffer}, resource::{GpuResource, OUTPUT_TEXTURE_FORMAT}, }; -pub struct RenderBindGroup(wgpu::BindGroup); +// MARK: Compute pipeline -impl AsRef for RenderBindGroup { - fn as_ref(&self) -> &wgpu::BindGroup { - &self.0 - } +pub struct VItemComputePipeline { + pipeline: wgpu::ComputePipeline, } -impl RenderBindGroup { - pub fn bind_group_layout(ctx: &WgpuContext) -> wgpu::BindGroupLayout { - ctx.device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("VItem2d Render Bind Group Layout"), - entries: &[ - // points2d - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // fill_rgbas - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // stroke_rgbas - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // stroke_widths - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // clip_info - wgpu::BindGroupLayoutEntry { - binding: 4, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // plane (origin, basis) - wgpu::BindGroupLayoutEntry { - binding: 5, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }) +impl Deref for VItemComputePipeline { + type Target = wgpu::ComputePipeline; + fn deref(&self) -> &Self::Target { + &self.pipeline } +} - fn new_bind_group( - ctx: &WgpuContext, - points: &wgpu::Buffer, - fill_rgbas: &wgpu::Buffer, - stroke_rgbas: &wgpu::Buffer, - stroke_widths: &wgpu::Buffer, - clip_info: &wgpu::Buffer, - plane: &wgpu::Buffer, - ) -> wgpu::BindGroup { - ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("VItem2d Render Bind Group"), - layout: &RenderBindGroup::bind_group_layout(ctx), - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(points.as_entire_buffer_binding()), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Buffer(fill_rgbas.as_entire_buffer_binding()), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Buffer( - stroke_rgbas.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::Buffer( - stroke_widths.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 4, - resource: wgpu::BindingResource::Buffer(clip_info.as_entire_buffer_binding()), - }, - wgpu::BindGroupEntry { - binding: 5, - resource: wgpu::BindingResource::Buffer(plane.as_entire_buffer_binding()), - }, - ], - }) - } - pub(crate) fn new( - ctx: &WgpuContext, - points: &wgpu::Buffer, - fill_rgbas: &wgpu::Buffer, - stroke_rgbas: &wgpu::Buffer, - stroke_widths: &wgpu::Buffer, - clip_info: &wgpu::Buffer, - plane: &wgpu::Buffer, - ) -> Self { - Self(Self::new_bind_group( - ctx, - points, - fill_rgbas, - stroke_rgbas, - stroke_widths, - clip_info, - plane, - )) +impl GpuResource for VItemComputePipeline { + fn new(ctx: &WgpuContext) -> Self { + let module = &ctx + .device + .create_shader_module(wgpu::include_wgsl!("./shaders/vitem_compute.wgsl")); + let layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("VItem Compute Pipeline Layout"), + bind_group_layouts: &[&VItemsBuffer::compute_bind_group_layout(ctx)], + push_constant_ranges: &[], + }); + let pipeline = ctx + .device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: Some("VItem Compute Pipeline"), + layout: Some(&layout), + module, + entry_point: Some("cs_main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + cache: None, + }); + Self { pipeline } } } +// MARK: Color pipeline + pub struct VItemColorPipeline { pipeline: wgpu::RenderPipeline, } @@ -167,63 +59,67 @@ impl Deref for VItemColorPipeline { } impl GpuResource for VItemColorPipeline { - fn new(wgpu_ctx: &WgpuContext) -> Self { - let WgpuContext { device, .. } = wgpu_ctx; - - let module = &device.create_shader_module(wgpu::include_wgsl!("./shaders/vitem.wgsl")); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("VItem2d Pipeline Layout"), - bind_group_layouts: &[ - &ResolutionInfo::create_bind_group_layout(wgpu_ctx), - &ViewportBindGroup::bind_group_layout(wgpu_ctx), - &RenderBindGroup::bind_group_layout(wgpu_ctx), - ], - push_constant_ranges: &[], - }); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("VItem2d Color Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module, - entry_point: Some("fs_main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - targets: &[Some(wgpu::ColorTargetState { - format: OUTPUT_TEXTURE_FORMAT, - blend: Some(wgpu::BlendState::ALPHA_BLENDING), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::LessEqual, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - + fn new(ctx: &WgpuContext) -> Self { + let module = &ctx + .device + .create_shader_module(wgpu::include_wgsl!("./shaders/vitem.wgsl")); + let layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("VItem Color Pipeline Layout"), + bind_group_layouts: &[ + &ResolutionInfo::create_bind_group_layout(ctx), + &ViewportBindGroup::bind_group_layout(ctx), + &VItemsBuffer::render_bind_group_layout(ctx), + ], + push_constant_ranges: &[], + }); + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("VItem Color Pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module, + entry_point: Some("fs_main"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(wgpu::ColorTargetState { + format: OUTPUT_TEXTURE_FORMAT, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: false, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); Self { pipeline } } } +// MARK: Depth pipeline + pub struct VItemDepthPipeline { pipeline: wgpu::RenderPipeline, } @@ -236,55 +132,57 @@ impl Deref for VItemDepthPipeline { } impl GpuResource for VItemDepthPipeline { - fn new(wgpu_ctx: &WgpuContext) -> Self { - let WgpuContext { device, .. } = wgpu_ctx; - - let module = &device.create_shader_module(wgpu::include_wgsl!("./shaders/vitem.wgsl")); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("VItem2d Pipeline Layout"), - bind_group_layouts: &[ - &ResolutionInfo::create_bind_group_layout(wgpu_ctx), - &ViewportBindGroup::bind_group_layout(wgpu_ctx), - &RenderBindGroup::bind_group_layout(wgpu_ctx), - ], - push_constant_ranges: &[], - }); - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("VItem2d Depth Pipeline"), - layout: Some(&pipeline_layout), - vertex: wgpu::VertexState { - module, - entry_point: Some("vs_main"), - buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module, - entry_point: Some("fs_depth_only"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - targets: &[], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleStrip, - ..Default::default() - }, - depth_stencil: Some(wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), - }), - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - + fn new(ctx: &WgpuContext) -> Self { + let module = &ctx + .device + .create_shader_module(wgpu::include_wgsl!("./shaders/vitem.wgsl")); + let layout = ctx + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("VItem Depth Pipeline Layout"), + bind_group_layouts: &[ + &ResolutionInfo::create_bind_group_layout(ctx), + &ViewportBindGroup::bind_group_layout(ctx), + &VItemsBuffer::render_bind_group_layout(ctx), + ], + push_constant_ranges: &[], + }); + let pipeline = ctx + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("VItem Depth Pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module, + entry_point: Some("fs_depth_only"), + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + ..Default::default() + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); Self { pipeline } } } diff --git a/packages/ranim-render/src/pipelines/vitem_compute.rs b/packages/ranim-render/src/pipelines/vitem_compute.rs deleted file mode 100644 index 1a169f2a..00000000 --- a/packages/ranim-render/src/pipelines/vitem_compute.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::ops::Deref; - -use crate::{WgpuContext, resource::GpuResource}; - -pub struct VItemComputePipeline { - pipeline: wgpu::ComputePipeline, -} - -pub struct VItemComputeBindGroup(wgpu::BindGroup); - -impl AsRef for VItemComputeBindGroup { - fn as_ref(&self) -> &wgpu::BindGroup { - &self.0 - } -} - -impl VItemComputeBindGroup { - pub fn bind_group_layout(ctx: &WgpuContext) -> wgpu::BindGroupLayout { - ctx.device - .create_bind_group_layout(&Self::bind_group_layout_desc()) - } - pub fn bind_group_layout_desc<'a>() -> wgpu::BindGroupLayoutDescriptor<'a> { - wgpu::BindGroupLayoutDescriptor { - label: Some("VItem Compute Bind Group Layout"), - entries: &[ - // plane: (origin, basis_u, basis_v) - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // points3d: (x, y, z, is_closed) - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // stroke_widths - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // points2d: (x, y, is_closed, padding) - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // clip_info: (min_x, max_x, min_y, max_y, max_w) - wgpu::BindGroupLayoutEntry { - binding: 4, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: false }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // point_cnt - wgpu::BindGroupLayoutEntry { - binding: 5, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - } - } - - fn new_bind_group( - ctx: &WgpuContext, - plane_buffer: &wgpu::Buffer, - points3d_buffer: &wgpu::Buffer, - stroke_width_buffer: &wgpu::Buffer, - points2d_buffer: &wgpu::Buffer, - clip_box_buffer: &wgpu::Buffer, - point_cnt_buffer: &wgpu::Buffer, - ) -> wgpu::BindGroup { - ctx.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("VItem Compute Bind Group"), - layout: &VItemComputeBindGroup::bind_group_layout(ctx), - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - plane_buffer.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Buffer( - points3d_buffer.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Buffer( - stroke_width_buffer.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::Buffer( - points2d_buffer.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 4, - resource: wgpu::BindingResource::Buffer( - clip_box_buffer.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 5, - resource: wgpu::BindingResource::Buffer( - point_cnt_buffer.as_entire_buffer_binding(), - ), - }, - ], - }) - } - pub(crate) fn new( - ctx: &WgpuContext, - plane_buffer: &wgpu::Buffer, - points3d_buffer: &wgpu::Buffer, - stroke_width_buffer: &wgpu::Buffer, - points2d_buffer: &wgpu::Buffer, - clip_box_buffer: &wgpu::Buffer, - point_cnt_buffer: &wgpu::Buffer, - ) -> Self { - Self(Self::new_bind_group( - ctx, - plane_buffer, - points3d_buffer, - stroke_width_buffer, - points2d_buffer, - clip_box_buffer, - point_cnt_buffer, - )) - } -} - -impl Deref for VItemComputePipeline { - type Target = wgpu::ComputePipeline; - fn deref(&self) -> &Self::Target { - &self.pipeline - } -} - -impl GpuResource for VItemComputePipeline { - fn new(wgpu_ctx: &WgpuContext) -> Self { - let WgpuContext { device, .. } = wgpu_ctx; - - let module = - &device.create_shader_module(wgpu::include_wgsl!("./shaders/vitem_compute.wgsl")); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("VItemCompute Pipeline Layout"), - bind_group_layouts: &[&VItemComputeBindGroup::bind_group_layout(wgpu_ctx)], - push_constant_ranges: &[], - }); - - let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: Some("VItemCompute Pipeline"), - layout: Some(&pipeline_layout), - module, - entry_point: Some("cs_main"), - compilation_options: wgpu::PipelineCompilationOptions::default(), - cache: None, - }); - - Self { pipeline } - } -} diff --git a/packages/ranim-render/src/primitives.rs b/packages/ranim-render/src/primitives.rs index 00ce0349..86f7023f 100644 --- a/packages/ranim-render/src/primitives.rs +++ b/packages/ranim-render/src/primitives.rs @@ -1,6 +1,5 @@ -pub mod merged_vitem; pub mod viewport; -pub mod vitem; +pub mod vitems; use crate::utils::WgpuContext; diff --git a/packages/ranim-render/src/primitives/vitem.rs b/packages/ranim-render/src/primitives/vitem.rs deleted file mode 100644 index 6965eb60..00000000 --- a/packages/ranim-render/src/primitives/vitem.rs +++ /dev/null @@ -1,423 +0,0 @@ -use crate::{ - pipelines::{vitem::RenderBindGroup, vitem_compute::VItemComputeBindGroup}, - utils::{WgpuBuffer, WgpuContext, WgpuVecBuffer}, -}; -use bytemuck::{Pod, Zeroable}; -use glam::Vec4; -use ranim_core::{ - components::{rgba::Rgba, width::Width}, - core_item::vitem::VItem, -}; - -use super::{Primitive, RenderResource}; - -impl Primitive for VItem { - type RenderPacket = VItemRenderInstance; -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub struct PlaneUniform { - origin: Vec4, // xyz, w=pad - basis_u: Vec4, // xyz, w=pad - basis_v: Vec4, // xyz, w=pad -} - -/// [`VItem`]'s render instance. -pub struct VItemRenderInstance { - // since the storage buffer is aligned to 16 bytes, we use vec4 for alignment - /// COMPUTE INPUT: (x, y, z, is_closed) - pub(crate) points3d_buffer: WgpuVecBuffer, - /// COMPUTE OUTPUT, RENDER INPUT: (x, y, is_closed, _padding) - pub(crate) points2d_buffer: WgpuVecBuffer, - /// COMPUTE OUTPUT, RENDER INPUT: (min_x, max_x, min_y, max_y, max_w) - pub(crate) clip_info_buffer: WgpuVecBuffer, - /// COMPUTE INPUT: point_cnt - pub(crate) point_cnt_buffer: Option>, - - /// RENDER INPUT - pub(crate) fill_rgbas: WgpuVecBuffer, - /// RENDER INPUT - pub(crate) stroke_rgbas: WgpuVecBuffer, - /// RENDER INPUT, COMPUTE INPUT - pub(crate) stroke_widths: WgpuVecBuffer, - /// RENDER UNIFORM - pub(crate) plane_buffer: WgpuBuffer, - - /// COMPUTE BIND GROUP - pub(crate) compute_bind_group: Option, - - /// RENDER BIND GROUP - pub(crate) render_bind_group: Option, -} - -impl RenderResource for VItemRenderInstance { - type Data = VItem; - - fn init(ctx: &WgpuContext, data: &Self::Data) -> Self { - let points3d_buffer = WgpuVecBuffer::new_init( - ctx, - Some("Points 3d Buffer"), - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - &data.points, - ); - - let points2d_buffer = WgpuVecBuffer::new_init( - ctx, - Some("Points 2d Buffer"), - wgpu::BufferUsages::STORAGE - | wgpu::BufferUsages::COPY_DST - | wgpu::BufferUsages::COPY_SRC, - &vec![Vec4::ZERO; data.points.len()], - ); - - let clip_info_buffer = WgpuVecBuffer::new_init( - ctx, - Some("Clip Info Buffer"), - wgpu::BufferUsages::STORAGE - | wgpu::BufferUsages::COPY_DST - | wgpu::BufferUsages::COPY_SRC, - &[i32::MAX, i32::MIN, i32::MAX, i32::MIN, 0], - ); - - let point_cnt_buffer = WgpuBuffer::new_init( - ctx, - Some("Point Cnt Buffer"), - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - data.points.len() as u32, - ); - - let fill_rgbas = WgpuVecBuffer::new_init( - ctx, - Some("Fill Rgbas Buffer"), - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - &data.fill_rgbas, - ); - - let stroke_rgbas = WgpuVecBuffer::new_init( - ctx, - Some("Stroke Rgbas Buffer"), - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - &data.stroke_rgbas, - ); - - let stroke_widths = WgpuVecBuffer::new_init( - ctx, - Some("Stroke Widths Buffer"), - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - &data.stroke_widths, - ); - - let plane_data = PlaneUniform { - origin: Vec4::from((data.origin, 0.0)), - basis_u: Vec4::from((data.basis.u().as_vec3(), 0.0)), - basis_v: Vec4::from((data.basis.v().as_vec3(), 0.0)), - }; - let plane_buffer = WgpuBuffer::new_init( - ctx, - Some("Plane Uniform Buffer"), - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - plane_data, - ); - - let compute_bind_group = VItemComputeBindGroup::new( - ctx, - plane_buffer.as_ref(), - &points3d_buffer.buffer, - &stroke_widths.buffer, - &points2d_buffer.buffer, - &clip_info_buffer.buffer, - point_cnt_buffer.as_ref(), - ); - - let render_bind_group = RenderBindGroup::new( - ctx, - &points2d_buffer.buffer, - &fill_rgbas.buffer, - &stroke_rgbas.buffer, - &stroke_widths.buffer, - &clip_info_buffer.buffer, - plane_buffer.as_ref(), - ); - - Self { - points3d_buffer, - points2d_buffer, - clip_info_buffer, - point_cnt_buffer: Some(point_cnt_buffer), - fill_rgbas, - stroke_rgbas, - stroke_widths, - plane_buffer, - compute_bind_group: Some(compute_bind_group), - render_bind_group: Some(render_bind_group), - } - } - fn update(&mut self, ctx: &WgpuContext, data: &Self::Data) { - if let Some(point_cnt_buffer) = self.point_cnt_buffer.as_mut() { - point_cnt_buffer.set(ctx, data.points.len() as u32); - } else { - self.point_cnt_buffer = Some(WgpuBuffer::new_init( - ctx, - Some("Point Cnt Buffer"), - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - data.points.len() as u32, - )); - } - - let plane_data = PlaneUniform { - origin: Vec4::from((data.origin, 0.0)), - basis_u: Vec4::from((data.basis.u().as_vec3(), 0.0)), - basis_v: Vec4::from((data.basis.v().as_vec3(), 0.0)), - }; - self.plane_buffer.set(ctx, plane_data); - - // Dynamic sized - let points2d_recreated = self - .points2d_buffer - .set(ctx, &vec![Vec4::ZERO; data.points.len()]); - if [ - self.points3d_buffer.set(ctx, &data.points), - points2d_recreated, - self.fill_rgbas.set(ctx, &data.fill_rgbas), - self.stroke_rgbas.set(ctx, &data.stroke_rgbas), - self.stroke_widths.set(ctx, &data.stroke_widths), - self.clip_info_buffer - .set(ctx, &[i32::MAX, i32::MIN, i32::MAX, i32::MIN, 0]), - self.compute_bind_group.is_none(), - ] - .iter() - .any(|x| *x) - { - self.compute_bind_group = Some(VItemComputeBindGroup::new( - ctx, - self.plane_buffer.as_ref(), - &self.points3d_buffer.buffer, - &self.stroke_widths.buffer, - &self.points2d_buffer.buffer, - &self.clip_info_buffer.buffer, - self.point_cnt_buffer.as_ref().unwrap().as_ref(), - )); - self.render_bind_group = Some(RenderBindGroup::new( - ctx, - &self.points2d_buffer.buffer, - &self.fill_rgbas.buffer, - &self.stroke_rgbas.buffer, - &self.stroke_widths.buffer, - &self.clip_info_buffer.buffer, - self.plane_buffer.as_ref(), - )); - } - } -} - -impl VItemRenderInstance { - pub fn encode_compute_pass_command(&self, cpass: &mut wgpu::ComputePass) { - cpass.set_bind_group(0, self.compute_bind_group.as_ref().unwrap().as_ref(), &[]); - cpass.dispatch_workgroups(self.points3d_buffer.len().div_ceil(256) as u32, 1, 1); - } - pub fn encode_depth_render_pass_command(&self, rpass: &mut wgpu::RenderPass) { - rpass.set_bind_group(2, self.render_bind_group.as_ref().unwrap().as_ref(), &[]); - rpass.draw(0..4, 0..1); - } - pub fn encode_render_pass_command(&self, rpass: &mut wgpu::RenderPass) { - rpass.set_bind_group(2, self.render_bind_group.as_ref().unwrap().as_ref(), &[]); - rpass.draw(0..4, 0..1); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{Renderer, resource::RenderPool, utils::WgpuContext}; - use glam::{DVec3, Vec3, Vec4, vec4}; - use ranim_core::{ - core_item::{CoreItem, camera_frame::CameraFrame, vitem::Basis2d}, - store::CoreItemStore, - }; - - #[test] - fn foo_render_vitem2d_primitive() { - let ctx = pollster::block_on(WgpuContext::new()); - let mut renderer = Renderer::new(&ctx, 1280, 720, 8); - let mut render_textures = renderer.new_render_textures(&ctx); - let clear_color = wgpu::Color { - r: 0.8, - g: 0.8, - b: 0.8, - a: 1.0, - }; - - let mut camera = CameraFrame::new(); - camera.pos = DVec3::new(3.0, 3.0, 3.0); - camera.facing = DVec3::new(-1.0, -1.0, -1.0).normalize(); - camera.up = DVec3::Y; - camera.perspective_blend = 1.0; // Use perspective - - // Set z=1.0 to enable fill (is_closed=true) - let scale = 2.0; - let mut points = vec![ - Vec4::new(-1.0, -1.0, 0.0, 1.0), - Vec4::new(-1.0, 0.0, 0.0, 1.0), - Vec4::new(-1.0, 1.0, 0.0, 1.0), - Vec4::new(0.0, 1.0, 0.0, 1.0), - Vec4::new(1.0, 1.0, 0.0, 1.0), - Vec4::new(1.0, 0.0, 0.0, 1.0), - Vec4::new(1.0, -1.0, 0.0, 1.0), - Vec4::new(0.0, -1.0, 0.0, 1.0), - Vec4::new(-1.0, -1.0, 0.0, 1.0), - ]; - let n = points.len().div_ceil(2); - points.iter_mut().for_each(|p| { - p.x *= scale; - p.y *= scale; - }); - - let make_items = |origin: Vec3, alpha: f32| { - // Red on XY plane - let item1 = VItem { - origin, - basis: Basis2d::XY, - points: points.clone(), - fill_rgbas: vec![Rgba(vec4(1.0, 0.0, 0.0, alpha)); n], - stroke_rgbas: vec![Rgba(vec4(0.5, 0.0, 0.0, 1.0)); n], - stroke_widths: vec![Width(0.02); n], - }; - - // Green on YZ - let item2 = VItem { - origin, - basis: Basis2d::YZ, - points: points.clone(), - fill_rgbas: vec![Rgba(vec4(0.0, 1.0, 0.0, alpha)); n], - stroke_rgbas: vec![Rgba(vec4(0.0, 0.5, 0.0, 1.0)); n], - stroke_widths: vec![Width(0.02); n], - }; - - // Blue on XZ - let item3 = VItem { - origin, - basis: Basis2d::XZ, - points: points.clone(), - fill_rgbas: vec![Rgba(vec4(0.0, 0.0, 1.0, alpha)); n], - stroke_rgbas: vec![Rgba(vec4(0.0, 0.0, 0.5, 1.0)); n], - stroke_widths: vec![Width(0.02); n], - }; - std::iter::once(item1) - .chain(std::iter::once(item2)) - .chain(std::iter::once(item3)) - }; - - let mut pool = RenderPool::new(); - let mut store = CoreItemStore::new(); - let center = Vec3::ZERO; - let dir = (Vec3::X + Vec3::NEG_Z).normalize(); - store.update( - make_items(-scale * 1.5 * dir + center, 1.0) - .chain(make_items(scale * 1.5 * dir + center, 0.5)) - .map(CoreItem::VItem) - .chain(std::iter::once(CoreItem::CameraFrame(camera))) - .enumerate() - .map(|(id, x)| ((id, id), x)), - ); - - renderer.render_store_with_pool(&ctx, &mut render_textures, clear_color, &store, &mut pool); - let img = render_textures.get_rendered_texture_img_buffer(&ctx); - img.save("../../output/vitem2d_intersecting_perspective.png") - .unwrap(); - let depth_img = render_textures.get_depth_texture_img_buffer(&ctx); - depth_img - .save("../../output/vitem2d_intersecting_perspective_depth.png") - .unwrap(); - } - - /// Render the same scene with the merged buffer path for visual comparison. - #[test] - fn render_merged_vitem2d_primitive() { - let ctx = pollster::block_on(WgpuContext::new()); - let mut renderer = Renderer::new(&ctx, 1280, 720, 8); - let mut render_textures = renderer.new_render_textures(&ctx); - let clear_color = wgpu::Color { - r: 0.8, - g: 0.8, - b: 0.8, - a: 1.0, - }; - - let mut camera = CameraFrame::new(); - camera.pos = DVec3::new(3.0, 3.0, 3.0); - camera.facing = DVec3::new(-1.0, -1.0, -1.0).normalize(); - camera.up = DVec3::Y; - camera.perspective_blend = 1.0; - - let scale = 2.0; - let mut points = vec![ - Vec4::new(-1.0, -1.0, 0.0, 1.0), - Vec4::new(-1.0, 0.0, 0.0, 1.0), - Vec4::new(-1.0, 1.0, 0.0, 1.0), - Vec4::new(0.0, 1.0, 0.0, 1.0), - Vec4::new(1.0, 1.0, 0.0, 1.0), - Vec4::new(1.0, 0.0, 0.0, 1.0), - Vec4::new(1.0, -1.0, 0.0, 1.0), - Vec4::new(0.0, -1.0, 0.0, 1.0), - Vec4::new(-1.0, -1.0, 0.0, 1.0), - ]; - let n = points.len().div_ceil(2); - points.iter_mut().for_each(|p| { - p.x *= scale; - p.y *= scale; - }); - - let make_items = |origin: Vec3, alpha: f32| { - let item1 = VItem { - origin, - basis: Basis2d::XY, - points: points.clone(), - fill_rgbas: vec![Rgba(vec4(1.0, 0.0, 0.0, alpha)); n], - stroke_rgbas: vec![Rgba(vec4(0.5, 0.0, 0.0, 1.0)); n], - stroke_widths: vec![Width(0.02); n], - }; - let item2 = VItem { - origin, - basis: Basis2d::YZ, - points: points.clone(), - fill_rgbas: vec![Rgba(vec4(0.0, 1.0, 0.0, alpha)); n], - stroke_rgbas: vec![Rgba(vec4(0.0, 0.5, 0.0, 1.0)); n], - stroke_widths: vec![Width(0.02); n], - }; - let item3 = VItem { - origin, - basis: Basis2d::XZ, - points: points.clone(), - fill_rgbas: vec![Rgba(vec4(0.0, 0.0, 1.0, alpha)); n], - stroke_rgbas: vec![Rgba(vec4(0.0, 0.0, 0.5, 1.0)); n], - stroke_widths: vec![Width(0.02); n], - }; - std::iter::once(item1) - .chain(std::iter::once(item2)) - .chain(std::iter::once(item3)) - }; - - let mut pool = RenderPool::new(); - let mut store = CoreItemStore::new(); - let center = Vec3::ZERO; - let dir = (Vec3::X + Vec3::NEG_Z).normalize(); - store.update( - make_items(-scale * 1.5 * dir + center, 1.0) - .chain(make_items(scale * 1.5 * dir + center, 0.5)) - .map(CoreItem::VItem) - .chain(std::iter::once(CoreItem::CameraFrame(camera))) - .enumerate() - .map(|(id, x)| ((id, id), x)), - ); - - renderer.render_store_with_pool(&ctx, &mut render_textures, clear_color, &store, &mut pool); - ctx.device - .poll(wgpu::PollType::wait_indefinitely()) - .unwrap(); - - let img = render_textures.get_rendered_texture_img_buffer(&ctx); - img.save("../../output/merged_vitem2d_intersecting_perspective.png") - .unwrap(); - } -} diff --git a/packages/ranim-render/src/primitives/merged_vitem.rs b/packages/ranim-render/src/primitives/vitems.rs similarity index 99% rename from packages/ranim-render/src/primitives/merged_vitem.rs rename to packages/ranim-render/src/primitives/vitems.rs index 1162bb80..e553a00c 100644 --- a/packages/ranim-render/src/primitives/merged_vitem.rs +++ b/packages/ranim-render/src/primitives/vitems.rs @@ -35,7 +35,7 @@ pub struct PlaneData { /// Instead of one set of buffers per VItem, all data is packed into /// contiguous arrays with an index table (`item_infos`) that tells /// shaders where each item's data lives. -pub struct MergedVItemBuffer { +pub struct VItemsBuffer { /// Per-item metadata: offsets and counts pub(crate) item_infos_buffer: WgpuVecBuffer, /// Per-item plane data (origin + basis) @@ -65,7 +65,7 @@ pub struct MergedVItemBuffer { pub(crate) render_bind_group: Option, } -impl MergedVItemBuffer { +impl VItemsBuffer { pub fn new(ctx: &WgpuContext) -> Self { // Start with empty buffers (minimum size 1 to avoid zero-size buffer) let storage_rw = wgpu::BufferUsages::STORAGE From 7bed8caea286f52286939dcbdb62ea061fb200fd Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Tue, 24 Feb 2026 18:38:23 +0800 Subject: [PATCH 2/3] fix lint --- packages/ranim-render/src/utils.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/ranim-render/src/utils.rs b/packages/ranim-render/src/utils.rs index 1cfa9a52..237ff2fc 100644 --- a/packages/ranim-render/src/utils.rs +++ b/packages/ranim-render/src/utils.rs @@ -327,6 +327,7 @@ impl WgpuVecBuffer { } } + #[allow(unused)] pub(crate) fn new_init( ctx: &WgpuContext, label: Option<&'static str>, @@ -338,6 +339,7 @@ impl WgpuVecBuffer { buffer } + #[allow(unused)] pub(crate) fn len(&self) -> usize { self.len } From 96a4b7301f5313915d9e85089315ba6f2969613d Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Tue, 24 Feb 2026 18:42:09 +0800 Subject: [PATCH 3/3] change oit_resolve to GlobalRenderNode --- packages/ranim-render/src/graph/mod.rs | 3 +++ packages/ranim-render/src/graph/{view => }/oit_resolve.rs | 7 ++----- packages/ranim-render/src/graph/view.rs | 3 --- packages/ranim-render/src/lib.rs | 4 ++-- packages/ranim-render/src/pipelines/oit_resolve.rs | 6 +----- .../ranim-render/src/pipelines/shaders/oit_resolve.wgsl | 7 ------- 6 files changed, 8 insertions(+), 22 deletions(-) rename packages/ranim-render/src/graph/{view => }/oit_resolve.rs (88%) diff --git a/packages/ranim-render/src/graph/mod.rs b/packages/ranim-render/src/graph/mod.rs index 5c6f0074..a74e07e7 100644 --- a/packages/ranim-render/src/graph/mod.rs +++ b/packages/ranim-render/src/graph/mod.rs @@ -5,6 +5,9 @@ pub mod view; pub mod clear; pub use clear::*; +pub mod oit_resolve; +pub use oit_resolve::*; + use variadics_please::all_tuples; use crate::{ diff --git a/packages/ranim-render/src/graph/view/oit_resolve.rs b/packages/ranim-render/src/graph/oit_resolve.rs similarity index 88% rename from packages/ranim-render/src/graph/view/oit_resolve.rs rename to packages/ranim-render/src/graph/oit_resolve.rs index d8e7b0e4..f5529268 100644 --- a/packages/ranim-render/src/graph/view/oit_resolve.rs +++ b/packages/ranim-render/src/graph/oit_resolve.rs @@ -1,13 +1,12 @@ use crate::{ RenderContext, RenderTextures, - graph::{RenderPacketsQuery, view::ViewRenderNodeTrait}, + graph::{GlobalRenderNodeTrait, RenderPacketsQuery}, pipelines::OITResolvePipeline, - primitives::viewport::ViewportGpuPacket, }; pub struct OITResolveNode; -impl ViewRenderNodeTrait for OITResolveNode { +impl GlobalRenderNodeTrait for OITResolveNode { type Query = (); fn run( &self, @@ -15,7 +14,6 @@ impl ViewRenderNodeTrait for OITResolveNode { #[cfg(feature = "profiling")] encoder: &mut wgpu_profiler::Scope<'_, wgpu::CommandEncoder>, _packets: ::Output<'_>, ctx: RenderContext, - viewport: &ViewportGpuPacket, ) { let RenderTextures { render_view, @@ -58,7 +56,6 @@ impl ViewRenderNodeTrait for OITResolveNode { .get_or_init::(ctx.wgpu_ctx), ); rpass.set_bind_group(0, &ctx.resolution_info.bind_group, &[]); - rpass.set_bind_group(1, &viewport.uniforms_bind_group.bind_group, &[]); rpass.draw(0..3, 0..1); } // Clear OIT pixel count buffer diff --git a/packages/ranim-render/src/graph/view.rs b/packages/ranim-render/src/graph/view.rs index 3f5d9347..61278096 100644 --- a/packages/ranim-render/src/graph/view.rs +++ b/packages/ranim-render/src/graph/view.rs @@ -1,8 +1,5 @@ use std::ops::{Deref, DerefMut}; -pub mod oit_resolve; -pub use oit_resolve::*; - pub mod vitem_compute; pub use vitem_compute::*; pub mod vitem_depth; diff --git a/packages/ranim-render/src/lib.rs b/packages/ranim-render/src/lib.rs index 7199969f..8d4c4fba 100644 --- a/packages/ranim-render/src/lib.rs +++ b/packages/ranim-render/src/lib.rs @@ -134,13 +134,13 @@ impl Renderer { let compute = render_graph.insert_node(MergedVItemComputeNode); let depth = render_graph.insert_node(MergedVItemDepthNode); let color = render_graph.insert_node(MergedVItemColorNode); - let oit_resolve = render_graph.insert_node(OITResolveNode); render_graph.insert_edge(compute, depth); render_graph.insert_edge(depth, color); - render_graph.insert_edge(color, oit_resolve); render_graph }); + let oit_resolve = render_graph.insert_node(OITResolveNode); render_graph.insert_edge(clear, view_render); + render_graph.insert_edge(view_render, oit_resolve); render_graph } diff --git a/packages/ranim-render/src/pipelines/oit_resolve.rs b/packages/ranim-render/src/pipelines/oit_resolve.rs index cf325758..8247ef2c 100644 --- a/packages/ranim-render/src/pipelines/oit_resolve.rs +++ b/packages/ranim-render/src/pipelines/oit_resolve.rs @@ -2,7 +2,6 @@ use std::ops::Deref; use crate::{ ResolutionInfo, WgpuContext, - primitives::viewport::ViewportBindGroup, resource::{GpuResource, OUTPUT_TEXTURE_FORMAT}, }; @@ -26,10 +25,7 @@ impl GpuResource for OITResolvePipeline { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("OIT Resolve Pipeline Layout"), - bind_group_layouts: &[ - &ResolutionInfo::create_bind_group_layout(wgpu_ctx), - &ViewportBindGroup::bind_group_layout(wgpu_ctx), - ], + bind_group_layouts: &[&ResolutionInfo::create_bind_group_layout(wgpu_ctx)], push_constant_ranges: &[], }); diff --git a/packages/ranim-render/src/pipelines/shaders/oit_resolve.wgsl b/packages/ranim-render/src/pipelines/shaders/oit_resolve.wgsl index 8ca7cced..f859ebcb 100644 --- a/packages/ranim-render/src/pipelines/shaders/oit_resolve.wgsl +++ b/packages/ranim-render/src/pipelines/shaders/oit_resolve.wgsl @@ -3,13 +3,6 @@ @group(0) @binding(2) var oit_colors: array; @group(0) @binding(3) var oit_depths: array; -struct CameraUniforms { - proj_mat: mat4x4, - view_mat: mat4x4, - half_frame_size: vec2, -} -@group(1) @binding(0) var cam_uniforms : CameraUniforms; - struct VertexOutput { @builtin(position) position: vec4, @location(0) uv: vec2,