1#[cfg(feature = "ui")]
3pub mod app_event;
4#[cfg(feature = "gpu")]
5pub mod app_trait;
6pub mod atlas;
7pub mod bbox2d;
8pub mod camera3d;
9pub mod chunk;
10pub mod core;
11pub mod dynamic;
12pub mod intodata;
13pub mod light;
14#[cfg(all(feature = "ui", not(target_arch = "wasm32")))]
15pub mod native_dialogs;
16pub mod poly2d;
17pub mod poly3d;
18pub mod texture;
19#[cfg(feature = "ui")]
20pub mod ui;
21#[cfg(feature = "gpu")]
22pub mod vm;
23
24#[derive(Debug, Clone)]
26pub enum SceneVMError {
27 GpuInitFailed(String),
28 BufferAllocationFailed(String),
29 ShaderCompilationFailed(String),
30 TextureUploadFailed(String),
31 InvalidGeometry(String),
32 AtlasFull(String),
33 InvalidOperation(String),
34}
35
36pub type SceneVMResult<T> = Result<T, SceneVMError>;
37
38use rust_embed::RustEmbed;
39#[derive(RustEmbed)]
41#[folder = "embedded/"]
42#[exclude = "*.txt"]
43#[exclude = "*.DS_Store"]
44pub struct Embedded;
45
46pub mod prelude {
47 pub use crate::{
50 Embedded, SceneVMError, SceneVMResult,
51 atlas::{AtlasEntry, SharedAtlas},
52 bbox2d::BBox2D,
53 camera3d::{Camera3D, CameraKind},
54 chunk::Chunk,
55 core::{Atom, GeoId, LayerBlendMode, LineStrip2D, RenderMode, VMDebugStats},
56 dynamic::{AlphaMode, DynamicKind, DynamicMeshVertex, DynamicObject, RepeatMode},
57 intodata::IntoDataInput,
58 light::{Light, LightType},
59 poly2d::Poly2D,
60 poly3d::Poly3D,
61 texture::Texture,
62 };
63
64 #[cfg(feature = "gpu")]
65 pub use crate::vm::VM;
66
67 #[cfg(feature = "gpu")]
68 pub use crate::app_trait::{SceneVMApp, SceneVMRenderCtx};
69
70 #[cfg(all(feature = "ui", feature = "gpu"))]
71 pub use crate::{
72 RenderResult,
73 app_event::{AppEvent, AppEventQueue},
74 ui::{
75 Alignment, Button, ButtonGroup, ButtonGroupOrientation, ButtonGroupStyle, ButtonKind,
76 ButtonStyle, Canvas, ColorButton, ColorButtonStyle, ColorWheel, Drawable, DropdownList,
77 DropdownListStyle, HAlign, HStack, Image, ImageStyle, Label, LabelRect, NodeId,
78 ParamList, ParamListStyle, PopupAlignment, Project, ProjectBrowser, ProjectBrowserItem,
79 ProjectBrowserStyle, ProjectError, ProjectMetadata, RecentProject, RecentProjects,
80 Slider, SliderStyle, Spacer, TabbedPanel, TabbedPanelStyle, TextButton, Theme, Toolbar,
81 ToolbarOrientation, ToolbarSeparator, ToolbarStyle, UiAction, UiEvent, UiEventKind,
82 UiRenderer, UiView, UndoCommand, UndoStack, VAlign, VStack, ViewContext, Workspace,
83 create_tile_material,
84 },
85 };
86
87 pub use rustc_hash::{FxHashMap, FxHashSet};
88 pub use vek::{Mat3, Mat4, Vec2, Vec3, Vec4};
89}
90
91#[cfg(feature = "gpu")]
92pub use crate::app_trait::{SceneVMApp, SceneVMRenderCtx};
93#[cfg(feature = "ui")]
94pub use crate::ui::{
95 Alignment, Button, ButtonGroup, ButtonGroupStyle, ButtonKind, ButtonStyle, Canvas, Drawable,
96 HAlign, HStack, Image, ImageStyle, Label, LabelRect, NodeId, ParamList, ParamListStyle,
97 PopupAlignment, Slider, SliderStyle, TextButton, Toolbar, ToolbarOrientation, ToolbarSeparator,
98 ToolbarStyle, UiAction, UiEvent, UiEventKind, UiRenderer, UiView, UndoCommand, UndoStack,
99 VAlign, VStack, ViewContext, Workspace,
100};
101#[cfg(feature = "gpu")]
102pub use crate::vm::VM;
103pub use crate::{
104 atlas::{AtlasEntry, SharedAtlas},
105 bbox2d::BBox2D,
106 camera3d::{Camera3D, CameraKind},
107 chunk::Chunk,
108 core::{Atom, GeoId, LayerBlendMode, LineStrip2D, RenderMode, VMDebugStats},
109 dynamic::{AlphaMode, DynamicKind, DynamicMeshVertex, DynamicObject, RepeatMode},
110 intodata::IntoDataInput,
111 light::{Light, LightType},
112 poly2d::Poly2D,
113 poly3d::Poly3D,
114 texture::Texture,
115};
116#[cfg(feature = "gpu")]
117use image;
118#[cfg(feature = "gpu")]
119use std::borrow::Cow;
120#[cfg(target_arch = "wasm32")]
121use std::cell::RefCell;
122#[cfg(all(not(target_arch = "wasm32"), feature = "gpu"))]
123use std::ffi::c_void;
124#[cfg(all(not(target_arch = "wasm32"), feature = "gpu"))]
125use std::sync::OnceLock;
126#[cfg(target_arch = "wasm32")]
127use std::{cell::Cell, future::Future, rc::Rc};
128#[cfg(target_arch = "wasm32")]
129use std::{
130 pin::Pin,
131 task::{Context, Poll},
132};
133#[cfg(all(not(target_arch = "wasm32"), feature = "windowing"))]
134use vek::Mat3;
135#[cfg(target_arch = "wasm32")]
136use wasm_bindgen::JsCast;
137#[cfg(target_arch = "wasm32")]
138use wasm_bindgen::prelude::*;
139#[cfg(target_arch = "wasm32")]
140use wasm_bindgen_futures::spawn_local;
141#[cfg(target_arch = "wasm32")]
142use web_sys::{CanvasRenderingContext2d, Document, HtmlCanvasElement, Window as WebWindow};
143#[cfg(all(feature = "windowing", not(target_arch = "wasm32")))]
144use winit::window::Window;
145#[cfg(all(feature = "windowing", not(target_arch = "wasm32")))]
146use winit::{dpi::PhysicalPosition, event::ElementState, event::MouseButton, event::WindowEvent};
147
148#[cfg(feature = "gpu")]
149#[derive(Copy, Clone, Debug, Eq, PartialEq)]
151pub enum RenderResult {
152 Presented,
154 InitPending,
156 ReadbackPending,
158}
159
160#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
162struct PresentPipeline {
163 pipeline: wgpu::RenderPipeline,
164 bind_group_layout: wgpu::BindGroupLayout,
165 bind_group: wgpu::BindGroup,
166 rect_buf: wgpu::Buffer,
167 sampler: wgpu::Sampler,
168 surface_format: wgpu::TextureFormat,
169}
170
171#[cfg(feature = "gpu")]
173struct CompositingPipeline {
174 pipeline: wgpu::RenderPipeline,
175 bind_group_layout: wgpu::BindGroupLayout,
176 mode_buf: wgpu::Buffer,
177 sampler: wgpu::Sampler,
178 target_format: wgpu::TextureFormat,
179}
180
181#[cfg(feature = "gpu")]
182impl CompositingPipeline {
183 fn new(device: &wgpu::Device, target_format: wgpu::TextureFormat) -> Self {
184 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
185 label: Some("scenevm-composite-shader"),
186 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(
187 "
188@group(0) @binding(0) var layer_tex: texture_2d<f32>;
189@group(0) @binding(1) var layer_sampler: sampler;
190@group(0) @binding(2) var<uniform> blend_mode_buf: u32;
191
192struct VsOut {
193 @builtin(position) pos: vec4<f32>,
194 @location(0) uv: vec2<f32>,
195};
196
197@vertex
198fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut {
199 var positions = array<vec2<f32>, 3>(
200 vec2<f32>(-1.0, -3.0),
201 vec2<f32>(3.0, 1.0),
202 vec2<f32>(-1.0, 1.0)
203 );
204 var uvs = array<vec2<f32>, 3>(
205 vec2<f32>(0.0, 2.0),
206 vec2<f32>(2.0, 0.0),
207 vec2<f32>(0.0, 0.0)
208 );
209 var out: VsOut;
210 out.pos = vec4<f32>(positions[vi], 0.0, 1.0);
211 out.uv = uvs[vi];
212 return out;
213}
214
215fn linear_to_srgb(c: vec3<f32>) -> vec3<f32> {
216 return pow(c, vec3<f32>(1.0 / 2.2));
217}
218
219@fragment
220fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
221 let src = textureSample(layer_tex, layer_sampler, in.uv);
222 if (blend_mode_buf == 1u) {
223 return vec4<f32>(linear_to_srgb(src.rgb), src.a);
224 }
225 return src;
226}
227 ",
228 )),
229 });
230
231 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
232 label: Some("scenevm-composite-bgl"),
233 entries: &[
234 wgpu::BindGroupLayoutEntry {
235 binding: 0,
236 visibility: wgpu::ShaderStages::FRAGMENT,
237 ty: wgpu::BindingType::Texture {
238 sample_type: wgpu::TextureSampleType::Float { filterable: true },
239 view_dimension: wgpu::TextureViewDimension::D2,
240 multisampled: false,
241 },
242 count: None,
243 },
244 wgpu::BindGroupLayoutEntry {
245 binding: 1,
246 visibility: wgpu::ShaderStages::FRAGMENT,
247 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
248 count: None,
249 },
250 wgpu::BindGroupLayoutEntry {
251 binding: 2,
252 visibility: wgpu::ShaderStages::FRAGMENT,
253 ty: wgpu::BindingType::Buffer {
254 ty: wgpu::BufferBindingType::Uniform,
255 has_dynamic_offset: false,
256 min_binding_size: None,
257 },
258 count: None,
259 },
260 ],
261 });
262
263 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
264 label: Some("scenevm-composite-pipeline-layout"),
265 bind_group_layouts: &[&bind_group_layout],
266 push_constant_ranges: &[],
267 });
268 let mode_buf = device.create_buffer(&wgpu::BufferDescriptor {
269 label: Some("scenevm-composite-mode"),
270 size: std::mem::size_of::<u32>() as u64,
271 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
272 mapped_at_creation: false,
273 });
274
275 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
276 label: Some("scenevm-composite-sampler"),
277 address_mode_u: wgpu::AddressMode::ClampToEdge,
278 address_mode_v: wgpu::AddressMode::ClampToEdge,
279 mag_filter: wgpu::FilterMode::Linear,
280 min_filter: wgpu::FilterMode::Linear,
281 ..Default::default()
282 });
283
284 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
285 label: Some("scenevm-composite-pipeline"),
286 layout: Some(&pipeline_layout),
287 vertex: wgpu::VertexState {
288 module: &shader,
289 entry_point: Some("vs_main"),
290 buffers: &[],
291 compilation_options: wgpu::PipelineCompilationOptions::default(),
292 },
293 fragment: Some(wgpu::FragmentState {
294 module: &shader,
295 entry_point: Some("fs_main"),
296 targets: &[Some(wgpu::ColorTargetState {
297 format: target_format,
298 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
299 write_mask: wgpu::ColorWrites::ALL,
300 })],
301 compilation_options: wgpu::PipelineCompilationOptions::default(),
302 }),
303 primitive: wgpu::PrimitiveState {
304 topology: wgpu::PrimitiveTopology::TriangleList,
305 ..Default::default()
306 },
307 depth_stencil: None,
308 multisample: wgpu::MultisampleState::default(),
309 multiview: None,
310 cache: None,
311 });
312
313 Self {
314 pipeline,
315 bind_group_layout,
316 mode_buf,
317 sampler,
318 target_format,
319 }
320 }
321}
322
323#[cfg(feature = "gpu")]
324struct RgbaOverlayCompositingPipeline {
325 pipeline: wgpu::RenderPipeline,
326 bind_group_layout: wgpu::BindGroupLayout,
327 rect_buf: wgpu::Buffer,
328 sampler: wgpu::Sampler,
329 target_format: wgpu::TextureFormat,
330}
331
332#[cfg(feature = "gpu")]
333impl RgbaOverlayCompositingPipeline {
334 fn new(device: &wgpu::Device, target_format: wgpu::TextureFormat) -> Self {
335 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
336 label: Some("scenevm-rgba-overlay-shader"),
337 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(
338 "
339@group(0) @binding(0) var overlay_tex: texture_2d<f32>;
340@group(0) @binding(1) var overlay_sampler: sampler;
341@group(0) @binding(2) var<uniform> rect: vec4<f32>;
342
343struct VsOut {
344 @builtin(position) pos: vec4<f32>,
345};
346
347@vertex
348fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut {
349 var positions = array<vec2<f32>, 3>(
350 vec2<f32>(-1.0, -3.0),
351 vec2<f32>(3.0, 1.0),
352 vec2<f32>(-1.0, 1.0)
353 );
354 var out: VsOut;
355 out.pos = vec4<f32>(positions[vi], 0.0, 1.0);
356 return out;
357}
358
359@fragment
360fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
361 let x = pos.x;
362 let y = pos.y;
363 if (x < rect.x || y < rect.y || x >= (rect.x + rect.z) || y >= (rect.y + rect.w)) {
364 return vec4<f32>(0.0);
365 }
366 let uv = vec2<f32>((x - rect.x) / max(rect.z, 1.0), (y - rect.y) / max(rect.w, 1.0));
367 return textureSample(overlay_tex, overlay_sampler, uv);
368}
369 ",
370 )),
371 });
372
373 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
374 label: Some("scenevm-rgba-overlay-bgl"),
375 entries: &[
376 wgpu::BindGroupLayoutEntry {
377 binding: 0,
378 visibility: wgpu::ShaderStages::FRAGMENT,
379 ty: wgpu::BindingType::Texture {
380 sample_type: wgpu::TextureSampleType::Float { filterable: true },
381 view_dimension: wgpu::TextureViewDimension::D2,
382 multisampled: false,
383 },
384 count: None,
385 },
386 wgpu::BindGroupLayoutEntry {
387 binding: 1,
388 visibility: wgpu::ShaderStages::FRAGMENT,
389 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
390 count: None,
391 },
392 wgpu::BindGroupLayoutEntry {
393 binding: 2,
394 visibility: wgpu::ShaderStages::FRAGMENT,
395 ty: wgpu::BindingType::Buffer {
396 ty: wgpu::BufferBindingType::Uniform,
397 has_dynamic_offset: false,
398 min_binding_size: None,
399 },
400 count: None,
401 },
402 ],
403 });
404
405 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
406 label: Some("scenevm-rgba-overlay-pipeline-layout"),
407 bind_group_layouts: &[&bind_group_layout],
408 push_constant_ranges: &[],
409 });
410
411 let rect_buf = device.create_buffer(&wgpu::BufferDescriptor {
412 label: Some("scenevm-rgba-overlay-rect"),
413 size: (std::mem::size_of::<f32>() * 4) as u64,
414 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
415 mapped_at_creation: false,
416 });
417
418 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
419 label: Some("scenevm-rgba-overlay-sampler"),
420 address_mode_u: wgpu::AddressMode::ClampToEdge,
421 address_mode_v: wgpu::AddressMode::ClampToEdge,
422 mag_filter: wgpu::FilterMode::Linear,
423 min_filter: wgpu::FilterMode::Linear,
424 ..Default::default()
425 });
426
427 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
428 label: Some("scenevm-rgba-overlay-pipeline"),
429 layout: Some(&pipeline_layout),
430 vertex: wgpu::VertexState {
431 module: &shader,
432 entry_point: Some("vs_main"),
433 buffers: &[],
434 compilation_options: wgpu::PipelineCompilationOptions::default(),
435 },
436 fragment: Some(wgpu::FragmentState {
437 module: &shader,
438 entry_point: Some("fs_main"),
439 targets: &[Some(wgpu::ColorTargetState {
440 format: target_format,
441 blend: Some(wgpu::BlendState {
444 color: wgpu::BlendComponent {
445 src_factor: wgpu::BlendFactor::One,
446 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
447 operation: wgpu::BlendOperation::Add,
448 },
449 alpha: wgpu::BlendComponent {
450 src_factor: wgpu::BlendFactor::One,
451 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
452 operation: wgpu::BlendOperation::Add,
453 },
454 }),
455 write_mask: wgpu::ColorWrites::ALL,
456 })],
457 compilation_options: wgpu::PipelineCompilationOptions::default(),
458 }),
459 primitive: wgpu::PrimitiveState {
460 topology: wgpu::PrimitiveTopology::TriangleList,
461 ..Default::default()
462 },
463 depth_stencil: None,
464 multisample: wgpu::MultisampleState::default(),
465 multiview: None,
466 cache: None,
467 });
468
469 Self {
470 pipeline,
471 bind_group_layout,
472 rect_buf,
473 sampler,
474 target_format,
475 }
476 }
477}
478
479#[cfg(feature = "gpu")]
480struct RgbaOverlayState {
481 texture: Texture,
482 rect: [f32; 4],
483}
484
485#[cfg(not(target_arch = "wasm32"))]
487#[cfg(feature = "gpu")]
488struct WindowSurface {
489 surface: wgpu::Surface<'static>,
490 config: wgpu::SurfaceConfiguration,
491 format: wgpu::TextureFormat,
492 present_pipeline: Option<PresentPipeline>,
493}
494
495#[cfg(feature = "gpu")]
496pub struct GPUState {
497 _instance: wgpu::Instance,
498 _adapter: wgpu::Adapter,
499 device: wgpu::Device,
500 queue: wgpu::Queue,
501 surface: Texture,
503 #[cfg(not(target_arch = "wasm32"))]
505 window_surface: Option<WindowSurface>,
506}
507
508#[allow(dead_code)]
509#[derive(Clone)]
510#[cfg(feature = "gpu")]
511struct GlobalGpu {
512 instance: wgpu::Instance,
513 adapter: wgpu::Adapter,
514 device: wgpu::Device,
515 queue: wgpu::Queue,
516}
517
518#[allow(dead_code)]
519#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
520static GLOBAL_GPU: OnceLock<GlobalGpu> = OnceLock::new();
521
522#[cfg(all(feature = "gpu", target_arch = "wasm32"))]
523thread_local! {
524 static GLOBAL_GPU_WASM: RefCell<Option<GlobalGpu>> = RefCell::new(None);
525}
526
527#[cfg(not(target_arch = "wasm32"))]
528#[cfg(feature = "gpu")]
529impl PresentPipeline {
530 fn new(
531 device: &wgpu::Device,
532 queue: &wgpu::Queue,
533 format: wgpu::TextureFormat,
534 source_view: &wgpu::TextureView,
535 overlay_view: &wgpu::TextureView,
536 overlay_rect: [f32; 4],
537 ) -> Self {
538 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
539 label: Some("scenevm-present-shader"),
540 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(
541 "
542@group(0) @binding(0) var src_tex: texture_2d<f32>;
543@group(0) @binding(1) var src_sampler: sampler;
544@group(0) @binding(2) var overlay_tex: texture_2d<f32>;
545@group(0) @binding(3) var overlay_sampler: sampler;
546@group(0) @binding(4) var<uniform> overlay_rect: vec4<f32>;
547
548struct VsOut {
549 @builtin(position) pos: vec4<f32>,
550 @location(0) uv: vec2<f32>,
551};
552
553@vertex
554fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut {
555 var positions = array<vec2<f32>, 3>(
556 vec2<f32>(-1.0, -3.0),
557 vec2<f32>(3.0, 1.0),
558 vec2<f32>(-1.0, 1.0)
559 );
560 var uvs = array<vec2<f32>, 3>(
561 vec2<f32>(0.0, 2.0),
562 vec2<f32>(2.0, 0.0),
563 vec2<f32>(0.0, 0.0)
564 );
565 var out: VsOut;
566 out.pos = vec4<f32>(positions[vi], 0.0, 1.0);
567 out.uv = uvs[vi];
568 return out;
569}
570
571@fragment
572fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
573 let base = textureSample(src_tex, src_sampler, in.uv);
574 if (overlay_rect.z <= 0.0 || overlay_rect.w <= 0.0) {
575 return base;
576 }
577
578 let x = in.uv.x;
579 let y = in.uv.y;
580 if (x < overlay_rect.x || y < overlay_rect.y || x >= (overlay_rect.x + overlay_rect.z) || y >= (overlay_rect.y + overlay_rect.w)) {
581 return base;
582 }
583
584 let uv = vec2<f32>((x - overlay_rect.x) / max(overlay_rect.z, 1e-6), (y - overlay_rect.y) / max(overlay_rect.w, 1e-6));
585 let over = textureSample(overlay_tex, overlay_sampler, uv);
586 // Premultiplied alpha over operation.
587 let out_rgb = over.rgb + base.rgb * (1.0 - over.a);
588 let out_a = over.a + base.a * (1.0 - over.a);
589 return vec4<f32>(out_rgb, out_a);
590}
591",
592 )),
593 });
594
595 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
596 label: Some("scenevm-present-bgl"),
597 entries: &[
598 wgpu::BindGroupLayoutEntry {
599 binding: 0,
600 visibility: wgpu::ShaderStages::FRAGMENT,
601 ty: wgpu::BindingType::Texture {
602 sample_type: wgpu::TextureSampleType::Float { filterable: true },
603 view_dimension: wgpu::TextureViewDimension::D2,
604 multisampled: false,
605 },
606 count: None,
607 },
608 wgpu::BindGroupLayoutEntry {
609 binding: 1,
610 visibility: wgpu::ShaderStages::FRAGMENT,
611 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
612 count: None,
613 },
614 wgpu::BindGroupLayoutEntry {
615 binding: 2,
616 visibility: wgpu::ShaderStages::FRAGMENT,
617 ty: wgpu::BindingType::Texture {
618 sample_type: wgpu::TextureSampleType::Float { filterable: true },
619 view_dimension: wgpu::TextureViewDimension::D2,
620 multisampled: false,
621 },
622 count: None,
623 },
624 wgpu::BindGroupLayoutEntry {
625 binding: 3,
626 visibility: wgpu::ShaderStages::FRAGMENT,
627 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
628 count: None,
629 },
630 wgpu::BindGroupLayoutEntry {
631 binding: 4,
632 visibility: wgpu::ShaderStages::FRAGMENT,
633 ty: wgpu::BindingType::Buffer {
634 ty: wgpu::BufferBindingType::Uniform,
635 has_dynamic_offset: false,
636 min_binding_size: None,
637 },
638 count: None,
639 },
640 ],
641 });
642
643 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
644 label: Some("scenevm-present-sampler"),
645 mag_filter: wgpu::FilterMode::Linear,
646 min_filter: wgpu::FilterMode::Linear,
647 mipmap_filter: wgpu::FilterMode::Nearest,
648 ..Default::default()
649 });
650
651 let rect_buf = device.create_buffer(&wgpu::BufferDescriptor {
652 label: Some("scenevm-present-overlay-rect"),
653 size: (std::mem::size_of::<f32>() * 4) as u64,
654 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
655 mapped_at_creation: false,
656 });
657 queue.write_buffer(&rect_buf, 0, bytemuck::cast_slice(&overlay_rect));
658
659 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
660 label: Some("scenevm-present-bind-group"),
661 layout: &bind_group_layout,
662 entries: &[
663 wgpu::BindGroupEntry {
664 binding: 0,
665 resource: wgpu::BindingResource::TextureView(source_view),
666 },
667 wgpu::BindGroupEntry {
668 binding: 1,
669 resource: wgpu::BindingResource::Sampler(&sampler),
670 },
671 wgpu::BindGroupEntry {
672 binding: 2,
673 resource: wgpu::BindingResource::TextureView(overlay_view),
674 },
675 wgpu::BindGroupEntry {
676 binding: 3,
677 resource: wgpu::BindingResource::Sampler(&sampler),
678 },
679 wgpu::BindGroupEntry {
680 binding: 4,
681 resource: rect_buf.as_entire_binding(),
682 },
683 ],
684 });
685
686 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
687 label: Some("scenevm-present-pipeline-layout"),
688 bind_group_layouts: &[&bind_group_layout],
689 push_constant_ranges: &[],
690 });
691
692 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
693 label: Some("scenevm-present-pipeline"),
694 layout: Some(&pipeline_layout),
695 vertex: wgpu::VertexState {
696 module: &shader,
697 entry_point: Some("vs_main"),
698 buffers: &[],
699 compilation_options: wgpu::PipelineCompilationOptions::default(),
700 },
701 fragment: Some(wgpu::FragmentState {
702 module: &shader,
703 entry_point: Some("fs_main"),
704 targets: &[Some(wgpu::ColorTargetState {
705 format,
706 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
707 write_mask: wgpu::ColorWrites::ALL,
708 })],
709 compilation_options: wgpu::PipelineCompilationOptions::default(),
710 }),
711 primitive: wgpu::PrimitiveState {
712 topology: wgpu::PrimitiveTopology::TriangleList,
713 ..Default::default()
714 },
715 depth_stencil: None,
716 multisample: wgpu::MultisampleState::default(),
717 multiview: None,
718 cache: None,
719 });
720
721 Self {
722 pipeline,
723 bind_group_layout,
724 bind_group,
725 rect_buf,
726 sampler,
727 surface_format: format,
728 }
729 }
730
731 fn update_bind_group(
732 &mut self,
733 device: &wgpu::Device,
734 queue: &wgpu::Queue,
735 source_view: &wgpu::TextureView,
736 overlay_view: &wgpu::TextureView,
737 overlay_rect: [f32; 4],
738 ) {
739 queue.write_buffer(&self.rect_buf, 0, bytemuck::cast_slice(&overlay_rect));
740 self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
741 label: Some("scenevm-present-bind-group"),
742 layout: &self.bind_group_layout,
743 entries: &[
744 wgpu::BindGroupEntry {
745 binding: 0,
746 resource: wgpu::BindingResource::TextureView(source_view),
747 },
748 wgpu::BindGroupEntry {
749 binding: 1,
750 resource: wgpu::BindingResource::Sampler(&self.sampler),
751 },
752 wgpu::BindGroupEntry {
753 binding: 2,
754 resource: wgpu::BindingResource::TextureView(overlay_view),
755 },
756 wgpu::BindGroupEntry {
757 binding: 3,
758 resource: wgpu::BindingResource::Sampler(&self.sampler),
759 },
760 wgpu::BindGroupEntry {
761 binding: 4,
762 resource: self.rect_buf.as_entire_binding(),
763 },
764 ],
765 });
766 }
767}
768
769#[cfg(not(target_arch = "wasm32"))]
770#[cfg(feature = "gpu")]
771impl WindowSurface {
772 fn reconfigure(&mut self, device: &wgpu::Device) {
773 self.surface.configure(device, &self.config);
774 }
775}
776
777#[cfg(target_arch = "wasm32")]
779#[cfg(feature = "gpu")]
780struct MapReadyFuture {
781 flag: Rc<Cell<bool>>,
782}
783
784#[cfg(target_arch = "wasm32")]
785#[cfg(feature = "gpu")]
786impl Future for MapReadyFuture {
787 type Output = ();
788 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
789 if self.flag.get() {
790 Poll::Ready(())
791 } else {
792 cx.waker().wake_by_ref();
794 Poll::Pending
795 }
796 }
797}
798
799#[cfg(feature = "gpu")]
800pub struct SceneVM {
801 size: (u32, u32),
803
804 gpu: Option<GPUState>,
806 #[cfg(target_arch = "wasm32")]
807 needs_gpu_init: bool,
808 #[cfg(target_arch = "wasm32")]
809 init_in_flight: bool,
810
811 atlas: SharedAtlas,
812 pub vm: VM,
813 overlay_vms: Vec<VM>,
814 active_vm_index: usize,
815 log_layer_activity: bool,
816 compositing_pipeline: Option<CompositingPipeline>,
817 rgba_overlay_pipeline: Option<RgbaOverlayCompositingPipeline>,
818 rgba_overlay: Option<RgbaOverlayState>,
819}
820
821#[derive(Debug, Clone)]
823pub struct ShaderCompilationResult {
824 pub success: bool,
826 pub warnings: Vec<ShaderDiagnostic>,
828 pub errors: Vec<ShaderDiagnostic>,
830}
831
832#[derive(Debug, Clone)]
834pub struct ShaderDiagnostic {
835 pub line: u32,
837 pub message: String,
839}
840
841#[cfg(feature = "gpu")]
842impl Default for SceneVM {
843 fn default() -> Self {
844 Self::new(100, 100)
845 }
846}
847
848#[cfg(feature = "gpu")]
849impl SceneVM {
850 fn refresh_layer_metadata(&mut self) {
851 self.vm.set_layer_index(0);
852 self.vm.set_activity_logging(self.log_layer_activity);
853 for (i, vm) in self.overlay_vms.iter_mut().enumerate() {
854 vm.set_layer_index(i + 1);
855 vm.set_activity_logging(self.log_layer_activity);
856 }
857 }
858
859 fn total_vm_count(&self) -> usize {
860 1 + self.overlay_vms.len()
861 }
862
863 fn vm_ref_by_index(&self, index: usize) -> Option<&VM> {
864 if index == 0 {
865 Some(&self.vm)
866 } else {
867 self.overlay_vms.get(index.saturating_sub(1))
868 }
869 }
870
871 fn vm_mut_by_index(&mut self, index: usize) -> Option<&mut VM> {
872 if index == 0 {
873 Some(&mut self.vm)
874 } else {
875 self.overlay_vms.get_mut(index.saturating_sub(1))
876 }
877 }
878
879 fn draw_all_vms(
880 base_vm: &mut VM,
881 overlays: &mut [VM],
882 device: &wgpu::Device,
883 queue: &wgpu::Queue,
884 surface: &mut Texture,
885 w: u32,
886 h: u32,
887 log_errors: bool,
888 compositing_pipeline: &mut Option<CompositingPipeline>,
889 rgba_overlay: &mut Option<RgbaOverlayState>,
890 rgba_overlay_pipeline: &mut Option<RgbaOverlayCompositingPipeline>,
891 composite_rgba_overlay_in_scene: bool,
892 ) {
893 let target_format = wgpu::TextureFormat::Rgba8Unorm;
895 if let Err(e) = base_vm.draw_into(device, queue, surface, w, h) {
896 if log_errors {
897 println!("[SceneVM] Error drawing base VM: {:?}", e);
898 }
899 }
900
901 for vm in overlays.iter_mut() {
902 if let Err(e) = vm.draw_into(device, queue, surface, w, h) {
903 if log_errors {
904 println!("[SceneVM] Error drawing overlay VM: {:?}", e);
905 }
906 }
907 }
908
909 surface.ensure_gpu_with(device);
911
912 if compositing_pipeline
914 .as_ref()
915 .map(|p| p.target_format != target_format)
916 .unwrap_or(true)
917 {
918 *compositing_pipeline = Some(CompositingPipeline::new(device, target_format));
919 }
920
921 let pipeline = compositing_pipeline.as_ref().unwrap();
922
923 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
925 label: Some("scenevm-compositing-encoder"),
926 });
927
928 let surface_view = &surface.gpu.as_ref().unwrap().view;
930
931 let mut vms_to_composite: Vec<&VM> = Vec::new();
933 if base_vm.is_enabled() {
934 vms_to_composite.push(base_vm);
935 }
936 for vm in overlays.iter() {
937 if vm.is_enabled() {
938 vms_to_composite.push(vm);
939 }
940 }
941
942 for (i, vm) in vms_to_composite.iter().enumerate() {
944 if let Some(layer_texture) = vm.composite_texture() {
945 if let Some(layer_gpu) = &layer_texture.gpu {
946 let mode_u32: u32 = match vm.blend_mode() {
948 LayerBlendMode::Alpha => 0,
949 LayerBlendMode::AlphaLinear => 1,
950 };
951 queue.write_buffer(&pipeline.mode_buf, 0, bytemuck::bytes_of(&mode_u32));
953
954 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
955 label: Some("scenevm-compositing-bind-group"),
956 layout: &pipeline.bind_group_layout,
957 entries: &[
958 wgpu::BindGroupEntry {
959 binding: 0,
960 resource: wgpu::BindingResource::TextureView(&layer_gpu.view),
961 },
962 wgpu::BindGroupEntry {
963 binding: 1,
964 resource: wgpu::BindingResource::Sampler(&pipeline.sampler),
965 },
966 wgpu::BindGroupEntry {
967 binding: 2,
968 resource: pipeline.mode_buf.as_entire_binding(),
969 },
970 ],
971 });
972
973 let load_op = if i == 0 {
977 wgpu::LoadOp::Clear(wgpu::Color::BLACK)
978 } else {
979 wgpu::LoadOp::Load
980 };
981
982 {
983 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
984 label: Some("scenevm-compositing-pass"),
985 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
986 view: surface_view,
987 resolve_target: None,
988 ops: wgpu::Operations {
989 load: load_op,
990 store: wgpu::StoreOp::Store,
991 },
992 depth_slice: None,
993 })],
994 depth_stencil_attachment: None,
995 timestamp_writes: None,
996 occlusion_query_set: None,
997 });
998
999 rpass.set_pipeline(&pipeline.pipeline);
1000 rpass.set_bind_group(0, &bind_group, &[]);
1001 rpass.draw(0..3, 0..1);
1002 }
1003 }
1004 }
1005 }
1006
1007 if composite_rgba_overlay_in_scene && let Some(overlay) = rgba_overlay.as_mut() {
1008 overlay.texture.ensure_gpu_with(device);
1009 overlay.texture.upload_to_gpu_with(device, queue);
1010 if let Some(overlay_gpu) = overlay.texture.gpu.as_ref() {
1011 if rgba_overlay_pipeline
1012 .as_ref()
1013 .map(|p| p.target_format != target_format)
1014 .unwrap_or(true)
1015 {
1016 *rgba_overlay_pipeline =
1017 Some(RgbaOverlayCompositingPipeline::new(device, target_format));
1018 }
1019
1020 let pipeline = rgba_overlay_pipeline.as_ref().unwrap();
1021 queue.write_buffer(&pipeline.rect_buf, 0, bytemuck::cast_slice(&overlay.rect));
1022
1023 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1024 label: Some("scenevm-rgba-overlay-bind-group"),
1025 layout: &pipeline.bind_group_layout,
1026 entries: &[
1027 wgpu::BindGroupEntry {
1028 binding: 0,
1029 resource: wgpu::BindingResource::TextureView(&overlay_gpu.view),
1030 },
1031 wgpu::BindGroupEntry {
1032 binding: 1,
1033 resource: wgpu::BindingResource::Sampler(&pipeline.sampler),
1034 },
1035 wgpu::BindGroupEntry {
1036 binding: 2,
1037 resource: pipeline.rect_buf.as_entire_binding(),
1038 },
1039 ],
1040 });
1041
1042 {
1043 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1044 label: Some("scenevm-rgba-overlay-pass"),
1045 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1046 view: surface_view,
1047 resolve_target: None,
1048 ops: wgpu::Operations {
1049 load: wgpu::LoadOp::Load,
1050 store: wgpu::StoreOp::Store,
1051 },
1052 depth_slice: None,
1053 })],
1054 depth_stencil_attachment: None,
1055 timestamp_writes: None,
1056 occlusion_query_set: None,
1057 });
1058 rpass.set_pipeline(&pipeline.pipeline);
1059 rpass.set_bind_group(0, &bind_group, &[]);
1060 rpass.draw(0..3, 0..1);
1061 }
1062 }
1063 }
1064
1065 queue.submit(Some(encoder.finish()));
1066 }
1067
1068 pub fn set_rgba_overlay(&mut self, width: u32, height: u32, rgba: Vec<u8>, rect: [f32; 4]) {
1069 let w = width.max(1);
1070 let h = height.max(1);
1071 let needed = (w as usize) * (h as usize) * 4;
1072 let mut data = rgba;
1073 if data.len() < needed {
1074 data.resize(needed, 0);
1075 }
1076 if data.len() > needed {
1077 data.truncate(needed);
1078 }
1079
1080 match self.rgba_overlay.as_mut() {
1081 Some(existing) if existing.texture.width == w && existing.texture.height == h => {
1082 existing.texture.data = data;
1083 existing.rect = rect;
1084 }
1085 _ => {
1086 let mut texture = Texture::new(w, h);
1087 texture.data = data;
1088 self.rgba_overlay = Some(RgbaOverlayState { texture, rect });
1089 }
1090 }
1091 }
1092
1093 pub fn set_rgba_overlay_bytes(&mut self, width: u32, height: u32, rgba: &[u8], rect: [f32; 4]) {
1094 let w = width.max(1);
1095 let h = height.max(1);
1096 let needed = (w as usize) * (h as usize) * 4;
1097
1098 match self.rgba_overlay.as_mut() {
1099 Some(existing) if existing.texture.width == w && existing.texture.height == h => {
1100 existing.texture.data.resize(needed, 0);
1101 let copy_len = rgba.len().min(needed);
1102 existing.texture.data[..copy_len].copy_from_slice(&rgba[..copy_len]);
1103 if copy_len < needed {
1104 existing.texture.data[copy_len..].fill(0);
1105 }
1106 existing.rect = rect;
1107 }
1108 _ => {
1109 let mut texture = Texture::new(w, h);
1110 texture.data.resize(needed, 0);
1111 let copy_len = rgba.len().min(needed);
1112 texture.data[..copy_len].copy_from_slice(&rgba[..copy_len]);
1113 self.rgba_overlay = Some(RgbaOverlayState { texture, rect });
1114 }
1115 }
1116 }
1117
1118 pub fn clear_rgba_overlay(&mut self) {
1119 self.rgba_overlay = None;
1120 }
1121
1122 pub fn vm_layer_count(&self) -> usize {
1124 self.total_vm_count()
1125 }
1126
1127 pub fn add_vm_layer(&mut self) -> usize {
1129 let mut vm = VM::new_with_shared_atlas(self.atlas.clone());
1131 vm.background = vek::Vec4::new(0.0, 0.0, 0.0, 0.0);
1132 self.overlay_vms.push(vm);
1133 self.refresh_layer_metadata();
1134 self.total_vm_count() - 1
1135 }
1136
1137 pub fn remove_vm_layer(&mut self, index: usize) -> Option<VM> {
1139 if index == 0 {
1140 return None;
1141 }
1142 let idx = index - 1;
1143 if idx >= self.overlay_vms.len() {
1144 return None;
1145 }
1146 let removed = self.overlay_vms.remove(idx);
1147 if self.active_vm_index >= self.total_vm_count() {
1148 self.active_vm_index = self.total_vm_count().saturating_sub(1);
1149 }
1150 self.refresh_layer_metadata();
1151 Some(removed)
1152 }
1153
1154 pub fn set_active_vm(&mut self, index: usize) -> bool {
1156 if index < self.total_vm_count() {
1157 self.active_vm_index = index;
1158 true
1159 } else {
1160 false
1161 }
1162 }
1163
1164 pub fn active_vm_index(&self) -> usize {
1166 self.active_vm_index
1167 }
1168
1169 pub fn atlas_sdf_uv4(&self, id: &uuid::Uuid, anim_frame: u32) -> Option<[f32; 4]> {
1171 self.atlas.sdf_uv4(id, anim_frame)
1172 }
1173
1174 pub fn atlas_dims(&self) -> (u32, u32) {
1175 self.atlas.dims()
1176 }
1177
1178 pub fn set_layer_enabled(&mut self, index: usize, enabled: bool) -> bool {
1180 if let Some(vm) = self.vm_mut_by_index(index) {
1181 vm.set_enabled(enabled);
1182 true
1183 } else {
1184 false
1185 }
1186 }
1187
1188 pub fn is_layer_enabled(&self, index: usize) -> Option<bool> {
1190 self.vm_ref_by_index(index).map(|vm| vm.is_enabled())
1191 }
1192
1193 pub fn set_layer_activity_logging(&mut self, enabled: bool) {
1195 self.log_layer_activity = enabled;
1196 self.refresh_layer_metadata();
1197 }
1198
1199 pub fn active_vm(&self) -> &VM {
1201 self.vm_ref_by_index(self.active_vm_index)
1202 .expect("active VM index out of range")
1203 }
1204
1205 pub fn active_vm_mut(&mut self) -> &mut VM {
1207 self.vm_mut_by_index(self.active_vm_index)
1208 .expect("active VM index out of range")
1209 }
1210
1211 pub fn pick_geo_id_at_uv(
1213 &self,
1214 fb_w: u32,
1215 fb_h: u32,
1216 screen_uv: [f32; 2],
1217 include_hidden: bool,
1218 include_billboards: bool,
1219 ) -> Option<(GeoId, vek::Vec3<f32>, f32)> {
1220 self.active_vm().pick_geo_id_at_uv(
1221 fb_w,
1222 fb_h,
1223 screen_uv,
1224 include_hidden,
1225 include_billboards,
1226 )
1227 }
1228
1229 pub fn ray_from_uv_with_size(
1231 &self,
1232 fb_w: u32,
1233 fb_h: u32,
1234 screen_uv: [f32; 2],
1235 ) -> Option<(vek::Vec3<f32>, vek::Vec3<f32>)> {
1236 self.active_vm().ray_from_uv(fb_w, fb_h, screen_uv)
1237 }
1238
1239 pub fn ray_from_uv(&self, screen_uv: [f32; 2]) -> Option<(vek::Vec3<f32>, vek::Vec3<f32>)> {
1241 let (w, h) = self.size;
1242 self.active_vm().ray_from_uv(w, h, screen_uv)
1243 }
1244
1245 pub fn print_geometry_stats(&self) {
1247 let mut total_2d = 0usize;
1248 let mut total_3d = 0usize;
1249 let mut total_lines = 0usize;
1250
1251 for vm in std::iter::once(&self.vm).chain(self.overlay_vms.iter()) {
1252 for (_cid, ch) in &vm.chunks_map {
1253 total_2d += ch.polys_map.len();
1254 total_3d += ch.polys3d_map.values().map(|v| v.len()).sum::<usize>();
1255 total_lines += ch.lines2d_px.len();
1256 }
1257 }
1258
1259 println!(
1260 "[SceneVM] Geometry Stats → 2D polys: {} | 3D polys: {} | 2D lines: {} | Total: {}",
1261 total_2d,
1262 total_3d,
1263 total_lines,
1264 total_2d + total_3d + total_lines
1265 );
1266 }
1267
1268 pub fn execute(&mut self, atom: Atom) {
1270 let affects_atlas = SceneVM::atom_touches_atlas(&atom);
1271 let active = self.active_vm_index;
1272 if active == 0 {
1273 self.vm.execute(atom);
1274 } else if let Some(vm) = self.vm_mut_by_index(active) {
1275 vm.execute(atom);
1276 }
1277 if affects_atlas {
1278 self.for_each_vm_mut(|vm| vm.mark_all_geometry_dirty());
1279 }
1280 }
1281
1282 pub fn is_gpu_ready(&self) -> bool {
1284 if self.gpu.is_some() {
1285 #[cfg(target_arch = "wasm32")]
1286 {
1287 return !self.needs_gpu_init && !self.init_in_flight;
1288 }
1289 #[cfg(not(target_arch = "wasm32"))]
1290 {
1291 return true;
1292 }
1293 }
1294 false
1295 }
1296
1297 pub fn frame_in_flight(&self) -> bool {
1299 #[cfg(target_arch = "wasm32")]
1300 {
1301 if let Some(gpu) = &self.gpu {
1302 return gpu
1303 .surface
1304 .gpu
1305 .as_ref()
1306 .and_then(|g| g.map_ready.as_ref())
1307 .is_some();
1308 }
1309 return false;
1310 }
1311 #[cfg(not(target_arch = "wasm32"))]
1312 {
1313 false
1314 }
1315 }
1316 pub fn new(initial_width: u32, initial_height: u32) -> Self {
1318 #[cfg(target_arch = "wasm32")]
1319 {
1320 let atlas = SharedAtlas::new(4096, 4096);
1321 let mut this = Self {
1322 size: (initial_width, initial_height),
1323 gpu: None,
1324 needs_gpu_init: true,
1325 init_in_flight: false,
1326 atlas: atlas.clone(),
1327 vm: VM::new_with_shared_atlas(atlas.clone()),
1328 overlay_vms: Vec::new(),
1329 active_vm_index: 0,
1330 log_layer_activity: false,
1331 compositing_pipeline: None,
1332 rgba_overlay_pipeline: None,
1333 rgba_overlay: None,
1334 };
1335 this.refresh_layer_metadata();
1336 this
1337 }
1338 #[cfg(not(target_arch = "wasm32"))]
1339 {
1340 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
1341 backends: { wgpu::Backends::all() },
1342 ..Default::default()
1343 });
1344 let adapter =
1345 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1346 power_preference: wgpu::PowerPreference::HighPerformance,
1347 force_fallback_adapter: false,
1348 compatible_surface: None,
1349 }))
1350 .expect("No compatible GPU adapter found");
1351
1352 let (device, queue) =
1353 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
1354 label: Some("scenevm-device"),
1355 required_features: wgpu::Features::empty(),
1356 required_limits: wgpu::Limits::default(),
1357 ..Default::default()
1358 }))
1359 .expect("Failed to create wgpu device");
1360
1361 let mut surface = Texture::new(initial_width, initial_height);
1362 surface.ensure_gpu_with(&device);
1363
1364 let gpu = GPUState {
1365 _instance: instance,
1366 _adapter: adapter,
1367 device,
1368 queue,
1369 surface,
1370 window_surface: None,
1371 };
1372
1373 let atlas = SharedAtlas::new(4096, 4096);
1374 let mut this = Self {
1375 size: (initial_width, initial_height),
1376 gpu: Some(gpu),
1377 atlas: atlas.clone(),
1378 vm: VM::new_with_shared_atlas(atlas.clone()),
1379 overlay_vms: Vec::new(),
1380 active_vm_index: 0,
1381 log_layer_activity: false,
1382 compositing_pipeline: None,
1383 rgba_overlay_pipeline: None,
1384 rgba_overlay: None,
1385 };
1386 this.refresh_layer_metadata();
1387 this
1388 }
1389 }
1390
1391 #[cfg(all(feature = "windowing", not(target_arch = "wasm32")))]
1393 pub fn new_with_window(window: &Window) -> Self {
1394 let initial_size = window.inner_size();
1395 let width = initial_size.width.max(1);
1396 let height = initial_size.height.max(1);
1397
1398 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
1399 backends: { wgpu::Backends::all() },
1400 ..Default::default()
1401 });
1402 let surface = unsafe {
1403 instance.create_surface_unsafe(
1404 wgpu::SurfaceTargetUnsafe::from_window(window)
1405 .expect("Failed to access raw window handle"),
1406 )
1407 }
1408 .expect("Failed to create wgpu surface for window");
1409 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1410 power_preference: wgpu::PowerPreference::HighPerformance,
1411 force_fallback_adapter: false,
1412 compatible_surface: Some(&surface),
1413 }))
1414 .expect("No compatible GPU adapter found");
1415
1416 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
1417 label: Some("scenevm-device"),
1418 required_features: wgpu::Features::empty(),
1419 required_limits: wgpu::Limits::default(),
1420 ..Default::default()
1421 }))
1422 .expect("Failed to create wgpu device");
1423
1424 let caps = surface.get_capabilities(&adapter);
1425 let surface_format = caps
1427 .formats
1428 .iter()
1429 .copied()
1430 .find(|f| !f.is_srgb())
1431 .unwrap_or(caps.formats[0]);
1432 let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
1433 wgpu::PresentMode::Fifo
1434 } else {
1435 caps.present_modes[0]
1436 };
1437 let alpha_mode = caps
1438 .alpha_modes
1439 .get(0)
1440 .copied()
1441 .unwrap_or(wgpu::CompositeAlphaMode::Auto);
1442
1443 let surface_config = wgpu::SurfaceConfiguration {
1444 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
1445 format: surface_format,
1446 width,
1447 height,
1448 present_mode,
1449 alpha_mode,
1450 view_formats: vec![],
1451 desired_maximum_frame_latency: 2,
1452 };
1453 surface.configure(&device, &surface_config);
1454
1455 let mut storage_surface = Texture::new(width, height);
1456 storage_surface.ensure_gpu_with(&device);
1457
1458 let gpu = GPUState {
1459 _instance: instance,
1460 _adapter: adapter,
1461 device,
1462 queue,
1463 surface: storage_surface,
1464 window_surface: Some(WindowSurface {
1465 surface,
1466 config: surface_config,
1467 format: surface_format,
1468 present_pipeline: None,
1469 }),
1470 };
1471
1472 let atlas = SharedAtlas::new(4096, 4096);
1473 let mut this = Self {
1474 size: (width, height),
1475 gpu: Some(gpu),
1476 atlas: atlas.clone(),
1477 vm: VM::new_with_shared_atlas(atlas.clone()),
1478 overlay_vms: Vec::new(),
1479 active_vm_index: 0,
1480 log_layer_activity: false,
1481 compositing_pipeline: None,
1482 rgba_overlay_pipeline: None,
1483 rgba_overlay: None,
1484 };
1485 this.refresh_layer_metadata();
1486 this
1487 }
1488
1489 #[cfg(all(
1491 not(target_arch = "wasm32"),
1492 any(target_os = "macos", target_os = "ios")
1493 ))]
1494 pub fn new_with_metal_layer(layer_ptr: *mut c_void, width: u32, height: u32) -> Self {
1495 let width = width.max(1);
1496 let height = height.max(1);
1497
1498 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
1499 backends: { wgpu::Backends::all() },
1500 ..Default::default()
1501 });
1502
1503 let surface = unsafe {
1504 instance.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::CoreAnimationLayer(layer_ptr))
1505 }
1506 .expect("Failed to create wgpu surface for CoreAnimationLayer");
1507
1508 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1509 power_preference: wgpu::PowerPreference::HighPerformance,
1510 force_fallback_adapter: false,
1511 compatible_surface: Some(&surface),
1512 }))
1513 .expect("No compatible GPU adapter found");
1514
1515 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
1516 label: Some("scenevm-device"),
1517 required_features: wgpu::Features::empty(),
1518 required_limits: wgpu::Limits::default(),
1519 ..Default::default()
1520 }))
1521 .expect("Failed to create wgpu device");
1522
1523 let caps = surface.get_capabilities(&adapter);
1524 let surface_format = caps
1526 .formats
1527 .iter()
1528 .copied()
1529 .find(|f| !f.is_srgb())
1530 .unwrap_or(caps.formats[0]);
1531 let present_mode = if caps.present_modes.contains(&wgpu::PresentMode::Fifo) {
1532 wgpu::PresentMode::Fifo
1533 } else {
1534 caps.present_modes[0]
1535 };
1536 let alpha_mode = caps
1537 .alpha_modes
1538 .get(0)
1539 .copied()
1540 .unwrap_or(wgpu::CompositeAlphaMode::Auto);
1541
1542 let surface_config = wgpu::SurfaceConfiguration {
1543 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST,
1544 format: surface_format,
1545 width,
1546 height,
1547 present_mode,
1548 alpha_mode,
1549 view_formats: vec![],
1550 desired_maximum_frame_latency: 2,
1551 };
1552 surface.configure(&device, &surface_config);
1553
1554 let mut storage_surface = Texture::new(width, height);
1555 storage_surface.ensure_gpu_with(&device);
1556
1557 let gpu = GPUState {
1558 _instance: instance,
1559 _adapter: adapter,
1560 device,
1561 queue,
1562 surface: storage_surface,
1563 window_surface: Some(WindowSurface {
1564 surface,
1565 config: surface_config,
1566 format: surface_format,
1567 present_pipeline: None,
1568 }),
1569 };
1570
1571 let atlas = SharedAtlas::new(4096, 4096);
1572 let mut this = Self {
1573 size: (width, height),
1574 gpu: Some(gpu),
1575 atlas: atlas.clone(),
1576 vm: VM::new_with_shared_atlas(atlas.clone()),
1577 overlay_vms: Vec::new(),
1578 active_vm_index: 0,
1579 log_layer_activity: false,
1580 compositing_pipeline: None,
1581 rgba_overlay_pipeline: None,
1582 rgba_overlay: None,
1583 };
1584 this.refresh_layer_metadata();
1585 this
1586 }
1587
1588 pub async fn init_async(&mut self) {
1590 if self.gpu.is_some() {
1592 return;
1593 }
1594
1595 #[cfg(target_arch = "wasm32")]
1596 {
1597 if !self.needs_gpu_init {
1598 return;
1599 }
1600 if global_gpu_get().is_none() {
1601 global_gpu_init_async().await;
1602 }
1603 let gg = global_gpu_get().expect("Global GPU not initialized");
1604 let (w, h) = self.size;
1605 let mut surface = Texture::new(w, h);
1606 surface.ensure_gpu_with(&gg.device);
1607 let gpu = GPUState {
1608 _instance: gg.instance,
1609 _adapter: gg.adapter,
1610 device: gg.device,
1611 queue: gg.queue,
1612 surface,
1613 };
1614 self.gpu = Some(gpu);
1615 self.needs_gpu_init = false;
1616 #[cfg(debug_assertions)]
1617 {
1618 web_sys::console::log_1(&"SceneVM WebGPU initialized (global)".into());
1619 }
1620 }
1621
1622 #[cfg(not(target_arch = "wasm32"))]
1623 {
1624 if self.gpu.is_some() {
1625 return;
1626 }
1627 let (w, h) = self.size;
1628 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
1629 backends: { wgpu::Backends::all() },
1630 ..Default::default()
1631 });
1632 let adapter =
1633 pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1634 power_preference: wgpu::PowerPreference::HighPerformance,
1635 force_fallback_adapter: false,
1636 compatible_surface: None,
1637 }))
1638 .expect("No compatible GPU adapter found");
1639
1640 let (device, queue) =
1641 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
1642 label: Some("scenevm-device"),
1643 required_features: wgpu::Features::empty(),
1644 required_limits: wgpu::Limits::default(),
1645 ..Default::default()
1646 }))
1647 .expect("Failed to create wgpu device");
1648
1649 let mut surface = Texture::new(w, h);
1650 surface.ensure_gpu_with(&device);
1651
1652 let gpu = GPUState {
1653 _instance: instance,
1654 _adapter: adapter,
1655 device,
1656 queue,
1657 surface,
1658 window_surface: None,
1659 };
1660 self.gpu = Some(gpu);
1661 }
1662 }
1663
1664 pub fn blit_texture(
1666 &mut self,
1667 tex: &mut Texture,
1668 _cpu_pixels: &mut [u8],
1669 _buf_w: u32,
1670 _buf_h: u32,
1671 ) {
1672 if let Some(g) = self.gpu.as_ref() {
1673 tex.gpu_blit_to_storage(g, &g.surface.gpu.as_ref().unwrap().texture);
1674 }
1675 }
1676
1677 #[cfg(not(target_arch = "wasm32"))]
1679 pub fn resize_window_surface(&mut self, width: u32, height: u32) {
1680 let Some(gpu) = self.gpu.as_mut() else {
1681 return;
1682 };
1683 let Some(ws) = gpu.window_surface.as_mut() else {
1684 return;
1685 };
1686
1687 let w = width.max(1);
1688 let h = height.max(1);
1689 if ws.config.width == w && ws.config.height == h {
1690 return;
1691 }
1692
1693 ws.config.width = w;
1694 ws.config.height = h;
1695 ws.reconfigure(&gpu.device);
1696
1697 self.size = (w, h);
1698 gpu.surface.width = w;
1699 gpu.surface.height = h;
1700 gpu.surface.ensure_gpu_with(&gpu.device);
1701
1702 ws.present_pipeline = None;
1704 }
1705
1706 #[cfg(not(target_arch = "wasm32"))]
1708 pub fn render_to_window(&mut self) -> SceneVMResult<RenderResult> {
1709 let (gpu_slot, base_vm, overlays) = (&mut self.gpu, &mut self.vm, &mut self.overlay_vms);
1710 let Some(gpu) = gpu_slot.as_mut() else {
1711 return Err(SceneVMError::InvalidOperation(
1712 "GPU not initialized".to_string(),
1713 ));
1714 };
1715 let Some(ws) = gpu.window_surface.as_mut() else {
1716 return Err(SceneVMError::InvalidOperation(
1717 "No window surface configured".to_string(),
1718 ));
1719 };
1720
1721 let target_w = ws.config.width.max(1);
1722 let target_h = ws.config.height.max(1);
1723
1724 if self.size != (target_w, target_h) {
1725 self.size = (target_w, target_h);
1726 gpu.surface.width = target_w;
1727 gpu.surface.height = target_h;
1728 gpu.surface.ensure_gpu_with(&gpu.device);
1729 ws.present_pipeline = None;
1730 }
1731
1732 let (w, h) = self.size;
1733 SceneVM::draw_all_vms(
1734 base_vm,
1735 overlays,
1736 &gpu.device,
1737 &gpu.queue,
1738 &mut gpu.surface,
1739 w,
1740 h,
1741 self.log_layer_activity,
1742 &mut self.compositing_pipeline,
1743 &mut self.rgba_overlay,
1744 &mut self.rgba_overlay_pipeline,
1745 false,
1746 );
1747 let frame = match ws.surface.get_current_texture() {
1748 Ok(frame) => frame,
1749 Err(wgpu::SurfaceError::Lost) | Err(wgpu::SurfaceError::Outdated) => {
1750 ws.reconfigure(&gpu.device);
1751 return Ok(RenderResult::InitPending);
1752 }
1753 Err(wgpu::SurfaceError::Timeout) => {
1754 return Ok(RenderResult::ReadbackPending);
1755 }
1756 Err(wgpu::SurfaceError::Other) => {
1757 return Err(SceneVMError::InvalidOperation(
1758 "Surface returned an unspecified error".to_string(),
1759 ));
1760 }
1761 Err(wgpu::SurfaceError::OutOfMemory) => {
1762 return Err(SceneVMError::BufferAllocationFailed(
1763 "Surface out of memory".to_string(),
1764 ));
1765 }
1766 };
1767
1768 let frame_view = frame
1769 .texture
1770 .create_view(&wgpu::TextureViewDescriptor::default());
1771 let src_view = gpu
1772 .surface
1773 .gpu
1774 .as_ref()
1775 .expect("Surface GPU not allocated")
1776 .view
1777 .clone();
1778 let (overlay_view, overlay_rect_px): (wgpu::TextureView, [f32; 4]) =
1779 if let Some(overlay) = self.rgba_overlay.as_mut() {
1780 overlay.texture.ensure_gpu_with(&gpu.device);
1781 overlay.texture.upload_to_gpu_with(&gpu.device, &gpu.queue);
1782 if let Some(overlay_gpu) = overlay.texture.gpu.as_ref() {
1783 (overlay_gpu.view.clone(), overlay.rect)
1784 } else {
1785 (src_view.clone(), [0.0, 0.0, 0.0, 0.0])
1786 }
1787 } else {
1788 (src_view.clone(), [0.0, 0.0, 0.0, 0.0])
1789 };
1790 let fw = ws.config.width.max(1) as f32;
1791 let fh = ws.config.height.max(1) as f32;
1792 let overlay_rect = [
1793 overlay_rect_px[0] / fw,
1794 overlay_rect_px[1] / fh,
1795 overlay_rect_px[2] / fw,
1796 overlay_rect_px[3] / fh,
1797 ];
1798
1799 if ws
1800 .present_pipeline
1801 .as_ref()
1802 .map(|p| p.surface_format != ws.format)
1803 .unwrap_or(true)
1804 {
1805 ws.present_pipeline = Some(PresentPipeline::new(
1806 &gpu.device,
1807 &gpu.queue,
1808 ws.format,
1809 &src_view,
1810 &overlay_view,
1811 overlay_rect,
1812 ));
1813 } else if let Some(pipeline) = ws.present_pipeline.as_mut() {
1814 pipeline.update_bind_group(
1815 &gpu.device,
1816 &gpu.queue,
1817 &src_view,
1818 &overlay_view,
1819 overlay_rect,
1820 );
1821 }
1822
1823 let present = ws
1824 .present_pipeline
1825 .as_ref()
1826 .expect("Present pipeline should be initialized");
1827
1828 let mut encoder = gpu
1829 .device
1830 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1831 label: Some("scenevm-present-encoder"),
1832 });
1833 {
1834 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1835 label: Some("scenevm-present-pass"),
1836 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1837 view: &frame_view,
1838 depth_slice: None,
1839 resolve_target: None,
1840 ops: wgpu::Operations {
1841 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1842 store: wgpu::StoreOp::Store,
1843 },
1844 })],
1845 depth_stencil_attachment: None,
1846 occlusion_query_set: None,
1847 timestamp_writes: None,
1848 });
1849 pass.set_pipeline(&present.pipeline);
1850 pass.set_bind_group(0, &present.bind_group, &[]);
1851 pass.draw(0..3, 0..1);
1852 }
1853 gpu.queue.submit(std::iter::once(encoder.finish()));
1854 frame.present();
1855
1856 Ok(RenderResult::Presented)
1857 }
1858
1859 #[cfg(not(target_arch = "wasm32"))]
1861 fn draw(&mut self, out_pixels: &mut [u8], out_w: u32, out_h: u32) {
1862 let (gpu_slot, base_vm, overlays) = (&mut self.gpu, &mut self.vm, &mut self.overlay_vms);
1864 let Some(gpu) = gpu_slot.as_mut() else {
1865 return;
1866 };
1867
1868 let buffer_width = out_w;
1869 let buffer_height = out_h;
1870
1871 if self.size != (buffer_width, buffer_height) {
1873 self.size = (buffer_width, buffer_height);
1874 gpu.surface.width = buffer_width;
1875 gpu.surface.height = buffer_height;
1876 gpu.surface.ensure_gpu_with(&gpu.device);
1877 }
1878
1879 let (w, h) = self.size;
1880
1881 SceneVM::draw_all_vms(
1883 base_vm,
1884 overlays,
1885 &gpu.device,
1886 &gpu.queue,
1887 &mut gpu.surface,
1888 w,
1889 h,
1890 self.log_layer_activity,
1891 &mut self.compositing_pipeline,
1892 &mut self.rgba_overlay,
1893 &mut self.rgba_overlay_pipeline,
1894 true,
1895 );
1896 let device = gpu.device.clone();
1898 let queue = gpu.queue.clone();
1899 gpu.surface.download_from_gpu_with(&device, &queue);
1900
1901 gpu.surface.copy_to_slice(out_pixels, out_w, out_h);
1904 }
1905
1906 #[cfg(target_arch = "wasm32")]
1908 pub async fn render_frame_async(&mut self, out_pixels: &mut [u8], out_w: u32, out_h: u32) {
1909 let (gpu_slot, base_vm, overlays) = (&mut self.gpu, &mut self.vm, &mut self.overlay_vms);
1910 let Some(gpu) = gpu_slot.as_mut() else {
1911 return;
1912 };
1913 let buffer_width = out_w;
1914 let buffer_height = out_h;
1915
1916 if self.size != (buffer_width, buffer_height) {
1917 self.size = (buffer_width, buffer_height);
1918 gpu.surface.width = buffer_width;
1919 gpu.surface.height = buffer_height;
1920 gpu.surface.ensure_gpu_with(&gpu.device);
1921 }
1922
1923 let (w, h) = self.size;
1924 SceneVM::draw_all_vms(
1925 base_vm,
1926 overlays,
1927 &gpu.device,
1928 &gpu.queue,
1929 &mut gpu.surface,
1930 w,
1931 h,
1932 self.log_layer_activity,
1933 &mut self.compositing_pipeline,
1934 &mut self.rgba_overlay,
1935 &mut self.rgba_overlay_pipeline,
1936 true,
1937 );
1938
1939 let device = gpu.device.clone();
1941 let queue = gpu.queue.clone();
1942 gpu.surface.download_from_gpu_with(&device, &queue);
1943 let flag = gpu
1944 .surface
1945 .gpu
1946 .as_ref()
1947 .and_then(|g| g.map_ready.as_ref().map(|f| std::rc::Rc::clone(f)));
1948 if let Some(flag) = flag {
1949 MapReadyFuture { flag }.await;
1950 }
1951 let _ = gpu.surface.try_finish_download_from_gpu();
1952 gpu.surface.copy_to_slice(out_pixels, out_w, out_h);
1953 }
1954
1955 #[cfg(not(target_arch = "wasm32"))]
1957 pub async fn render_frame_async(&mut self, out_pixels: &mut [u8], out_w: u32, out_h: u32) {
1958 self.draw(out_pixels, out_w, out_h);
1959 }
1960
1961 pub fn render_frame(&mut self, out_pixels: &mut [u8], out_w: u32, out_h: u32) -> RenderResult {
1966 #[cfg(not(target_arch = "wasm32"))]
1969 {
1970 self.draw(out_pixels, out_w, out_h);
1972
1973 return RenderResult::Presented;
1977 }
1978
1979 #[cfg(target_arch = "wasm32")]
1980 {
1981 if self.gpu.is_none() {
1983 if !self.init_in_flight && self.needs_gpu_init {
1984 self.init_in_flight = true;
1985 let this: *mut SceneVM = self as *mut _;
1986 spawn_local(async move {
1987 unsafe {
1991 (&mut *this).init_async().await;
1992 (&mut *this).init_in_flight = false;
1993 }
1994 });
1995 }
1996 return RenderResult::InitPending;
1998 }
1999 let (gpu_slot, base_vm, overlays) =
2000 (&mut self.gpu, &mut self.vm, &mut self.overlay_vms);
2001 let gpu = gpu_slot.as_mut().unwrap();
2002
2003 if self.size != (out_w, out_h) {
2005 self.size = (out_w, out_h);
2006 gpu.surface.width = out_w;
2007 gpu.surface.height = out_h;
2008 gpu.surface.ensure_gpu_with(&gpu.device);
2009 }
2010
2011 let inflight = gpu
2013 .surface
2014 .gpu
2015 .as_ref()
2016 .and_then(|g| g.map_ready.as_ref())
2017 .is_some();
2018
2019 let mut presented_frame = false;
2023 if inflight {
2024 let ready = gpu.surface.try_finish_download_from_gpu();
2025 gpu.surface.copy_to_slice(out_pixels, out_w, out_h);
2026 if !ready {
2027 return RenderResult::ReadbackPending;
2028 }
2029 presented_frame = true;
2030 } else {
2031 gpu.surface.copy_to_slice(out_pixels, out_w, out_h);
2033 }
2034
2035 let (w, h) = self.size;
2037 SceneVM::draw_all_vms(
2038 base_vm,
2039 overlays,
2040 &gpu.device,
2041 &gpu.queue,
2042 &mut gpu.surface,
2043 w,
2044 h,
2045 self.log_layer_activity,
2046 &mut self.compositing_pipeline,
2047 &mut self.rgba_overlay,
2048 &mut self.rgba_overlay_pipeline,
2049 true,
2050 );
2051 let device = gpu.device.clone();
2052 let queue = gpu.queue.clone();
2053 gpu.surface.download_from_gpu_with(&device, &queue);
2054
2055 if presented_frame {
2056 RenderResult::Presented
2057 } else {
2058 RenderResult::ReadbackPending
2059 }
2060 }
2061 }
2062
2063 pub fn load_image_rgba<I: IntoDataInput>(&self, input: I) -> Option<(Vec<u8>, u32, u32)> {
2065 let bytes = match input.load_data() {
2066 Ok(b) => b,
2067 Err(_) => return None,
2068 };
2069 let img = match image::load_from_memory(&bytes) {
2070 Ok(i) => i,
2071 Err(_) => return None,
2072 };
2073 let rgba = img.to_rgba8();
2074 let (w, h) = rgba.dimensions();
2075 Some((rgba.into_raw(), w, h))
2076 }
2077
2078 pub fn compile_shader_2d(&mut self, body_source: &str) -> ShaderCompilationResult {
2081 self.compile_shader_internal(body_source, true)
2082 }
2083
2084 pub fn compile_shader_3d(&mut self, body_source: &str) -> ShaderCompilationResult {
2087 self.compile_shader_internal(body_source, false)
2088 }
2089
2090 pub fn compile_shader_sdf(&mut self, body_source: &str) -> ShaderCompilationResult {
2093 use wgpu::ShaderSource;
2094
2095 let header_source = if let Some(bytes) = Embedded::get("sdf_header.wgsl") {
2096 std::str::from_utf8(bytes.data.as_ref())
2097 .unwrap_or("")
2098 .to_string()
2099 } else {
2100 "".to_string()
2101 };
2102
2103 let full_source = format!("{}\n{}", header_source, body_source);
2104
2105 let device = if let Some(gpu) = &self.gpu {
2106 &gpu.device
2107 } else {
2108 return ShaderCompilationResult {
2109 success: false,
2110 warnings: vec![],
2111 errors: vec![ShaderDiagnostic {
2112 line: 0,
2113 message: "GPU device not initialized. Cannot compile shader.".to_string(),
2114 }],
2115 };
2116 };
2117
2118 let _shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
2119 label: Some("scenevm-compile-sdf"),
2120 source: ShaderSource::Wgsl(full_source.into()),
2121 });
2122
2123 self.vm.execute(Atom::SetSourceSdf(body_source.to_string()));
2124
2125 ShaderCompilationResult {
2126 success: true,
2127 warnings: vec![],
2128 errors: vec![],
2129 }
2130 }
2131
2132 pub fn default_shader_source(kind: &str) -> Option<String> {
2134 let file_name = match kind {
2135 "ui" => "ui_body.wgsl",
2136 "2d" => "2d_body.wgsl",
2137 "3d" => "3d_body.wgsl",
2138 "sdf" => "sdf_body.wgsl",
2139 _ => return None,
2140 };
2141
2142 Embedded::get(file_name).map(|bytes| {
2143 String::from_utf8_lossy(bytes.data.as_ref()).into_owned()
2145 })
2146 }
2147
2148 fn compile_shader_internal(
2150 &mut self,
2151 body_source: &str,
2152 is_2d: bool,
2153 ) -> ShaderCompilationResult {
2154 use wgpu::ShaderSource;
2155
2156 let header_source = if is_2d {
2158 if let Some(bytes) = Embedded::get("2d_header.wgsl") {
2159 std::str::from_utf8(bytes.data.as_ref())
2160 .unwrap_or("")
2161 .to_string()
2162 } else {
2163 "".to_string()
2164 }
2165 } else {
2166 if let Some(bytes) = Embedded::get("3d_header.wgsl") {
2167 std::str::from_utf8(bytes.data.as_ref())
2168 .unwrap_or("")
2169 .to_string()
2170 } else {
2171 "".to_string()
2172 }
2173 };
2174
2175 let full_source = format!("{}\n{}", header_source, body_source);
2177
2178 let device = if let Some(gpu) = &self.gpu {
2180 &gpu.device
2182 } else {
2183 return ShaderCompilationResult {
2185 success: false,
2186 warnings: vec![],
2187 errors: vec![ShaderDiagnostic {
2188 line: 0,
2189 message: "GPU device not initialized. Cannot compile shader.".to_string(),
2190 }],
2191 };
2192 };
2193
2194 let _shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
2195 label: Some(if is_2d {
2196 "scenevm-compile-2d"
2197 } else {
2198 "scenevm-compile-3d"
2199 }),
2200 source: ShaderSource::Wgsl(full_source.into()),
2201 });
2202
2203 let success = true; if success {
2213 if is_2d {
2215 self.vm.execute(Atom::SetSource2D(body_source.to_string()));
2216 } else {
2217 self.vm.execute(Atom::SetSource3D(body_source.to_string()));
2218 }
2219 }
2220
2221 ShaderCompilationResult {
2222 success,
2223 warnings: vec![], errors: vec![], }
2226 }
2227}
2228
2229#[cfg(target_arch = "wasm32")]
2231fn global_gpu_get() -> Option<GlobalGpu> {
2232 GLOBAL_GPU_WASM.with(|c| c.borrow().clone())
2233}
2234
2235#[cfg(target_arch = "wasm32")]
2236async fn global_gpu_init_async() {
2237 if global_gpu_get().is_some() {
2238 return;
2239 }
2240 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
2241 backends: wgpu::Backends::BROWSER_WEBGPU,
2242 ..Default::default()
2243 });
2244 let adapter = instance
2245 .request_adapter(&wgpu::RequestAdapterOptions {
2246 power_preference: wgpu::PowerPreference::HighPerformance,
2247 force_fallback_adapter: false,
2248 compatible_surface: None,
2249 })
2250 .await
2251 .expect("No compatible GPU adapter found (WebGPU)");
2252 let (device, queue) = adapter
2253 .request_device(&wgpu::DeviceDescriptor {
2254 label: Some("scenevm-device"),
2255 required_features: wgpu::Features::empty(),
2256 required_limits: wgpu::Limits::default(),
2257 ..Default::default()
2258 })
2259 .await
2260 .expect("Failed to create wgpu device (WebGPU)");
2261 let gg = GlobalGpu {
2262 instance,
2263 adapter,
2264 device,
2265 queue,
2266 };
2267 GLOBAL_GPU_WASM.with(|c| *c.borrow_mut() = Some(gg));
2268}
2269#[cfg(feature = "gpu")]
2270impl SceneVM {
2271 fn for_each_vm_mut(&mut self, mut f: impl FnMut(&mut VM)) {
2272 f(&mut self.vm);
2273 for vm in &mut self.overlay_vms {
2274 f(vm);
2275 }
2276 }
2277
2278 fn atom_touches_atlas(atom: &Atom) -> bool {
2279 matches!(
2280 atom,
2281 Atom::AddTile { .. }
2282 | Atom::AddSolid { .. }
2283 | Atom::SetTileMaterialFrames { .. }
2284 | Atom::BuildAtlas
2285 | Atom::Clear
2286 | Atom::ClearTiles
2287 )
2288 }
2289}
2290
2291#[cfg(all(not(target_arch = "wasm32"), feature = "windowing"))]
2296#[cfg(feature = "gpu")]
2297struct NativeRenderCtx {
2298 size: (u32, u32),
2299 last_result: RenderResult,
2300 present_called: bool,
2301}
2302
2303#[cfg(all(not(target_arch = "wasm32"), feature = "windowing"))]
2304#[cfg(feature = "gpu")]
2305impl NativeRenderCtx {
2306 fn new(size: (u32, u32)) -> Self {
2307 Self {
2308 size,
2309 last_result: RenderResult::InitPending,
2310 present_called: false,
2311 }
2312 }
2313
2314 fn begin_frame(&mut self) {
2315 self.present_called = false;
2316 }
2317
2318 fn ensure_presented(&mut self, vm: &mut SceneVM) -> SceneVMResult<RenderResult> {
2319 if !self.present_called {
2320 self.present(vm)?;
2321 }
2322 Ok(self.last_result)
2323 }
2324}
2325
2326#[cfg(all(not(target_arch = "wasm32"), feature = "windowing"))]
2327#[cfg(feature = "gpu")]
2328impl SceneVMRenderCtx for NativeRenderCtx {
2329 fn size(&self) -> (u32, u32) {
2330 self.size
2331 }
2332
2333 fn present(&mut self, vm: &mut SceneVM) -> SceneVMResult<RenderResult> {
2334 let res = vm.render_to_window();
2335 if let Ok(r) = res {
2336 self.last_result = r;
2337 }
2338 self.present_called = true;
2339 res
2340 }
2341}
2342
2343#[cfg(all(not(target_arch = "wasm32"), feature = "windowing"))]
2345#[cfg(feature = "gpu")]
2346pub fn run_scenevm_app<A: SceneVMApp + 'static>(
2347 mut app: A,
2348) -> Result<(), Box<dyn std::error::Error>> {
2349 use winit::dpi::LogicalSize;
2350 use winit::event::{Event, StartCause};
2351 use winit::event_loop::{ControlFlow, EventLoop};
2352 use winit::window::WindowAttributes;
2353
2354 let frame_interval = app.target_fps().and_then(|fps| {
2355 if fps > 0.0 {
2356 Some(std::time::Duration::from_secs_f32(1.0 / fps))
2357 } else {
2358 None
2359 }
2360 });
2361
2362 let event_loop = EventLoop::new()?;
2363 let mut window: Option<winit::window::Window> = None;
2364 let mut vm: Option<SceneVM> = None;
2365 let mut ctx: Option<NativeRenderCtx> = None;
2366 let mut cursor_pos: PhysicalPosition<f64> = PhysicalPosition { x: 0.0, y: 0.0 };
2367 let mut last_frame_at = std::time::Instant::now();
2368 #[cfg(feature = "ui")]
2369 let mut modifiers = winit::event::Modifiers::default();
2370 let apply_logical_scale = |vm_ref: &mut SceneVM, scale: f64| {
2371 let s = scale as f32;
2373 let m = Mat3::<f32>::new(s, 0.0, 0.0, 0.0, s, 0.0, 0.0, 0.0, 1.0);
2374 vm_ref.execute(Atom::SetTransform2D(m));
2375 };
2376 #[allow(deprecated)]
2377 event_loop.run(move |event, target| match event {
2378 Event::NewEvents(StartCause::Init) => {
2379 let mut attrs = WindowAttributes::default()
2380 .with_title(app.window_title().unwrap_or_else(|| "SceneVM".to_string()));
2381 if let Some((w, h)) = app.initial_window_size() {
2382 attrs = attrs.with_inner_size(LogicalSize::new(w as f64, h as f64));
2383 }
2384 let win = target
2385 .create_window(attrs)
2386 .expect("failed to create window");
2387 win.set_cursor_visible(false);
2388 let size = win.inner_size();
2389 let scale = win.scale_factor();
2390 let logical = size.to_logical::<f64>(scale);
2391 let logical_size = (logical.width.round() as u32, logical.height.round() as u32);
2392 let mut new_vm = SceneVM::new_with_window(&win);
2393 apply_logical_scale(&mut new_vm, scale);
2394 let new_ctx = NativeRenderCtx::new(logical_size);
2395 app.set_scale(scale as f32);
2396 app.set_native_mode(true); app.init(&mut new_vm, logical_size);
2398 window = Some(win);
2399 vm = Some(new_vm);
2400 ctx = Some(new_ctx);
2401 target.set_control_flow(ControlFlow::Poll);
2402 }
2403 Event::WindowEvent { window_id, event } => {
2404 if let (Some(win), Some(vm_ref), Some(ctx_ref)) =
2405 (window.as_ref(), vm.as_mut(), ctx.as_mut())
2406 {
2407 if window_id == win.id() {
2408 match event {
2409 WindowEvent::CloseRequested => target.exit(),
2410 WindowEvent::Resized(size) => {
2411 let scale = win.scale_factor();
2412 let logical = size.to_logical::<f64>(scale);
2413 let logical_size =
2414 (logical.width.round() as u32, logical.height.round() as u32);
2415 ctx_ref.size = logical_size;
2416 vm_ref.resize_window_surface(size.width, size.height);
2417 apply_logical_scale(vm_ref, scale);
2418 app.set_scale(scale as f32);
2419 app.resize(vm_ref, logical_size);
2420 }
2421 WindowEvent::ScaleFactorChanged {
2422 scale_factor,
2423 mut inner_size_writer,
2424 } => {
2425 let size = win.inner_size();
2426 let _ = inner_size_writer.request_inner_size(size);
2427 let logical = size.to_logical::<f64>(scale_factor);
2428 let logical_size =
2429 (logical.width.round() as u32, logical.height.round() as u32);
2430 ctx_ref.size = logical_size;
2431 vm_ref.resize_window_surface(size.width, size.height);
2432 app.set_scale(scale_factor as f32);
2433 apply_logical_scale(vm_ref, scale_factor);
2434 }
2435 WindowEvent::CursorMoved { position, .. } => {
2436 cursor_pos = position;
2437 let scale = win.scale_factor() as f32;
2438 app.mouse_move(
2439 vm_ref,
2440 (cursor_pos.x as f32) / scale,
2441 (cursor_pos.y as f32) / scale,
2442 );
2443 }
2444 WindowEvent::MouseInput {
2445 state,
2446 button: MouseButton::Left,
2447 ..
2448 } => match state {
2449 ElementState::Pressed => {
2450 let scale = win.scale_factor() as f32;
2451 app.mouse_down(
2452 vm_ref,
2453 (cursor_pos.x as f32) / scale,
2454 (cursor_pos.y as f32) / scale,
2455 );
2456 }
2457 ElementState::Released => {
2458 let scale = win.scale_factor() as f32;
2459 app.mouse_up(
2460 vm_ref,
2461 (cursor_pos.x as f32) / scale,
2462 (cursor_pos.y as f32) / scale,
2463 );
2464 }
2465 },
2466 WindowEvent::MouseWheel { delta, .. } => {
2467 let (dx, dy) = match delta {
2468 winit::event::MouseScrollDelta::LineDelta(x, y) => {
2469 (x * 120.0, y * 120.0)
2470 }
2471 winit::event::MouseScrollDelta::PixelDelta(pos) => {
2472 (pos.x as f32, pos.y as f32)
2473 }
2474 };
2475 let scale = win.scale_factor() as f32;
2476 app.scroll(vm_ref, dx / scale, dy / scale);
2477 }
2478 WindowEvent::RedrawRequested => {
2479 if let Some(dt) = frame_interval {
2480 let now = std::time::Instant::now();
2481 if now.duration_since(last_frame_at) < dt {
2482 return;
2483 }
2484 last_frame_at = now;
2485 }
2486 if app.needs_update(vm_ref) {
2487 ctx_ref.begin_frame();
2488 app.update(vm_ref);
2489 let _ = app.render(vm_ref, ctx_ref);
2490 let _ = ctx_ref.ensure_presented(vm_ref);
2491
2492 #[cfg(feature = "ui")]
2494 {
2495 use crate::app_event::AppEvent;
2496 let events = app.take_app_events();
2497 for event in events {
2498 match event {
2499 AppEvent::RequestUndo => {
2500 app.undo(vm_ref);
2501 }
2502 AppEvent::RequestRedo => {
2503 app.redo(vm_ref);
2504 }
2505 AppEvent::RequestExport { format, filename } => {
2506 #[cfg(all(
2507 not(target_arch = "wasm32"),
2508 not(target_os = "ios")
2509 ))]
2510 {
2511 crate::native_dialogs::handle_export(
2512 &mut app, vm_ref, &format, &filename,
2513 );
2514 }
2515 }
2516 AppEvent::RequestSave {
2517 filename,
2518 extension,
2519 } => {
2520 #[cfg(all(
2521 not(target_arch = "wasm32"),
2522 not(target_os = "ios")
2523 ))]
2524 {
2525 crate::native_dialogs::handle_save(
2526 &mut app, vm_ref, &filename, &extension,
2527 );
2528 }
2529 }
2530 AppEvent::RequestOpen { extension } => {
2531 #[cfg(all(
2532 not(target_arch = "wasm32"),
2533 not(target_os = "ios")
2534 ))]
2535 {
2536 crate::native_dialogs::handle_open(
2537 &mut app, vm_ref, &extension,
2538 );
2539 }
2540 }
2541 AppEvent::RequestImport { file_types } => {
2542 #[cfg(all(
2543 not(target_arch = "wasm32"),
2544 not(target_os = "ios")
2545 ))]
2546 {
2547 crate::native_dialogs::handle_import(
2548 &mut app,
2549 vm_ref,
2550 &file_types,
2551 );
2552 }
2553 }
2554 _ => {
2555 }
2557 }
2558 }
2559 }
2560 }
2561 }
2562 #[cfg(feature = "ui")]
2563 WindowEvent::ModifiersChanged(new_modifiers) => {
2564 modifiers = new_modifiers;
2565 }
2566 WindowEvent::KeyboardInput { event, .. } => {
2567 use winit::keyboard::{Key, NamedKey};
2568 #[cfg(feature = "ui")]
2569 use winit::keyboard::{KeyCode, PhysicalKey};
2570
2571 let key = match &event.logical_key {
2572 Key::Character(text) => text.to_lowercase(),
2573 Key::Named(NamedKey::ArrowUp) => "up".to_string(),
2574 Key::Named(NamedKey::ArrowDown) => "down".to_string(),
2575 Key::Named(NamedKey::ArrowLeft) => "left".to_string(),
2576 Key::Named(NamedKey::ArrowRight) => "right".to_string(),
2577 Key::Named(NamedKey::Space) => "space".to_string(),
2578 Key::Named(NamedKey::Enter) => "enter".to_string(),
2579 Key::Named(NamedKey::Tab) => "tab".to_string(),
2580 Key::Named(NamedKey::Escape) => "escape".to_string(),
2581 _ => String::new(),
2582 };
2583 if !key.is_empty() {
2584 match event.state {
2585 ElementState::Pressed => app.key_down(vm_ref, &key),
2586 ElementState::Released => app.key_up(vm_ref, &key),
2587 }
2588 }
2589
2590 #[cfg(feature = "ui")]
2591 if event.state == ElementState::Pressed {
2592 if event.physical_key == PhysicalKey::Code(KeyCode::KeyZ) {
2594 #[cfg(target_os = "macos")]
2595 let cmd_pressed = modifiers.state().super_key();
2596 #[cfg(not(target_os = "macos"))]
2597 let cmd_pressed = modifiers.state().control_key();
2598
2599 if cmd_pressed && !modifiers.state().shift_key() {
2600 app.undo(vm_ref);
2602 } else if cmd_pressed && modifiers.state().shift_key() {
2603 app.redo(vm_ref);
2605 }
2606 }
2607 #[cfg(not(target_os = "macos"))]
2609 if event.physical_key == PhysicalKey::Code(KeyCode::KeyY) {
2610 if modifiers.state().control_key() {
2611 app.redo(vm_ref);
2612 }
2613 }
2614 }
2615 }
2616 _ => {}
2617 }
2618 }
2619 }
2620 }
2621 Event::AboutToWait => {
2622 if let (Some(win), Some(vm_ref)) = (window.as_ref(), vm.as_mut()) {
2623 let wants_frame = app.needs_update(vm_ref);
2624 if let Some(dt) = frame_interval {
2625 let next = std::time::Instant::now() + dt;
2626 target.set_control_flow(ControlFlow::WaitUntil(next));
2627 if wants_frame {
2628 win.request_redraw();
2629 }
2630 } else if wants_frame {
2631 target.set_control_flow(ControlFlow::Poll);
2632 win.request_redraw();
2633 } else {
2634 target.set_control_flow(ControlFlow::Wait);
2635 }
2636 }
2637 }
2638 _ => {}
2639 })?;
2640 #[allow(unreachable_code)]
2641 Ok(())
2642}
2643
2644#[cfg(target_arch = "wasm32")]
2645#[cfg(feature = "gpu")]
2646struct WasmRenderCtx {
2647 buffer: Vec<u8>,
2648 width: u32,
2649 height: u32,
2650 canvas: HtmlCanvasElement,
2651 ctx: CanvasRenderingContext2d,
2652 pending_present: bool,
2654}
2655
2656#[cfg(target_arch = "wasm32")]
2657#[cfg(feature = "gpu")]
2658impl WasmRenderCtx {
2659 fn resize(&mut self, width: u32, height: u32) {
2660 if width == 0 || height == 0 {
2661 return;
2662 }
2663 self.width = width;
2664 self.height = height;
2665 self.canvas.set_width(width);
2666 self.canvas.set_height(height);
2667 self.buffer.resize((width * height * 4) as usize, 0);
2668 }
2669}
2670
2671#[cfg(target_arch = "wasm32")]
2672#[cfg(feature = "gpu")]
2673impl SceneVMRenderCtx for WasmRenderCtx {
2674 fn size(&self) -> (u32, u32) {
2675 (self.width, self.height)
2676 }
2677
2678 fn present(&mut self, vm: &mut SceneVM) -> SceneVMResult<RenderResult> {
2679 let mut res = vm.render_frame(&mut self.buffer, self.width, self.height);
2680
2681 if res != RenderResult::Presented {
2683 if let Some(gpu) = vm.gpu.as_mut() {
2684 let ready = gpu.surface.try_finish_download_from_gpu();
2685 if ready {
2686 res = RenderResult::Presented;
2687 }
2688 }
2689 }
2690
2691 let clamped = wasm_bindgen::Clamped(&self.buffer[..]);
2693 let image_data =
2694 web_sys::ImageData::new_with_u8_clamped_array_and_sh(clamped, self.width, self.height)
2695 .map_err(|e| SceneVMError::InvalidOperation(format!("{:?}", e)))?;
2696 self.ctx
2697 .put_image_data(&image_data, 0.0, 0.0)
2698 .map_err(|e| SceneVMError::InvalidOperation(format!("{:?}", e)))?;
2699
2700 self.pending_present = res != RenderResult::Presented;
2701 Ok(res)
2702 }
2703}
2704
2705#[cfg(target_arch = "wasm32")]
2706fn create_or_get_canvas(document: &Document) -> Result<HtmlCanvasElement, JsValue> {
2707 if let Some(existing) = document
2708 .get_element_by_id("canvas")
2709 .and_then(|el| el.dyn_into::<HtmlCanvasElement>().ok())
2710 {
2711 return Ok(existing);
2712 }
2713 let canvas: HtmlCanvasElement = document
2714 .create_element("canvas")?
2715 .dyn_into::<HtmlCanvasElement>()?;
2716 document
2717 .body()
2718 .ok_or_else(|| JsValue::from_str("no body"))?
2719 .append_child(&canvas)?;
2720 Ok(canvas)
2721}
2722
2723#[cfg(target_arch = "wasm32")]
2725#[cfg(feature = "gpu")]
2726pub fn run_scenevm_app<A: SceneVMApp + 'static>(mut app: A) -> Result<(), JsValue> {
2727 let window: WebWindow = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
2728 let document = window
2729 .document()
2730 .ok_or_else(|| JsValue::from_str("no document"))?;
2731 let canvas = create_or_get_canvas(&document)?;
2732
2733 let (width, height) = app.initial_window_size().unwrap_or_else(|| {
2734 let w = window
2735 .inner_width()
2736 .ok()
2737 .and_then(|v| v.as_f64())
2738 .unwrap_or(800.0)
2739 .round() as u32;
2740 let h = window
2741 .inner_height()
2742 .ok()
2743 .and_then(|v| v.as_f64())
2744 .unwrap_or(600.0)
2745 .round() as u32;
2746 (w, h)
2747 });
2748 canvas.set_width(width);
2749 canvas.set_height(height);
2750
2751 let ctx = canvas
2752 .get_context("2d")?
2753 .ok_or_else(|| JsValue::from_str("2d context missing"))?
2754 .dyn_into::<CanvasRenderingContext2d>()?;
2755
2756 let mut vm = SceneVM::new(width, height);
2757 let render_ctx = WasmRenderCtx {
2758 buffer: vec![0u8; (width * height * 4) as usize],
2759 width,
2760 height,
2761 canvas,
2762 ctx,
2763 pending_present: true, };
2765 app.init(&mut vm, (width, height));
2766
2767 let app_rc = Rc::new(RefCell::new(app));
2768 let vm_rc = Rc::new(RefCell::new(vm));
2769 let ctx_rc = Rc::new(RefCell::new(render_ctx));
2770 let first_frame = Rc::new(Cell::new(true));
2771
2772 {
2774 let app = Rc::clone(&app_rc);
2775 let vm = Rc::clone(&vm_rc);
2776 let ctx = Rc::clone(&ctx_rc);
2777 let window_resize = window.clone();
2778 let resize_closure = Closure::<dyn FnMut()>::new(move || {
2779 if let (Ok(w), Ok(h)) = (window_resize.inner_width(), window_resize.inner_height()) {
2780 let w = w.as_f64().unwrap_or(800.0).round() as u32;
2781 let h = h.as_f64().unwrap_or(600.0).round() as u32;
2782 ctx.borrow_mut().resize(w, h);
2783 app.borrow_mut().resize(&mut vm.borrow_mut(), (w, h));
2784 }
2785 });
2786 window
2787 .add_event_listener_with_callback("resize", resize_closure.as_ref().unchecked_ref())?;
2788 resize_closure.forget();
2789 }
2790
2791 {
2793 let app = Rc::clone(&app_rc);
2794 let vm = Rc::clone(&vm_rc);
2795 let canvas = ctx_rc.borrow().canvas.clone();
2796 let down_closure =
2797 Closure::<dyn FnMut(web_sys::PointerEvent)>::new(move |e: web_sys::PointerEvent| {
2798 let rect = canvas.get_bounding_client_rect();
2799 let x = e.client_x() as f64 - rect.left();
2800 let y = e.client_y() as f64 - rect.top();
2801 app.borrow_mut()
2802 .mouse_down(&mut vm.borrow_mut(), x as f32, y as f32);
2803 });
2804 ctx_rc.borrow().canvas.add_event_listener_with_callback(
2805 "pointerdown",
2806 down_closure.as_ref().unchecked_ref(),
2807 )?;
2808 down_closure.forget();
2809 }
2810
2811 {
2813 let app = Rc::clone(&app_rc);
2814 let vm = Rc::clone(&vm_rc);
2815 let ctx = Rc::clone(&ctx_rc);
2816 let first = Rc::clone(&first_frame);
2817 let f = Rc::new(RefCell::new(None::<Closure<dyn FnMut()>>));
2818 let f_clone = Rc::clone(&f);
2819 let window_clone = window.clone();
2820 *f.borrow_mut() = Some(Closure::<dyn FnMut()>::new(move || {
2821 {
2822 let mut app_mut = app.borrow_mut();
2823 let mut vm_mut = vm.borrow_mut();
2824 let ctx_pending = ctx.borrow().pending_present;
2825 let do_render = app_mut.needs_update(&vm_mut) || first.get() || ctx_pending;
2826 if do_render {
2827 first.set(false);
2828 app_mut.update(&mut vm_mut);
2829 app_mut.render(&mut vm_mut, &mut *ctx.borrow_mut());
2830 }
2831 }
2832 let _ = window_clone.request_animation_frame(
2833 f_clone.borrow().as_ref().unwrap().as_ref().unchecked_ref(),
2834 );
2835 }));
2836 let _ =
2837 window.request_animation_frame(f.borrow().as_ref().unwrap().as_ref().unchecked_ref());
2838 }
2839 Ok(())
2840}
2841
2842#[cfg(all(
2846 feature = "gpu",
2847 not(target_arch = "wasm32"),
2848 any(target_os = "macos", target_os = "ios")
2849))]
2850#[unsafe(no_mangle)]
2851pub unsafe extern "C" fn scenevm_ca_create(
2852 layer_ptr: *mut c_void,
2853 width: u32,
2854 height: u32,
2855) -> *mut SceneVM {
2856 if layer_ptr.is_null() {
2857 return std::ptr::null_mut();
2858 }
2859 let vm = SceneVM::new_with_metal_layer(layer_ptr, width, height);
2860 Box::into_raw(Box::new(vm))
2861}
2862
2863#[cfg(all(
2864 feature = "gpu",
2865 not(target_arch = "wasm32"),
2866 any(target_os = "macos", target_os = "ios")
2867))]
2868#[unsafe(no_mangle)]
2869pub unsafe extern "C" fn scenevm_ca_destroy(ptr: *mut SceneVM) {
2870 if ptr.is_null() {
2871 return;
2872 }
2873 unsafe {
2874 drop(Box::from_raw(ptr));
2875 }
2876}
2877
2878#[cfg(all(
2879 feature = "gpu",
2880 not(target_arch = "wasm32"),
2881 any(target_os = "macos", target_os = "ios")
2882))]
2883#[unsafe(no_mangle)]
2884pub unsafe extern "C" fn scenevm_ca_resize(ptr: *mut SceneVM, width: u32, height: u32) {
2885 if let Some(vm) = unsafe { ptr.as_mut() } {
2886 vm.resize_window_surface(width, height);
2887 }
2888}
2889
2890#[cfg(all(
2891 feature = "gpu",
2892 not(target_arch = "wasm32"),
2893 any(target_os = "macos", target_os = "ios")
2894))]
2895#[unsafe(no_mangle)]
2896pub unsafe extern "C" fn scenevm_ca_render(ptr: *mut SceneVM) -> i32 {
2897 if let Some(vm) = unsafe { ptr.as_mut() } {
2898 match vm.render_to_window() {
2899 Ok(RenderResult::Presented) => 0,
2900 Ok(RenderResult::InitPending) => 1,
2901 Ok(RenderResult::ReadbackPending) => 2,
2902 Err(_) => -1,
2903 }
2904 } else {
2905 -1
2906 }
2907}