1use oxiui_core::paint::BlendMode;
32
33use crate::gpu::buffer::Vertex;
34use crate::gpu::device::TARGET_FORMAT;
35
36pub fn blend_state_for_mode(mode: BlendMode) -> wgpu::BlendState {
43 match mode {
44 BlendMode::Normal => wgpu::BlendState::ALPHA_BLENDING,
45
46 BlendMode::Multiply => wgpu::BlendState {
47 color: wgpu::BlendComponent {
48 src_factor: wgpu::BlendFactor::Dst,
50 dst_factor: wgpu::BlendFactor::Zero,
51 operation: wgpu::BlendOperation::Add,
52 },
53 alpha: wgpu::BlendComponent {
54 src_factor: wgpu::BlendFactor::One,
55 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
56 operation: wgpu::BlendOperation::Add,
57 },
58 },
59
60 BlendMode::Screen => wgpu::BlendState {
61 color: wgpu::BlendComponent {
62 src_factor: wgpu::BlendFactor::One,
64 dst_factor: wgpu::BlendFactor::OneMinusSrc,
65 operation: wgpu::BlendOperation::Add,
66 },
67 alpha: wgpu::BlendComponent {
68 src_factor: wgpu::BlendFactor::One,
69 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
70 operation: wgpu::BlendOperation::Add,
71 },
72 },
73
74 BlendMode::Overlay | BlendMode::Darken | BlendMode::Lighten => {
77 wgpu::BlendState::ALPHA_BLENDING
78 }
79
80 BlendMode::Copy => wgpu::BlendState::REPLACE,
81
82 BlendMode::Destination => wgpu::BlendState {
83 color: wgpu::BlendComponent {
84 src_factor: wgpu::BlendFactor::Zero,
85 dst_factor: wgpu::BlendFactor::One,
86 operation: wgpu::BlendOperation::Add,
87 },
88 alpha: wgpu::BlendComponent {
89 src_factor: wgpu::BlendFactor::Zero,
90 dst_factor: wgpu::BlendFactor::One,
91 operation: wgpu::BlendOperation::Add,
92 },
93 },
94
95 #[allow(unreachable_patterns)]
97 _ => wgpu::BlendState::ALPHA_BLENDING,
98 }
99}
100
101pub struct BlendPipelineSet {
109 pub globals_layout: wgpu::BindGroupLayout,
111 pub normal: wgpu::RenderPipeline,
113 pub multiply: wgpu::RenderPipeline,
115 pub screen: wgpu::RenderPipeline,
117 pub copy: wgpu::RenderPipeline,
119 pub destination: wgpu::RenderPipeline,
121}
122
123impl BlendPipelineSet {
124 pub fn new(device: &wgpu::Device, sample_count: u32) -> Self {
128 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
129 label: Some("oxiui-render-wgpu blend-mode solid.wgsl"),
130 source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/solid.wgsl").into()),
131 });
132
133 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
134 label: Some("oxiui-render-wgpu blend globals layout"),
135 entries: &[wgpu::BindGroupLayoutEntry {
136 binding: 0,
137 visibility: wgpu::ShaderStages::VERTEX,
138 ty: wgpu::BindingType::Buffer {
139 ty: wgpu::BufferBindingType::Uniform,
140 has_dynamic_offset: false,
141 min_binding_size: None,
142 },
143 count: None,
144 }],
145 });
146
147 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
148 label: Some("oxiui-render-wgpu blend pipeline layout"),
149 bind_group_layouts: &[Some(&globals_layout)],
150 immediate_size: 0,
151 });
152
153 let build = |label: &'static str, blend: wgpu::BlendState| {
154 let attrs = [
155 wgpu::VertexAttribute {
156 format: wgpu::VertexFormat::Float32x2,
157 offset: 0,
158 shader_location: 0,
159 },
160 wgpu::VertexAttribute {
161 format: wgpu::VertexFormat::Float32x4,
162 offset: 8,
163 shader_location: 1,
164 },
165 wgpu::VertexAttribute {
166 format: wgpu::VertexFormat::Float32x2,
167 offset: 24,
168 shader_location: 2,
169 },
170 wgpu::VertexAttribute {
171 format: wgpu::VertexFormat::Float32x2,
172 offset: 32,
173 shader_location: 3,
174 },
175 wgpu::VertexAttribute {
176 format: wgpu::VertexFormat::Float32,
177 offset: 40,
178 shader_location: 4,
179 },
180 wgpu::VertexAttribute {
181 format: wgpu::VertexFormat::Float32,
182 offset: 44,
183 shader_location: 5,
184 },
185 wgpu::VertexAttribute {
186 format: wgpu::VertexFormat::Float32x2,
187 offset: 48,
188 shader_location: 6,
189 },
190 ];
191 let vertex_layout = wgpu::VertexBufferLayout {
192 array_stride: core::mem::size_of::<Vertex>() as wgpu::BufferAddress,
193 step_mode: wgpu::VertexStepMode::Vertex,
194 attributes: &attrs,
195 };
196 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
197 label: Some(label),
198 layout: Some(&pipeline_layout),
199 vertex: wgpu::VertexState {
200 module: &shader,
201 entry_point: Some("vs_main"),
202 buffers: &[vertex_layout],
203 compilation_options: wgpu::PipelineCompilationOptions::default(),
204 },
205 fragment: Some(wgpu::FragmentState {
206 module: &shader,
207 entry_point: Some("fs_main"),
208 targets: &[Some(wgpu::ColorTargetState {
209 format: TARGET_FORMAT,
210 blend: Some(blend),
211 write_mask: wgpu::ColorWrites::ALL,
212 })],
213 compilation_options: wgpu::PipelineCompilationOptions::default(),
214 }),
215 primitive: wgpu::PrimitiveState {
216 topology: wgpu::PrimitiveTopology::TriangleList,
217 strip_index_format: None,
218 front_face: wgpu::FrontFace::Ccw,
219 cull_mode: None,
220 unclipped_depth: false,
221 polygon_mode: wgpu::PolygonMode::Fill,
222 conservative: false,
223 },
224 depth_stencil: None,
225 multisample: wgpu::MultisampleState {
226 count: sample_count,
227 mask: !0,
228 alpha_to_coverage_enabled: false,
229 },
230 multiview_mask: None,
231 cache: None,
232 })
233 };
234
235 let normal = build(
236 "oxiui-render-wgpu blend normal",
237 blend_state_for_mode(BlendMode::Normal),
238 );
239 let multiply = build(
240 "oxiui-render-wgpu blend multiply",
241 blend_state_for_mode(BlendMode::Multiply),
242 );
243 let screen = build(
244 "oxiui-render-wgpu blend screen",
245 blend_state_for_mode(BlendMode::Screen),
246 );
247 let copy = build(
248 "oxiui-render-wgpu blend copy",
249 blend_state_for_mode(BlendMode::Copy),
250 );
251 let destination = build(
252 "oxiui-render-wgpu blend destination",
253 blend_state_for_mode(BlendMode::Destination),
254 );
255
256 Self {
257 globals_layout,
258 normal,
259 multiply,
260 screen,
261 copy,
262 destination,
263 }
264 }
265
266 pub fn pipeline_for(&self, mode: BlendMode) -> &wgpu::RenderPipeline {
271 match mode {
272 BlendMode::Normal | BlendMode::Overlay | BlendMode::Darken | BlendMode::Lighten => {
273 &self.normal
274 }
275 BlendMode::Multiply => &self.multiply,
276 BlendMode::Screen => &self.screen,
277 BlendMode::Copy => &self.copy,
278 BlendMode::Destination => &self.destination,
279 #[allow(unreachable_patterns)]
280 _ => &self.normal,
281 }
282 }
283}
284
285#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn blend_state_for_normal_is_alpha_blending() {
293 let bs = blend_state_for_mode(BlendMode::Normal);
294 assert_eq!(bs, wgpu::BlendState::ALPHA_BLENDING);
295 }
296
297 #[test]
298 fn blend_state_for_copy_is_replace() {
299 let bs = blend_state_for_mode(BlendMode::Copy);
300 assert_eq!(bs, wgpu::BlendState::REPLACE);
301 }
302
303 #[test]
304 fn overlay_falls_back_to_normal() {
305 let overlay = blend_state_for_mode(BlendMode::Overlay);
306 let normal = blend_state_for_mode(BlendMode::Normal);
307 assert_eq!(overlay, normal);
308 }
309
310 fn try_device() -> Option<(wgpu::Device, wgpu::Queue)> {
311 let instance = wgpu::Instance::default();
312 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
313 power_preference: wgpu::PowerPreference::default(),
314 force_fallback_adapter: false,
315 compatible_surface: None,
316 }))
317 .ok()?;
318 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
319 label: Some("blend test device"),
320 required_features: wgpu::Features::empty(),
321 required_limits: wgpu::Limits::downlevel_defaults(),
322 memory_hints: wgpu::MemoryHints::Performance,
323 experimental_features: wgpu::ExperimentalFeatures::disabled(),
324 trace: wgpu::Trace::Off,
325 }))
326 .ok()
327 }
328
329 #[test]
330 fn blend_pipeline_set_compiles() {
331 let Some((device, _)) = try_device() else {
332 return;
333 };
334 let set = BlendPipelineSet::new(&device, 1);
335 let _normal = set.pipeline_for(BlendMode::Normal);
337 let _multiply = set.pipeline_for(BlendMode::Multiply);
338 let _screen = set.pipeline_for(BlendMode::Screen);
339 let _copy = set.pipeline_for(BlendMode::Copy);
340 let _ = set.pipeline_for(BlendMode::Overlay);
342 }
343}