1use ailloli_ui_runtime::BlendMode;
4use bytemuck::{Pod, Zeroable};
5use wgpu::util::DeviceExt;
6
7use crate::isolated_plan::PlannedIsolatedComposite;
8use crate::vertices::TexVertex;
9
10#[repr(C)]
12#[derive(Clone, Copy, Pod, Zeroable)]
13struct CompositeBlendParamsGpu {
14 mode: u32,
16 opacity: f32,
18 _pad: [f32; 2],
20}
21
22fn blend_mode_shader_id(mode: BlendMode) -> u32 {
24 match mode {
25 BlendMode::Multiply => 1,
26 BlendMode::Screen => 2,
27 BlendMode::Normal => 0,
28 }
29}
30
31pub struct CompositeBlendPipelines {
40 pub pipeline: wgpu::RenderPipeline,
42 pub params_layout: wgpu::BindGroupLayout,
44 pub tex_layout: wgpu::BindGroupLayout,
46 pub sampler: wgpu::Sampler,
48}
49
50impl CompositeBlendPipelines {
51 pub fn new(device: &wgpu::Device, surface_format: wgpu::TextureFormat) -> Self {
62 let params_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
63 label: Some("composite blend params layout"),
64 entries: &[wgpu::BindGroupLayoutEntry {
65 binding: 0,
66 visibility: wgpu::ShaderStages::FRAGMENT,
67 ty: wgpu::BindingType::Buffer {
68 ty: wgpu::BufferBindingType::Uniform,
69 has_dynamic_offset: false,
70 min_binding_size: None,
71 },
72 count: None,
73 }],
74 });
75 let tex_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
76 label: Some("composite blend tex layout"),
77 entries: &[
78 wgpu::BindGroupLayoutEntry {
79 binding: 0,
80 visibility: wgpu::ShaderStages::FRAGMENT,
81 ty: wgpu::BindingType::Texture {
82 multisampled: false,
83 view_dimension: wgpu::TextureViewDimension::D2,
84 sample_type: wgpu::TextureSampleType::Float { filterable: true },
85 },
86 count: None,
87 },
88 wgpu::BindGroupLayoutEntry {
89 binding: 1,
90 visibility: wgpu::ShaderStages::FRAGMENT,
91 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
92 count: None,
93 },
94 ],
95 });
96 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
97 label: Some("composite blend layout"),
98 bind_group_layouts: &[¶ms_layout, &tex_layout, &tex_layout],
99 push_constant_ranges: &[],
100 });
101 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
102 label: Some("composite blend shader"),
103 source: wgpu::ShaderSource::Wgsl(include_str!("shaders/composite_blend.wgsl").into()),
104 });
105 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
106 label: Some("composite blend pipeline"),
107 layout: Some(&layout),
108 vertex: wgpu::VertexState {
109 module: &shader,
110 entry_point: "vs_main",
111 compilation_options: Default::default(),
112 buffers: &[TexVertex::desc()],
113 },
114 fragment: Some(wgpu::FragmentState {
115 module: &shader,
116 entry_point: "fs_main",
117 compilation_options: Default::default(),
118 targets: &[Some(wgpu::ColorTargetState {
119 format: surface_format,
120 blend: None,
121 write_mask: wgpu::ColorWrites::ALL,
122 })],
123 }),
124 primitive: wgpu::PrimitiveState::default(),
125 depth_stencil: None,
126 multisample: wgpu::MultisampleState::default(),
127 multiview: None,
128 });
129 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
130 label: Some("composite blend sampler"),
131 mag_filter: wgpu::FilterMode::Linear,
132 min_filter: wgpu::FilterMode::Linear,
133 ..Default::default()
134 });
135 Self {
136 pipeline,
137 params_layout,
138 tex_layout,
139 sampler,
140 }
141 }
142}
143
144#[allow(clippy::too_many_arguments)]
164pub fn draw_composite_blend(
165 device: &wgpu::Device,
166 encoder: &mut wgpu::CommandEncoder,
167 pipelines: &CompositeBlendPipelines,
168 fg_bind_group: &wgpu::BindGroup,
169 bg_view: &wgpu::TextureView,
170 comp: &PlannedIsolatedComposite,
171 composite_buf: &wgpu::Buffer,
172 main_view: &wgpu::TextureView,
173 depth_stencil: Option<&wgpu::RenderPassDepthStencilAttachment<'_>>,
174) {
175 let params = CompositeBlendParamsGpu {
176 mode: blend_mode_shader_id(comp.blend_mode),
177 opacity: comp.opacity.clamp(0.0, 1.0),
178 _pad: [0.0; 2],
179 };
180 let params_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
181 label: Some("composite blend params"),
182 contents: bytemuck::bytes_of(¶ms),
183 usage: wgpu::BufferUsages::UNIFORM,
184 });
185 let params_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
186 label: Some("composite blend params bg"),
187 layout: &pipelines.params_layout,
188 entries: &[wgpu::BindGroupEntry {
189 binding: 0,
190 resource: params_buf.as_entire_binding(),
191 }],
192 });
193 let bg_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
194 label: Some("composite blend bg tex"),
195 layout: &pipelines.tex_layout,
196 entries: &[
197 wgpu::BindGroupEntry {
198 binding: 0,
199 resource: wgpu::BindingResource::TextureView(bg_view),
200 },
201 wgpu::BindGroupEntry {
202 binding: 1,
203 resource: wgpu::BindingResource::Sampler(&pipelines.sampler),
204 },
205 ],
206 });
207
208 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
209 label: Some("composite blend to main"),
210 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
211 view: main_view,
212 resolve_target: None,
213 ops: wgpu::Operations {
214 load: wgpu::LoadOp::Load,
215 store: wgpu::StoreOp::Store,
216 },
217 })],
218 depth_stencil_attachment: depth_stencil.cloned(),
219 timestamp_writes: None,
220 occlusion_query_set: None,
221 });
222 pass.set_pipeline(&pipelines.pipeline);
223 pass.set_bind_group(0, ¶ms_bg, &[]);
224 pass.set_bind_group(1, fg_bind_group, &[]);
225 pass.set_bind_group(2, &bg_bg, &[]);
226 pass.set_vertex_buffer(0, composite_buf.slice(..));
227 pass.draw(comp.vertex_range.clone(), 0..1);
228}