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