diff --git a/Cargo.lock b/Cargo.lock index dde3dd39..5a5866ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2750,6 +2750,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "wgpu", "winres", ] diff --git a/Cargo.toml b/Cargo.toml index fcf8392a..8e89ad9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -185,9 +185,16 @@ tracing.workspace = true web-sys = { version = "0.3", features = ["Window"] } +# Enable wgpu's `webgl` feature if Luminol's `webgl` feature is enabled, +# enabling its WebGL backend and disabling its WebGPU backend +[target.'cfg(target_arch = "wasm32")'.dependencies.wgpu] +workspace = true +optional = true +features = ["webgl"] [features] steamworks = ["dep:steamworks", "crc"] +webgl = ["dep:wgpu"] [target.'cfg(windows)'.build-dependencies] winres = "0.1" diff --git a/assets/main.js b/assets/main.js index f14e1a35..0bc1d068 100644 --- a/assets/main.js +++ b/assets/main.js @@ -14,7 +14,38 @@ // // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . -import wasm_bindgen, { luminol_main_start } from '/luminol.js'; -await wasm_bindgen(); -luminol_main_start(); +// Check if the user's browser supports WebGPU +console.log('Checking for WebGPU support in web workers…'); +const worker = new Worker('/webgpu-test-worker.js'); +const promise = new Promise(function (resolve) { + worker.onmessage = function (e) { + resolve(e.data); + }; +}); +worker.postMessage(null); +const gpu = await promise; +worker.terminate(); +if (gpu) { + console.log('WebGPU is supported. Using WebGPU backend if available.'); +} else { + console.log('No support detected. Using WebGL backend if available.'); +} + +// If WebGPU is supported, always use luminol.js +// If WebGPU is not supported, use luminol_webgl.js if it's available or fallback to luminol.js +let fallback = false; +let luminol; +if (gpu) { + luminol = await import('/luminol.js'); +} else { + try { + luminol = await import('/luminol_webgl.js'); + fallback = true; + } catch (e) { + luminol = await import('/luminol.js'); + } +} + +await luminol.default(fallback ? '/luminol_webgl_bg.wasm' : '/luminol_bg.wasm'); +luminol.luminol_main_start(fallback); diff --git a/assets/webgpu-test-worker.js b/assets/webgpu-test-worker.js new file mode 100644 index 00000000..f3fe6fd5 --- /dev/null +++ b/assets/webgpu-test-worker.js @@ -0,0 +1,25 @@ +// Copyright (C) 2023 Lily Lyons +// +// This file is part of Luminol. +// +// Luminol is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Luminol is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Luminol. If not, see . + +onmessage = async function () { + let gpu = false; + try { + let adapter = await navigator.gpu?.requestAdapter(); + gpu = typeof GPUAdapter === 'function' && adapter instanceof GPUAdapter; + } catch (e) {} + postMessage(gpu); +} diff --git a/assets/worker.js b/assets/worker.js index 0df15da6..809d4d31 100644 --- a/assets/worker.js +++ b/assets/worker.js @@ -14,11 +14,12 @@ // // You should have received a copy of the GNU General Public License // along with Luminol. If not, see . -import wasm_bindgen, { luminol_worker_start } from '/luminol.js'; onmessage = async function (e) { - if (e.data[0] === 'init') { - await wasm_bindgen(undefined, e.data[1]); - await luminol_worker_start(e.data[2]); - } + const [fallback, memory, canvas] = e.data; + + const luminol = await import(fallback ? '/luminol_webgl.js' : '/luminol.js'); + + await luminol.default(fallback ? '/luminol_webgl_bg.wasm' : '/luminol_bg.wasm', memory); + await luminol.luminol_worker_start(canvas); }; diff --git a/crates/graphics/src/sprite/graphic.rs b/crates/graphics/src/sprite/graphic.rs index 0d5fabfb..83acd179 100644 --- a/crates/graphics/src/sprite/graphic.rs +++ b/crates/graphics/src/sprite/graphic.rs @@ -36,6 +36,7 @@ struct Data { hue: f32, opacity: f32, opacity_multiplier: f32, + _padding: u32, } impl Graphic { @@ -51,6 +52,7 @@ impl Graphic { hue, opacity, opacity_multiplier: 1., + _padding: 0, }; let uniform = diff --git a/crates/graphics/src/sprite/shader.rs b/crates/graphics/src/sprite/shader.rs index 1414674b..5004e89a 100644 --- a/crates/graphics/src/sprite/shader.rs +++ b/crates/graphics/src/sprite/shader.rs @@ -62,7 +62,7 @@ fn create_shader( }, wgpu::PushConstantRange { stages: wgpu::ShaderStages::FRAGMENT, - range: 64..(64 + 4 + 4 + 4), + range: 64..(64 + 16), }, ], }) diff --git a/crates/graphics/src/sprite/sprite.wgsl b/crates/graphics/src/sprite/sprite.wgsl index c4f222c0..f3d4f04e 100644 --- a/crates/graphics/src/sprite/sprite.wgsl +++ b/crates/graphics/src/sprite/sprite.wgsl @@ -17,6 +17,7 @@ struct Graphic { hue: f32, opacity: f32, opacity_multiplier: f32, + _padding: u32, } #if USE_PUSH_CONSTANTS == true diff --git a/crates/graphics/src/tiles/autotiles.rs b/crates/graphics/src/tiles/autotiles.rs index e3fc6535..4821ac83 100644 --- a/crates/graphics/src/tiles/autotiles.rs +++ b/crates/graphics/src/tiles/autotiles.rs @@ -30,12 +30,14 @@ struct AutotilesUniform { bind_group: wgpu::BindGroup, } -#[repr(C)] +#[repr(C, align(16))] #[derive(Copy, Clone, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] struct Data { + autotile_frames: [u32; 7], + _array_padding: u32, ani_index: u32, autotile_region_width: u32, - autotile_frames: [u32; 7], + _end_padding: u64, } impl Autotiles { @@ -48,6 +50,8 @@ impl Autotiles { autotile_frames: atlas.autotile_frames, autotile_region_width: atlas.autotile_width, ani_index: 0, + _array_padding: 0, + _end_padding: 0, }; let uniform = @@ -56,9 +60,7 @@ impl Autotiles { &wgpu::util::BufferInitDescriptor { label: Some("tilemap autotile buffer"), contents: bytemuck::cast_slice(&[autotiles]), - usage: wgpu::BufferUsages::STORAGE - | wgpu::BufferUsages::COPY_DST - | wgpu::BufferUsages::UNIFORM, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, }, ); let bind_group = graphics_state.render_state.device.create_bind_group( @@ -120,7 +122,7 @@ pub fn create_bind_group_layout(render_state: &egui_wgpu::RenderState) -> wgpu:: binding: 0, visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, + ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None, }, diff --git a/crates/graphics/src/tiles/mod.rs b/crates/graphics/src/tiles/mod.rs index 57f86825..e3ff17d5 100644 --- a/crates/graphics/src/tiles/mod.rs +++ b/crates/graphics/src/tiles/mod.rs @@ -82,7 +82,7 @@ impl Tiles { #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] struct VertexPushConstant { viewport: [u8; 64], - autotiles: [u8; 36], + autotiles: [u8; 48], } render_pass.push_debug_group("tilemap tiles renderer"); @@ -113,7 +113,7 @@ impl Tiles { if self.use_push_constants { render_pass.set_push_constants( wgpu::ShaderStages::FRAGMENT, - 64 + 36, + 64 + 48, bytemuck::bytes_of::(&opacity), ); } diff --git a/crates/graphics/src/tiles/shader.rs b/crates/graphics/src/tiles/shader.rs index 653b4220..6e7af927 100644 --- a/crates/graphics/src/tiles/shader.rs +++ b/crates/graphics/src/tiles/shader.rs @@ -65,12 +65,12 @@ pub fn create_render_pipeline( // Viewport + Autotiles wgpu::PushConstantRange { stages: wgpu::ShaderStages::VERTEX, - range: 0..(64 + 36), + range: 0..(64 + 48), }, // Fragment wgpu::PushConstantRange { stages: wgpu::ShaderStages::FRAGMENT, - range: (64 + 36)..(64 + 36 + 4), + range: (64 + 48)..(64 + 48 + 4), }, ], }) diff --git a/crates/graphics/src/tiles/tilemap.wgsl b/crates/graphics/src/tiles/tilemap.wgsl index 07cbf26a..e00b12a9 100644 --- a/crates/graphics/src/tiles/tilemap.wgsl +++ b/crates/graphics/src/tiles/tilemap.wgsl @@ -21,9 +21,9 @@ struct Viewport { } struct Autotiles { + frame_counts: array, 2>, animation_index: u32, max_frame_count: u32, - frame_counts: array } #if USE_PUSH_CONSTANTS == true @@ -37,7 +37,7 @@ var push_constants: PushConstants; @group(1) @binding(0) var viewport: Viewport; @group(2) @binding(0) -var autotiles: Autotiles; +var autotiles: Autotiles; @group(3) @binding(0) var opacity: array, 1>; #endif @@ -89,12 +89,13 @@ fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VertexOutput { } if is_autotile { + let autotile_type = instance.tile_id / 48 - 1; // we get an error about non constant indexing without this. // not sure why #if USE_PUSH_CONSTANTS == true - let frame_count = push_constants.autotiles.frame_counts[instance.tile_id / 48 - 1]; + let frame_count = push_constants.autotiles.frame_counts[autotile_type / 4][autotile_type % 4]; #else - let frame_count = autotiles.frame_counts[instance.tile_id / 48 - 1]; + let frame_count = autotiles.frame_counts[autotile_type / 4][autotile_type % 4]; #endif let frame = autotiles.animation_index % frame_count; diff --git a/crates/graphics/src/viewport.rs b/crates/graphics/src/viewport.rs index b7667b25..e70a0156 100644 --- a/crates/graphics/src/viewport.rs +++ b/crates/graphics/src/viewport.rs @@ -42,9 +42,7 @@ impl Viewport { &wgpu::util::BufferInitDescriptor { label: Some("tilemap viewport buffer"), contents: bytemuck::cast_slice(&[proj]), - usage: wgpu::BufferUsages::STORAGE - | wgpu::BufferUsages::COPY_DST - | wgpu::BufferUsages::UNIFORM, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, }, ); let bind_group = graphics_state.render_state.device.create_bind_group( diff --git a/crates/web/src/web_worker_runner.rs b/crates/web/src/web_worker_runner.rs index a6bce979..9780ead0 100644 --- a/crates/web/src/web_worker_runner.rs +++ b/crates/web/src/web_worker_runner.rs @@ -113,7 +113,6 @@ struct WebWorkerRunnerState { } /// A runner for wgpu egui applications intended to be run in a web worker. -/// Currently only targets WebGPU, not WebGL. #[derive(Clone)] pub struct WebWorkerRunner { state: std::rc::Rc>, @@ -452,15 +451,15 @@ impl WebWorkerRunner { ) }; + // Create texture to render onto + // This variable needs to live for the entire remaining duration we use + // `state.render_state` or WebGL will break + let render_texture = state.surface.get_current_texture().unwrap(); + // Execute egui's render pass { let renderer = state.render_state.renderer.read(); - let view = state - .surface - .get_current_texture() - .unwrap() - .texture - .create_view(&Default::default()); + let view = render_texture.texture.create_view(&Default::default()); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, @@ -495,7 +494,7 @@ impl WebWorkerRunner { .into_iter() .chain(std::iter::once(encoder.finish())), ); - state.surface.get_current_texture().unwrap().present(); + render_texture.present(); self.time_lock.store( bindings::performance(&worker).now() / 1000. diff --git a/index.html b/index.html index d90251c9..edd3bc29 100644 --- a/index.html +++ b/index.html @@ -24,6 +24,7 @@ + diff --git a/src/main.rs b/src/main.rs index 1fc26dcc..3c774f87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,7 +181,7 @@ static WORKER_DATA: parking_lot::RwLock> = parking_lot::RwLoc #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub fn luminol_main_start() { +pub fn luminol_main_start(fallback: bool) { let (panic_tx, mut panic_rx) = flume::unbounded::<()>(); wasm_bindgen_futures::spawn_local(async move { @@ -205,7 +205,11 @@ pub fn luminol_main_start() { })); // Redirect tracing to console.log and friends: - tracing_wasm::set_as_global_default(); + tracing_wasm::set_as_global_default_with_config( + tracing_wasm::WASMLayerConfigBuilder::new() + .set_max_level(tracing::Level::INFO) + .build(), + ); // Redirect log (currently used by egui) to tracing tracing_log::LogTracer::init().expect("failed to initialize tracing-log"); @@ -260,7 +264,7 @@ pub fn luminol_main_start() { .expect("failed to spawn web worker"); let message = js_sys::Array::new(); - message.push(&JsValue::from("init")); + message.push(&JsValue::from(fallback)); message.push(&wasm_bindgen::memory()); message.push(&offscreen_canvas); let transfer = js_sys::Array::new();