v2.20.0

x402-foundation/x402v2.20.0Jun 23, 2026by mvaligursky

AI Summary

Major update introducing WebGPU transient attachments, HTML-in-Canvas texture support, and removing the compute renderer. Includes breaking changes to gsplat LOD range management and significant component architecture refactoring.

Key Highlights

  • WebGPU transient (memoryless) attachments support
  • HTML-in-Canvas texture support via copyElementImageToTexture
  • GSplat AA support in hybrid renderer
  • Breaking: Move gsplat lodRangeMin/lodRangeMax to GSplatComponent
  • Remove compute renderer, promote GPU sort to stable

Breaking Changes

  • Move gsplat lodRangeMin/lodRangeMax to GSplatComponent
  • World-space PCSS penumbra changes
  • Remove compute renderer
  • Drop 'unified' framing (now default)

New Features

  • Automatic bind-group reflection for compute shaders
  • Stereo XR support for hybrid GPU-sort gsplat renderer
  • Foveated contribution culling for GPU-sort renderer
  • Procedural sky ESM script with day/night cycle
  • Draco mesh decoding in Node via worker_threads

Full Release Notes

## Breaking changes

* [BREAKING] Move gsplat lodRangeMin/lodRangeMax to GSplatComponent by @mvaligursky in #8908

## Changes

* Drop "unified" framing now that it's the default by @mvaligursky in #8805
* Support GSPLAT_AA in the hybrid gsplat renderer by @mvaligursky in #8807
* Support HTML-in-Canvas textures via copyElementImageToTexture (WebGPU) by @mvaligursky in #8810
* Replace opaque any/* types with real types in API reference by @willeastcott in #8811
* Elevate the Entity class reference to peak quality by @willeastcott in #8814
* Support transient (memoryless) attachments (WebGPU) by @mvaligursky in #8816
* [BREAKING] World-space PCSS penumbra, plus gsplat & cascade fixes by @mvaligursky in #8818
* Automatic bind-group reflection for compute shaders (WebGPU) by @mvaligursky in #8821
* Apply render-stage modify hooks in the hybrid gsplat renderer by @mvaligursky in #8822
* Move test fixture server off port 3000 and use relative asset URLs by @mvaligursky in #8823
* Add post-build tests for ESM/UMD bundles and API surface gate by @kpal81xd in #8845
* Comment the public API diff on PRs (informative) by @kpal81xd in #8848
* Add MeshInstance.shaderPassMask to control shader pass participation by @mvaligursky in #8852
* Add stereo XR support to hybrid GPU-sort gsplat renderer by @mvaligursky in #8861
* Reduce API report comment noise by @kpal81xd in #8863
* Warn and fail GLB loading when Draco decoder is unavailable by @mvaligursky in #8868
* Add foveated contribution culling to the GPU-sort gsplat renderer by @mvaligursky in #8869
* Align ButtonComponent with CameraComponent architecture by @willeastcott in #8870
* Align CollisionComponent with CameraComponent architecture by @willeastcott in #8871
* Add gsplatModifyPS hook for gsplat fragment color customization by @mvaligursky in #8872
* Align ParticleSystemComponent with CameraComponent architecture by @willeastcott in #8873
* Eliminate ComponentData classes and engine schema usage by @willeastcott in #8876
* Share camera property list between init and clone by @willeastcott in #8878
* Drive cloneComponent from shared _properties lists by @willeastcott in #8879
* Hoist property lists in model, sound and rigid-body components by @willeastcott in #8880
* Share animation property list between init and clone by @willeastcott in #8881
* Standardize beforeremove handler naming by @willeastcott in #8882
* Drop shape from shared collision properties, modernize initialize/clone by @willeastcott in #8883
* Remove dead falsy guards in collision shape implementations by @willeastcott in #8884
* Standardize component teardown method to onBeforeRemove by @willeastcott in #8885
* Deduplicate rigid body / trigger rebuild in collision by @willeastcott in #8886
* Add user varying streams to gsplat shader customization by @mvaligursky in #8887
* Correct JSDoc visibility tags on internal collision methods by @willeastcott in #8888
* Promote JointComponent to public API with typed joints by @willeastcott in #8891
* Split rigid-body data classes into separate modules by @willeastcott in #8897
* Cover reparenting an element after its screen is destroyed by @willeastcott in #8899
* Remove dead and broken GamePads previous getter by @willeastcott in #8900
* Source geometry from work buffer for SOG spherical harmonics updates by @mvaligursky in #8902
* Move XR per-view data from the XR manager to the Camera (RenderView) by @mvaligursky in #8907
* Apply gsplat scene settings in applySceneSettings by @mvaligursky in #8909
* Allow ArrayBuffer in StorageBuffer.write data type by @mvaligursky in #8911
* Update @webgpu/types to 0.1.70 and migrate deprecated type names by @mvaligursky in #8912
* Extract GSplatWorld from GSplatManager by @mvaligursky in #8916
* Document transient attachment validation invariants by @mvaligursky in #8918
* Decouple gsplat streaming from rendering for autoRender=false by @mvaligursky in #8919
* Remove unused JSDoc @import lines from sound and rigid-body components by @mmjinglin163 in #8920
* Remove the gsplat compute renderer by @mvaligursky in #8921
* Report minified bundle size changes on PRs by @mvaligursky in #8922
* Move GPU sort/cull pipeline into the hybrid gsplat renderer by @mvaligursky in #8924
* Use lossless RGBA32U fallback instead of pack8 for particles by @mvaligursky in #8926
* Drop redundant particle material reset on camera change by @mvaligursky in #8928
* Throttle concurrent asset requests by @mvaligursky in #8929
* Support dynamic cookie textures with clustered lighting by @mvaligursky in #8930
* Cast directional shadows from the hybrid gsplat renderer by @mvaligursky in #8933
* Apply vertex-modify to hybrid cast shadows by @mvaligursky in #8937
* Remove highQualitySH option, always use high-quality per-splat SH by @mvaligursky in #8938
* Scalable per-splat culling for hybrid gsplat shadows by @mvaligursky in #8939
* Remove compute-renderer leftovers by @mvaligursky in #8940
* Auto-select GPU sort on WebGPU and promote it to stable by @mvaligursky in #8941
* Procedural sky ESM script with day/night cycle and dynamic IBL by @mvaligursky in #8943
* Backend-aware WebXR support detection (isDeviceSupported) by @mvaligursky in #8948
* Remove fragile WebGL mipmap-based radix sort by @mvaligursky in #8949
* Support Draco mesh decoding in Node via worker_threads by @mvaligursky in #8951
* Make app.assets available immediately after AppBase construction by @mvaligursky in #8952

## Fixes

* Sort worker ReferenceError after esbuild migration by @mvaligursky in #8808
* Null particle module-scope vars after construction to prevent memory leak by @Skullheadx in #8809
* Fix @param names/types that contradict the function signatures by @willeastcott in #8813
* Expose mesh-level shader defines to the fragment shader by @mvaligursky in #8817
* Update HTML-in-Canvas texture upload to new copyElementImageToTexture/texElementImage2D API by @mvaligursky in #8828
* Apply gsplat material changes made in frame:ready the same frame by @mvaligursky in #8830
* Fix ESM script registration by name and subclass name collision by @mvaligursky in #8831
* Ensure DoF effect is resolution independent (#8833) by @MAG-AdrianMeredith in #8834
* Use a Map for the script registry and guard reserved names in add() by @mvaligursky in #8835
* Preserve live bindings of mutable UMD exports (pc.app) by @mvaligursky in #8837
* Keep UMD exports overridable while preserving live bindings by @mvaligursky in #8839
* Tile gsplat interval-scatter dispatch over workgroup limit by @mvaligursky in #8841
* Register nameless scripts under their verbatim class name by @mvaligursky in #8844
* Remove inline {@link} from camera-frame LUT attribute description by @mvaligursky in #8849
* Fix XrNavigation NaN rig corruption on gamepads without thumbstick axes by @mvaligursky in #8850
* Select scene depth decode via CAPS_TEXTURE_FLOAT_RENDERABLE by @mvaligursky in #8856
* Temporarily disable WebGPU on Imagination PowerVR GPUs (Pixel 10) by @mvaligursky in #8875
* Fix XR over-culling: correct combined frustum and per-eye gsplat visibility by @mvaligursky in #8889
* Reset active WebGL texture unit on device init for reused contexts by @willeastcott in #8894
* Clear cached collisions when a collision component is removed by @willeastcott in #8898
* Report particles isPlaying() in sync with particle lifetime by @willeastcott in #8901
* Fix overestimated WebGPU GPU frame timing in profiler by @mvaligursky in #8903
* Rebuild bind group when its dynamic uniform buffer is re-allocated mid-frame by @mvaligursky in #8906
* Align WebGPU backbuffer format with WebXR projection-layer format by @mvaligursky in #8914
* Keep physics interpolation in sync on teleport for high refresh rates by @willeastcott in #8915
* Don't render previous locale's text with the new font (#3043) by @willeastcott in #8927
* Use mesh UVs when texturing mesh particles (#7858) by @mvaligursky in #8931
* Cull screen-space text by content bounds by @willeastcott in #8934
* Render MSDF text at the true glyph edge by @willeastcott in #8935
* Correct and clarify ElementComponent JSDoc and types by @willeastcott in #8936
* Don't apply hardware depth bias to PCSS shadows by @mvaligursky in #8942
* Upload CPU vertex buffer on rebuild/reset so paused emitters don't render garbage by @willeastcott in #8945
* Support non-interleaved meshes as particle meshes by @willeastcott in #8946
* Support cloning anim components with dynamically-added layers by @willeastcott in #8947
* Fix camera.screenToWorld returning NaN when called in initialize by @mvaligursky in #8950

## Examples

* Share dialog with state-encoded URLs by @kpal81xd in #8796
* Author controls as JSX components by @kpal81xd in #8819
* Download example as a standalone Vite project by @kpal81xd in #8825
* Resolve code editor autocomplete, go-to-definition and peek by @kpal81xd in #8826
* Support category: and example: tags in sidebar filter by @kpal81xd in #8829
* Add a GSplat streaming test example, PREFERRED_DEVICE flag, and numSplats accessor by @mvaligursky in #8847
* Add Gaussian Splatting Depth of Field example by @mvaligursky in #8853
* Tune the Depth of Field example (splat budget + WebGPU) by @mvaligursky in #8854
* Add SpAItial AI attribution to the Depth of Field example by @mvaligursky in #8855
* Remove alphabetical import-order requirement from examples lint by @mvaligursky in #8862
* Improve vr-lod XR example and fix XR menu/navigation on hand-tracking devices by @mvaligursky in #8864
* Add Gaussian Splat downtown streaming example by @mvaligursky in #8866
* Serve gsplat example data over the CDN by @mvaligursky in #8867
* Add gsplat shader rings example using the gsplatModifyPS hook by @mvaligursky in #8877
* Add noise-based dissolve shader effect for gaussian splats by @mvaligursky in #8890
* Restore MiniStats panel after hot reload by @willeastcott in #8896
* Add UI element masking example by @mvaligursky in #8923
* Show resolved gsplat renderer in dropdown at startup by @mvaligursky in #8953
* Make gsplat VR LOD example visible in Gaussian Splatting section by @mvaligursky in #8954

## Engine Dependencies

* Update actions/github-script action to v9 in #8857
* Update dependency esbuild to v0.28.1 [security] in #8892
* Update dependency vite to v8.0.16 [security] in #8913
* Update actions/checkout action to v7 in #8925
* Update all npm dependencies in #8944

### Contributors

* @kpal81xd
* @MAG-AdrianMeredith
* @mmjinglin163
* @mvaligursky
* @Skullheadx
* @willeastcott

## New Contributors
* @Skullheadx made their first contribution in #8809
* @mmjinglin163 made their first contribution in #8920

**Full Changelog**: https://github.com/playcanvas/engine/compare/v2.19.7...v2.20.0