all_is_cubes_gpu/in_wgpu/
headless.rs1use std::sync::Arc;
4
5use futures_channel::oneshot;
6use futures_core::future::BoxFuture;
7
8use all_is_cubes::character::Cursor;
9use all_is_cubes::listen;
10use all_is_cubes::util::Executor;
11use all_is_cubes_render::camera::{StandardCameras, Viewport};
12use all_is_cubes_render::{Flaws, HeadlessRenderer, RenderError, Rendering};
13
14use crate::common::{AdaptedInstant, FrameBudget};
15use crate::in_wgpu::{self, init};
16
17#[derive(Clone, Debug)]
22pub struct Builder {
23 executor: Arc<dyn Executor>,
24 adapter: Arc<wgpu::Adapter>,
25 device: Arc<wgpu::Device>,
26 queue: Arc<wgpu::Queue>,
27}
28
29impl Builder {
30 #[cfg_attr(target_family = "wasm", expect(clippy::arc_with_non_send_sync))]
32 pub async fn from_adapter(
33 label: &str,
34 adapter: wgpu::Adapter,
35 ) -> Result<Self, wgpu::RequestDeviceError> {
36 let (device, queue) = adapter
37 .request_device(
38 &in_wgpu::EverythingRenderer::<AdaptedInstant>::device_descriptor(
39 label,
40 adapter.limits(),
41 ),
42 None,
43 )
44 .await?;
45 Ok(Self {
46 device: Arc::new(device),
47 queue: Arc::new(queue),
48 adapter: Arc::new(adapter),
49 executor: Arc::new(()),
50 })
51 }
52
53 #[must_use]
55 pub fn executor(mut self, executor: Arc<dyn Executor>) -> Self {
56 self.executor = executor;
57 self
58 }
59
60 pub fn build(&self, cameras: StandardCameras) -> Renderer {
62 let viewport_source = cameras.viewport_source();
63 let everything = in_wgpu::EverythingRenderer::new(
64 self.executor.clone(),
65 self.device.clone(),
66 cameras,
67 wgpu::TextureFormat::Rgba8UnormSrgb,
68 &self.adapter,
69 );
70
71 let viewport_dirty = listen::Flag::listening(false, &viewport_source);
72 let viewport = viewport_source.get();
73 let color_texture = create_color_texture(&self.device, viewport);
74
75 Renderer::wrap(RendererImpl {
76 device: self.device.clone(),
77 queue: self.queue.clone(),
78 color_texture,
79 everything,
80 viewport_source,
81 viewport_dirty,
82 flaws: Flaws::UNFINISHED, })
84 }
85}
86
87#[derive(Debug)]
92pub struct Renderer {
93 #[cfg(target_family = "wasm")]
95 inner: futures_channel::mpsc::Sender<RenderMsg>,
96 #[cfg(not(target_family = "wasm"))]
97 inner: RendererImpl,
98}
99
100#[derive(Debug)]
102struct RendererImpl {
103 device: Arc<wgpu::Device>,
104 queue: Arc<wgpu::Queue>,
105 color_texture: wgpu::Texture,
106 everything: super::EverythingRenderer<AdaptedInstant>,
107 viewport_source: listen::DynSource<Viewport>,
108 viewport_dirty: listen::Flag,
109 flaws: Flaws,
110}
111
112pub(super) enum RenderMsg {
114 Update(Option<Cursor>, oneshot::Sender<Result<(), RenderError>>),
115 Render(String, oneshot::Sender<Result<Rendering, RenderError>>),
116}
117
118impl Renderer {
119 fn wrap(inner: RendererImpl) -> Renderer {
120 Self {
121 #[cfg(target_family = "wasm")]
122 inner: {
123 let (tx, mut rx) = futures_channel::mpsc::channel(1);
127 wasm_bindgen_futures::spawn_local(async move {
128 use futures_util::stream::StreamExt as _;
129 let mut inner = inner;
130 while let Some(msg) = rx.next().await {
131 inner.handle(msg).await;
132 }
133 });
134
135 tx
136 },
137 #[cfg(not(target_family = "wasm"))]
138 inner,
139 }
140 }
141
142 async fn send_maybe_wait(&mut self, msg: RenderMsg) {
143 #[cfg(target_family = "wasm")]
144 {
145 use futures_util::sink::SinkExt as _;
146 self.inner
147 .send(msg)
148 .await
149 .expect("Renderer actor unexpectedly disconnected");
150 }
151 #[cfg(not(target_family = "wasm"))]
152 {
153 self.inner.handle(msg).await;
154 }
155 }
156}
157
158impl HeadlessRenderer for Renderer {
159 fn update<'a>(
160 &'a mut self,
161 cursor: Option<&'a Cursor>,
162 ) -> BoxFuture<'a, Result<(), RenderError>> {
163 let (tx, rx) = oneshot::channel();
164 Box::pin(async move {
165 self.send_maybe_wait(RenderMsg::Update(cursor.cloned(), tx))
166 .await;
167 rx.await.unwrap()
168 })
169 }
170
171 fn draw<'a>(&'a mut self, info_text: &'a str) -> BoxFuture<'a, Result<Rendering, RenderError>> {
172 let (tx, rx) = oneshot::channel();
173 Box::pin(async move {
174 self.send_maybe_wait(RenderMsg::Render(info_text.to_owned(), tx))
175 .await;
176 rx.await.unwrap()
177 })
178 }
179}
180
181impl RendererImpl {
182 async fn handle(&mut self, msg: RenderMsg) {
183 match msg {
184 RenderMsg::Update(cursor, reply) => {
185 _ = reply.send(self.update(cursor.as_ref()));
186 }
187 RenderMsg::Render(info_text, reply) => {
188 _ = reply.send(self.draw(&info_text).await);
189 }
190 }
191 }
192
193 fn update(&mut self, cursor: Option<&Cursor>) -> Result<(), RenderError> {
194 let info =
195 self.everything
196 .update(&self.queue, cursor, &FrameBudget::PRACTICALLY_INFINITE)?;
197 self.flaws = info.flaws();
198 Ok(())
199 }
200
201 async fn draw(&mut self, info_text: &str) -> Result<Rendering, RenderError> {
202 let viewport = self.viewport_source.get();
204
205 if viewport.is_empty() {
206 return Ok(Rendering {
209 size: viewport.framebuffer_size,
210 data: Vec::new(),
211 flaws: Flaws::empty(),
212 });
213 }
214
215 if self.viewport_dirty.get_and_clear() {
216 self.color_texture = create_color_texture(&self.device, viewport);
217 }
218
219 let draw_info = self.everything.draw_frame_linear(&self.queue);
220 let post_flaws = self.everything.add_info_text_and_postprocess(
221 &self.queue,
222 &self
223 .color_texture
224 .create_view(&wgpu::TextureViewDescriptor::default()),
225 info_text,
226 );
227 let image = init::get_image_from_gpu(
228 &self.device,
229 &self.queue,
230 &self.color_texture,
231 self.flaws | draw_info.flaws() | post_flaws,
232 )
233 .await;
234 debug_assert_eq!(viewport.framebuffer_size, image.size);
235 Ok(image)
236 }
237}
238
239fn create_color_texture(device: &wgpu::Device, viewport: Viewport) -> wgpu::Texture {
240 device.create_texture(&wgpu::TextureDescriptor {
241 label: Some("headless::Renderer::color_texture"),
242 size: wgpu::Extent3d {
243 width: viewport.framebuffer_size.width.max(1),
244 height: viewport.framebuffer_size.height.max(1),
245 depth_or_array_layers: 1,
246 },
247 mip_level_count: 1,
248 sample_count: 1,
249 dimension: wgpu::TextureDimension::D2,
250 format: wgpu::TextureFormat::Rgba8UnormSrgb,
251 view_formats: &[],
252 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
253 })
254}