WebGL2/wasm/Android: GLES buffer, texture, and fence fixes; GLES/Vulkan + egui robustness#353
WebGL2/wasm/Android: GLES buffer, texture, and fence fixes; GLES/Vulkan + egui robustness#353ohsalmeron wants to merge 3 commits into
Conversation
Multiple fixes and improvements across graphics backends and egui integration: - blade-egui: Replace raw vertex storage with a proper vertex input layout (pos/uv/color), add egui vertex VertexLayout and VertexFetchState, bind vertex buffers, add textures_to_free queue and sync() method, and move texture free logic into after_submit. Update WGSL shader to use vec2 inputs and simplify gamma conversion with mix. - GLES: PassEncoder now carries target_size; scissor rect Y is inverted to match GL window coordinates. Use integer enums for integer texture formats, set texture wrap to CLAMP_TO_EDGE by default. Pipeline now binds attribute locations (including an e_ prefixed name) and adds robust uniform-block lookup with fallbacks and logging of available blocks. EGL platform selection improved with error handling and fallback to default when surfaceless fails. - Vulkan: Make debug utils optional and only enable the extension when available; guard debug utils calls, conditionally create device debug utils, and log adapter inspection rejections. Ensure debug object naming is invoked only when extension is present. - Shader processing: Only replace module types when bindings were actually modified and ensure attributes have bindings assigned or assert appropriately. - Utility: Add BufferBelt::sync to flush active buffers to the GPU. These changes improve robustness, platform compatibility, and correctness of vertex/texture handling across backends.
Add WebAssembly-specific handling and performance fixes for GLES backend: - Introduce a separate raw_index buffer for ELEMENT_ARRAY_BUFFER on wasm and create/bind/upload/delete it alongside ARRAY_BUFFER to satisfy WebGL requirements. - Use raw_index when binding index buffers on wasm builds. - Avoid setting UNPACK_ROW_LENGTH when row length equals width (prevents Firefox defensive copy); restore UNPACK_ROW_LENGTH to 0 after operations. - On texture unit binding for wasm, explicitly unbind other texture targets when binding a target to avoid stale bindings. - Adjust client_wait_sync timeout handling for WebGL2: use 0 on wasm (blocking forbidden) and clamp non-wasm timeouts to MAX_TIMEOUT. - Improve shader texture binding: track assigned texture slots and set uniform once (avoid repeatedly querying uniform slot values). - Set canvas element size from config in web backend (lookup element with id "blade" and set width/height). These changes improve WebGL compatibility, avoid browser-specific performance pitfalls, and ensure correct buffer/texture behavior on wasm targets.
kvark
left a comment
There was a problem hiding this comment.
Looks to be very well thought, thank you for filing!
| target, | ||
| } => { | ||
| gl.active_texture(glow::TEXTURE0 + slot); | ||
| #[cfg(target_arch = "wasm32")] |
There was a problem hiding this comment.
can we turn this into a normal if condition, e.g. if cfg!(target_arch = "wasm32") {...}
| ptr::null_mut(), | ||
| &display_attributes, | ||
| ) | ||
| .unwrap(), |
There was a problem hiding this comment.
this was actually intentional: we don't expect it to fail, and we do want to see the error, not just skip over it
| ); | ||
| match d { | ||
| Ok(display) => (display, None), | ||
| Err(e) => { |
| #[cfg(target_arch = "wasm32")] | ||
| let timeout_ns_i32 = 0i32; | ||
| #[cfg(not(target_arch = "wasm32"))] | ||
| let timeout_ns_i32 = { |
There was a problem hiding this comment.
let's prefer run-time checks over compile time: more compilation code coverage
| Tf::R32Uint => (glow::R32UI, glow::RED, glow::UNSIGNED_INT), | ||
| Tf::Rg32Uint => (glow::RG32UI, glow::RG, glow::UNSIGNED_INT), | ||
| Tf::Rgba32Uint => (glow::RGBA32UI, glow::RGBA, glow::UNSIGNED_INT), | ||
| Tf::R32Uint => (glow::R32UI, glow::RED_INTEGER, glow::UNSIGNED_INT), |
|
|
||
| let location = attributes.len() as u32; | ||
| gl.bind_attrib_location(program, location, attrib_name); | ||
| gl.bind_attrib_location(program, location, &format!("e_{}", attrib_name)); |
There was a problem hiding this comment.
what's happening here? why are we binding it twice?
| gl.get_uniform_i32(program, location, &mut slots); | ||
| targets.push(slots[0] as u32); | ||
| let slot = *texture_slots | ||
| .entry(glsl_name.to_string()) |
There was a problem hiding this comment.
when would this happen? is this about using the same texture in multiple stages?
| let mut index_opt = gl.get_uniform_block_index(program, glsl_name); | ||
| if index_opt.is_none() { | ||
| if let Some(ref type_name) = sf.shader.module.types[var.ty].name | ||
| { |
| } | ||
| } | ||
| if index_opt.is_none() { | ||
| let type_name = format!("type_{}", var.ty.index()); |
| pub struct Buffer { | ||
| raw: glow::Buffer, | ||
| #[cfg(target_arch = "wasm32")] | ||
| raw_index: glow::Buffer, |
There was a problem hiding this comment.
This is really unfortunate. It makes all of the buffers on WebGL less efficient, just because there is one quirk in the spec.
What do you think of a counter-proposal: we just assert that the user isn't hitting this path on webgl but keep the implementation clean? that would lift the burden to the user.
Remove redundant unsafe blocks and clean up Metal API usage across the Metal backend. Changes touch command.rs, mod.rs, pipeline.rs and surface.rs: create_* and new() calls no longer wrapped in unsafe, autoreleasepool closures simplified, render layer configuration unwrapped, and vertex/attribute descriptor handling reformatted. The MTLCounterSampleBufferDescriptor sample count is still set inside an unsafe block where required. These edits improve safety/readability without changing runtime behavior.
Problem
Running Blade on WebGL2 / wasm hit spec and browser constraints: the same
WebGLBuffercannot be bound as bothARRAY_BUFFERandELEMENT_ARRAY_BUFFER;clientWaitSynccannot block on the main thread; texture unit / unpack state could produce wrong sampling or extra copies (e.g. Firefox). On GLES, legacy drivers needed safer uniform-block lookup, correct scissor/wrap/format handling, and explicit buffer sync. blade-egui needed a proper vertex layout/fetch path and deferred texture destruction after submit. Vulkan debug utils were assumed present.What changed
client_wait_synctimeout0on wasm (non-wasm clamped toMAX_CLIENT_WAIT_TIMEOUT_WEBGL), texture target unbind hygiene,UNPACK_ROW_LENGTHonly when needed (restored to 0), shader texture slots tracked and set once.CLAMP_TO_EDGE, attribute location binding (incl.e_prefix), uniform-block reflection fallbacks + logging,BufferBelt::sync, EGL platform fallback when surfaceless init fails.sync()after submit.VK_EXT_debug_utilsonly when the extension exists; guard naming calls.Testing
wasm32-unknown-unknown— Chrome / Firefox WebGL2Note
The web backend sets the canvas pixel size from config (element id
"blade"in our tree). Happy to make that id/config upstream-friendly if you want a different default.