1use alloc::boxed::Box;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6
7use futures_core::future::BoxFuture;
8
9use all_is_cubes::character::Cursor;
10use all_is_cubes::listen;
11use all_is_cubes::universe::ReadTicket;
12use all_is_cubes::util::Executor;
13use all_is_cubes_render::camera::{ImageSize, Layers, StandardCameras, Viewport};
14use all_is_cubes_render::{Flaws, HeadlessRenderer, RenderError, Rendering};
15
16use crate::common::FrameBudget;
17use crate::glue::size2d_to_extent;
18use crate::init;
19
20#[derive(Clone, Debug)]
25pub struct Builder {
26 executor: Arc<dyn Executor>,
27 pub(crate) adapter: wgpu::Adapter,
28 device: wgpu::Device,
29 queue: wgpu::Queue,
30}
31
32impl Builder {
33 pub async fn from_adapter(
35 label: &str,
36 adapter: wgpu::Adapter,
37 ) -> Result<Self, wgpu::RequestDeviceError> {
38 let (device, queue) = adapter
39 .request_device(&crate::EverythingRenderer::device_descriptor(
40 label,
41 adapter.limits(),
42 ))
43 .await?;
44 Ok(Self {
45 device,
46 queue,
47 adapter,
48 executor: Arc::new(()),
49 })
50 }
51
52 #[must_use]
54 pub fn executor(mut self, executor: Arc<dyn Executor>) -> Self {
55 self.executor = executor;
56 self
57 }
58
59 pub fn build(&self, cameras: StandardCameras) -> Renderer {
61 let viewport_source = cameras.viewport_source();
62 let everything = crate::EverythingRenderer::new(
63 self.executor.clone(),
64 self.device.clone(),
65 &self.queue,
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 adapter_info: self.adapter.get_info(),
77 device: self.device.clone(),
78 queue: self.queue.clone(),
79 color_texture,
80 everything,
81 viewport_source,
82 viewport_dirty,
83 flaws: Flaws::UNFINISHED, })
85 }
86}
87
88#[derive(Debug)]
93pub struct Renderer {
94 #[cfg(target_family = "wasm")]
97 inner: send_wrapper::SendWrapper<RendererImpl>,
98 #[cfg(not(target_family = "wasm"))]
99 inner: RendererImpl,
100}
101
102#[derive(Debug)]
104struct RendererImpl {
105 adapter_info: wgpu::AdapterInfo,
106 device: wgpu::Device,
107 queue: wgpu::Queue,
108
109 color_texture: wgpu::Texture,
110 everything: super::EverythingRenderer,
111
112 viewport_source: listen::DynSource<Viewport>,
113 viewport_dirty: listen::Flag,
114
115 flaws: Flaws,
117}
118
119impl Renderer {
120 fn wrap(inner: RendererImpl) -> Renderer {
121 Self {
122 #[cfg(target_family = "wasm")]
123 inner: send_wrapper::SendWrapper::new(inner),
124 #[cfg(not(target_family = "wasm"))]
125 inner,
126 }
127 }
128}
129
130impl HeadlessRenderer for Renderer {
131 fn update(
132 &mut self,
133 read_tickets: Layers<ReadTicket<'_>>,
134 cursor: Option<&Cursor>,
135 ) -> Result<(), RenderError> {
136 self.inner.update(read_tickets, cursor)
138 }
139
140 fn draw<'a>(&'a mut self, info_text: &'a str) -> BoxFuture<'a, Result<Rendering, RenderError>> {
141 let future = async move { self.inner.draw(info_text).await };
142 #[cfg(target_family = "wasm")]
143 let future = send_wrapper::SendWrapper::new(future);
144 Box::pin(future)
145 }
146}
147
148impl RendererImpl {
149 fn update(
150 &mut self,
151 read_tickets: Layers<ReadTicket<'_>>,
152 cursor: Option<&Cursor>,
153 ) -> Result<(), RenderError> {
154 let info = self.everything.update(
155 read_tickets,
156 &self.queue,
157 cursor,
158 &FrameBudget::PRACTICALLY_INFINITE,
159 )?;
160 self.flaws = info.flaws();
161 Ok(())
162 }
163
164 async fn draw(&mut self, info_text: &str) -> Result<Rendering, RenderError> {
165 let viewport = self.viewport_source.get();
167
168 let adapter_flaws = if self.adapter_info.backend == wgpu::Backend::Noop {
171 Flaws::OTHER
172 } else {
173 Flaws::empty()
174 };
175
176 if viewport.is_empty() {
177 return Ok(Rendering {
180 size: viewport.framebuffer_size,
181 data: Vec::new(),
182 flaws: Flaws::empty(),
183 });
184 }
185
186 if self.viewport_dirty.get_and_clear() {
187 self.color_texture = create_color_texture(&self.device, viewport);
188 }
189
190 let draw_info = self.everything.draw_frame_linear(&self.queue);
191 let (post_cmd, post_flaws) = self.everything.add_info_text_and_postprocess(
192 &self.queue,
193 &self.color_texture.create_view(&wgpu::TextureViewDescriptor::default()),
194 info_text,
195 );
196 self.queue.submit([post_cmd]);
197 let image = init::get_image_from_gpu(
198 &self.device,
199 &self.queue,
200 &self.color_texture,
201 self.flaws | draw_info.flaws() | post_flaws | adapter_flaws,
202 )
203 .await;
204 debug_assert_eq!(viewport.framebuffer_size, image.size);
205 Ok(image)
206 }
207}
208
209fn create_color_texture(device: &wgpu::Device, viewport: Viewport) -> wgpu::Texture {
210 device.create_texture(&wgpu::TextureDescriptor {
211 label: Some("headless::Renderer::color_texture"),
212 size: size2d_to_extent(viewport.framebuffer_size.max(ImageSize::splat(1))),
213 mip_level_count: 1,
214 sample_count: 1,
215 dimension: wgpu::TextureDimension::D2,
216 format: wgpu::TextureFormat::Rgba8UnormSrgb,
217 view_formats: &[],
218 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
219 })
220}