fix(web): use adapter limits (Functionality) so /play renders at native res

The 2048 surface limit was never wgpu's or the GPU's — it's
downlevel_webgl2_defaults().max_texture_dimension_2d (2048), which
WgpuSettingsPriority::WebGL2 forces. Switch to Functionality: on the WebGL2
(Gl) backend Bevy then adopts the adapter's real limits, which are still
WebGL2-constrained for features/buffers (shaders stay GLES-compatible) but
report the GPU's true max texture dimension (e.g. 16384). The device is
requested with exactly what the adapter offers, so creation can't fail, the
surface is no longer capped, and large viewports (4K) render with no letterbox
and no hardcoded cap.

Removes the resize_constraints cap and the play.html max-width/height caps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-24 09:18:07 -07:00
parent 56e05caaa9
commit 81893788c1
2 changed files with 23 additions and 43 deletions
+4 -17
View File
@@ -7,23 +7,10 @@
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; background: #000; overflow: hidden; }
/* Cap the canvas at WebGL2's 2048 max texture dimension (this mirrors
the Window `resize_constraints` in solitaire_web/src/lib.rs — winit
applies that constraint as the canvas's own max-width/max-height).
On wasm Bevy creates the device with downlevel_webgl2_defaults()
whose max_texture_dimension_2d is a fixed 2048, so a larger surface
(e.g. a 4K display at 150% scale → a 2560x1440 logical viewport)
makes Surface::configure panic. `max-*` directly on the canvas keeps
the surface ≤ 2048 regardless of how fit_canvas_to_parent resolves
its 100% width; `margin: 0 auto` centres the letterbox horizontally. */
#bevy-canvas {
display: block;
width: 100%;
height: 100%;
max-width: 2048px;
max-height: 2048px;
margin: 0 auto;
}
/* No size cap: the wgpu device now takes its max_texture_dimension from
the adapter (see solitaire_web/src/lib.rs), so the surface can match
the full viewport. fit_canvas_to_parent sizes the canvas to 100%. */
#bevy-canvas { display: block; width: 100%; height: 100%; }
</style>
</head>
<body>
+19 -26
View File
@@ -13,7 +13,7 @@ use bevy::asset::AssetMetaCheck;
use bevy::prelude::*;
use bevy::render::RenderPlugin;
use bevy::render::settings::{RenderCreation, WgpuSettings, WgpuSettingsPriority};
use bevy::window::{Window, WindowPlugin, WindowResizeConstraints, WindowResolution};
use bevy::window::{Window, WindowPlugin, WindowResolution};
use solitaire_data::LocalOnlyProvider;
use solitaire_engine::CoreGamePlugin;
use wasm_bindgen::prelude::*;
@@ -34,27 +34,15 @@ pub fn start() {
fit_canvas_to_parent: true,
// Prevent the browser stealing keyboard events and scroll.
prevent_default_event_handling: true,
// Force scale_factor = 1.0 so the wgpu surface is sized in
// CSS/logical pixels rather than physical pixels. Without this,
// HiDPI displays (devicePixelRatio ≥ 2) produce a framebuffer
// whose physical width can exceed WebGL2's 2048-pixel per-
// dimension limit, causing a wgpu validation panic on the first
// resize event and killing the WASM thread.
// Render at CSS/logical pixels (scale_factor 1.0) rather
// than physical (CSS × devicePixelRatio). This keeps the
// surface smaller on HiDPI displays — lighter GPU load and
// stable sizing — at the cost of some crispness. The wgpu
// texture-dimension limit is now taken from the adapter (see
// the RenderPlugin below), so this is purely a quality/perf
// choice, no longer a crash-avoidance hack.
resolution: WindowResolution::default()
.with_scale_factor_override(1.0),
// Cap the surface at WebGL2's max texture dimension (2048).
// On wasm Bevy creates the device with
// `Limits::downlevel_webgl2_defaults()` (max_texture_dimension_2d
// = 2048), so a larger surface — e.g. a 4K display at 150% scale
// gives a 2560x1440 logical viewport — makes Surface::configure
// panic on the first frame. winit maps this constraint to the
// canvas's `max-width`/`max-height` style, so the surface can
// never exceed it. Viewports wider/taller than 2048 letterbox.
resize_constraints: WindowResizeConstraints {
max_width: 2048.0,
max_height: 2048.0,
..default()
},
..default()
}),
..default()
@@ -66,14 +54,19 @@ pub fn start() {
meta_check: AssetMetaCheck::Never,
..default()
})
// WebGL2 priority constrains naga (the shader translator) to emit
// GLES 300es-compatible GLSL. Without this, Chromium's ANGLE driver
// rejects certain shader constructs (storage buffers, tight component
// limits) causing a fatal wgpu "Shader translation error". Firefox is
// more lenient; this setting makes both browsers work identically.
// `Functionality` makes wgpu adopt the *adapter's* real limits
// instead of the conservative `downlevel_webgl2_defaults()` that
// `WebGL2` priority forces. On the WebGL2 (Gl) backend the adapter
// already reports WebGL2-constrained features/limits — no storage
// buffers, etc., so shaders stay GLES-compatible on both Firefox and
// Chromium — but it reports the GPU's *true* `max_texture_dimension`
// (e.g. 16384) rather than 2048. The device is requested with exactly
// what the adapter offers, so creation can't fail, and the surface is
// no longer capped at 2048: large viewports (4K, etc.) render natively
// with no letterbox and no hardcoded cap.
.set(RenderPlugin {
render_creation: RenderCreation::Automatic(WgpuSettings {
priority: WgpuSettingsPriority::WebGL2,
priority: WgpuSettingsPriority::Functionality,
..default()
}),
..default()