v2.19.0

playcanvas/enginev2.19.0May 28, 2026by mvaligursky

AI Summary

A major update introducing WebXR support, GSplat hybrid rendering, and architectural refactoring.

Key Highlights

  • Adds WebXR support for Apple Vision Pro and WebGPU
  • Introduces GSplat hybrid raster renderer
  • Migrates engine builds to esbuild
  • Refactors components to use class fields

Breaking Changes

  • Remove HTMLAudioElement fallback from sound system
  • Deprecate GSplatComponent#unified and default it to true

New Features

  • WebGPU XR rendering support
  • GSplat hybrid raster renderer
  • WebGPU radix sort implementation
  • esbuild migration for engine builds
  • Class field migration for components

Full Release Notes

## Breaking changes
* [BREAKING] Remove HTMLAudioElement fallback from sound system by @willeastcott in #8636
* [BREAKING] Deprecate GSplatComponent#unified and default it to true by @mvaligursky in #8802

## Changes

* JSDoc whitespace cleanup by @willeastcott in #8616
* Standardize JSDoc class overviews across components by @willeastcott in #8617
* Unlink SHADER_ constants in Material.update by @willeastcott in #8621
* Unlink renderPasses note in CameraComponent by @willeastcott in #8622
* GSplat local rasterize early-out via precomputed power cutoff by @mvaligursky in #8623
* Simplify self-referential JSDoc @link tags by @willeastcott in #8624
* Remove redundant JSDoc @type tags from inferable fields by @willeastcott in #8625
* Clean up 'Other' category on API reference index by @willeastcott in #8627
* Read SOG gsplat data directly from source textures by @mvaligursky in #8628
* Calculate camera aspect ratio in CameraComponent onEnable by @lucaheft in #8632
* Document both halves of accessor pairs in public API by @willeastcott in #8634
* Hoist constructor-assigned properties to class fields in src/core and src/platform/input by @willeastcott in #8635
* Make camera aspect ratio auto-refresh in Camera class by @mvaligursky in #8633
* Fold gsplat debug AABB flags into debug enum by @mvaligursky in #8637
* Drop vendor-prefixed Page Visibility API support by @willeastcott in #8638
* Include GPU vendor/architecture in WebGPU feature log by @mvaligursky in #8642
* Inline AppBase tick as a class-field arrow by @willeastcott in #8639
* Move per-frame stats collection from AppBase to ApplicationStats by @willeastcott in #8643
* Remove dead `this.controller` references from AppBase and hide Controller class by @willeastcott in #8644
* Expose device.minSubgroupSize and device.maxSubgroupSize on GraphicsDevice by @mvaligursky in #8645
* Add WebGPU compute radix sort (4-bit portable + OneSweep NVIDIA) by @mvaligursky in #8620
* Add WGSL linear_indexing and CAPS_STORAGE_TEXTURE_READ by @mvaligursky in #8648
* Radix sort facade, indirect mode, and WebGPU subgroup sizing by @mvaligursky in #8647
* Standardize Layer on class fields with grouped ordering by @willeastcott in #8649
* Defer WebGPU buffer destruction until submit by @slimbuck in #8652
* Use texImage2D for gsplat splat-order uploads by @slimbuck in #8656
* Hybrid raster renderer with compute projector by @mvaligursky in #8658
* Clarify spotlight cone angles are half-angles by @willeastcott in #8665
* Align LightComponent with CameraComponent architecture by @willeastcott in #8666
* Tighten LightComponent JSDoc by @willeastcott in #8667
* Migrate ComponentData classes to class fields by @willeastcott in #8668
* Remove redundant constructor assignments in ButtonComponent by @willeastcott in #8669
* Remove redundant get data() overrides by @willeastcott in #8670
* Migrate ZoneComponent and ScreenComponent to class fields by @willeastcott in #8671
* Migrate SpriteComponent state to class fields by @willeastcott in #8672
* Remove unused _batchGroup field from batched components by @willeastcott in #8673
* Clean up vestigial Model plumbing on SpriteComponent by @willeastcott in #8674
* Remove unused JSDoc @import lines by @willeastcott in #8677
* SpriteAnimationClip class fields, layer typings, and docs by @willeastcott in #8679
* SpriteAnimationClip name, fps, loop property JSDoc by @willeastcott in #8680
* Remove unused WebGPU compute perf benchmark by @mvaligursky in #8681
* Speed up GSplat octree evaluateNodeLods by @mvaligursky in #8683
* Fuse GSplat budget bucket indices into LOD evaluation by @mvaligursky in #8684
* Remove raster GPU sort path and sort-key compute by @mvaligursky in #8685
* Rename GSPLAT_RENDERER_RASTER_HYBRID to RASTER_GPU_SORT by @mvaligursky in #8686
* Respect gsplat alphaCull on CPU raster and compute paths by @mvaligursky in #8687
* Remove unused gsplat-material module by @mvaligursky in #8688
* Exclude StorageBuffer from device.buffers tracking by @slimbuck in #8690
* Add Scene#gsplatCentersEnabled opt-out for gsplat CPU centers by @mvaligursky in #8691
* Align ScrollbarComponent with CameraComponent architecture by @willeastcott in #8693
* Tighten ScrollbarComponent internals by @willeastcott in #8694
* Handle lostpointercapture as well by @slimbuck in #8696
* Expose parsed manifest on GSplatOctreeResource by @MattiasVik in #8699
* TRACEID_BUFFERS trace, example control, and storage buffer debug names by @mvaligursky in #8705
* Reduce radix sort GPU buffer footprint by @mvaligursky in #8706
* Add XrBridge for backend-specific WebXR presentation by @mvaligursky in #8712
* Route XR framebuffer through XrBridge; refresh VR LOD example by @mvaligursky in #8713
* WebGPU immersive XR presentation by @mvaligursky in #8717
* WebGPU XR device loss, devicelost events, and graphics binding typings by @mvaligursky in #8719
* WebGPU AR camera color and syncCameraColorTexture bridge by @mvaligursky in #8720
* Parallelize engine builds with turbo by @kpal81xd in #8716
* WebGPU XR depth bridge, examples, and XR camera sync fixes by @mvaligursky in #8721
* WebGPU multiview frame pass, XR bridge, and clustered lights by @mvaligursky in #8723
* WebGPU texture-array projection and vr-basic gallery refresh by @mvaligursky in #8724
* Migrate js builds to esbuild by @kpal81xd in #8722
* WebGPU fragment shader optimization by @cabanier in #8733
* Data-driven gating for WGSL built-in inputs by @mvaligursky in #8739
* Align WGSL and GLSL clustered-light chunks by @mvaligursky in #8740
* Support for exporting aoMaps and the second UV channels by @tanaydimri in #8728
* Omit default texCoord in glTF exporter texture info by @mvaligursky in #8741
* Enable asset load retries by default by @mvaligursky in #8744
* Tidy ResourceLoader.enableRetry defaults and input handling by @mvaligursky in #8745
* Dual Color LUT crossfade and lut-grading example by @mvaligursky in #8749
* Warn when WebGPU or WebXR is used in an insecure context by @mvaligursky in #8752
* Log uncaptured WebGPU errors by @mvaligursky in #8757
* Mark platform.visionos as @ignore in JSDoc by @mvaligursky in #8765
* XrMenu label items, ray picking, always-visible HUD mode by @mvaligursky in #8770
* Add smooth turn mode to XrNavigation by @mvaligursky in #8771
* Update examples URL in READMEs to playcanvas.com/examples by @willeastcott in #8772
* Reuse clear options object in Renderer.clear by @willeastcott in #8776
* Replace pow(x, 5.0) with multiplies in Schlick Fresnel by @willeastcott in #8774
* Avoid iterator allocations in Frustum.setFromMat4 by @willeastcott in #8775
* Avoid per-frame clearVariants in unified material sync by @mvaligursky in #8778
* Align ScrollViewComponent with CameraComponent architecture by @willeastcott in #8777
* Add alphaDither property for independent dither strength by @mvaligursky in #8782
* Expose WGSL unrestricted_pointer_parameters as a device cap by @mvaligursky in #8785
* Expose WGSL pointer_composite_access as a device cap by @mvaligursky in #8786
* Expose WGSL packed_4x8_integer_dot_product as a device cap by @mvaligursky in #8787
* Expose WGSL texture_and_sampler_let as a device cap by @mvaligursky in #8790
* Make getGuid() part of the public API by @mvaligursky in #8795
* OneSweep on NVIDIA, indirect bind fix, GSplat bit alignment by @mvaligursky in #8657
* Expose guid as a public getter by @mvaligursky in #8797
* WebGPU XR rendering on Apple Vision Pro by @mvaligursky in #8756
* Release ImageBitmap memory after streamed SOG texture upload by @mvaligursky in #8758

## Fixes

* Fix incorrect JSDoc in public APIs by @willeastcott in #8612
* Fix more incorrect JSDoc in public APIs by @willeastcott in #8613
* Fix more incorrect JSDoc in public APIs by @willeastcott in #8614
* Fix more incorrect JSDoc in public APIs by @willeastcott in #8615
* Fix JSDoc link warnings for ShaderUtils and GSplatComponent by @willeastcott in #8618
* Fix url param JSDoc in ResourceHandler and ContainerHandler by @willeastcott in #8619
* Avoid camera_params redefinition in pickPS by @gameatp in #8640
* Avoid camera_params redefinition in pickPS (WGSL) by @mvaligursky in #8641
* Split batches by castShadow and shadowCascadeMask by @mvaligursky in #8650
* Fix Picker depth clear decoding by @slimbuck in #8651
* Defer GPU resource destruction for pending commands by @mvaligursky in #8655
* Fix executeCallback JSDoc type in gsplat-resolve-sh by @willeastcott in #8682
* Register StorageBuffer for WebGPU device lose/restore by @mvaligursky in #8692
* Texture#lock allocates when mip level slot is missing by @mvaligursky in #8695
* Default xrCompatible for WebGL and WebGPU paths by @mvaligursky in #8714
* TAA resolve premultiplied alpha for transparent clears by @mvaligursky in #8726
* visionOS MSAA resolve into XR framebuffer by @mvaligursky in #8730
* Avoid GLSL ES reserved word 'packed' in compact format shader by @mvaligursky in #8737
* Apply specular factor/color when map is present by @mvaligursky in #8742
* Include error message context in load failure logs by @mvaligursky in #8743
* Align projector projection with WebGPU shader uniforms by @mvaligursky in #8662
* Stereo XR viewport width for hybrid projector by @mvaligursky in #8664
* Fix renderSceneColorMap/renderSceneDepthMap toggling to false when already set by @MAG-AdrianMeredith in #8731
* Apply Color LUT in correct sRGB color space by @mvaligursky in #8747
* Resolve sog texture urls from document base by @kpal81xd in #8754
* Gate ImageBitmap release on opt-in flag, make it one-shot by @mvaligursky in #8760
* Fix WebGPU XR stereo viewport and harden vr-lod example by @mvaligursky in #8761
* Ensure opacity dither fully hides at alpha 0 and fully shows at alpha 1 by @lucaheft in #8767
* Make writeSortIndirectArgs WGSL helper portable for Firefox WebGPU by @mvaligursky in #8784
* Fire EVENT_PRECULL after frustum update by @mvaligursky in #8793
* Stop http retry tail from leaking across unit tests by @mvaligursky in #8794
* Unified-mode follow-ups (orbit-camera AABB, internal warnings) by @mvaligursky in #8803

## Examples

* Save whole GSplat benchmark page as PNG instead of chart only by @mvaligursky in #8611
* Viewer: support drag-and-drop of unpacked SOG by @mvaligursky in #8626
* Keep HIDDEN examples reachable by URL in production builds by @mvaligursky in #8629
* Overhaul GSplat benchmark example UI and measurement by @mvaligursky in #8660
* Unified gsplat for annotations and multi-splat by @mvaligursky in #8661
* GSplat renderer controls and unified orbit fixes by @mvaligursky in #8663
* Add animated sprite example using SpriteComponent by @willeastcott in #8676
* Add Camera Frame toggle to portal example by @mvaligursky in #8697
* Dual-backend shader-material example by @mvaligursky in #8698
* LOD streaming Debug panel and one-frame texture trace by @mvaligursky in #8702
* Update examples sidebar styling by @kpal81xd in #8704
* Scroll landscape controls panel when content overflows by @mvaligursky in #8707
* Add Gaussian splatting XR category with VR LOD demo by @mvaligursky in #8708
* Avoid clipping select dropdowns in landscape controls by @mvaligursky in #8715
* Convert XR examples to AppBase and WebGPU device setup by @mvaligursky in #8725
* Migrate examples to vite by @kpal81xd in #8729
* First-person and third-person gsplat examples on Sunnyvale by @mvaligursky in #8736
* Add splat-portal gaussian-splatting example by @mvaligursky in #8738
* Allow trycloudflare.com in Vite allowedHosts by @mvaligursky in #8750
* HTTPS dev mode for mobile / XR testing by @mvaligursky in #8753
* Inline example runtime urls by @kpal81xd in #8746
* Streamline HTTPS dev cert workflow by @kpal81xd in #8759
* Overhaul examples mobile view by @kpal81xd in #8766
* Support cross-origin example iframes by @kpal81xd in #8768
* Remove unified toggle from gsplat global-sorting by @mvaligursky in #8780
* Portal select dropdowns to body by @kpal81xd in #8781
* Example credits, keybinds, and description overlay by @kpal81xd in #8783
* Add @credit tags and relax credit field requirements by @mvaligursky in #8800
* Hide description overlay in fullscreen mode by @mvaligursky in #8801
* Set unified=false in gsplat-legacy picking example by @mvaligursky in #8804

## Engine Dependencies

* Update all npm dependencies in #8630
* Update all npm dependencies in #8751
* Update all npm dependencies in #8799
* Update all npm dependencies (major) in #8631

### Contributors

* @cabanier
* @gameatp
* @kpal81xd
* @lucaheft
* @MAG-AdrianMeredith
* @MattiasVik
* @mvaligursky
* @slimbuck
* @tanaydimri
* @willeastcott

## New Contributors
* @MattiasVik made their first contribution in #8699
* @cabanier made their first contribution in #8733
* @tanaydimri made their first contribution in #8728

**Full Changelog**: https://github.com/playcanvas/engine/compare/v2.18.2...v2.19.0