|
1 | | -use dyn_any::DynAny; |
2 | | -#[cfg(feature = "wgpu")] |
3 | | -use graphene_application_io::ImageTexture; |
4 | | -use std::sync::Arc; |
5 | | -use std::sync::atomic::{AtomicU64, Ordering}; |
6 | | -use web_sys::js_sys::{Object, Reflect}; |
7 | | -use web_sys::wasm_bindgen::{JsCast, JsValue}; |
8 | | -use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, window}; |
9 | | -#[cfg(feature = "wgpu")] |
10 | | -use wgpu_executor::WgpuExecutor; |
11 | | - |
12 | | -const CANVASES_OBJECT_KEY: &str = "imageCanvases"; |
13 | | - |
14 | | -pub type CanvasId = u64; |
15 | | - |
16 | | -static CANVAS_IDS: AtomicU64 = AtomicU64::new(0); |
17 | | - |
18 | | -pub trait Canvas { |
19 | | - fn id(&mut self) -> CanvasId; |
20 | | - fn context(&mut self) -> CanvasRenderingContext2d; |
21 | | - fn set_resolution(&mut self, resolution: glam::UVec2); |
22 | | -} |
23 | | - |
24 | | -#[cfg(feature = "wgpu")] |
25 | | -pub trait CanvasSurface: Canvas { |
26 | | - fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor); |
27 | | -} |
28 | | - |
29 | | -#[derive(Clone, DynAny)] |
30 | | -pub struct CanvasHandle(Option<Arc<CanvasImpl>>); |
31 | | -impl CanvasHandle { |
32 | | - pub fn new() -> Self { |
33 | | - Self(None) |
34 | | - } |
35 | | - fn get(&mut self) -> &CanvasImpl { |
36 | | - if self.0.is_none() { |
37 | | - self.0 = Some(Arc::new(CanvasImpl::new())); |
38 | | - } |
39 | | - self.0.as_ref().unwrap() |
40 | | - } |
41 | | -} |
42 | | -impl Canvas for CanvasHandle { |
43 | | - fn id(&mut self) -> CanvasId { |
44 | | - self.get().canvas_id |
45 | | - } |
46 | | - fn context(&mut self) -> CanvasRenderingContext2d { |
47 | | - self.get().context() |
48 | | - } |
49 | | - fn set_resolution(&mut self, resolution: glam::UVec2) { |
50 | | - self.get().set_resolution(resolution); |
51 | | - } |
52 | | -} |
53 | | - |
54 | | -#[cfg(feature = "wgpu")] |
55 | | -pub struct CanvasSurfaceHandle(CanvasHandle, Option<Arc<wgpu::Surface<'static>>>); |
56 | | -#[cfg(feature = "wgpu")] |
57 | | -impl CanvasSurfaceHandle { |
58 | | - pub fn new() -> Self { |
59 | | - Self(CanvasHandle::new(), None) |
60 | | - } |
61 | | - fn surface(&mut self, executor: &WgpuExecutor) -> &wgpu::Surface<'_> { |
62 | | - if self.1.is_none() { |
63 | | - let canvas = self.0.get().canvas.clone(); |
64 | | - let surface = executor |
65 | | - .context |
66 | | - .instance |
67 | | - .create_surface(wgpu::SurfaceTarget::Canvas(canvas)) |
68 | | - .expect("Failed to create surface from canvas"); |
69 | | - self.1 = Some(Arc::new(surface)); |
70 | | - } |
71 | | - self.1.as_ref().unwrap() |
72 | | - } |
73 | | -} |
74 | | -#[cfg(feature = "wgpu")] |
75 | | -impl Canvas for CanvasSurfaceHandle { |
76 | | - fn id(&mut self) -> CanvasId { |
77 | | - self.0.id() |
78 | | - } |
79 | | - fn context(&mut self) -> CanvasRenderingContext2d { |
80 | | - self.0.context() |
81 | | - } |
82 | | - fn set_resolution(&mut self, resolution: glam::UVec2) { |
83 | | - self.0.set_resolution(resolution); |
84 | | - } |
85 | | -} |
86 | | -#[cfg(feature = "wgpu")] |
87 | | -impl CanvasSurface for CanvasSurfaceHandle { |
88 | | - fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor) { |
89 | | - let source_texture: &wgpu::Texture = image_texture.as_ref(); |
90 | | - |
91 | | - let surface = self.surface(executor); |
92 | | - |
93 | | - // Blit the texture to the surface |
94 | | - let mut encoder = executor.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { |
95 | | - label: Some("Texture to Surface Blit"), |
96 | | - }); |
97 | | - |
98 | | - let size = source_texture.size(); |
99 | | - |
100 | | - // Configure the surface at physical resolution (for HiDPI displays) |
101 | | - let surface_caps = surface.get_capabilities(&executor.context.adapter); |
102 | | - surface.configure( |
103 | | - &executor.context.device, |
104 | | - &wgpu::SurfaceConfiguration { |
105 | | - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, |
106 | | - format: wgpu::TextureFormat::Rgba8Unorm, |
107 | | - width: size.width, |
108 | | - height: size.height, |
109 | | - present_mode: surface_caps.present_modes[0], |
110 | | - alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied, |
111 | | - view_formats: vec![], |
112 | | - desired_maximum_frame_latency: 2, |
113 | | - }, |
114 | | - ); |
115 | | - |
116 | | - let surface_texture = surface.get_current_texture().expect("Failed to get surface texture"); |
117 | | - |
118 | | - encoder.copy_texture_to_texture( |
119 | | - wgpu::TexelCopyTextureInfoBase { |
120 | | - texture: source_texture, |
121 | | - mip_level: 0, |
122 | | - origin: Default::default(), |
123 | | - aspect: Default::default(), |
124 | | - }, |
125 | | - wgpu::TexelCopyTextureInfoBase { |
126 | | - texture: &surface_texture.texture, |
127 | | - mip_level: 0, |
128 | | - origin: Default::default(), |
129 | | - aspect: Default::default(), |
130 | | - }, |
131 | | - source_texture.size(), |
132 | | - ); |
133 | | - |
134 | | - executor.context.queue.submit([encoder.finish()]); |
135 | | - surface_texture.present(); |
136 | | - } |
137 | | -} |
138 | | - |
139 | | -/// A wgpu surface backed by an HTML canvas element. |
140 | | -/// Holds a reference to the canvas to prevent garbage collection. |
141 | | -pub struct CanvasImpl { |
142 | | - canvas_id: u64, |
143 | | - canvas: HtmlCanvasElement, |
144 | | -} |
145 | | - |
146 | | -impl CanvasImpl { |
147 | | - fn new() -> Self { |
148 | | - let document = window().expect("should have a window in this context").document().expect("window should have a document"); |
149 | | - |
150 | | - let canvas: HtmlCanvasElement = document.create_element("canvas").unwrap().dyn_into::<HtmlCanvasElement>().unwrap(); |
151 | | - let canvas_id = CANVAS_IDS.fetch_add(1, Ordering::SeqCst); |
152 | | - |
153 | | - // Store the canvas in the global scope so it doesn't get garbage collected |
154 | | - let window = window().expect("should have a window in this context"); |
155 | | - let window_obj = Object::from(window); |
156 | | - |
157 | | - let image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY); |
158 | | - |
159 | | - let mut canvases = Reflect::get(&window_obj, &image_canvases_key); |
160 | | - if canvases.is_err() { |
161 | | - Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap(); |
162 | | - canvases = Reflect::get(&window_obj, &image_canvases_key); |
163 | | - } |
164 | | - |
165 | | - // Convert key and value to JsValue |
166 | | - let js_key = JsValue::from_str(canvas_id.to_string().as_str()); |
167 | | - let js_value = JsValue::from(canvas.clone()); |
168 | | - |
169 | | - let canvases = Object::from(canvases.unwrap()); |
170 | | - |
171 | | - // Use Reflect API to set property |
172 | | - Reflect::set(&canvases, &js_key, &js_value).unwrap(); |
173 | | - |
174 | | - Self { canvas_id, canvas } |
175 | | - } |
176 | | - fn context(&self) -> CanvasRenderingContext2d { |
177 | | - self.canvas |
178 | | - .get_context("2d") |
179 | | - .expect("Failed to get 2D context from canvas") |
180 | | - .unwrap() |
181 | | - .dyn_into::<CanvasRenderingContext2d>() |
182 | | - .expect("Failed to cast context to CanvasRenderingContext2d") |
183 | | - } |
184 | | - fn set_resolution(&self, resolution: glam::UVec2) { |
185 | | - self.canvas.set_width(resolution.x); |
186 | | - self.canvas.set_height(resolution.y); |
187 | | - } |
188 | | -} |
189 | | - |
190 | | -impl Drop for CanvasImpl { |
191 | | - fn drop(&mut self) { |
192 | | - let canvas_id = self.canvas_id; |
193 | | - let window = window().expect("should have a window in this context"); |
194 | | - let window_obj = Object::from(window); |
195 | | - |
196 | | - let image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY); |
197 | | - |
198 | | - if let Ok(canvases) = Reflect::get(&window_obj, &image_canvases_key) { |
199 | | - let canvases = Object::from(canvases); |
200 | | - let js_key = JsValue::from_str(canvas_id.to_string().as_str()); |
201 | | - Reflect::delete_property(&canvases, &js_key).unwrap(); |
202 | | - } |
203 | | - } |
204 | | -} |
205 | | - |
206 | | -// SAFETY: WASM is single-threaded, so Send/Sync are safe |
207 | | -unsafe impl Send for CanvasImpl {} |
208 | | -unsafe impl Sync for CanvasImpl {} |
| 1 | +///! A collection of utilities for working with the HTML canvases. |
| 2 | +///! This library is designed to be used in a WebAssembly context. |
| 3 | +///! It doesn't expose any functionality when compiled for non-WebAssembly targets |
| 4 | +
|
| 5 | +#[cfg(target_family = "wasm")] |
| 6 | +mod wasm; |
| 7 | +#[cfg(target_family = "wasm")] |
| 8 | +pub use wasm::*; |
0 commit comments