1//! Every backend a platform offers in one build, its maker picking one: wgpu everywhere,
2//! with Direct3D 11 and OpenGL 2.1 on Windows, where Windows 7 has no Direct3D 12, OpenGL
3//! on macOS, and the 2D canvas in a browser with neither WebGPU nor WebGL2.
4#[cfg(target_arch = "wasm32")]
5use super::canvas2d;
6#[cfg(windows)]
7use super::d3d11;
8#[cfg(any(windows, target_os = "macos"))]
9use super::gl;
10use super::{webgpu, *};
11
12/// What a frame is drawn into, for the backend the renderer draws with.
13pub enum Target {
14 Wgpu(wgpu::TextureView),
15 #[cfg(windows)]
16 D3d11(d3d11::Target),
17 #[cfg(any(windows, target_os = "macos"))]
18 Gl(gl::Target),
19 #[cfg(target_arch = "wasm32")]
20 Canvas(canvas2d::Target),
21}
22
23impl From<wgpu::TextureView> for Target {
24 fn from(view: wgpu::TextureView) -> Self {
25 Self::Wgpu(view)
26 }
27}
28
29#[cfg(target_arch = "wasm32")]
30impl Target {
31 /// Frames drawn into `canvas` with its 2D context.
32 pub fn canvas2d(canvas: web_sys::HtmlCanvasElement) -> Result<Self, String> {
33 canvas2d::Target::new(canvas).map(Self::Canvas)
34 }
35}
36
37impl Target {
38 pub fn size(&self) -> [u32; 2] {
39 match self {
40 Self::Wgpu(view) => [view.texture().width(), view.texture().height()],
41 #[cfg(windows)]
42 Self::D3d11(target) => target.size(),
43 #[cfg(any(windows, target_os = "macos"))]
44 Self::Gl(target) => target.size(),
45 #[cfg(target_arch = "wasm32")]
46 Self::Canvas(target) => target.size(),
47 }
48 }
49}
50
51pub(super) enum Image {
52 Wgpu(webgpu::Image),
53 #[cfg(windows)]
54 D3d11(d3d11::Image),
55 #[cfg(any(windows, target_os = "macos"))]
56 Gl(gl::Image),
57 #[cfg(target_arch = "wasm32")]
58 Canvas(canvas2d::Image),
59}
60
61/// `AsRef` from `Image` to its variant `$variant`, which holds `$image`.
62macro_rules! image {
63 ($variant:ident, $image:ty) => {
64 impl AsRef<$image> for Image {
65 fn as_ref(&self) -> &$image {
66 match self {
67 Self::$variant(image) => image,
68 #[allow(unreachable_patterns)]
69 _ => unreachable!("Images belong to the renderer's backend"),
70 }
71 }
72 }
73 };
74}
75
76image!(Wgpu, webgpu::Image);
77#[cfg(windows)]
78image!(D3d11, d3d11::Image);
79#[cfg(any(windows, target_os = "macos"))]
80image!(Gl, gl::Image);
81#[cfg(target_arch = "wasm32")]
82image!(Canvas, canvas2d::Image);
83
84// A renderer holds one, so its size goes unnoticed.
85#[allow(clippy::large_enum_variant)]
86pub(super) enum Gpu {
87 Wgpu(webgpu::Gpu),
88 #[cfg(windows)]
89 D3d11(d3d11::Gpu),
90 #[cfg(any(windows, target_os = "macos"))]
91 Gl(gl::Gpu),
92 #[cfg(target_arch = "wasm32")]
93 Canvas(canvas2d::Gpu),
94}
95
96impl From<webgpu::Gpu> for Gpu {
97 fn from(gpu: webgpu::Gpu) -> Self {
98 Self::Wgpu(gpu)
99 }
100}
101
102#[cfg(any(windows, target_os = "macos"))]
103impl From<gl::Gpu> for Gpu {
104 fn from(gpu: gl::Gpu) -> Self {
105 Self::Gl(gpu)
106 }
107}
108
109#[cfg(target_arch = "wasm32")]
110impl From<canvas2d::Gpu> for Gpu {
111 fn from(gpu: canvas2d::Gpu) -> Self {
112 Self::Canvas(gpu)
113 }
114}
115
116#[cfg(not(windows))]
117impl Renderer {
118 /// The device a renderer `Renderer::new` made draws with.
119 pub fn device(&self) -> &wgpu::Device {
120 self.wgpu().device()
121 }
122
123 pub fn queue(&self) -> &wgpu::Queue {
124 self.wgpu().queue()
125 }
126
127 fn wgpu(&self) -> &webgpu::Gpu {
128 match &self.gpu {
129 Gpu::Wgpu(gpu) => gpu,
130 _ => unreachable!("Only `Renderer::new` makes a renderer drawing with wgpu"),
131 }
132 }
133}
134
135#[cfg(windows)]
136impl Renderer {
137 /// Draws with Direct3D 11 into a swap chain on `window`, an `HWND`; with the adapter's
138 /// description.
139 pub fn direct3d11(window: *mut std::ffi::c_void) -> Result<(Self, String), String> {
140 d3d11::Gpu::new(window)
141 .map(|(gpu, adapter)| (Self::with_gpu(Gpu::D3d11(gpu)), adapter))
142 .map_err(|error| error.to_string())
143 }
144}
145
146/// `$gpu` as whichever backend it holds, named `$backend` in `$body`.
147macro_rules! each {
148 ($gpu:expr, $backend:ident => $body:expr) => {
149 match $gpu {
150 Gpu::Wgpu($backend) => $body,
151 #[cfg(windows)]
152 Gpu::D3d11($backend) => $body,
153 #[cfg(any(windows, target_os = "macos"))]
154 Gpu::Gl($backend) => $body,
155 #[cfg(target_arch = "wasm32")]
156 Gpu::Canvas($backend) => $body,
157 }
158 };
159}
160
161impl Gpu {
162 #[cfg(any(windows, target_os = "macos"))]
163 pub(super) fn target(&self, size: [u32; 2]) -> Result<Target, String> {
164 match self {
165 #[cfg(windows)]
166 Self::D3d11(gpu) => gpu
167 .target(size)
168 .map(Target::D3d11)
169 .map_err(|error| error.to_string()),
170 Self::Gl(gpu) => gpu.target(size).map(Target::Gl),
171 Self::Wgpu(_) => unreachable!("wgpu's targets are its surface's"),
172 }
173 }
174
175 #[cfg(any(windows, target_os = "macos"))]
176 pub(super) fn present(&self, target: &Target, translucent: bool) {
177 match (self, target) {
178 // Direct3D 11 premultiplies every frame again, which leaves opaque ones be.
179 #[cfg(windows)]
180 (Self::D3d11(gpu), Target::D3d11(target)) => {
181 if let Err(error) = gpu.present(target) {
182 eprintln!("Presenting failed: {error}");
183 }
184 }
185 (Self::Gl(gpu), Target::Gl(target)) => gpu.present(target, translucent),
186 _ => unreachable!("Frames are drawn into the renderer's backend's targets"),
187 }
188 }
189
190 pub(super) fn read_pixels(&self, target: &Target) -> Result<Vec<u8>, String> {
191 match (self, target) {
192 #[cfg(windows)]
193 (Self::D3d11(gpu), Target::D3d11(target)) => {
194 gpu.read_pixels(target).map_err(|error| error.to_string())
195 }
196 #[cfg(any(windows, target_os = "macos"))]
197 (Self::Gl(gpu), Target::Gl(target)) => gpu.read_pixels(target),
198 #[cfg(target_arch = "wasm32")]
199 (Self::Canvas(gpu), Target::Canvas(target)) => gpu.read_pixels(target),
200 _ => unreachable!("Snapshots come from the renderer's backend's targets"),
201 }
202 }
203
204 pub(super) fn blends_linear(&self) -> bool {
205 each!(self, gpu => gpu.blends_linear())
206 }
207
208 pub(super) fn flipped(&self) -> bool {
209 each!(self, gpu => gpu.flipped())
210 }
211
212 pub(super) fn max_texture_dimension(&self) -> u32 {
213 each!(self, gpu => gpu.max_texture_dimension())
214 }
215
216 pub(super) fn atlas_side(&self) -> u32 {
217 each!(self, gpu => gpu.atlas_side())
218 }
219
220 pub(super) fn new_atlas(&mut self, side: u32) {
221 each!(self, gpu => gpu.new_atlas(side))
222 }
223
224 pub(super) fn write_atlas(&self, origin: [u32; 2], size: [u32; 2], rgba: &[u8]) {
225 each!(self, gpu => gpu.write_atlas(origin, size, rgba))
226 }
227
228 pub(super) fn upload_image(&self, image: &RasterImage) -> Image {
229 match self {
230 Self::Wgpu(gpu) => Image::Wgpu(gpu.upload_image(image)),
231 #[cfg(windows)]
232 Self::D3d11(gpu) => Image::D3d11(gpu.upload_image(image)),
233 #[cfg(any(windows, target_os = "macos"))]
234 Self::Gl(gpu) => Image::Gl(gpu.upload_image(image)),
235 #[cfg(target_arch = "wasm32")]
236 Self::Canvas(gpu) => Image::Canvas(gpu.upload_image(image)),
237 }
238 }
239
240 pub(super) fn submit(
241 &mut self,
242 frame: &Frame<'_>,
243 target: &Target,
244 size: [u32; 2],
245 clear: [f32; 4],
246 ) {
247 match (self, target) {
248 (Self::Wgpu(gpu), Target::Wgpu(view)) => gpu.submit(frame, view, size, clear),
249 #[cfg(windows)]
250 (Self::D3d11(gpu), Target::D3d11(target)) => gpu.submit(frame, target, size, clear),
251 #[cfg(any(windows, target_os = "macos"))]
252 (Self::Gl(gpu), Target::Gl(target)) => gpu.submit(frame, target, size, clear),
253 #[cfg(target_arch = "wasm32")]
254 (Self::Canvas(gpu), Target::Canvas(target)) => gpu.submit(frame, target, size, clear),
255 #[allow(unreachable_patterns)]
256 _ => unreachable!("Frames are drawn into the renderer's backend's targets"),
257 }
258 }
259}