khora_infra/graphics/wgpu/
system.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The concrete, WGPU-based implementation of the `RenderSystem` trait.
16
17use crate::telemetry::gpu_monitor::GpuMonitor;
18
19use super::backend::WgpuBackendSelector;
20use super::context::WgpuGraphicsContext;
21use super::device::WgpuDevice;
22use super::profiler::WgpuTimestampProfiler;
23use khora_core::math::LinearRgba;
24use khora_core::platform::window::{KhoraWindow, KhoraWindowHandle};
25use khora_core::renderer::api::command::{
26    LoadOp, RenderPassColorAttachment, RenderPassDescriptor, StoreOp,
27};
28use khora_core::renderer::api::texture::TextureViewId;
29use khora_core::renderer::traits::{GpuProfiler, GraphicsBackendSelector};
30use khora_core::renderer::{
31    BackendSelectionConfig, BindGroupId, BindGroupLayoutId, BufferId, GraphicsDevice, IndexFormat,
32    Operations, RenderError, RenderObject, RenderSettings, RenderStats, RenderSystem,
33    RendererAdapterInfo, ViewInfo,
34};
35use khora_core::telemetry::ResourceMonitor;
36use khora_core::Stopwatch;
37use std::fmt;
38use std::sync::{Arc, Mutex};
39use std::time::Instant;
40use winit::dpi::PhysicalSize;
41
42/// The concrete, WGPU-based implementation of the [`RenderSystem`] trait.
43///
44/// This struct encapsulates all the state necessary to drive rendering with WGPU,
45/// including the graphics context, the logical device, GPU profiler, and complex
46/// state for handling window resizing gracefully.
47///
48/// It acts as the primary rendering backend for the engine when the WGPU feature is enabled.
49pub struct WgpuRenderSystem {
50    graphics_context_shared: Option<Arc<Mutex<WgpuGraphicsContext>>>,
51    wgpu_device: Option<Arc<WgpuDevice>>,
52    gpu_monitor: Option<Arc<GpuMonitor>>,
53    current_width: u32,
54    current_height: u32,
55    frame_count: u64,
56    last_frame_stats: RenderStats,
57    gpu_profiler: Option<Box<dyn GpuProfiler>>,
58    current_frame_view_id: Option<TextureViewId>,
59
60    // --- Camera Uniform Resources ---
61    camera_uniform_buffer: Option<BufferId>,
62    camera_bind_group: Option<BindGroupId>,
63    camera_bind_group_layout: Option<BindGroupLayoutId>,
64
65    // --- Resize Heuristics State ---
66    last_resize_event: Option<Instant>,
67    pending_resize: bool,
68    last_surface_config: Option<Instant>,
69    pending_resize_frames: u32,
70    last_pending_size: Option<(u32, u32)>,
71    stable_size_frame_count: u32,
72}
73
74impl fmt::Debug for WgpuRenderSystem {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.debug_struct("WgpuRenderSystem")
77            .field("graphics_context_shared", &self.graphics_context_shared)
78            .field("wgpu_device", &self.wgpu_device)
79            .field("gpu_monitor", &self.gpu_monitor)
80            .field("current_width", &self.current_width)
81            .field("current_height", &self.current_height)
82            .field("frame_count", &self.frame_count)
83            .field("last_frame_stats", &self.last_frame_stats)
84            .field(
85                "gpu_profiler",
86                &self.gpu_profiler.as_ref().map(|_| "GpuProfiler(...)"),
87            )
88            .field("current_frame_view_id", &self.current_frame_view_id)
89            .field(
90                "camera_uniform_buffer",
91                &self.camera_uniform_buffer.as_ref().map(|_| "Buffer(...)"),
92            )
93            .field(
94                "camera_bind_group",
95                &self.camera_bind_group.as_ref().map(|_| "BindGroup(...)"),
96            )
97            .field(
98                "camera_bind_group_layout",
99                &self
100                    .camera_bind_group_layout
101                    .as_ref()
102                    .map(|_| "BindGroupLayout(...)"),
103            )
104            .finish()
105    }
106}
107
108impl Default for WgpuRenderSystem {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114impl WgpuRenderSystem {
115    /// Creates a new, uninitialized `WgpuRenderSystem`.
116    ///
117    /// The system is not usable until [`RenderSystem::init`] is called.
118    pub fn new() -> Self {
119        log::info!("WgpuRenderSystem created (uninitialized).");
120        Self {
121            graphics_context_shared: None,
122            wgpu_device: None,
123            gpu_monitor: None,
124            current_width: 0,
125            current_height: 0,
126            frame_count: 0,
127            last_frame_stats: RenderStats::default(),
128            gpu_profiler: None,
129            current_frame_view_id: None,
130            camera_uniform_buffer: None,
131            camera_bind_group: None,
132            camera_bind_group_layout: None,
133            last_resize_event: None,
134            pending_resize: false,
135            last_surface_config: None,
136            pending_resize_frames: 0,
137            last_pending_size: None,
138            stable_size_frame_count: 0,
139        }
140    }
141
142    async fn initialize(
143        &mut self,
144        window_handle: KhoraWindowHandle,
145        window_size: PhysicalSize<u32>,
146    ) -> Result<Vec<Arc<dyn ResourceMonitor>>, RenderError> {
147        if self.graphics_context_shared.is_some() {
148            return Err(RenderError::InitializationFailed(
149                "WgpuRenderSystem is already initialized.".to_string(),
150            ));
151        }
152        log::info!("WgpuRenderSystem: Initializing...");
153
154        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
155        let backend_selector = WgpuBackendSelector::new(instance.clone());
156        let selection_config = BackendSelectionConfig::default();
157
158        let selection_result = backend_selector
159            .select_backend(&selection_config)
160            .await
161            .map_err(|e| RenderError::InitializationFailed(e.to_string()))?;
162        let adapter = selection_result.adapter;
163
164        let context = WgpuGraphicsContext::new(&instance, window_handle, adapter, window_size)
165            .await
166            .map_err(|e| RenderError::InitializationFailed(e.to_string()))?;
167
168        self.current_width = context.get_size().0;
169        self.current_height = context.get_size().1;
170        let context_arc = Arc::new(Mutex::new(context));
171        self.graphics_context_shared = Some(context_arc.clone());
172
173        log::info!(
174            "WgpuRenderSystem: GraphicsContext created with size: {}x{}",
175            self.current_width,
176            self.current_height
177        );
178
179        let graphics_device = WgpuDevice::new(context_arc.clone());
180        let device_arc = Arc::new(graphics_device);
181        self.wgpu_device = Some(device_arc.clone());
182
183        if let Ok(gc_guard) = context_arc.lock() {
184            if WgpuTimestampProfiler::feature_available(gc_guard.active_device_features) {
185                if let Some(mut profiler) = WgpuTimestampProfiler::new(&gc_guard.device) {
186                    let period = gc_guard.queue.get_timestamp_period();
187                    profiler.set_timestamp_period(period);
188                    self.gpu_profiler = Some(Box::new(profiler));
189                }
190            } else {
191                log::info!("GPU timestamp feature not available; instrumentation disabled.");
192            }
193        }
194
195        let mut created_monitors: Vec<Arc<dyn ResourceMonitor>> = Vec::new();
196        let gpu_monitor = Arc::new(GpuMonitor::new("WGPU".to_string()));
197        created_monitors.push(gpu_monitor.clone());
198        self.gpu_monitor = Some(gpu_monitor);
199
200        let vram_monitor = device_arc as Arc<dyn ResourceMonitor>;
201        created_monitors.push(vram_monitor);
202
203        // Initialize camera uniform resources
204        self.initialize_camera_uniforms()?;
205
206        Ok(created_monitors)
207    }
208
209    /// Initializes the camera uniform buffer and bind group.
210    ///
211    /// This creates:
212    /// - A uniform buffer to hold camera data (view-projection matrix and camera position)
213    /// - A bind group layout describing the shader resource binding
214    /// - A bind group that binds the buffer to group 0, binding 0
215    fn initialize_camera_uniforms(&mut self) -> Result<(), RenderError> {
216        use khora_core::renderer::{
217            BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry,
218            BindingResource, BindingType, BufferBinding, BufferBindingType, BufferDescriptor,
219            BufferUsage, CameraUniformData, ShaderStageFlags,
220        };
221
222        let device = self.wgpu_device.as_ref().ok_or_else(|| {
223            RenderError::InitializationFailed("WGPU device not initialized".to_string())
224        })?;
225
226        let buffer_size = std::mem::size_of::<CameraUniformData>() as u64;
227
228        // Create the uniform buffer using the abstract API
229        let buffer_descriptor = BufferDescriptor {
230            label: Some(std::borrow::Cow::Borrowed("Camera Uniform Buffer")),
231            size: buffer_size,
232            usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
233            mapped_at_creation: false,
234        };
235
236        let uniform_buffer = device.create_buffer(&buffer_descriptor).map_err(|e| {
237            RenderError::InitializationFailed(format!(
238                "Failed to create camera uniform buffer: {:?}",
239                e
240            ))
241        })?;
242
243        // Create the bind group layout using the abstract API
244        let layout_entry = BindGroupLayoutEntry {
245            binding: 0,
246            visibility: ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
247            ty: BindingType::Buffer {
248                ty: BufferBindingType::Uniform,
249                has_dynamic_offset: false,
250                min_binding_size: None,
251            },
252        };
253
254        let layout_descriptor = BindGroupLayoutDescriptor {
255            label: Some("Camera Bind Group Layout"),
256            entries: &[layout_entry],
257        };
258
259        let bind_group_layout = device
260            .create_bind_group_layout(&layout_descriptor)
261            .map_err(|e| {
262                RenderError::InitializationFailed(format!(
263                    "Failed to create camera bind group layout: {:?}",
264                    e
265                ))
266            })?;
267
268        // Create the bind group using the abstract API
269        let bind_group_entry = BindGroupEntry {
270            binding: 0,
271            resource: BindingResource::Buffer(BufferBinding {
272                buffer: uniform_buffer,
273                offset: 0,
274                size: None,
275            }),
276            _phantom: std::marker::PhantomData,
277        };
278
279        let bind_group_descriptor = BindGroupDescriptor {
280            label: Some("Camera Bind Group"),
281            layout: bind_group_layout,
282            entries: &[bind_group_entry],
283        };
284
285        let bind_group = device
286            .create_bind_group(&bind_group_descriptor)
287            .map_err(|e| {
288                RenderError::InitializationFailed(format!(
289                    "Failed to create camera bind group: {:?}",
290                    e
291                ))
292            })?;
293
294        self.camera_uniform_buffer = Some(uniform_buffer);
295        self.camera_bind_group_layout = Some(bind_group_layout);
296        self.camera_bind_group = Some(bind_group);
297
298        log::info!("Camera uniform resources initialized with abstract API");
299
300        Ok(())
301    }
302
303    /// Updates the camera uniform buffer with the current ViewInfo data.
304    ///
305    /// This method is called every frame to upload the latest camera matrices
306    /// to the GPU uniform buffer.
307    fn update_camera_uniforms(&mut self, view_info: &ViewInfo) {
308        use khora_core::renderer::CameraUniformData;
309
310        let uniform_data = CameraUniformData::from_view_info(view_info);
311
312        if let (Some(device), Some(buffer_id)) = (&self.wgpu_device, &self.camera_uniform_buffer) {
313            // Write the uniform data to the buffer using the abstract API
314            if let Err(e) =
315                device.write_buffer(*buffer_id, 0, bytemuck::cast_slice(&[uniform_data]))
316            {
317                log::warn!("Failed to write camera uniform data: {:?}", e);
318            }
319        }
320    }
321}
322
323impl RenderSystem for WgpuRenderSystem {
324    fn init(
325        &mut self,
326        window: &dyn KhoraWindow,
327    ) -> Result<Vec<Arc<dyn ResourceMonitor>>, RenderError> {
328        let (width, height) = window.inner_size();
329        let window_size = PhysicalSize::new(width, height);
330        let window_handle_arc = window.clone_handle_arc();
331        pollster::block_on(self.initialize(window_handle_arc, window_size))
332    }
333
334    fn resize(&mut self, new_width: u32, new_height: u32) {
335        if new_width > 0 && new_height > 0 {
336            log::debug!(
337                "WgpuRenderSystem: resize_surface called with W:{new_width}, H:{new_height}"
338            );
339            self.current_width = new_width;
340            self.current_height = new_height;
341            let now = Instant::now();
342            if let Some((lw, lh)) = self.last_pending_size {
343                if lw == new_width && lh == new_height {
344                    self.stable_size_frame_count = self.stable_size_frame_count.saturating_add(1);
345                } else {
346                    self.stable_size_frame_count = 0;
347                }
348            }
349            self.last_pending_size = Some((new_width, new_height));
350
351            let immediate_threshold_ms: u128 = 80;
352            let can_immediate = self
353                .last_surface_config
354                .map(|t| t.elapsed().as_millis() >= immediate_threshold_ms)
355                .unwrap_or(true);
356            let early_stable = self.stable_size_frame_count >= 2
357                && self
358                    .last_surface_config
359                    .map(|t| t.elapsed().as_millis() >= 20)
360                    .unwrap_or(true);
361            if can_immediate || early_stable {
362                if let Some(gc_arc_mutex) = &self.graphics_context_shared {
363                    if let Ok(mut gc_guard) = gc_arc_mutex.lock() {
364                        gc_guard.resize(self.current_width, self.current_height);
365                        self.last_surface_config = Some(now);
366                        self.pending_resize = false;
367                        self.pending_resize_frames = 0;
368                        log::info!(
369                            "WGPUGraphicsContext: Immediate/Early surface configuration to {}x{}",
370                            self.current_width,
371                            self.current_height
372                        );
373                        return;
374                    }
375                }
376            }
377            self.last_resize_event = Some(now);
378            self.pending_resize = true;
379            self.pending_resize_frames = 0;
380        } else {
381            log::warn!(
382                "WgpuRenderSystem::resize_surface called with zero size ({new_width}, {new_height}). Ignoring."
383            );
384        }
385    }
386
387    fn prepare_frame(&mut self, view_info: &ViewInfo) {
388        if self.graphics_context_shared.is_none() {
389            return;
390        }
391        let stopwatch = Stopwatch::new();
392
393        // Update camera uniform buffer with the current ViewInfo
394        self.update_camera_uniforms(view_info);
395
396        self.last_frame_stats.cpu_preparation_time_ms = stopwatch.elapsed_ms().unwrap_or(0) as f32;
397    }
398
399    fn render(
400        &mut self,
401        renderables: &[RenderObject],
402        _view_info: &ViewInfo,
403        settings: &RenderSettings,
404    ) -> Result<RenderStats, RenderError> {
405        let full_frame_timer = Stopwatch::new();
406
407        let device = self
408            .wgpu_device
409            .as_ref()
410            .ok_or(RenderError::NotInitialized)?;
411
412        // Poll the device to process any pending GPU-to-CPU callbacks, such as
413        // those from the profiler's `map_async` calls. This is crucial.
414        device.poll_device_non_blocking();
415
416        let gc = self
417            .graphics_context_shared
418            .as_ref()
419            .ok_or(RenderError::NotInitialized)?;
420
421        if let Some(p) = self.gpu_profiler.as_mut() {
422            p.try_read_previous_frame();
423        }
424
425        // --- Handle Pending Resizes ---
426        if self.pending_resize {
427            self.pending_resize_frames = self.pending_resize_frames.saturating_add(1);
428            let mut resized_this_frame = false;
429            if let Some(t) = self.last_resize_event {
430                let quiet_elapsed = t.elapsed().as_millis();
431                let debounce_quiet_ms = settings.resize_debounce_ms as u128;
432                let max_pending_frames = settings.resize_max_pending_frames;
433                let early_stable = self.stable_size_frame_count >= 3;
434
435                if quiet_elapsed >= debounce_quiet_ms
436                    || self.pending_resize_frames >= max_pending_frames
437                    || early_stable
438                {
439                    if let Ok(mut gc_guard) = gc.lock() {
440                        gc_guard.resize(self.current_width, self.current_height);
441                        self.pending_resize = false;
442                        self.last_surface_config = Some(Instant::now());
443                        self.stable_size_frame_count = 0;
444                        resized_this_frame = true;
445                        log::info!(
446                            "Deferred surface configuration to {}x{}",
447                            self.current_width,
448                            self.current_height
449                        );
450                    }
451                }
452            }
453            if self.pending_resize && !resized_this_frame {
454                return Ok(self.last_frame_stats.clone());
455            }
456        }
457
458        // --- 1. Acquire Frame from Swap Chain ---
459        let output_surface_texture = loop {
460            let mut gc_guard = gc.lock().unwrap();
461            match gc_guard.get_current_texture() {
462                Ok(texture) => break texture,
463                Err(e @ wgpu::SurfaceError::Lost) | Err(e @ wgpu::SurfaceError::Outdated) => {
464                    if self.current_width > 0 && self.current_height > 0 {
465                        log::warn!(
466                            "WgpuRenderSystem: Swapchain surface lost or outdated ({:?}). Reconfiguring with current dimensions: W={}, H={}",
467                            e,
468                            self.current_width,
469                            self.current_height
470                        );
471                        gc_guard.resize(self.current_width, self.current_height);
472                        self.last_surface_config = Some(Instant::now());
473                        self.pending_resize = false; // reset pending state after forced reconfigure
474                    } else {
475                        log::error!(
476                            "WgpuRenderSystem: Swapchain lost/outdated ({:?}), but current stored size is zero ({},{}). Cannot reconfigure. Waiting for valid resize event.",
477                            e,
478                            self.current_width,
479                            self.current_height
480                        );
481                        return Err(RenderError::SurfaceAcquisitionFailed(format!(
482                            "Surface Lost/Outdated ({e:?}) and current size is zero",
483                        )));
484                    }
485                }
486                Err(e @ wgpu::SurfaceError::OutOfMemory) => {
487                    log::error!("WgpuRenderSystem: Swapchain OutOfMemory! ({e:?})");
488                    return Err(RenderError::SurfaceAcquisitionFailed(format!(
489                        "OutOfMemory: {e:?}"
490                    )));
491                }
492                Err(e @ wgpu::SurfaceError::Timeout) => {
493                    log::warn!("WgpuRenderSystem: Swapchain Timeout acquiring frame. ({e:?})");
494                    return Err(RenderError::SurfaceAcquisitionFailed(format!(
495                        "Timeout: {e:?}"
496                    )));
497                }
498                Err(e) => {
499                    log::error!("WgpuRenderSystem: Unexpected SurfaceError: {e:?}");
500                    return Err(RenderError::SurfaceAcquisitionFailed(format!(
501                        "Unexpected SurfaceError: {e:?}"
502                    )));
503                }
504            }
505        };
506
507        let command_recording_timer = Stopwatch::new();
508
509        // --- 2. Create a managed, abstract view for the swap chain texture ---
510        if let Some(old_id) = self.current_frame_view_id.take() {
511            device.destroy_texture_view(old_id)?;
512        }
513        let target_view_id = device.create_texture_view_for_surface(
514            &output_surface_texture.texture,
515            Some("Primary Swap Chain View"),
516        )?;
517        self.current_frame_view_id = Some(target_view_id);
518
519        // --- 3. Create an abstract Command Encoder ---
520        let mut command_encoder = device.create_command_encoder(Some("Khora Main Command Encoder"));
521
522        // --- 4. Profiler Pass A (records start timestamps) ---
523        if settings.enable_gpu_timestamps {
524            if let Some(profiler) = self.gpu_profiler.as_ref() {
525                let _pass_a = command_encoder.begin_profiler_compute_pass(
526                    Some("Timestamp Pass A"),
527                    profiler.as_ref(),
528                    0,
529                );
530            }
531        }
532
533        // --- 5. Main Render Pass (drawing all objects) ---
534        {
535            let gc_guard = gc.lock().unwrap();
536            let wgpu_color = gc_guard.get_clear_color();
537            let clear_color = LinearRgba::new(
538                wgpu_color.r as f32,
539                wgpu_color.g as f32,
540                wgpu_color.b as f32,
541                wgpu_color.a as f32,
542            );
543
544            let color_attachment = RenderPassColorAttachment {
545                view: &target_view_id,
546                resolve_target: None,
547                ops: Operations {
548                    load: LoadOp::Clear(clear_color),
549                    store: StoreOp::Store,
550                },
551            };
552            let pass_descriptor = RenderPassDescriptor {
553                label: Some("Khora Main Abstract Render Pass"),
554                color_attachments: &[color_attachment],
555            };
556
557            let mut render_pass = command_encoder.begin_render_pass(&pass_descriptor);
558
559            // Bind the camera uniform bind group (group 0)
560            if let Some(camera_bind_group) = &self.camera_bind_group {
561                render_pass.set_bind_group(0, camera_bind_group);
562            }
563
564            let (draw_calls, triangles) = renderables.iter().fold((0, 0), |(dc, tris), obj| {
565                render_pass.set_pipeline(&obj.pipeline);
566                render_pass.set_vertex_buffer(0, &obj.vertex_buffer, 0);
567                render_pass.set_index_buffer(&obj.index_buffer, 0, IndexFormat::Uint16);
568                render_pass.draw_indexed(0..obj.index_count, 0, 0..1);
569                (dc + 1, tris + obj.index_count / 3)
570            });
571            self.last_frame_stats.draw_calls = draw_calls;
572            self.last_frame_stats.triangles_rendered = triangles;
573        }
574
575        // --- 6. Profiler Pass B and Timestamp Resolution ---
576        if settings.enable_gpu_timestamps {
577            if let Some(profiler) = self.gpu_profiler.as_ref() {
578                // This scope ensures the compute pass ends, releasing its mutable borrow on the encoder,
579                // before we try to mutably borrow the encoder again for resolve/copy.
580                {
581                    let _pass_b = command_encoder.begin_profiler_compute_pass(
582                        Some("Timestamp Pass B"),
583                        profiler.as_ref(),
584                        1,
585                    );
586                }
587                profiler.resolve_and_copy(command_encoder.as_mut());
588                profiler.copy_to_staging(command_encoder.as_mut(), self.frame_count);
589            }
590        }
591
592        // --- 7. Finalize and Submit Commands ---
593        let submission_timer = Stopwatch::new();
594        let command_buffer = command_encoder.finish();
595        device.submit_command_buffer(command_buffer);
596        let submission_ms = submission_timer.elapsed_ms().unwrap_or(0);
597
598        if settings.enable_gpu_timestamps {
599            if let Some(p) = self.gpu_profiler.as_mut() {
600                p.schedule_map_after_submit(self.frame_count);
601            }
602        }
603
604        // --- 8. Present the final image to the screen ---
605        output_surface_texture.present();
606
607        // --- 9. Update final frame statistics ---
608        self.frame_count += 1;
609        if let Some(p) = self.gpu_profiler.as_ref() {
610            self.last_frame_stats.gpu_main_pass_time_ms = p.last_main_pass_ms();
611            self.last_frame_stats.gpu_frame_total_time_ms = p.last_frame_total_ms();
612        }
613        let full_frame_ms = full_frame_timer.elapsed_ms().unwrap_or(0);
614        self.last_frame_stats.frame_number = self.frame_count;
615        self.last_frame_stats.cpu_preparation_time_ms =
616            (full_frame_ms - command_recording_timer.elapsed_ms().unwrap_or(0)) as f32;
617        self.last_frame_stats.cpu_render_submission_time_ms = submission_ms as f32;
618
619        if let Some(monitor) = &self.gpu_monitor {
620            monitor.update_from_frame_stats(&self.last_frame_stats);
621        }
622
623        Ok(self.last_frame_stats.clone())
624    }
625
626    fn get_last_frame_stats(&self) -> &RenderStats {
627        &self.last_frame_stats
628    }
629
630    fn supports_feature(&self, feature_name: &str) -> bool {
631        self.wgpu_device
632            .as_ref()
633            .is_some_and(|d| d.supports_feature(feature_name))
634    }
635
636    fn shutdown(&mut self) {
637        log::info!("WgpuRenderSystem shutting down...");
638        if let Some(mut profiler) = self.gpu_profiler.take() {
639            if let Some(device) = self.wgpu_device.as_ref() {
640                if let Some(wgpu_profiler) = profiler
641                    .as_any_mut()
642                    .downcast_mut::<WgpuTimestampProfiler>()
643                {
644                    wgpu_profiler.shutdown(device);
645                }
646            }
647        }
648        if let Some(old_id) = self.current_frame_view_id.take() {
649            if let Some(device) = self.wgpu_device.as_ref() {
650                let _ = device.destroy_texture_view(old_id);
651            }
652        }
653        self.wgpu_device = None;
654        self.graphics_context_shared = None;
655        self.gpu_monitor = None;
656    }
657
658    fn as_any(&self) -> &dyn std::any::Any {
659        self
660    }
661
662    fn get_adapter_info(&self) -> Option<RendererAdapterInfo> {
663        self.wgpu_device.as_ref().map(|d| d.get_adapter_info())
664    }
665
666    fn graphics_device(&self) -> Arc<dyn GraphicsDevice> {
667        self.wgpu_device
668            .clone()
669            .expect("WgpuRenderSystem: No WgpuDevice available.")
670    }
671}
672
673unsafe impl Send for WgpuRenderSystem {}
674unsafe impl Sync for WgpuRenderSystem {}