pebble/graphics/
render.rs1use std::sync::Arc;
2
3use crate::{
4 app::BackendReady,
5 ecs::{
6 commands::Commands,
7 plugin::Plugin,
8 promise::{Promise, PromiseState},
9 resources::{Read, Write},
10 },
11 graphics::{
12 render::frame::{CurrentFrame, Frame},
13 types::TextureFormat,
14 window::Window,
15 },
16};
17
18pub mod compute_pass;
19pub mod frame;
20pub(crate) mod gpu_context;
21pub mod render_pass;
22pub mod targets;
23
24pub struct Backend {
30 pub(crate) device: wgpu::Device,
31 pub(crate) queue: wgpu::Queue,
32 surface: wgpu::Surface<'static>,
33 surface_configuration: wgpu::SurfaceConfiguration,
34}
35
36impl Backend {
37 pub fn surface_width(&self) -> u32 {
38 self.surface_configuration.width
39 }
40
41 pub fn surface_height(&self) -> u32 {
42 self.surface_configuration.height
43 }
44
45 pub fn surface_format(&self) -> TextureFormat {
46 self.surface_configuration.format.into()
47 }
48
49 pub fn dispatch_compute(&self, record: impl FnOnce(&mut compute_pass::ComputePass)) {
55 let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
56 {
57 let raw_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor::default());
58 let mut pass = compute_pass::ComputePass::new(raw_pass);
59 record(&mut pass);
60 }
61 self.queue.submit(std::iter::once(encoder.finish()));
62 }
63}
64
65pub(crate) struct BackendPlugin;
66impl Plugin for BackendPlugin {
67 fn build(self, app: crate::app::App) -> crate::app::App {
68 app.insert_resource(CurrentFrame::default())
69 .add_gpu_system(crate::ecs::system::SystemStage::Update, obtain_gpu)
70 .add_gpu_system(crate::ecs::system::SystemStage::Update, poll_gpu)
71 .add_gpu_system(
72 crate::ecs::system::SystemStage::Update,
73 clean_up_gpu_acquisition_resources,
74 )
75 .add_system(crate::ecs::system::SystemStage::PreRender, begin_frame)
76 .add_system(crate::ecs::system::SystemStage::PostRender, end_frame)
77 .add_system(crate::ecs::system::SystemStage::PostRender, maintain_gpu)
78 }
79}
80
81pub struct GPUReceiver {
82 promise: Promise<Backend>,
83}
84
85async fn init_gpu(window: Arc<winit::window::Window>) -> Backend {
86 let instance = wgpu::Instance::default();
87
88 let surface = instance.create_surface(window.clone()).unwrap();
89
90 let adapter = instance
91 .request_adapter(&wgpu::RequestAdapterOptions {
92 power_preference: wgpu::PowerPreference::HighPerformance,
93 compatible_surface: Some(&surface),
94 force_fallback_adapter: false,
95 })
96 .await
97 .unwrap();
98
99 #[cfg(not(target_arch = "wasm32"))]
100 let required_features = wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER;
101 #[cfg(target_arch = "wasm32")]
102 let required_features = wgpu::Features::empty();
103
104 let (device, queue) = adapter
105 .request_device(&wgpu::DeviceDescriptor {
106 required_features,
107 ..Default::default()
108 })
109 .await
110 .unwrap();
111
112 let window_size = window.inner_size();
113
114 let caps = surface.get_capabilities(&adapter);
115 let surface_configuration = wgpu::SurfaceConfiguration {
116 alpha_mode: caps.alpha_modes[0],
117 present_mode: caps.present_modes[0],
118 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
119 format: caps.formats.iter().find(|f| !f.is_srgb()).unwrap().clone(),
120 width: window_size.width,
121 height: window_size.height,
122 view_formats: vec![],
123 desired_maximum_frame_latency: 2,
124 };
125 surface.configure(&device, &surface_configuration);
126
127 let backend = Backend {
128 device,
129 queue,
130 surface,
131 surface_configuration,
132 };
133
134 backend
135}
136
137pub(crate) fn obtain_gpu(
138 mut commands: Commands,
139 window: Read<Window>,
140 receiver: Option<Read<GPUReceiver>>,
141) {
142 if receiver.is_some() {
144 return;
145 }
146
147 let window = window.raw();
148 let (fulfiller, promise) = Promise::new();
149
150 let fut = async move {
151 let result = init_gpu(window).await;
152 fulfiller.fulfill(result);
153 };
154
155 #[cfg(not(target_arch = "wasm32"))]
159 {
160 pollster::block_on(fut);
161 }
162
163 #[cfg(target_arch = "wasm32")]
164 {
165 wasm_bindgen_futures::spawn_local(fut);
166 }
167
168 commands.insert_resource(GPUReceiver { promise });
169}
170
171pub(crate) fn poll_gpu(
172 mut commands: Commands,
173 mut ready: Write<BackendReady>,
174 receiver: Option<Read<GPUReceiver>>,
175) {
176 let Some(receiver) = receiver else {
177 return;
178 };
179
180 if !ready.0 {
181 match receiver.promise.poll() {
183 PromiseState::Ready(backend) => {
184 println!("GPU Ready adding as resource");
185 commands.insert_resource(backend);
186 ready.0 = true;
187 }
188 PromiseState::Pending => {}
189 PromiseState::Disconnected => {
190 tracing::error!(
191 "GPU backend init sender was dropped without ever sending a value — the \
192 app has no usable backend and will stay idle forever"
193 );
194 }
195 }
196 }
197}
198
199pub(crate) fn clean_up_gpu_acquisition_resources(
200 mut commands: Commands,
201 ready: Read<BackendReady>,
202 receiver: Option<Read<GPUReceiver>>,
203) {
204 if ready.0 && receiver.is_some() {
205 commands.remove_resource::<GPUReceiver>();
206 }
207}
208
209pub(crate) fn begin_frame(
210 mut backend: Write<Backend>,
211 mut current_frame: Write<CurrentFrame>,
212 window: Read<Window>,
213) {
214 let (width, height) = window.inner_size();
215 if width > 0
216 && height > 0
217 && (width != backend.surface_configuration.width
218 || height != backend.surface_configuration.height)
219 {
220 backend.surface_configuration.width = width;
221 backend.surface_configuration.height = height;
222 backend.surface.configure(&backend.device, &backend.surface_configuration);
223 }
224
225 let surface_texture = match backend.surface.get_current_texture() {
226 wgpu::CurrentSurfaceTexture::Success(texture) => texture,
227 wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
228 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => return,
229 wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
230 backend.surface.configure(&backend.device, &backend.surface_configuration);
231 return;
232 }
233 wgpu::CurrentSurfaceTexture::Validation => {
234 tracing::error!("surface validation error acquiring the next frame");
235 return;
236 }
237 };
238
239 let view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default());
240 let encoder = backend.device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default());
241
242 current_frame.set(Frame::new(encoder, view, surface_texture));
243}
244
245pub(crate) fn maintain_gpu(backend: Read<Backend>) {
249 let _ = backend.device.poll(wgpu::PollType::Poll);
250}
251
252pub(crate) fn end_frame(backend: Read<Backend>, mut current_frame: Write<CurrentFrame>) {
253 let Some(frame) = current_frame.take() else {
254 return;
255 };
256
257 let (encoder, surface_texture) = frame.finish();
258 backend.queue.submit(std::iter::once(encoder.finish()));
259 surface_texture.present();
260}