1use std::{io, sync::Arc, time::Duration};
8
9use winit::window::Window;
10
11use crate::{
12 CompositeCommand, ComputablePipeline, ComputeCommand, DrawCommand, DrawablePipeline, PxSize,
13 compute::resource::ComputeResourceManager,
14 pipeline_cache::save_cache,
15 render_graph::RenderTextureDesc,
16 renderer::{
17 composite::{CompositeContext, CompositePipelineRegistry},
18 external::ExternalTextureRegistry,
19 },
20};
21
22use super::{compute::ComputePipelineRegistry, drawer::Drawer};
23
24mod frame;
25mod init;
26
27struct RenderPipelines {
28 drawer: Drawer,
29 compute_registry: ComputePipelineRegistry,
30 composite_registry: CompositePipelineRegistry,
31}
32
33struct FrameTargets {
34 offscreen: wgpu::TextureView,
35 offscreen_copy: wgpu::TextureView,
36 msaa_texture: Option<wgpu::Texture>,
37 msaa_view: Option<wgpu::TextureView>,
38 sample_count: u32,
39}
40
41#[derive(Clone, Copy, Debug, Default)]
43pub(crate) struct RenderTimingBreakdown {
44 pub acquire: Duration,
46 pub build_passes: Duration,
48 pub encode: Duration,
50 pub submit: Duration,
52 pub present: Duration,
54 pub total: Duration,
56}
57
58struct ComputeState {
59 target_a: wgpu::TextureView,
60 target_b: wgpu::TextureView,
61 resource_manager: ComputeResourceManager,
62}
63
64struct BlitState {
65 pipeline: wgpu::RenderPipeline,
66 pipeline_rgba: wgpu::RenderPipeline,
67 bind_group_layout: wgpu::BindGroupLayout,
68 sampler: wgpu::Sampler,
69 #[cfg(feature = "debug-dirty-overlay")]
70 dirty_overlay_pipeline: wgpu::RenderPipeline,
71}
72
73#[derive(Clone, Copy, PartialEq, Eq, Hash)]
74struct RenderTextureDescKey {
75 size: PxSize,
76 format: wgpu::TextureFormat,
77 sample_count: u32,
78}
79
80impl RenderTextureDescKey {
81 fn from_desc(desc: &RenderTextureDesc, sample_count: u32) -> Self {
82 Self {
83 size: desc.size,
84 format: desc.format,
85 sample_count,
86 }
87 }
88}
89
90struct TextureHandle {
91 view: wgpu::TextureView,
92}
93
94struct LocalTextureSlot {
95 desc: RenderTextureDescKey,
96 front: TextureHandle,
97 back: TextureHandle,
98 msaa_view: Option<wgpu::TextureView>,
99 in_use: bool,
100 last_used_frame: u64,
101}
102
103impl LocalTextureSlot {
104 fn front_view(&self) -> &wgpu::TextureView {
105 &self.front.view
106 }
107
108 fn back_view(&self) -> &wgpu::TextureView {
109 &self.back.view
110 }
111
112 fn swap_front_back(&mut self) {
113 std::mem::swap(&mut self.front, &mut self.back);
114 }
115}
116
117struct LocalTexturePool {
118 slots: Vec<LocalTextureSlot>,
119}
120
121impl LocalTexturePool {
122 const MAX_SLOTS: usize = 16;
123
124 fn new() -> Self {
125 Self { slots: Vec::new() }
126 }
127
128 fn clear(&mut self) {
129 self.slots.clear();
130 }
131
132 fn begin_frame(&mut self, _current_frame: u64) {
133 for slot in &mut self.slots {
134 slot.in_use = false;
135 }
136 }
137
138 fn allocate(
139 &mut self,
140 device: &wgpu::Device,
141 desc: &RenderTextureDesc,
142 sample_count: u32,
143 current_frame: u64,
144 ) -> usize {
145 let key = RenderTextureDescKey::from_desc(desc, sample_count);
146 if let Some((index, slot)) = self
147 .slots
148 .iter_mut()
149 .enumerate()
150 .find(|(_, slot)| slot.desc == key && !slot.in_use)
151 {
152 slot.in_use = true;
153 slot.last_used_frame = current_frame;
154 return index;
155 }
156
157 if self.slots.len() >= Self::MAX_SLOTS
158 && let Some(index) = self.lru_unused_index()
159 {
160 self.slots.swap_remove(index);
161 }
162
163 let front = create_local_texture(device, desc, "Local Front");
164 let back = create_local_texture(device, desc, "Local Back");
165 let msaa_view = if sample_count > 1 {
166 Some(create_msaa_view(device, desc, sample_count))
167 } else {
168 None
169 };
170
171 let slot = LocalTextureSlot {
172 desc: key,
173 front,
174 back,
175 msaa_view,
176 in_use: true,
177 last_used_frame: current_frame,
178 };
179 self.slots.push(slot);
180 self.slots.len() - 1
181 }
182
183 fn lru_unused_index(&self) -> Option<usize> {
184 self.slots
185 .iter()
186 .enumerate()
187 .filter(|(_, slot)| !slot.in_use)
188 .min_by_key(|(_, slot)| slot.last_used_frame)
189 .map(|(index, _)| index)
190 }
191
192 fn slot(&self, index: usize) -> Option<&LocalTextureSlot> {
193 self.slots.get(index)
194 }
195
196 fn slot_mut(&mut self, index: usize) -> Option<&mut LocalTextureSlot> {
197 self.slots.get_mut(index)
198 }
199}
200
201fn create_local_texture(
202 device: &wgpu::Device,
203 desc: &RenderTextureDesc,
204 label: &str,
205) -> TextureHandle {
206 let width = desc.size.width.positive().max(1);
207 let height = desc.size.height.positive().max(1);
208 let texture = device.create_texture(&wgpu::TextureDescriptor {
209 label: Some(label),
210 size: wgpu::Extent3d {
211 width,
212 height,
213 depth_or_array_layers: 1,
214 },
215 mip_level_count: 1,
216 sample_count: 1,
217 dimension: wgpu::TextureDimension::D2,
218 format: desc.format,
219 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
220 | wgpu::TextureUsages::TEXTURE_BINDING
221 | wgpu::TextureUsages::STORAGE_BINDING
222 | wgpu::TextureUsages::COPY_SRC
223 | wgpu::TextureUsages::COPY_DST,
224 view_formats: &[],
225 });
226 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
227 TextureHandle { view }
228}
229
230fn create_msaa_view(
231 device: &wgpu::Device,
232 desc: &RenderTextureDesc,
233 sample_count: u32,
234) -> wgpu::TextureView {
235 let width = desc.size.width.positive().max(1);
236 let height = desc.size.height.positive().max(1);
237 let texture = device.create_texture(&wgpu::TextureDescriptor {
238 label: Some("Local MSAA"),
239 size: wgpu::Extent3d {
240 width,
241 height,
242 depth_or_array_layers: 1,
243 },
244 mip_level_count: 1,
245 sample_count,
246 dimension: wgpu::TextureDimension::D2,
247 format: desc.format,
248 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
249 view_formats: &[],
250 });
251 texture.create_view(&wgpu::TextureViewDescriptor::default())
252}
253
254pub struct RenderCore {
256 #[allow(unused)]
258 window: Arc<Window>,
259 instance: wgpu::Instance,
261 device: wgpu::Device,
263 surface: wgpu::Surface<'static>,
265 queue: wgpu::Queue,
267 config: wgpu::SurfaceConfiguration,
269 size: winit::dpi::PhysicalSize<u32>,
271 size_changed: bool,
273 pipelines: RenderPipelines,
275
276 pipeline_cache: Option<wgpu::PipelineCache>,
278 adapter_info: wgpu::AdapterInfo,
280
281 targets: FrameTargets,
283 compute: ComputeState,
285 blit: BlitState,
287 local_textures: LocalTexturePool,
289 external_textures: ExternalTextureRegistry,
291 frame_index: u64,
293 last_render_breakdown: Option<RenderTimingBreakdown>,
295}
296
297pub struct RenderResources<'a> {
299 pub device: &'a wgpu::Device,
301 pub queue: &'a wgpu::Queue,
303 pub surface_config: &'a wgpu::SurfaceConfiguration,
305 pub pipeline_cache: Option<&'a wgpu::PipelineCache>,
307 pub sample_count: u32,
309}
310
311impl RenderCore {
312 pub fn resources(&self) -> RenderResources<'_> {
314 RenderResources {
315 device: &self.device,
316 queue: &self.queue,
317 surface_config: &self.config,
318 pipeline_cache: self.pipeline_cache.as_ref(),
319 sample_count: self.targets.sample_count,
320 }
321 }
322
323 pub fn window(&self) -> &Window {
325 &self.window
326 }
327
328 pub fn window_arc(&self) -> Arc<Window> {
330 self.window.clone()
331 }
332
333 pub fn device(&self) -> &wgpu::Device {
335 &self.device
336 }
337
338 pub fn queue(&self) -> &wgpu::Queue {
340 &self.queue
341 }
342
343 pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
345 &self.config
346 }
347
348 pub fn pipeline_cache(&self) -> Option<&wgpu::PipelineCache> {
350 self.pipeline_cache.as_ref()
351 }
352
353 pub fn sample_count(&self) -> u32 {
355 self.targets.sample_count
356 }
357
358 pub fn compute_resource_manager_mut(&mut self) -> &mut ComputeResourceManager {
360 &mut self.compute.resource_manager
361 }
362
363 pub fn record_resources(&mut self) -> (&wgpu::Device, &mut ComputeResourceManager) {
365 (&self.device, &mut self.compute.resource_manager)
366 }
367
368 pub(crate) fn last_render_breakdown(&self) -> Option<RenderTimingBreakdown> {
370 self.last_render_breakdown
371 }
372
373 pub fn register_draw_pipeline<T, P>(&mut self, pipeline: P)
379 where
380 T: DrawCommand + 'static,
381 P: DrawablePipeline<T> + 'static,
382 {
383 self.pipelines.drawer.pipeline_registry.register(pipeline);
384 }
385
386 pub fn register_compute_pipeline<T, P>(&mut self, pipeline: P)
392 where
393 T: ComputeCommand + 'static,
394 P: ComputablePipeline<T> + 'static,
395 {
396 self.pipelines.compute_registry.register(pipeline);
397 }
398
399 pub fn register_composite_pipeline<T, P>(&mut self, pipeline: P)
401 where
402 T: CompositeCommand + 'static,
403 P: super::composite::CompositePipeline<T> + 'static,
404 {
405 self.pipelines.composite_registry.register(pipeline);
406 }
407
408 pub(crate) fn composite_context_parts(
409 &mut self,
410 frame_size: PxSize,
411 frame_index: u64,
412 ) -> (CompositeContext<'_>, &mut CompositePipelineRegistry) {
413 let RenderCore {
414 device,
415 queue,
416 config,
417 pipeline_cache,
418 targets,
419 pipelines,
420 ..
421 } = self;
422 let resources = RenderResources {
423 device,
424 queue,
425 surface_config: config,
426 pipeline_cache: pipeline_cache.as_ref(),
427 sample_count: targets.sample_count,
428 };
429 let context = CompositeContext {
430 resources,
431 external_textures: self.external_textures.clone(),
432 frame_size,
433 surface_format: config.format,
434 sample_count: targets.sample_count,
435 frame_index,
436 };
437 (context, &mut pipelines.composite_registry)
438 }
439
440 pub(crate) fn save_pipeline_cache(&self) -> io::Result<()> {
441 if let Some(cache) = self.pipeline_cache.as_ref() {
442 save_cache(cache, &self.adapter_info)?;
443 }
444 Ok(())
445 }
446}