Skip to main content

pebble/wgpu/
backend.rs

1use crate::{
2    app::App,
3    ecs::plugin::Plugin,
4    rendering::{
5        backend::{Backend, ColorTarget, FrameOperations, Pass},
6        errors::AcquireError,
7        sync::InitSender,
8        window::{GPUSurfaceHandle, WindowConfig},
9    },
10    wgpu::window::WinitWindow,
11};
12
13pub struct WGPUBackend {
14    pub device: wgpu::Device,
15    pub queue: wgpu::Queue,
16    pub surface: wgpu::Surface<'static>,
17    pub config: wgpu::SurfaceConfiguration,
18}
19
20pub struct WGPUFrame {
21    encoder: wgpu::CommandEncoder,
22    view: wgpu::TextureView,
23    surface_texture: wgpu::SurfaceTexture,
24}
25
26impl FrameOperations for WGPUFrame {
27    type Context<'a> = wgpu::RenderPass<'a>;
28    type Attachment = wgpu::TextureView;
29    type DepthAttachment = wgpu::TextureView;
30
31    fn begin(&mut self, pass: Pass<'_, Self>) -> Self::Context<'_> {
32        let color_attachments: Vec<_> = pass
33            .colors
34            .iter()
35            .map(|target| {
36                let (view, clear) = match target {
37                    ColorTarget::Default { clear } => (&self.view, clear),
38                    ColorTarget::Custom { attachment, clear } => (*attachment, clear),
39                };
40                Some(wgpu::RenderPassColorAttachment {
41                    view,
42                    depth_slice: None,
43                    resolve_target: None,
44                    ops: wgpu::Operations {
45                        load: clear
46                            .map(|[r, g, b, a]| {
47                                wgpu::LoadOp::Clear(wgpu::Color {
48                                    r: r as f64,
49                                    g: g as f64,
50                                    b: b as f64,
51                                    a: a as f64,
52                                })
53                            })
54                            .unwrap_or(wgpu::LoadOp::Load),
55                        store: wgpu::StoreOp::Store,
56                    },
57                })
58            })
59            .collect();
60
61        let depth_stencil_attachment =
62            pass.depth
63                .as_ref()
64                .map(|d| wgpu::RenderPassDepthStencilAttachment {
65                    view: d.attachment,
66                    depth_ops: Some(wgpu::Operations {
67                        load: d
68                            .clear
69                            .map(wgpu::LoadOp::Clear)
70                            .unwrap_or(wgpu::LoadOp::Load),
71                        store: wgpu::StoreOp::Store,
72                    }),
73                    stencil_ops: None,
74                });
75
76        self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
77            label: None,
78            color_attachments: &color_attachments,
79            depth_stencil_attachment,
80            timestamp_writes: None,
81            occlusion_query_set: None,
82            multiview_mask: None,
83        })
84    }
85}
86
87impl WGPUFrame {
88    /// Begin a compute pass on this frame's command encoder.
89    pub fn compute_pass(&mut self, label: Option<&str>) -> wgpu::ComputePass<'_> {
90        self.encoder
91            .begin_compute_pass(&wgpu::ComputePassDescriptor {
92                label,
93                timestamp_writes: None,
94            })
95    }
96}
97
98impl Backend for WGPUBackend {
99    type Frame = WGPUFrame;
100
101    fn init(handle: impl GPUSurfaceHandle, width: u32, height: u32, sender: InitSender<Self>) {
102        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
103            display: None,
104            backends: wgpu::Backends::default(),
105            flags: wgpu::InstanceFlags::default(),
106            memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
107            backend_options: wgpu::BackendOptions::default(),
108        });
109
110        let surface = instance.create_surface(handle).unwrap();
111
112        let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
113            power_preference: wgpu::PowerPreference::HighPerformance,
114            force_fallback_adapter: false,
115            compatible_surface: Some(&surface),
116        }))
117        .unwrap();
118
119        let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
120            label: None,
121            required_features: wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER,
122            required_limits: wgpu::Limits::default(),
123            ..Default::default()
124        }))
125        .unwrap();
126
127        let caps = surface.get_capabilities(&adapter);
128        let config = wgpu::SurfaceConfiguration {
129            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
130            format: caps.formats[0],
131            present_mode: caps.present_modes[0],
132            alpha_mode: caps.alpha_modes[0],
133            width,
134            height,
135            desired_maximum_frame_latency: 2,
136            view_formats: vec![],
137        };
138        surface.configure(&device, &config);
139
140        sender.send(WGPUBackend {
141            device,
142            queue,
143            surface,
144            config,
145        });
146    }
147
148    fn resize(&mut self, width: u32, height: u32) {
149        if width == 0 || height == 0 {
150            return; // minimized — don't reconfigure to a degenerate size
151        }
152        self.config.width = width;
153        self.config.height = height;
154        self.surface.configure(&self.device, &self.config);
155    }
156
157    fn acquire(&mut self) -> Result<Self::Frame, AcquireError> {
158        let surface_texture = match self.surface.get_current_texture() {
159            wgpu::CurrentSurfaceTexture::Success(texture) => texture,
160            wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
161            wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Outdated => {
162                return Err(AcquireError::Transient);
163            }
164            other => {
165                return Err(AcquireError::Fatal(format!(
166                    "unexpected surface state: {other:?}"
167                )));
168            }
169        };
170
171        let view = surface_texture
172            .texture
173            .create_view(&wgpu::TextureViewDescriptor::default());
174        let encoder = self
175            .device
176            .create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
177
178        Ok(WGPUFrame {
179            encoder,
180            view,
181            surface_texture,
182        })
183    }
184
185    fn present(&mut self, frame: Self::Frame) {
186        self.queue.submit(std::iter::once(frame.encoder.finish()));
187        frame.surface_texture.present();
188    }
189}
190
191pub struct WGPUPlugin {
192    config: WindowConfig,
193}
194
195impl WGPUPlugin {
196    pub fn new(config: WindowConfig) -> Self {
197        Self { config }
198    }
199}
200
201impl Plugin for WGPUPlugin {
202    fn build(&self, app: &mut App) {
203        app.add_plugin(crate::prelude::WindowPlugin::<WinitWindow>::new(
204            WindowConfig {
205                title: self.config.title,
206                width: self.config.width,
207                height: self.config.height,
208            },
209        ))
210        .add_plugin(crate::prelude::GraphicsPlugin::<WGPUBackend, WinitWindow>::new())
211        .add_plugin(crate::prelude::RenderPlugin::<WGPUBackend>::new())
212        .add_plugin(crate::wgpu::textures::TexturePlugin)
213        .add_plugin(crate::wgpu::texture_array::TextureArrayPlugin)
214        .add_plugin(crate::wgpu::cubemap::CubemapPlugin)
215        .add_plugin(crate::wgpu::mesh::MeshPlugin::new())
216        .add_plugin(crate::wgpu::material::MaterialPlugin::new())
217        .add_plugin(crate::wgpu::material_instance::MaterialInstancePlugin::new())
218        .add_plugin(crate::wgpu::compute::ComputePlugin::new())
219        .add_plugin(crate::prelude::LazyResourcePlugin::<
220            WGPUBackend,
221            crate::wgpu::samplers::GlobalSamplers,
222        >::new());
223    }
224}