-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathwasm.rs
More file actions
230 lines (200 loc) · 7.21 KB
/
wasm.rs
File metadata and controls
230 lines (200 loc) · 7.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
use dyn_any::DynAny;
#[cfg(feature = "wgpu")]
use graphene_application_io::ImageTexture;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use web_sys::js_sys::{Object, Reflect};
use web_sys::wasm_bindgen::{JsCast, JsValue};
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, window};
#[cfg(feature = "wgpu")]
use wgpu_executor::WgpuExecutor;
const CANVASES_OBJECT_KEY: &str = "imageCanvases";
pub type CanvasId = u64;
static CANVAS_IDS: AtomicU64 = AtomicU64::new(0);
pub trait Canvas {
fn id(&mut self) -> CanvasId;
fn context(&mut self) -> CanvasRenderingContext2d;
fn set_resolution(&mut self, resolution: glam::UVec2);
}
#[cfg(feature = "wgpu")]
pub trait CanvasSurface: Canvas {
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor);
}
#[derive(Clone, DynAny)]
pub struct CanvasHandle(Option<Arc<CanvasImpl>>);
impl CanvasHandle {
pub fn new() -> Self {
Self(None)
}
fn get(&mut self) -> &CanvasImpl {
if self.0.is_none() {
self.0 = Some(Arc::new(CanvasImpl::new()));
}
self.0.as_ref().unwrap()
}
}
impl Canvas for CanvasHandle {
fn id(&mut self) -> CanvasId {
self.get().canvas_id
}
fn context(&mut self) -> CanvasRenderingContext2d {
self.get().context()
}
fn set_resolution(&mut self, resolution: glam::UVec2) {
self.get().set_resolution(resolution);
}
}
#[cfg(feature = "wgpu")]
struct SurfaceState {
surface: Arc<wgpu::Surface<'static>>,
format: wgpu::TextureFormat,
blitter: wgpu_executor::cached_blitter::CachedBlitter,
}
#[cfg(feature = "wgpu")]
pub struct CanvasSurfaceHandle(CanvasHandle, Option<SurfaceState>);
#[cfg(feature = "wgpu")]
impl CanvasSurfaceHandle {
pub fn new() -> Self {
Self(CanvasHandle::new(), None)
}
fn state(&mut self, executor: &WgpuExecutor) -> &SurfaceState {
if self.1.is_none() {
let canvas = self.0.get().canvas.clone();
let surface = executor
.context
.instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
.expect("Failed to create surface from canvas");
// Use the surface's preferred format (Firefox WebGL prefers Bgra8Unorm, Chrome prefers Rgba8Unorm)
let surface_caps = surface.get_capabilities(&executor.context.adapter);
let surface_format = surface_caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(surface_caps.formats[0]);
let blitter = wgpu_executor::cached_blitter::CachedBlitter::new(&executor.context.device, surface_format);
self.1 = Some(SurfaceState {
surface: Arc::new(surface),
format: surface_format,
blitter,
});
}
self.1.as_ref().unwrap()
}
}
#[cfg(feature = "wgpu")]
impl Canvas for CanvasSurfaceHandle {
fn id(&mut self) -> CanvasId {
self.0.id()
}
fn context(&mut self) -> CanvasRenderingContext2d {
self.0.context()
}
fn set_resolution(&mut self, resolution: glam::UVec2) {
self.0.set_resolution(resolution);
}
}
#[cfg(feature = "wgpu")]
impl CanvasSurface for CanvasSurfaceHandle {
fn present(&mut self, image_texture: &ImageTexture, executor: &WgpuExecutor) {
let source_texture: &wgpu::Texture = image_texture.as_ref();
let state = self.state(executor);
let mut encoder = executor.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Texture to Surface Blit"),
});
let size = source_texture.size();
// Configure the surface at the detected preferred format
let surface_caps = state.surface.get_capabilities(&executor.context.adapter);
state.surface.configure(
&executor.context.device,
&wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
format: state.format,
width: size.width,
height: size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: wgpu::CompositeAlphaMode::PreMultiplied,
view_formats: vec![],
desired_maximum_frame_latency: 2,
},
);
let surface_texture = state.surface.get_current_texture().expect("Failed to get surface texture");
// If the surface format matches the source, use a direct copy; otherwise use the cached blitter
// for format conversion (e.g., Rgba8Unorm source to Bgra8Unorm surface on Firefox)
if state.format == source_texture.format() {
encoder.copy_texture_to_texture(
wgpu::TexelCopyTextureInfoBase {
texture: source_texture,
mip_level: 0,
origin: Default::default(),
aspect: Default::default(),
},
wgpu::TexelCopyTextureInfoBase {
texture: &surface_texture.texture,
mip_level: 0,
origin: Default::default(),
aspect: Default::default(),
},
source_texture.size(),
);
} else {
let target_view = surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default());
state.blitter.copy(&executor.context.device, &mut encoder, source_texture, &target_view);
}
executor.context.queue.submit([encoder.finish()]);
surface_texture.present();
}
}
/// A wgpu surface backed by an HTML canvas element.
/// Holds a reference to the canvas to prevent garbage collection.
pub struct CanvasImpl {
canvas_id: u64,
canvas: HtmlCanvasElement,
}
impl CanvasImpl {
fn new() -> Self {
let document = window().expect("should have a window in this context").document().expect("window should have a document");
let canvas: HtmlCanvasElement = document.create_element("canvas").unwrap().dyn_into::<HtmlCanvasElement>().unwrap();
let canvas_id = CANVAS_IDS.fetch_add(1, Ordering::SeqCst);
// Store the canvas in the global scope so it doesn't get garbage collected
let window = window().expect("should have a window in this context");
let window_obj = Object::from(window);
let image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY);
let mut canvases = Reflect::get(&window_obj, &image_canvases_key);
if canvases.is_err() || canvases.as_ref().map_or(false, |v| v.is_undefined() || v.is_null()) {
Reflect::set(&window_obj.clone(), &image_canvases_key, &Object::new()).unwrap();
canvases = Reflect::get(&window_obj, &image_canvases_key);
}
// Convert key and value to JsValue
let js_key = JsValue::from_str(canvas_id.to_string().as_str());
let js_value = JsValue::from(canvas.clone());
let canvases = Object::from(canvases.unwrap());
// Use Reflect API to set property
Reflect::set(&canvases, &js_key, &js_value).unwrap();
Self { canvas_id, canvas }
}
fn context(&self) -> CanvasRenderingContext2d {
self.canvas
.get_context("2d")
.expect("Failed to get 2D context from canvas")
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()
.expect("Failed to cast context to CanvasRenderingContext2d")
}
fn set_resolution(&self, resolution: glam::UVec2) {
self.canvas.set_width(resolution.x);
self.canvas.set_height(resolution.y);
}
}
impl Drop for CanvasImpl {
fn drop(&mut self) {
let canvas_id = self.canvas_id;
let window = window().expect("should have a window in this context");
let window_obj = Object::from(window);
let image_canvases_key = JsValue::from_str(CANVASES_OBJECT_KEY);
if let Ok(canvases) = Reflect::get(&window_obj, &image_canvases_key) {
let canvases = Object::from(canvases);
let js_key = JsValue::from_str(canvas_id.to_string().as_str());
Reflect::delete_property(&canvases, &js_key).unwrap();
}
}
}
// SAFETY: WASM is single-threaded, so Send/Sync are safe
unsafe impl Send for CanvasImpl {}
unsafe impl Sync for CanvasImpl {}