| 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")] |
| 5 | use super::canvas2d; |
| 6 | #[cfg(windows)] |
| 7 | use super::d3d11; |
| 8 | #[cfg(any(windows, target_os = "macos"))] |
| 9 | use super::gl; |
| 10 | use super::{webgpu, *}; |
| 11 | |
| 12 | /// What a frame is drawn into, for the backend the renderer draws with. |
| 13 | pub 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 | |
| 23 | impl From<wgpu::TextureView> for Target { |
| 24 | fn from(view: wgpu::TextureView) -> Self { |
| 25 | Self::Wgpu(view) |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | #[cfg(target_arch = "wasm32")] |
| 30 | impl 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 | |
| 37 | impl 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 | |
| 51 | pub(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`. |
| 62 | macro_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 | |
| 76 | image!(Wgpu, webgpu::Image); |
| 77 | #[cfg(windows)] |
| 78 | image!(D3d11, d3d11::Image); |
| 79 | #[cfg(any(windows, target_os = "macos"))] |
| 80 | image!(Gl, gl::Image); |
| 81 | #[cfg(target_arch = "wasm32")] |
| 82 | image!(Canvas, canvas2d::Image); |
| 83 | |
| 84 | // A renderer holds one, so its size goes unnoticed. |
| 85 | #[allow(clippy::large_enum_variant)] |
| 86 | pub(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 | |
| 96 | impl 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"))] |
| 103 | impl From<gl::Gpu> for Gpu { |
| 104 | fn from(gpu: gl::Gpu) -> Self { |
| 105 | Self::Gl(gpu) |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | #[cfg(target_arch = "wasm32")] |
| 110 | impl From<canvas2d::Gpu> for Gpu { |
| 111 | fn from(gpu: canvas2d::Gpu) -> Self { |
| 112 | Self::Canvas(gpu) |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | #[cfg(not(windows))] |
| 117 | impl 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)] |
| 136 | impl 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`. |
| 147 | macro_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 | |
| 161 | impl 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 | } |