diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-13 04:40:29 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-13 05:01:51 +0200 |
| commit | 8230faf082c405c9fd0fa640f4ce2284315782ae (patch) | |
| tree | e00bf694a83b4d14d6d56942ba89ba92402d854e /rust/crates/compositor/src/compositor.rs | |
| parent | dbd68ff1396bf498eb97f095810b78d2bddd0011 (diff) | |
| download | opencut-8230faf082c405c9fd0fa640f4ce2284315782ae.tar.gz | |
feat: wasm compositor
Made-with: Cursor
Diffstat (limited to 'rust/crates/compositor/src/compositor.rs')
| -rw-r--r-- | rust/crates/compositor/src/compositor.rs | 816 |
1 files changed, 816 insertions, 0 deletions
diff --git a/rust/crates/compositor/src/compositor.rs b/rust/crates/compositor/src/compositor.rs new file mode 100644 index 00000000..92d253d1 --- /dev/null +++ b/rust/crates/compositor/src/compositor.rs @@ -0,0 +1,816 @@ +use bytemuck::{Pod, Zeroable}; +use effects::{ApplyEffectsOptions, EffectPass, EffectPipeline, UniformValue}; +use gpu::{FULLSCREEN_SHADER_SOURCE, GpuContext, wgpu}; +use masks::{ApplyMaskFeatherOptions, MaskFeatherPipeline}; +use thiserror::Error; +use wgpu::util::DeviceExt; + +use crate::{ + BlendMode, + frame::{ + EffectPassDescriptor, EffectUniformValueDescriptor, FrameDescriptor, FrameItemDescriptor, + LayerDescriptor, + }, + texture_pool::TexturePool, + texture_store::TextureStore, +}; + +const LAYER_SHADER_SOURCE: &str = include_str!("shaders/layer.wgsl"); +const BLEND_SHADER_SOURCE: &str = include_str!("shaders/blend.wgsl"); +const MASK_SHADER_SOURCE: &str = include_str!("shaders/mask.wgsl"); + +pub struct RenderFrameOptions<'a, 'surface> { + pub frame: &'a FrameDescriptor, + pub surface: &'a wgpu::Surface<'surface>, +} + +pub struct Compositor { + textures: TextureStore, + texture_pool: TexturePool, + effects: EffectPipeline, + masks: MaskFeatherPipeline, + layer_uniform_bind_group_layout: wgpu::BindGroupLayout, + layer_pipeline: wgpu::RenderPipeline, + blend_uniform_bind_group_layout: wgpu::BindGroupLayout, + blend_pipeline: wgpu::RenderPipeline, + mask_uniform_bind_group_layout: wgpu::BindGroupLayout, + mask_pipeline: wgpu::RenderPipeline, +} + +#[derive(Debug, Error)] +pub enum CompositorError { + #[error("Texture '{texture_id}' is not available")] + MissingTexture { texture_id: String }, + #[error("Failed to apply effects: {0}")] + Effects(#[from] effects::EffectsError), + #[error("Failed to present frame: {0}")] + Gpu(#[from] gpu::GpuError), +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct LayerUniformBuffer { + resolution: [f32; 2], + center: [f32; 2], + size: [f32; 2], + rotation_radians: f32, + opacity: f32, + flip_x: f32, + flip_y: f32, +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct BlendUniformBuffer { + blend_mode: u32, + _padding: [u32; 3], +} + +#[repr(C)] +#[derive(Clone, Copy, Pod, Zeroable)] +struct MaskUniformBuffer { + inverted: f32, + _padding: [f32; 3], +} + +impl Compositor { + pub fn new(context: &GpuContext) -> Self { + let device = context.device(); + let fullscreen_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("compositor-fullscreen-shader"), + source: wgpu::ShaderSource::Wgsl(FULLSCREEN_SHADER_SOURCE.into()), + }); + let layer_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("compositor-layer-shader"), + source: wgpu::ShaderSource::Wgsl(LAYER_SHADER_SOURCE.into()), + }); + let blend_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("compositor-blend-shader"), + source: wgpu::ShaderSource::Wgsl(BLEND_SHADER_SOURCE.into()), + }); + let mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("compositor-mask-shader"), + source: wgpu::ShaderSource::Wgsl(MASK_SHADER_SOURCE.into()), + }); + + let layer_uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("compositor-layer-uniform-layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + let blend_uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("compositor-blend-uniform-layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + let mask_uniform_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("compositor-mask-uniform-layout"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let layer_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("compositor-layer-pipeline-layout"), + bind_group_layouts: &[ + Some(context.texture_sampler_bind_group_layout()), + Some(&layer_uniform_bind_group_layout), + ], + immediate_size: 0, + }); + let blend_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("compositor-blend-pipeline-layout"), + bind_group_layouts: &[ + Some(context.texture_sampler_bind_group_layout()), + Some(context.texture_sampler_bind_group_layout()), + Some(&blend_uniform_bind_group_layout), + ], + immediate_size: 0, + }); + let mask_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("compositor-mask-pipeline-layout"), + bind_group_layouts: &[ + Some(context.texture_sampler_bind_group_layout()), + Some(context.texture_sampler_bind_group_layout()), + Some(&mask_uniform_bind_group_layout), + ], + immediate_size: 0, + }); + + let layer_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("compositor-layer-pipeline"), + layout: Some(&layer_pipeline_layout), + vertex: wgpu::VertexState { + module: &fullscreen_shader, + entry_point: Some("vertex_main"), + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::<[f32; 2]>() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }], + }], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &layer_shader, + entry_point: Some("fragment_main"), + targets: &[Some(wgpu::ColorTargetState { + format: context.texture_format(), + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + let blend_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("compositor-blend-pipeline"), + layout: Some(&blend_pipeline_layout), + vertex: wgpu::VertexState { + module: &fullscreen_shader, + entry_point: Some("vertex_main"), + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::<[f32; 2]>() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }], + }], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &blend_shader, + entry_point: Some("fragment_main"), + targets: &[Some(wgpu::ColorTargetState { + format: context.texture_format(), + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + let mask_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("compositor-mask-pipeline"), + layout: Some(&mask_pipeline_layout), + vertex: wgpu::VertexState { + module: &fullscreen_shader, + entry_point: Some("vertex_main"), + buffers: &[wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::<[f32; 2]>() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + format: wgpu::VertexFormat::Float32x2, + offset: 0, + shader_location: 0, + }], + }], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &mask_shader, + entry_point: Some("fragment_main"), + targets: &[Some(wgpu::ColorTargetState { + format: context.texture_format(), + blend: None, + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + + Self { + textures: TextureStore::default(), + texture_pool: TexturePool::default(), + effects: EffectPipeline::new(context), + masks: MaskFeatherPipeline::new(context), + layer_uniform_bind_group_layout, + layer_pipeline, + blend_uniform_bind_group_layout, + blend_pipeline, + mask_uniform_bind_group_layout, + mask_pipeline, + } + } + + pub fn upsert_texture(&mut self, id: String, texture: wgpu::Texture) { + self.textures.upsert(id, texture); + } + + pub fn release_texture(&mut self, id: &str) { + self.textures.remove(id); + } + + pub fn render_frame( + &mut self, + context: &GpuContext, + options: RenderFrameOptions<'_, '_>, + ) -> Result<(), CompositorError> { + let frame = options.frame; + self.texture_pool.recycle_frame(); + context.configure_surface(options.surface, frame.width, frame.height)?; + let surface_texture = context.acquire_surface_texture(options.surface)?; + let surface_view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = + context + .device() + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("compositor-frame-encoder"), + }); + let mut scene = self.create_cleared_texture( + context, + &mut encoder, + frame.width, + frame.height, + frame.clear.color, + ); + + for item in &frame.items { + match item { + FrameItemDescriptor::Layer(layer) => { + let layer_texture = self.render_layer(context, &mut encoder, frame, layer)?; + scene = self.blend_texture( + context, + &mut encoder, + &scene, + &layer_texture, + layer.blend_mode, + frame.width, + frame.height, + )?; + } + FrameItemDescriptor::SceneEffect { effect_pass_groups } => { + scene = self.apply_effect_groups( + context, + &mut encoder, + &scene, + frame.width, + frame.height, + effect_pass_groups, + )?; + } + } + } + + context.encode_texture_blit_to_view( + &mut encoder, + &scene, + &surface_view, + "compositor-present-pass", + ); + context.queue().submit([encoder.finish()]); + surface_texture.present(); + Ok(()) + } + + fn render_layer( + &mut self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + frame: &FrameDescriptor, + layer: &LayerDescriptor, + ) -> Result<wgpu::Texture, CompositorError> { + let source = self.textures.get(&layer.texture_id).ok_or_else(|| { + CompositorError::MissingTexture { + texture_id: layer.texture_id.clone(), + } + })?; + + let mut current = + self.texture_pool + .acquire(context, frame.width, frame.height, "compositor-layer"); + self.render_source_to_texture( + context, + encoder, + source.texture(), + ¤t, + frame.width, + frame.height, + layer, + ); + + if !layer.effect_pass_groups.is_empty() { + current = self.apply_effect_groups( + context, + encoder, + ¤t, + frame.width, + frame.height, + &layer.effect_pass_groups, + )?; + } + + if let Some(mask) = &layer.mask { + let mask_source = self.textures.get(&mask.texture_id).ok_or_else(|| { + CompositorError::MissingTexture { + texture_id: mask.texture_id.clone(), + } + })?; + let mask_source_texture = mask_source.texture().clone(); + let mask_texture = if mask.feather > 0.0 { + self.masks.apply_mask_feather_with_encoder( + context, + encoder, + ApplyMaskFeatherOptions { + mask: &mask_source_texture, + width: frame.width, + height: frame.height, + feather: mask.feather, + }, + ) + } else { + self.copy_texture( + context, + encoder, + &mask_source_texture, + frame.width, + frame.height, + ) + }; + current = self.apply_mask( + context, + encoder, + ¤t, + &mask_texture, + mask.inverted, + frame.width, + frame.height, + ); + } + + Ok(current) + } + + fn apply_effect_groups( + &mut self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + source: &wgpu::Texture, + width: u32, + height: u32, + effect_pass_groups: &[Vec<EffectPassDescriptor>], + ) -> Result<wgpu::Texture, CompositorError> { + let mut current = self.copy_texture(context, encoder, source, width, height); + for group in effect_pass_groups { + let passes = map_effect_passes(group); + current = self.effects.apply_with_encoder( + context, + encoder, + ApplyEffectsOptions { + source: ¤t, + width, + height, + passes: &passes, + }, + )?; + } + Ok(current) + } + + fn create_cleared_texture( + &mut self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + width: u32, + height: u32, + clear_color: [f32; 4], + ) -> wgpu::Texture { + let texture = + self.texture_pool + .acquire(context, width, height, "compositor-cleared-texture"); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + { + let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("compositor-clear-pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: clear_color[0] as f64, + g: clear_color[1] as f64, + b: clear_color[2] as f64, + a: clear_color[3] as f64, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + } + texture + } + + fn copy_texture( + &mut self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + source: &wgpu::Texture, + width: u32, + height: u32, + ) -> wgpu::Texture { + let texture = self + .texture_pool + .acquire(context, width, height, "compositor-copy-texture"); + self.blit_texture(context, encoder, source, &texture); + texture + } + + fn render_source_to_texture( + &self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + source: &wgpu::Texture, + target: &wgpu::Texture, + width: u32, + height: u32, + layer: &LayerDescriptor, + ) { + let source_view = source.create_view(&wgpu::TextureViewDescriptor::default()); + let target_view = target.create_view(&wgpu::TextureViewDescriptor::default()); + let source_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-layer-source-bind-group"), + layout: context.texture_sampler_bind_group_layout(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&source_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(context.linear_sampler()), + }, + ], + }); + let uniform_buffer = + context + .device() + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("compositor-layer-uniform-buffer"), + contents: bytemuck::bytes_of(&LayerUniformBuffer { + resolution: [width as f32, height as f32], + center: [layer.transform.center_x, layer.transform.center_y], + size: [layer.transform.width, layer.transform.height], + rotation_radians: layer.transform.rotation_degrees.to_radians(), + opacity: layer.opacity, + flip_x: if layer.transform.flip_x { 1.0 } else { 0.0 }, + flip_y: if layer.transform.flip_y { 1.0 } else { 0.0 }, + }), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let uniform_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-layer-uniform-bind-group"), + layout: &self.layer_uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("compositor-layer-pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &target_view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + render_pass.set_pipeline(&self.layer_pipeline); + render_pass.set_vertex_buffer(0, context.fullscreen_quad().slice(..)); + render_pass.set_bind_group(0, &source_bind_group, &[]); + render_pass.set_bind_group(1, &uniform_bind_group, &[]); + render_pass.draw(0..6, 0..1); + } + } + + fn apply_mask( + &mut self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + layer_texture: &wgpu::Texture, + mask_texture: &wgpu::Texture, + inverted: bool, + width: u32, + height: u32, + ) -> wgpu::Texture { + let target = self + .texture_pool + .acquire(context, width, height, "compositor-masked-texture"); + let layer_view = layer_texture.create_view(&wgpu::TextureViewDescriptor::default()); + let mask_view = mask_texture.create_view(&wgpu::TextureViewDescriptor::default()); + let target_view = target.create_view(&wgpu::TextureViewDescriptor::default()); + + let layer_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-mask-layer-bind-group"), + layout: context.texture_sampler_bind_group_layout(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&layer_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(context.linear_sampler()), + }, + ], + }); + let mask_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-mask-mask-bind-group"), + layout: context.texture_sampler_bind_group_layout(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&mask_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(context.linear_sampler()), + }, + ], + }); + let uniform_buffer = + context + .device() + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("compositor-mask-uniform-buffer"), + contents: bytemuck::bytes_of(&MaskUniformBuffer { + inverted: if inverted { 1.0 } else { 0.0 }, + _padding: [0.0; 3], + }), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let uniform_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-mask-uniform-bind-group"), + layout: &self.mask_uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("compositor-mask-pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &target_view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + render_pass.set_pipeline(&self.mask_pipeline); + render_pass.set_vertex_buffer(0, context.fullscreen_quad().slice(..)); + render_pass.set_bind_group(0, &layer_bind_group, &[]); + render_pass.set_bind_group(1, &mask_bind_group, &[]); + render_pass.set_bind_group(2, &uniform_bind_group, &[]); + render_pass.draw(0..6, 0..1); + } + target + } + + fn blend_texture( + &mut self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + base: &wgpu::Texture, + layer: &wgpu::Texture, + blend_mode: BlendMode, + width: u32, + height: u32, + ) -> Result<wgpu::Texture, CompositorError> { + let target = + self.texture_pool + .acquire(context, width, height, "compositor-blended-texture"); + let base_view = base.create_view(&wgpu::TextureViewDescriptor::default()); + let layer_view = layer.create_view(&wgpu::TextureViewDescriptor::default()); + let target_view = target.create_view(&wgpu::TextureViewDescriptor::default()); + let base_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-base-bind-group"), + layout: context.texture_sampler_bind_group_layout(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&base_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(context.linear_sampler()), + }, + ], + }); + let layer_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-layer-bind-group"), + layout: context.texture_sampler_bind_group_layout(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&layer_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(context.linear_sampler()), + }, + ], + }); + let uniform_buffer = + context + .device() + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("compositor-blend-uniform-buffer"), + contents: bytemuck::bytes_of(&BlendUniformBuffer { + blend_mode: blend_mode.shader_code(), + _padding: [0; 3], + }), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + let uniform_bind_group = context + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("compositor-blend-uniform-bind-group"), + layout: &self.blend_uniform_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("compositor-blend-pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &target_view, + resolve_target: None, + depth_slice: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + multiview_mask: None, + }); + render_pass.set_pipeline(&self.blend_pipeline); + render_pass.set_vertex_buffer(0, context.fullscreen_quad().slice(..)); + render_pass.set_bind_group(0, &base_bind_group, &[]); + render_pass.set_bind_group(1, &layer_bind_group, &[]); + render_pass.set_bind_group(2, &uniform_bind_group, &[]); + render_pass.draw(0..6, 0..1); + } + Ok(target) + } + + fn blit_texture( + &self, + context: &GpuContext, + encoder: &mut wgpu::CommandEncoder, + source: &wgpu::Texture, + target: &wgpu::Texture, + ) { + let target_view = target.create_view(&wgpu::TextureViewDescriptor::default()); + context.encode_texture_blit_to_view(encoder, source, &target_view, "compositor-blit-pass"); + } +} + +fn map_effect_passes(passes: &[EffectPassDescriptor]) -> Vec<EffectPass> { + passes + .iter() + .map(|pass| EffectPass { + shader: pass.shader.clone(), + uniforms: pass + .uniforms + .iter() + .map(|(name, value)| { + let uniform_value = match value { + EffectUniformValueDescriptor::Number(n) => UniformValue::Number(*n), + EffectUniformValueDescriptor::Vector(v) => UniformValue::Vector(v.clone()), + }; + (name.clone(), uniform_value) + }) + .collect(), + }) + .collect() +} |
