forked from 1b-agent/opencadstudio-upstream
fix(web): grow-only viewport textures to stop Windows-Firefox divider-drag freeze (#191)
Dragging a model-tile pane_grid divider changed each pane's pixel size every frame, and `ensure_depth_texture` recreated the depth + 4×MSAA + resolve textures on any size delta. On Windows Firefox (WebGL2 → ANGLE → D3D11) this per-frame texture realloc hangs the D3D driver — the same hang class as the native DX12 lock in #120. Linux Firefox (native GL) and Chromium/Edge tolerate the churn, which is why the freeze is specific to Windows Firefox. Round the depth/MSAA/resolve allocation up to a 128px grid and only recreate when the rounded size changes, so a live drag reallocates at most once per 128px instead of every frame. Geometry passes still render at the real size into the [0,0,render] corner of the larger texture, and the blit UV is scaled by render/alloc so it samples exactly the filled region — visually a no-op on every platform. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
b3c8c0a2f1
commit
54a29409e7
1 file changed
+44
-6
@@ -90,7 +90,16 @@ pub struct Pipeline {
|
||||
/// analogue of the selected-wire xray overlay). Rebuilt on selection change.
|
||||
text_highlight_vbuf: Option<wgpu::Buffer>,
|
||||
text_highlight_vcount: u32,
|
||||
/// Last requested render size (the full viewport rect, in pixels). The
|
||||
/// geometry passes render at this size; the blit UV is scaled by
|
||||
/// `depth_texture_size / alloc_size` so it samples only the filled region.
|
||||
depth_texture_size: Size<u32>,
|
||||
/// Actual allocated size of the depth / MSAA / resolve textures. Rounded
|
||||
/// up from the requested size to a coarse grid so a divider drag (which
|
||||
/// changes the pane size a few pixels every frame) doesn't recreate these
|
||||
/// textures every frame. Recreation is what hangs the ANGLE → D3D11 path
|
||||
/// on Windows-Firefox (issue #191); grow-only bucketing makes it rare.
|
||||
alloc_size: Size<u32>,
|
||||
depth_view: wgpu::TextureView,
|
||||
/// 4× MSAA color buffer for the main drawing passes.
|
||||
msaa_view: wgpu::TextureView,
|
||||
@@ -1167,6 +1176,9 @@ impl Pipeline {
|
||||
hatch_batched_bgl1,
|
||||
image_bgl1,
|
||||
depth_texture_size: Size::new(1, 1),
|
||||
// (0, 0) forces the first `ensure_depth_texture` to allocate at the
|
||||
// real rounded size — the constructor textures above are placeholders.
|
||||
alloc_size: Size::new(0, 0),
|
||||
depth_view,
|
||||
msaa_view,
|
||||
resolve_view,
|
||||
@@ -1630,10 +1642,21 @@ impl Pipeline {
|
||||
/// Write the blit shader's UV crop uniform. Call in `prepare` (the only
|
||||
/// place with a `&Queue`) — `render` then just submits the draw call.
|
||||
pub fn upload_blit_uv(&self, queue: &wgpu::Queue, uv_offset: [f32; 2], uv_scale: [f32; 2]) {
|
||||
// The geometry passes render at `depth_texture_size` into the top-left
|
||||
// corner of the (possibly larger) `alloc_size` resolve texture, so the
|
||||
// rendered image occupies UV [0, render/alloc]. Scale the incoming crop
|
||||
// — computed in full-image-normalized units — down into that region.
|
||||
let fx = self.depth_texture_size.width as f32 / self.alloc_size.width.max(1) as f32;
|
||||
let fy = self.depth_texture_size.height as f32 / self.alloc_size.height.max(1) as f32;
|
||||
queue.write_buffer(
|
||||
&self.blit_uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[uv_offset[0], uv_offset[1], uv_scale[0], uv_scale[1]]),
|
||||
bytemuck::cast_slice(&[
|
||||
uv_offset[0] * fx,
|
||||
uv_offset[1] * fy,
|
||||
uv_scale[0] * fx,
|
||||
uv_scale[1] * fy,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2255,9 +2278,11 @@ impl Pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resolve MSAA → clip-sized resolve texture ─────────────────────
|
||||
// Both msaa_view and resolve_view are sized to clip_bounds, so the
|
||||
// resolve does NOT touch any pixels outside the shader widget's area.
|
||||
// ── Resolve MSAA → resolve texture ────────────────────────────────
|
||||
// Both are the same (rounded `alloc_size`) offscreen texture, so the
|
||||
// resolve never touches the surface. Only the drawn [0, render_size]
|
||||
// corner holds content; the rounded border stays the cleared bg color
|
||||
// and is never sampled (the blit UV is scaled to render/alloc).
|
||||
{
|
||||
let _resolve = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("msaa.resolve_pass"),
|
||||
@@ -2314,7 +2339,12 @@ impl Pipeline {
|
||||
}
|
||||
|
||||
pub fn ensure_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
|
||||
if self.depth_texture_size != size {
|
||||
// Record the requested render size every frame (the blit UV scale reads
|
||||
// it); only reallocate the textures when the *rounded* size changes.
|
||||
self.depth_texture_size = size;
|
||||
let alloc = Size::new(round_up_tex(size.width), round_up_tex(size.height));
|
||||
if self.alloc_size != alloc {
|
||||
let size = alloc;
|
||||
let depth_tex = create_depth_texture(device, size);
|
||||
self.depth_view = depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let msaa_tex = create_msaa_texture(device, size, self.surface_format);
|
||||
@@ -2340,11 +2370,19 @@ impl Pipeline {
|
||||
],
|
||||
});
|
||||
self.resolve_view = resolve_view;
|
||||
self.depth_texture_size = size;
|
||||
self.alloc_size = alloc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Round a texture dimension up to a coarse grid so a live divider drag
|
||||
/// doesn't recreate the depth / MSAA / resolve textures on every frame. See
|
||||
/// `alloc_size` — this is the mitigation for the Windows-Firefox freeze (#191).
|
||||
fn round_up_tex(n: u32) -> u32 {
|
||||
const GRID: u32 = 128;
|
||||
((n.max(1) + GRID - 1) / GRID) * GRID
|
||||
}
|
||||
|
||||
/// Project a mesh's `world_aabb` and pick a LOD slot:
|
||||
/// diagonal > 200 px → 0 (HIGH)
|
||||
/// diagonal 50–200 px → 1 (MID)
|
||||
|
||||
Reference in new issue
Block a user