1use bevy::{tasks::block_on, window::RawHandleWrapperHolder};
4use wde_logger::prelude::*;
5use wgpu::{
6 BackendOptions, Device, Features, Limits as WLimits, MemoryBudgetThresholds,
7 PresentMode as WPresentMode, Surface, SurfaceConfiguration, SurfaceTexture
8};
9
10use crate::texture::TextureView;
11
12pub type Limits = WLimits;
13pub type PresentMode = WPresentMode;
14
15#[derive(Debug)]
30pub enum RenderError {
31 CannotPresent,
33 CannotResize,
35 PipelineNotSet,
37 PipelineNotInitialized,
39 MissingShader,
41 MissingVertexBuffer,
43 MissingIndexBuffer,
45 UnsupportedSwapchainFormat,
47 UnsupportedDepthFormat,
49 ShaderCompilationError,
51 MissingBindGroup,
53 CannotCreateBindGroupLayout,
55 CannotCreateBindGroup
57}
58
59#[derive(Debug)]
61pub struct RenderTexture {
62 pub texture: wgpu::SurfaceTexture,
64 pub view: TextureView
66}
67
68#[derive(Debug)]
73pub enum RenderEvent {
74 Redraw(RenderTexture),
76 Resize,
78 None
80}
81
82pub struct RenderInstanceData<'a> {
98 pub device: Device,
100 pub queue: wgpu::Queue,
102 pub surface: Option<Surface<'a>>,
104 pub adapter: wgpu::Adapter,
106 pub instance: wgpu::Instance,
108 pub surface_config: Option<SurfaceConfiguration>
110}
111
112pub async fn create_instance(
126 label: &str,
127 primary_window: Option<&RawHandleWrapperHolder>
128) -> RenderInstanceData<'static> {
129 info!(label, "Creating render instance.");
130 let _trace = info_span!("wgpu-instance-create").entered();
131
132 let mut flags = if cfg!(debug_assertions) {
134 wgpu::InstanceFlags::DEBUG
135 | wgpu::InstanceFlags::VALIDATION
136 | wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL
137 } else {
138 wgpu::InstanceFlags::DISCARD_HAL_LABELS
139 };
140 if cfg!(feature = "gpu-debug") {
141 flags |= wgpu::InstanceFlags::GPU_BASED_VALIDATION
142 }
143
144 debug!(label, "Creating wgpu instance.");
146 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
147 backends: wgpu::Backends::all(),
148 flags,
149 memory_budget_thresholds: MemoryBudgetThresholds::default(),
150 backend_options: BackendOptions::default()
151 });
152
153 let surface = primary_window.and_then(|wrapper| unsafe {
155 let maybe_handle = wrapper
156 .0
157 .lock()
158 .expect("Couldn't get the window handle in time for renderer initialization");
159 if let Some(wrapper) = maybe_handle.as_ref() {
160 let handle = wrapper.get_handle();
161 Some(
162 instance
163 .create_surface(handle)
164 .expect("Failed to create wgpu surface")
165 )
166 } else {
167 None
168 }
169 });
170
171 let adapter = instance
173 .enumerate_adapters(wgpu::Backends::all())
174 .into_iter()
175 .find(|a| a.get_info().backend == wgpu::Backend::Vulkan)
176 .or_else(|| {
177 block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
178 power_preference: wgpu::PowerPreference::HighPerformance,
179 compatible_surface: surface.as_ref(),
180 ..Default::default()
181 }))
182 .ok()
183 })
184 .expect("Failed to find a suitable GPU adapter.");
185
186 let adapter_info = adapter.get_info();
188 info!(
189 "Using {} adapter {}.",
190 match adapter_info.device_type {
191 wgpu::DeviceType::DiscreteGpu => "discrete GPU",
192 wgpu::DeviceType::IntegratedGpu => "integrated GPU",
193 wgpu::DeviceType::Cpu => "CPU",
194 wgpu::DeviceType::VirtualGpu => "virtual GPU",
195 wgpu::DeviceType::Other => "other"
196 },
197 adapter_info.name
198 );
199 debug!("Adapter info: {:#?}", adapter_info);
200 if adapter_info.device_type == wgpu::DeviceType::Cpu {
201 warn!(
202 "The selected adapter is using a driver that only supports software rendering, this will be very slow."
203 );
204 }
205
206 let required_features =
208 wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | Features::PUSH_CONSTANTS;
209
210 let required_limits = Limits {
212 max_push_constant_size: 128,
213 max_compute_invocations_per_workgroup: 1024,
214 ..Default::default()
215 };
216
217 trace!(label, "Requesting device.");
219 let (device, queue) = adapter
220 .request_device(&wgpu::DeviceDescriptor {
221 label: Some(label),
222 required_features,
223 required_limits,
224 memory_hints: wgpu::MemoryHints::Performance,
225 experimental_features: wgpu::ExperimentalFeatures::disabled(),
226 trace: wgpu::Trace::Off
227 })
228 .await
229 .unwrap_or_else(|e| panic!("Failed to create wgpu device: {:?}", e));
230
231 debug!("Configured wgpu adapter limits: {:#?}", device.limits());
233 debug!("Configured wgpu adapter features: {:#?}", device.features());
234
235 device.on_uncaptured_error(std::sync::Arc::new(|error| match error {
237 wgpu::Error::OutOfMemory { source } => {
238 error!("Out of memory error from wgpu: {:#?}", source)
239 }
240 wgpu::Error::Validation {
241 description,
242 source
243 } => warn!(
244 "Uncaptured validation error from wgpu.\n{}\n{:#?}",
245 description, source
246 ),
247 wgpu::Error::Internal {
248 source,
249 description
250 } => error!("Internal error from wgpu: {}\n{:#?}", description, source)
251 }));
252
253 device.set_device_lost_callback(|reason, message| match reason {
255 wgpu::DeviceLostReason::Destroyed => error!("The GPU device was destroyed: {}", message),
256 wgpu::DeviceLostReason::Unknown => {
257 error!("The GPU device was lost for an unknown reason: {}", message)
258 }
259 });
260
261 RenderInstanceData {
263 device,
264 queue,
265 surface,
266 adapter,
267 instance,
268 surface_config: None
269 }
270}
271
272pub fn setup_surface(
284 label: &str,
285 size: (u32, u32),
286 device: &Device,
287 surface: &Surface,
288 adapter: &wgpu::Adapter,
289 present_mode: PresentMode
290) -> SurfaceConfiguration {
291 debug!(label, "Configuring surface.");
292
293 let surface_caps = surface.get_capabilities(adapter);
295 debug!("Surface capabilities: {:#?}", surface_caps);
296 let surface_format = surface_caps
297 .formats
298 .iter()
299 .copied()
300 .find(|f| f.is_srgb())
301 .unwrap_or(surface_caps.formats[0]);
302
303 let mut usage = wgpu::TextureUsages::RENDER_ATTACHMENT;
305 if cfg!(debug_assertions) {
306 usage |= wgpu::TextureUsages::COPY_SRC;
307 }
308
309 let surface_config = wgpu::SurfaceConfiguration {
311 usage,
312 format: surface_format,
313 width: size.0,
314 height: size.1,
315 present_mode,
316 alpha_mode: surface_caps.alpha_modes[0],
317 view_formats: vec![],
318 desired_maximum_frame_latency: 2
319 };
320 surface.configure(device, &surface_config);
321
322 surface_config
323}
324
325pub fn get_current_texture(
346 surface: &Surface,
347 surface_config: &SurfaceConfiguration
348) -> RenderEvent {
349 event!(LogLevel::TRACE, "Getting current texture.");
350
351 let _get_current_texture = info_span!("acquire_texture").entered();
353 let render_texture = surface.get_current_texture();
354 drop(_get_current_texture);
355
356 event!(
358 LogLevel::TRACE,
359 "Texture acquired. Creating render view and checking status."
360 );
361 match render_texture {
362 Ok(surface_texture) => {
363 let render_view = surface_texture
365 .texture
366 .create_view(&wgpu::TextureViewDescriptor {
367 label: Some("Main render texture"),
368 format: match surface_config.format {
369 wgpu::TextureFormat::Bgra8UnormSrgb => {
370 Some(wgpu::TextureFormat::Bgra8UnormSrgb)
371 }
372 wgpu::TextureFormat::Rgba8UnormSrgb => {
373 Some(wgpu::TextureFormat::Rgba8UnormSrgb)
374 }
375 wgpu::TextureFormat::Bgra8Unorm => Some(wgpu::TextureFormat::Bgra8Unorm),
376 wgpu::TextureFormat::Rgba8Unorm => Some(wgpu::TextureFormat::Rgba8Unorm),
377 _ => unreachable!(
378 "Unsupported swapchain format: {:?}",
379 surface_config.format
380 )
381 },
382 dimension: Some(wgpu::TextureViewDimension::D2),
383 aspect: wgpu::TextureAspect::All,
384 base_mip_level: 0,
385 mip_level_count: None,
386 base_array_layer: 0,
387 array_layer_count: None,
388 usage: None
389 });
390 let cur_render = RenderTexture {
391 texture: surface_texture,
392 view: render_view
393 };
394 RenderEvent::Redraw(cur_render)
395 }
396 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => RenderEvent::Resize,
398 Err(wgpu::SurfaceError::OutOfMemory) => {
400 error!("System out of memory.");
401 RenderEvent::None
402 }
403 Err(wgpu::SurfaceError::Timeout) => {
405 warn!("Timeout of the surface.");
406 RenderEvent::None
407 }
408 Err(e) => {
410 error!("Failed to acquire next swapchain texture: {:?}.", e);
411 RenderEvent::None
412 }
413 }
414}
415
416pub fn present(surface_texture: SurfaceTexture) -> Result<(), RenderError> {
420 event!(LogLevel::TRACE, "Presenting render texture.");
421 surface_texture.present();
422 Ok(())
423}
424
425pub fn resize(device: &Device, surface: &Surface, surface_config: &SurfaceConfiguration) {
429 event!(
430 LogLevel::TRACE,
431 "Resizing surface to {}x{}.",
432 surface_config.width,
433 surface_config.height
434 );
435 surface.configure(device, surface_config);
436}