1use std::{any::TypeId, io, sync::Arc, time::Duration};
8
9use rustc_hash::FxHashMap;
10use winit::window::Window;
11
12use crate::{
13 ComputablePipeline, ComputeCommand, DrawCommand, DrawablePipeline, pipeline_cache::save_cache,
14};
15
16use super::{compute::ComputePipelineRegistry, drawer::Drawer};
17
18mod clip;
19mod frame;
20#[cfg(all(feature = "profiling", not(target_arch = "wasm32")))]
21mod gpu_timing;
22mod init;
23
24struct RenderPipelines {
25 clipper: clip::ShapeClipper,
26 drawer: Drawer,
27 compute_registry: ComputePipelineRegistry,
28}
29
30#[derive(Clone, Copy, Eq, Hash, PartialEq)]
31enum PipelineKind {
32 Draw,
33 Compute,
34}
35
36type PipelineFactory = Box<dyn FnOnce(&mut RenderCore) + Send>;
37
38struct FrameTargets {
39 offscreen: wgpu::TextureView,
40 offscreen_copy: Option<wgpu::TextureView>,
42 msaa_texture: Option<wgpu::Texture>,
43 msaa_view: Option<wgpu::TextureView>,
44 stencil: wgpu::TextureView,
45 sample_count: u32,
46}
47
48#[derive(Clone, Copy, Debug, Default)]
50pub(crate) struct RenderTimingBreakdown {
51 pub acquire: Duration,
53 pub build_passes: Duration,
55 pub encode: Duration,
57 pub submit: Duration,
59 pub present: Duration,
61 pub total: Duration,
63}
64
65#[derive(Clone, Debug)]
67pub enum OffscreenReadbackError {
68 EmptyTarget,
70 Poll(String),
72 Map(String),
74}
75
76impl std::fmt::Display for OffscreenReadbackError {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 match self {
79 Self::EmptyTarget => write!(f, "offscreen render target has zero size"),
80 Self::Poll(err) => write!(f, "device poll failed during offscreen readback: {err}"),
81 Self::Map(err) => write!(f, "failed to map offscreen readback buffer: {err}"),
82 }
83 }
84}
85
86impl std::error::Error for OffscreenReadbackError {}
87
88struct BlitState {
89 clear_pipeline: wgpu::RenderPipeline,
90 present_pipeline: wgpu::RenderPipeline,
91 pipeline_rgba: wgpu::RenderPipeline,
92 bind_group_layout: wgpu::BindGroupLayout,
93 sampler: wgpu::Sampler,
94 #[cfg(feature = "debug-dirty-overlay")]
95 dirty_overlay_pipeline: wgpu::RenderPipeline,
96}
97
98pub struct RenderCore {
100 graphics: crate::GraphicsDevice,
101 #[allow(unused)]
102 window: Option<Arc<Window>>,
103 pub(crate) instance: wgpu::Instance,
104 device: wgpu::Device,
105 surface: Option<wgpu::Surface<'static>>,
106 queue: wgpu::Queue,
107 config: wgpu::SurfaceConfiguration,
108 draw_config: wgpu::SurfaceConfiguration,
109 size: winit::dpi::PhysicalSize<u32>,
110 size_changed: bool,
111 pipelines: RenderPipelines,
112 pipeline_factories: FxHashMap<(PipelineKind, TypeId), PipelineFactory>,
113
114 pipeline_cache: Option<wgpu::PipelineCache>,
115 adapter_info: wgpu::AdapterInfo,
116
117 targets: FrameTargets,
118 blit: BlitState,
119 frame_index: u64,
120 previous_graph: Option<crate::RenderGraph>,
121 last_render_breakdown: Option<RenderTimingBreakdown>,
122 #[cfg(all(feature = "profiling", not(target_arch = "wasm32")))]
123 gpu_timing: Option<gpu_timing::GpuTimingProbe>,
124}
125
126pub struct RenderResources<'a> {
128 pub graphics: &'a crate::GraphicsDevice,
130 pub device: &'a wgpu::Device,
132 pub queue: &'a wgpu::Queue,
134 pub surface_config: &'a wgpu::SurfaceConfiguration,
136 pub pipeline_cache: Option<&'a wgpu::PipelineCache>,
138 pub sample_count: u32,
140}
141
142impl RenderCore {
143 pub fn resources(&self) -> RenderResources<'_> {
145 RenderResources {
146 graphics: &self.graphics,
147 device: &self.device,
148 queue: &self.queue,
149 surface_config: &self.draw_config,
150 pipeline_cache: self.pipeline_cache.as_ref(),
151 sample_count: self.targets.sample_count,
152 }
153 }
154
155 pub fn window(&self) -> &Window {
160 self.window
161 .as_deref()
162 .expect("RenderCore has no window in offscreen mode")
163 }
164
165 pub fn window_arc(&self) -> Arc<Window> {
169 self.window
170 .clone()
171 .expect("RenderCore has no window in offscreen mode")
172 }
173
174 pub fn has_surface(&self) -> bool {
178 self.surface.is_some()
179 }
180
181 pub fn device(&self) -> &wgpu::Device {
183 &self.device
184 }
185
186 pub fn queue(&self) -> &wgpu::Queue {
188 &self.queue
189 }
190
191 pub fn surface_config(&self) -> &wgpu::SurfaceConfiguration {
193 &self.config
194 }
195
196 pub fn pipeline_cache(&self) -> Option<&wgpu::PipelineCache> {
198 self.pipeline_cache.as_ref()
199 }
200
201 pub fn sample_count(&self) -> u32 {
203 self.targets.sample_count
204 }
205
206 pub(crate) fn last_render_breakdown(&self) -> Option<RenderTimingBreakdown> {
208 self.last_render_breakdown
209 }
210
211 pub fn register_draw_pipeline<T, P>(&mut self, pipeline: P)
213 where
214 T: DrawCommand + 'static,
215 P: DrawablePipeline<T> + 'static,
216 {
217 self.pipeline_factories
218 .remove(&(PipelineKind::Draw, TypeId::of::<T>()));
219 self.pipelines.drawer.pipeline_registry.register(pipeline);
220 }
221
222 pub fn register_compute_pipeline<T, P>(&mut self, pipeline: P)
224 where
225 T: ComputeCommand + 'static,
226 P: ComputablePipeline<T> + 'static,
227 {
228 self.pipeline_factories
229 .remove(&(PipelineKind::Compute, TypeId::of::<T>()));
230 self.pipelines.compute_registry.register(pipeline);
231 }
232
233 pub(crate) fn register_draw_pipeline_factory<T, P>(
234 &mut self,
235 factory: impl FnOnce(RenderResources<'_>) -> P + Send + 'static,
236 ) where
237 T: DrawCommand + 'static,
238 P: DrawablePipeline<T> + 'static,
239 {
240 let type_id = TypeId::of::<T>();
241 self.pipelines
242 .drawer
243 .pipeline_registry
244 .pipelines
245 .remove(&type_id);
246 self.pipeline_factories.insert(
247 (PipelineKind::Draw, type_id),
248 Box::new(move |core| {
249 let pipeline = factory(core.resources());
250 core.register_draw_pipeline(pipeline);
251 }),
252 );
253 }
254
255 pub(crate) fn register_compute_pipeline_factory<T, P>(
256 &mut self,
257 factory: impl FnOnce(RenderResources<'_>) -> P + Send + 'static,
258 ) where
259 T: ComputeCommand + 'static,
260 P: ComputablePipeline<T> + 'static,
261 {
262 let type_id = TypeId::of::<T>();
263 self.pipelines.compute_registry.remove(type_id);
264 self.pipeline_factories.insert(
265 (PipelineKind::Compute, type_id),
266 Box::new(move |core| {
267 let pipeline = factory(core.resources());
268 core.register_compute_pipeline(pipeline);
269 }),
270 );
271 }
272
273 pub(crate) fn save_pipeline_cache(&self) -> io::Result<()> {
274 if let Some(cache) = self.pipeline_cache.as_ref() {
275 save_cache(cache, &self.adapter_info)?;
276 }
277 Ok(())
278 }
279}