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