|
1 | | -use std::sync::Arc; |
| 1 | +mod frame_buffer_ref; |
| 2 | +pub(crate) use frame_buffer_ref::FrameBufferRef; |
2 | 3 |
|
3 | | -use bytemuck::{Pod, Zeroable}; |
4 | | -use glam::UVec2; |
5 | | -use graphene_std::raster::color::Color; |
6 | | -use thiserror::Error; |
7 | | -use wgpu_executor::WgpuExecutor; |
8 | | -use winit::window::Window; |
9 | | - |
10 | | -pub(crate) struct FrameBufferRef<'a> { |
11 | | - buffer: &'a [u8], |
12 | | - width: usize, |
13 | | - height: usize, |
14 | | -} |
15 | | -impl<'a> FrameBufferRef<'a> { |
16 | | - pub(crate) fn new(buffer: &'a [u8], width: usize, height: usize) -> Result<Self, FrameBufferError> { |
17 | | - let fb = Self { buffer, width, height }; |
18 | | - fb.validate_size()?; |
19 | | - Ok(fb) |
20 | | - } |
21 | | - pub(crate) fn buffer(&self) -> &[u8] { |
22 | | - self.buffer |
23 | | - } |
24 | | - |
25 | | - pub(crate) fn width(&self) -> usize { |
26 | | - self.width |
27 | | - } |
28 | | - |
29 | | - pub(crate) fn height(&self) -> usize { |
30 | | - self.height |
31 | | - } |
32 | | - |
33 | | - fn validate_size(&self) -> Result<(), FrameBufferError> { |
34 | | - if self.buffer.len() != self.width * self.height * 4 { |
35 | | - Err(FrameBufferError::InvalidSize { |
36 | | - buffer_size: self.buffer.len(), |
37 | | - expected_size: self.width * self.height * 4, |
38 | | - width: self.width, |
39 | | - height: self.height, |
40 | | - }) |
41 | | - } else { |
42 | | - Ok(()) |
43 | | - } |
44 | | - } |
45 | | -} |
46 | | -impl<'a> std::fmt::Debug for FrameBufferRef<'a> { |
47 | | - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
48 | | - f.debug_struct("FrameBuffer") |
49 | | - .field("width", &self.width) |
50 | | - .field("height", &self.height) |
51 | | - .field("len", &self.buffer.len()) |
52 | | - .finish() |
53 | | - } |
54 | | -} |
55 | | - |
56 | | -#[derive(Error, Debug)] |
57 | | -pub(crate) enum FrameBufferError { |
58 | | - #[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")] |
59 | | - InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize }, |
60 | | -} |
61 | | - |
62 | | -pub use wgpu_executor::Context as WgpuContext; |
63 | | - |
64 | | -#[derive(Debug)] |
65 | | -pub(crate) struct GraphicsState { |
66 | | - surface: wgpu::Surface<'static>, |
67 | | - context: WgpuContext, |
68 | | - wgpu_executor: WgpuExecutor, |
69 | | - config: wgpu::SurfaceConfiguration, |
70 | | - render_pipeline: wgpu::RenderPipeline, |
71 | | - transparent_texture: wgpu::Texture, |
72 | | - sampler: wgpu::Sampler, |
73 | | - viewport_scale: [f32; 2], |
74 | | - viewport_offset: [f32; 2], |
75 | | - viewport_texture: Option<wgpu::Texture>, |
76 | | - overlays_texture: Option<wgpu::Texture>, |
77 | | - ui_texture: Option<wgpu::Texture>, |
78 | | - bind_group: Option<wgpu::BindGroup>, |
79 | | -} |
80 | | - |
81 | | -impl GraphicsState { |
82 | | - pub(crate) fn new(window: Arc<Window>, context: WgpuContext) -> Self { |
83 | | - let size = window.inner_size(); |
84 | | - |
85 | | - let surface = context.instance.create_surface(window).unwrap(); |
86 | | - |
87 | | - let surface_caps = surface.get_capabilities(&context.adapter); |
88 | | - let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]); |
89 | | - |
90 | | - let config = wgpu::SurfaceConfiguration { |
91 | | - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, |
92 | | - format: surface_format, |
93 | | - width: size.width, |
94 | | - height: size.height, |
95 | | - present_mode: surface_caps.present_modes[0], |
96 | | - alpha_mode: surface_caps.alpha_modes[0], |
97 | | - view_formats: vec![], |
98 | | - desired_maximum_frame_latency: 2, |
99 | | - }; |
100 | | - |
101 | | - surface.configure(&context.device, &config); |
102 | | - |
103 | | - let transparent_texture = context.device.create_texture(&wgpu::TextureDescriptor { |
104 | | - label: Some("Transparent Texture"), |
105 | | - size: wgpu::Extent3d { |
106 | | - width: 1, |
107 | | - height: 1, |
108 | | - depth_or_array_layers: 1, |
109 | | - }, |
110 | | - mip_level_count: 1, |
111 | | - sample_count: 1, |
112 | | - dimension: wgpu::TextureDimension::D2, |
113 | | - format: wgpu::TextureFormat::Bgra8UnormSrgb, |
114 | | - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, |
115 | | - view_formats: &[], |
116 | | - }); |
117 | | - |
118 | | - // Create shader module |
119 | | - let shader = context.device.create_shader_module(wgpu::include_wgsl!("render/fullscreen_texture.wgsl")); |
120 | | - |
121 | | - // Create sampler |
122 | | - let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor { |
123 | | - address_mode_u: wgpu::AddressMode::ClampToEdge, |
124 | | - address_mode_v: wgpu::AddressMode::ClampToEdge, |
125 | | - address_mode_w: wgpu::AddressMode::ClampToEdge, |
126 | | - mag_filter: wgpu::FilterMode::Linear, |
127 | | - min_filter: wgpu::FilterMode::Nearest, |
128 | | - mipmap_filter: wgpu::FilterMode::Nearest, |
129 | | - ..Default::default() |
130 | | - }); |
131 | | - |
132 | | - let texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { |
133 | | - entries: &[ |
134 | | - wgpu::BindGroupLayoutEntry { |
135 | | - binding: 0, |
136 | | - visibility: wgpu::ShaderStages::FRAGMENT, |
137 | | - ty: wgpu::BindingType::Texture { |
138 | | - multisampled: false, |
139 | | - view_dimension: wgpu::TextureViewDimension::D2, |
140 | | - sample_type: wgpu::TextureSampleType::Float { filterable: true }, |
141 | | - }, |
142 | | - count: None, |
143 | | - }, |
144 | | - wgpu::BindGroupLayoutEntry { |
145 | | - binding: 1, |
146 | | - visibility: wgpu::ShaderStages::FRAGMENT, |
147 | | - ty: wgpu::BindingType::Texture { |
148 | | - multisampled: false, |
149 | | - view_dimension: wgpu::TextureViewDimension::D2, |
150 | | - sample_type: wgpu::TextureSampleType::Float { filterable: true }, |
151 | | - }, |
152 | | - count: None, |
153 | | - }, |
154 | | - wgpu::BindGroupLayoutEntry { |
155 | | - binding: 2, |
156 | | - visibility: wgpu::ShaderStages::FRAGMENT, |
157 | | - ty: wgpu::BindingType::Texture { |
158 | | - multisampled: false, |
159 | | - view_dimension: wgpu::TextureViewDimension::D2, |
160 | | - sample_type: wgpu::TextureSampleType::Float { filterable: true }, |
161 | | - }, |
162 | | - count: None, |
163 | | - }, |
164 | | - wgpu::BindGroupLayoutEntry { |
165 | | - binding: 3, |
166 | | - visibility: wgpu::ShaderStages::FRAGMENT, |
167 | | - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), |
168 | | - count: None, |
169 | | - }, |
170 | | - ], |
171 | | - label: Some("texture_bind_group_layout"), |
172 | | - }); |
173 | | - |
174 | | - let render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { |
175 | | - label: Some("Render Pipeline Layout"), |
176 | | - bind_group_layouts: &[&texture_bind_group_layout], |
177 | | - push_constant_ranges: &[wgpu::PushConstantRange { |
178 | | - stages: wgpu::ShaderStages::FRAGMENT, |
179 | | - range: 0..size_of::<Constants>() as u32, |
180 | | - }], |
181 | | - }); |
182 | | - |
183 | | - let render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { |
184 | | - label: Some("Render Pipeline"), |
185 | | - layout: Some(&render_pipeline_layout), |
186 | | - vertex: wgpu::VertexState { |
187 | | - module: &shader, |
188 | | - entry_point: Some("vs_main"), |
189 | | - buffers: &[], |
190 | | - compilation_options: Default::default(), |
191 | | - }, |
192 | | - fragment: Some(wgpu::FragmentState { |
193 | | - module: &shader, |
194 | | - entry_point: Some("fs_main"), |
195 | | - targets: &[Some(wgpu::ColorTargetState { |
196 | | - format: config.format, |
197 | | - blend: Some(wgpu::BlendState::REPLACE), |
198 | | - write_mask: wgpu::ColorWrites::ALL, |
199 | | - })], |
200 | | - compilation_options: Default::default(), |
201 | | - }), |
202 | | - primitive: wgpu::PrimitiveState { |
203 | | - topology: wgpu::PrimitiveTopology::TriangleList, |
204 | | - strip_index_format: None, |
205 | | - front_face: wgpu::FrontFace::Ccw, |
206 | | - cull_mode: Some(wgpu::Face::Back), |
207 | | - polygon_mode: wgpu::PolygonMode::Fill, |
208 | | - unclipped_depth: false, |
209 | | - conservative: false, |
210 | | - }, |
211 | | - depth_stencil: None, |
212 | | - multisample: wgpu::MultisampleState { |
213 | | - count: 1, |
214 | | - mask: !0, |
215 | | - alpha_to_coverage_enabled: false, |
216 | | - }, |
217 | | - multiview: None, |
218 | | - cache: None, |
219 | | - }); |
220 | | - |
221 | | - let wgpu_executor = WgpuExecutor::with_context(context.clone()).expect("Failed to create WgpuExecutor"); |
222 | | - |
223 | | - Self { |
224 | | - surface, |
225 | | - context, |
226 | | - wgpu_executor, |
227 | | - config, |
228 | | - render_pipeline, |
229 | | - transparent_texture, |
230 | | - sampler, |
231 | | - viewport_scale: [1.0, 1.0], |
232 | | - viewport_offset: [0.0, 0.0], |
233 | | - viewport_texture: None, |
234 | | - overlays_texture: None, |
235 | | - ui_texture: None, |
236 | | - bind_group: None, |
237 | | - } |
238 | | - } |
239 | | - |
240 | | - pub(crate) fn resize(&mut self, width: u32, height: u32) { |
241 | | - if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { |
242 | | - self.config.width = width; |
243 | | - self.config.height = height; |
244 | | - self.surface.configure(&self.context.device, &self.config); |
245 | | - } |
246 | | - } |
247 | | - |
248 | | - pub(crate) fn bind_viewport_texture(&mut self, viewport_texture: wgpu::Texture) { |
249 | | - self.viewport_texture = Some(viewport_texture); |
250 | | - self.update_bindgroup(); |
251 | | - } |
252 | | - |
253 | | - pub(crate) fn bind_overlays_texture(&mut self, overlays_texture: wgpu::Texture) { |
254 | | - self.overlays_texture = Some(overlays_texture); |
255 | | - self.update_bindgroup(); |
256 | | - } |
257 | | - |
258 | | - pub(crate) fn bind_ui_texture(&mut self, bind_ui_texture: wgpu::Texture) { |
259 | | - self.ui_texture = Some(bind_ui_texture); |
260 | | - self.update_bindgroup(); |
261 | | - } |
262 | | - |
263 | | - pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) { |
264 | | - self.viewport_scale = scale; |
265 | | - } |
266 | | - |
267 | | - pub(crate) fn set_viewport_offset(&mut self, offset: [f32; 2]) { |
268 | | - self.viewport_offset = offset; |
269 | | - } |
270 | | - |
271 | | - pub(crate) fn render_overlays(&mut self, scene: &vello::Scene) { |
272 | | - let Some(viewport_texture) = self.viewport_texture.as_ref() else { |
273 | | - tracing::warn!("No viewport texture bound, cannot render overlays"); |
274 | | - return; |
275 | | - }; |
276 | | - let size = UVec2::new(viewport_texture.width(), viewport_texture.height()); |
277 | | - let texture = futures::executor::block_on(self.wgpu_executor.render_vello_scene_to_texture(scene, size, &Default::default(), Color::TRANSPARENT)); |
278 | | - let Ok(texture) = texture else { |
279 | | - tracing::error!("Error rendering overlays"); |
280 | | - return; |
281 | | - }; |
282 | | - self.bind_overlays_texture(texture); |
283 | | - } |
284 | | - |
285 | | - pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> { |
286 | | - let output = self.surface.get_current_texture()?; |
287 | | - let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); |
288 | | - |
289 | | - let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") }); |
290 | | - |
291 | | - { |
292 | | - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { |
293 | | - label: Some("Render Pass"), |
294 | | - color_attachments: &[Some(wgpu::RenderPassColorAttachment { |
295 | | - view: &view, |
296 | | - resolve_target: None, |
297 | | - ops: wgpu::Operations { |
298 | | - load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }), |
299 | | - store: wgpu::StoreOp::Store, |
300 | | - }, |
301 | | - })], |
302 | | - depth_stencil_attachment: None, |
303 | | - occlusion_query_set: None, |
304 | | - timestamp_writes: None, |
305 | | - }); |
306 | | - |
307 | | - render_pass.set_pipeline(&self.render_pipeline); |
308 | | - render_pass.set_push_constants( |
309 | | - wgpu::ShaderStages::FRAGMENT, |
310 | | - 0, |
311 | | - bytemuck::bytes_of(&Constants { |
312 | | - viewport_scale: self.viewport_scale, |
313 | | - viewport_offset: self.viewport_offset, |
314 | | - }), |
315 | | - ); |
316 | | - if let Some(bind_group) = &self.bind_group { |
317 | | - render_pass.set_bind_group(0, bind_group, &[]); |
318 | | - render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle |
319 | | - } else { |
320 | | - tracing::warn!("No bind group available - showing clear color only"); |
321 | | - } |
322 | | - } |
323 | | - self.context.queue.submit(std::iter::once(encoder.finish())); |
324 | | - output.present(); |
325 | | - |
326 | | - Ok(()) |
327 | | - } |
328 | | - |
329 | | - fn update_bindgroup(&mut self) { |
330 | | - let viewport_texture_view = self.viewport_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); |
331 | | - let overlays_texture_view = self.overlays_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); |
332 | | - let ui_texture_view = self.ui_texture.as_ref().unwrap_or(&self.transparent_texture).create_view(&wgpu::TextureViewDescriptor::default()); |
333 | | - |
334 | | - let bind_group = self.context.device.create_bind_group(&wgpu::BindGroupDescriptor { |
335 | | - layout: &self.render_pipeline.get_bind_group_layout(0), |
336 | | - entries: &[ |
337 | | - wgpu::BindGroupEntry { |
338 | | - binding: 0, |
339 | | - resource: wgpu::BindingResource::TextureView(&viewport_texture_view), |
340 | | - }, |
341 | | - wgpu::BindGroupEntry { |
342 | | - binding: 1, |
343 | | - resource: wgpu::BindingResource::TextureView(&overlays_texture_view), |
344 | | - }, |
345 | | - wgpu::BindGroupEntry { |
346 | | - binding: 2, |
347 | | - resource: wgpu::BindingResource::TextureView(&ui_texture_view), |
348 | | - }, |
349 | | - wgpu::BindGroupEntry { |
350 | | - binding: 3, |
351 | | - resource: wgpu::BindingResource::Sampler(&self.sampler), |
352 | | - }, |
353 | | - ], |
354 | | - label: Some("texture_bind_group"), |
355 | | - }); |
356 | | - |
357 | | - self.bind_group = Some(bind_group); |
358 | | - } |
359 | | -} |
360 | | - |
361 | | -#[repr(C)] |
362 | | -#[derive(Copy, Clone, Pod, Zeroable)] |
363 | | -struct Constants { |
364 | | - viewport_scale: [f32; 2], |
365 | | - viewport_offset: [f32; 2], |
366 | | -} |
| 4 | +mod graphics_state; |
| 5 | +pub(crate) use graphics_state::{GraphicsState, WgpuContext}; |
0 commit comments