v2.23.0

X-PLUG/mPLUG-DocOwlv2.23.0Oct 1, 2026by mvaligursky

Upgrading?

AI Summary

This is a major release focusing on WebGPU rendering optimizations, a comprehensive refactoring of the material system, and advanced Gaussian Splatting capabilities. It also removes obsolete browser workarounds and deprecated APIs.

Key Highlights

  • Significant WebGPU rendering optimizations including persistent storage buffers and pipeline caching
  • Major material system refactoring with bind groups and uniform buffers
  • Enhanced Gaussian Splatting with LOD streaming and stochastic alpha rendering
  • Removal of obsolete browser workarounds (Safari, Chrome) and immediate mesh rendering

Breaking Changes

  • Renamed `supportsUniformBuffers` to `usesMeshBindGroups`
  • Deprecated `noFog` in favor of `useFog`
  • Removed obsolete immediate mesh rendering functionality
  • Removed the shader dump and precompile workflow
  • Removed obsolete Mac Chrome blitFramebuffer and Safari texture unit workarounds

New Features

  • Added `distanceSq` utility function
  • Added `useFog` and `useTonemap` properties to ParticleSystemComponent
  • Added ability to load audio assets regardless of URL file extension
  • Added headless Node.js execution support without jsdom
  • Added bloom threshold to CameraFrame
  • Added per-attachment clear colors for multiple render targets
  • Added stencil clears inside a render pass on WebGPU
  • Added partial vertex and index buffer uploads
  • Added stochastic alpha rendering for GPU-sort Gaussian Splatting renderer

Full Release Notes

## Changes

* Add distanceSq by @AlexAPPi in #9313
* Add useFog/useTonemap to ParticleSystemComponent, deprecate noFog by @mvaligursky in #9340
* Load audio assets regardless of the URL's file extension by @mvaligursky in #9365
* Poll gamepads every frame without allocating by @willeastcott in #9470
* Run the engine headless in Node.js without jsdom by @willeastcott in #9504
* Replace batch.origMeshInstances[0] with sourceMeshInstance variable by @AlexAPPi in #9454

## Rendering

* Store mesh instance matrices in a persistent storage buffer on WebGPU by @mvaligursky in #9614
* Add a bloom threshold to CameraFrame by @mvaligursky in #9455
* Add per-attachment clear colors to the camera for multiple render targets by @mvaligursky in #9333
* Support stencil clears inside a render pass on WebGPU by @mvaligursky in #9612
* Allow camera viewports extending past the render target on WebGPU by @mvaligursky in #9516
* Add partial vertex and index buffer uploads by @mvaligursky in #9401
* Add screen_size to forward view uniforms and deprecate uScreenSize by @mvaligursky in #9331
* Pack forward view uniforms to reduce alignment padding by @mvaligursky in #9332
* Move per-pass uniforms into the view uniform buffer by @mvaligursky in #9582
* Move textureBias into the view uniform buffer by @mvaligursky in #9484
* Supply per-pass textures through the WebGPU view bind group by @mvaligursky in #9521
* Publish the scene environment textures under their own uniforms by @mvaligursky in #9364
* Add an opt-in std140 packing order to UniformBufferFormat by @mvaligursky in #9400
* Stop creating typed array views for every dynamic uniform buffer update by @mvaligursky in #9418
* Retain WebGPU render pipelines between draws by @mvaligursky in #9380
* Look the WebGPU render pipeline up only when its state changes by @mvaligursky in #9492
* Write WebGPU buffer data directly from its storage by @mvaligursky in #9379
* Pass WebGPU bind group dynamic offsets as a typed array by @mvaligursky in #9487
* Skip WebGPU vertex and index buffer binds the render pass already has by @mvaligursky in #9496
* Keep the backing store of the device vertex buffer list across draws by @mvaligursky in #9498
* Assign bind group slots by index during the update by @mvaligursky in #9425
* Split the bind group update into a scope path and an owner path by @mvaligursky in #9429
* Leave a texture out of the mesh bind group when a supplied group has it by @mvaligursky in #9436
* Let the graphics device own the built-in textures by @mvaligursky in #9438
* Rebuild layer mesh arrays lazily after removals by @mvaligursky in #9378
* Pack the mesh instance light mask into 8 bits, for 24 shader define flags by @mvaligursky in #9617
* Rename supportsUniformBuffers to usesMeshBindGroups by @mvaligursky in #9383
* Split WebGPU device creation from device initialization by @mvaligursky in #9483
* Ignore MeshInstance#setIndirect where indirect draw is not supported by @mvaligursky in #9462
* Remove obsolete immediate mesh rendering by @mvaligursky in #9385
* Remove the shader dump and precompile workflow by @mvaligursky in #9477
* Remove obsolete Mac Chrome blitFramebuffer workaround by @mvaligursky in #9318
* Remove obsolete Safari texture unit workaround by @mvaligursky in #9319

## Lighting & Shadows

* Move light uniforms into the view uniform buffer, uploaded once per pass by @mvaligursky in #9482
* Give every light an absolute slot, shared by all light masks by @mvaligursky in #9475
* Dispatch the lights of a pass once, not per light mask by @mvaligursky in #9479
* Consolidate light slots into a LightList shared by renderer, shaders and lightmapper by @mvaligursky in #9481
* Make world-cluster allocation request/execute based by @mvaligursky in #9473
* Fade directional shadows at the far distance by @mvaligursky in #9417
* Skip shadow sampling beyond the far distance by @mvaligursky in #9419
* Request directional shadow culling from scheduled render passes by @mvaligursky in #9423
* Request local shadow culling from scheduled render passes by @mvaligursky in #9428
* Cull only requested directional shadow cascades by @mvaligursky in #9437
* Skip spot VSM blur when the shadow pass does not execute by @mvaligursky in #9440
* Set shadow caster material state once per material run by @mvaligursky in #9524
* Derive the normal matrix of shader materials in the shadow pass by @mvaligursky in #9585
* Give a mesh instance lightmap its own texture slot by @mvaligursky in #9415
* Bind only the lightmap slots a bake writes by @mvaligursky in #9420
* Fix shadow shader selection when changing cascade count by @mvaligursky in #9343
* Reset clustered shadow lights before preparing each frame by @mvaligursky in #9414
* Fix WebGPU shadow shader compile error for PCSS spot lights by @mvaligursky in #9545
* Pass an empty LightList, not 0, to the shadow pass shaders by @willeastcott in #9556
* Hidden mesh instances with culling disabled no longer cast shadows by @mvaligursky in #9606

## Materials

* Add a material bind group slot and cached shader processing keys by @mvaligursky in #9370
* Hold the textures of a material in its own bind group by @mvaligursky in #9457
* Bind the material bind group at a material switch instead of at every draw by @mvaligursky in #9413
* Store StandardMaterial diffuse in a material-owned uniform buffer by @mvaligursky in #9381
* Store emissive color and intensity in the material uniform buffer by @mvaligursky in #9392
* Store the remaining StandardMaterial colors and numbers in the material uniform buffer by @mvaligursky in #9398
* Store the derived StandardMaterial uniforms in the material uniform buffer by @mvaligursky in #9402
* Store the texture map transforms in the material uniform buffer by @mvaligursky in #9405
* Store the cube map projection box in the material uniform buffer by @mvaligursky in #9406
* Copy the cube map projection box into the material instead of keeping a reference by @mvaligursky in #9411
* Support mesh instance overrides of material uniform buffer values by @mvaligursky in #9386
* Let a mesh instance override a texture of the material bind group by @mvaligursky in #9445
* Restore global scope values after mesh instance parameter overrides by @mvaligursky in #9588
* Warn in debug builds when a mesh instance override array changes in place by @mvaligursky in #9395
* Warn about material and mesh instance parameters of view uniforms by @mvaligursky in #9619
* Add support for all eight UV sets in StandardMaterial by @mvaligursky in #9376
* Make alphaTest an accessor of Material and retire the generic float property helper by @mvaligursky in #9408
* Separate sprite color from material uniforms by @mvaligursky in #9337
* Separate UI element colors from material uniforms by @mvaligursky in #9338
* Pass the model scale to dynamic refraction from the vertex shader by @mvaligursky in #9616
* Fix LitMaterial ambient by making the ambient tint a lit argument by @mvaligursky in #9538
* Fix StandardMaterial shader invalidation for environment textures and 0/1 number changes by @mvaligursky in #9359
* Fix StandardMaterial shader invalidation for refraction thresholds by @mvaligursky in #9363
* Make StandardMaterial texture map iteration ignore prototype properties by @mvaligursky in #9368
* Stop placeholder textures from forcing shader recompiles by @mvaligursky in #9592

## Gaussian Splatting

* Make gsplat LOD distance-only, with target or limit splat budgets by @mvaligursky in #9584
* Only judge oversized gsplat LOD nodes by their middle by @mvaligursky in #9586
* Gsplat LOD: empty level for nodes without coarse data; distance default by @mvaligursky in #9326
* Load streamed gsplat LOD files nearest the camera first by @mvaligursky in #9520
* Update StreamedGSplat presets to use LOD falloff by @mvaligursky in #9369
* Stochastic alpha rendering for the GPU-sort gsplat renderer by @slimbuck in #9443
* Flat per-splat gsplat varyings and fix ComputeRadixSort reallocation on growth by @slimbuck in #9372
* Allow either provoking vertex for WGSL splat varyings by @mvaligursky in #9374
* Prefer first provoking vertex on WebGL by @mvaligursky in #9375
* Use a per-axis focal length for the gsplat footprint by @dfattal in #9486
* Skip compute work for cached Gaussian splat shadows by @mvaligursky in #9416
* Make Gaussian splatting GLB support tree-shakeable by @mvaligursky in #9327
* Make GSplat scene parameters tree-shakeable by @mvaligursky in #9328
* Avoid retaining GSplat work buffer shaders in container bundles by @mvaligursky in #9322
* Apply withCredentials to the gsplat loaders that stream with fetch by @mvaligursky in #9458
* Warn when gaussian splats render into a multisampled target by @mvaligursky in #9532
* Fix gsplat GPU sort flicker from sort key precision loss by @mvaligursky in #9493
* Fix mirrored gsplat footprints in flipped render targets by @mvaligursky in #9490
* Fix fisheye gsplats and sky in flipped render targets by @mvaligursky in #9495
* Correct gsplat spherical harmonics with orthographic cameras by @mvaligursky in #9531
* Fix NVIDIA GPU hangs during gsplat depth picking by @slimbuck in #9572
* Prevent gsplat crash after destroying a splat entity by @mvaligursky in #9522
* Prevent gsplat streaming crash after destroying a camera by @mvaligursky in #9533
* Render gsplats again after a camera re-adds their layer by @mvaligursky in #9535
* Keep gsplat placement changes made in frame:ready handlers by @mvaligursky in #9536
* Stop a gsplat from blanking for a frame when its asset is replaced by @willeastcott in #9610

## Physics

* Add RigidBodyComponent#gravityScale by @willeastcott in #9465
* Add a hitBackFaces raycast option to skip mesh collider back faces by @willeastcott in #9506
* Tighten the public surface of the physics API by @willeastcott in #9519
* Harden physics raycasts and forward the Application physicsWorld option by @willeastcott in #9517
* Ship an Ammo build whose heap starts at 16MB and grows on demand by @willeastcott in #9488
* Route the Ammo internal tick callback through one dispatcher per module by @willeastcott in #9463
* Apply gravity to the physics backend on install and when it changes by @willeastcott in #9439
* Refresh Ammo broadphase bounds when a body is teleported by @willeastcott in #9489
* Mirror mesh colliders with negative scale factors by @willeastcott in #9499
* Keep the orientation of mirrored dynamic bodies across physics steps by @willeastcott in #9500
* Fix mesh colliders sharing a mesh at different scales and rebuild them on scale changes by @willeastcott in #9311
* Sync compound children from their own transforms, not the root's dirty flag by @willeastcott in #9456
* Rebuild a collision component as a trigger when its rigid body is removed by @willeastcott in #9464
* Give both entities of a contact the normal of the other surface by @willeastcott in #9450

## XR

* Stop a double XR start from leaving a broken session by @willeastcott in #9599
* Stop the app freezing when an XR session ends by @willeastcott in #9600
* Stop XR culling from using the previous frame's head pose by @willeastcott in #9601
* Remove XR event listeners that outlive their owners by @willeastcott in #9623
* Apply camera clip plane changes to a running XR session by @willeastcott in #9624
* Correct XR input source rays and velocities by @willeastcott in #9625
* Start XrSession sessions facing the way the camera looks by @willeastcott in #9626
* Keep removed XR planes and meshes readable, and harden hit testing by @willeastcott in #9627
* Keep XrControllers models loaded while input sources share them by @willeastcott in #9631
* Hide XrControllers models while their pose is not tracked by @willeastcott in #9635
* Stop XrNavigation teleporting on system menu gestures by @willeastcott in #9636
* Document the XR select events of ElementComponent by @willeastcott in #9633

## UI

* Warn when image materials have no opaque render pass by @mvaligursky in #9346
* Keep an element's position when it is added to an entity under a screen by @willeastcott in #9525
* Let ElementDragHelper drag an element that is a direct child of a screen by @willeastcott in #9551
* Update a scrollbar's handle when its track is resized by @willeastcott in #9573
* Fix layout group subscriptions when components change by @mvaligursky in #9399
* Build text meshes without a pass over empty arrays by @willeastcott in #9562
* Skip drawing text with an empty render range by @willeastcott in #9567
* Draw CanvasFont glyph cells fully transparent by @willeastcott in #9568
* Fix a markup parser crash when a tag name shadows hasOwnProperty by @willeastcott in #9468
* Correct UI component docs, the Spanish locale fallback and button examples by @willeastcott in #9552

## Device Recovery

* Restore WebGPU rendering resources after device loss by @mvaligursky in #9355
* Restore WebGPU profiling after device loss by @mvaligursky in #9357
* Fix WebGPU recovery for compute, readback and generated data by @mvaligursky in #9358
* Cancel WebGPU recovery when the device is destroyed by @mvaligursky in #9397
* Fix WebGPU resource cleanup on device destruction by @mvaligursky in #9394
* Restore the WebGL backbuffer before recovery callbacks by @mvaligursky in #9347
* Recover SOG streaming across WebGL context loss by @mvaligursky in #9350
* Restore procedural sky lighting after device recovery by @mvaligursky in #9351
* Add cross-backend debug device loss testing by @mvaligursky in #9353
* Restore generated HDR environments in examples after device loss by @mvaligursky in #9345
* Redraw the on-demand splat example after device recovery by @mvaligursky in #9348
* Rebake example lightmaps after device recovery by @mvaligursky in #9352

## Profiling & Debugging

* Redesign MiniStats with compact and grouped profiling views by @mvaligursky in #9307
* Add collapsible MiniStats sections by @mvaligursky in #9361
* Add tracked resource counts to MiniStats by @mvaligursky in #9366
* Expose application performance statistics through AppStats by @mvaligursky in #9308
* Add user counters to AppStats by @mvaligursky in #9341
* Correct MiniStats timing and build availability documentation by @mvaligursky in #9310
* Add TextureRenderer for debug texture previews by @mvaligursky in #9382
* Add per-camera culling and display fixes to TextureRenderer by @mvaligursky in #9387
* Add a debug-engine draw limit for stepping through a render pass by @mvaligursky in #9501
* Add descriptive GPU profiling names for quad passes by @mvaligursky in #9329
* Track shaders on every graphics device by @mvaligursky in #9466
* Add a bare WebGL 2 device type for testing on constrained devices by @mvaligursky in #9471
* Announce apps to a devtools hook by @mvaligursky in #9618
* Add AppOptions#devtools to opt out of the devtools hook by @mvaligursky in #9637

## Fixes

* Add missing TEXTURE_2D_ARRAY entry to targetToSlot by @mvaligursky in #9321
* Fix sprite tint color space conversion by @mvaligursky in #9334
* Clear keyboard and mouse states on detach and focus loss by @mvaligursky in #9344
* Reset stale mouse movement deltas by @mvaligursky in #9349
* Fix WebGPU texture-formats-tier feature names and rename their flags by @mvaligursky in #9390
* Fix skydome depth of field with scene-pass depth by @mvaligursky in #9391
* Wait for container meshes before marking render assets ready by @mvaligursky in #9404
* Fix Picker readback rectangle in the top-left to bottom-left flip by @mvaligursky in #9441
* Reconstruct the picked world position at the pixel center by @mvaligursky in #9446
* Call super.frameUpdate() from RenderPassCompose by @mvaligursky in #9449
* Copy the render style onto a batched mesh instance by @mvaligursky in #9448
* Fix CameraComponent#setShaderPass() throwing when resetting the pass by @mvaligursky in #9451
* Resolve glb model resources against the directory of the glb by @mvaligursky in #9453
* Stop leaking a DrawCommands set per indirect draw per frame by @mvaligursky in #9459
* Fix bundle asset load retrying and error-response handling by @mvaligursky in #9460
* Fix the WebGL multi-draw fallback submitting no draws by @mvaligursky in #9461
* Treat every 2xx HTTP status as success by @willeastcott in #9507
* Accept status 0 responses from non-http schemes by @willeastcott in #9508
* Fix Curve.closest outside unit range by @humairaambreen in #9543
* Return the first key from Curve#closest for times before the curve by @willeastcott in #9544
* Accept mesh instances without a material in Layer#addMeshInstances by @mvaligursky in #9579
* Skip disabled entities in OutlineRenderer by @mvaligursky in #9452
* Fix OutlineRenderer shader errors when clustered lighting is disabled by @mvaligursky in #9590
* Fix OutlineRenderer material cleanup and frameUpdate listener leak by @mvaligursky in #9589
* Fix OutlineRenderer outlines missing with some CameraFrame settings by @mvaligursky in #9594
* Blur the TAA output in the camera frame's bloom and depth of field by @willeastcott in #9591
* Fix calculateNormals returning NaN for vertices with no triangle area by @mvaligursky in #9596
* Fix FloatPacking.float2Half corrupting values too large for a half float by @mvaligursky in #9595
* Clean up pole and tip triangles in sphere, capsule and cone geometry by @mvaligursky in #9597
* Keep screen-space particles square on any canvas aspect by @willeastcott in #9570
* Export skinned meshes to USDZ where their bones place them by @willeastcott in #9546
* Use the WGSL built-in saturate, fixing lightVertexColor on WebGPU by @mvaligursky in #9598
* Flip the vertex normal of back faces for two sided lighting by @willeastcott in #9559
* Initialize GraphicsDevice renderPassIndex to 0 by @mvaligursky in #9605
* Load cubemap faces given as URLs as sRGB color, not RGBP by @mvaligursky in #9607
* Fix Texture#lock on cubemap faces and mip levels by @mvaligursky in #9608
* Fix WebGPU stencil read mask, reuse pipelines across stencil refs by @mvaligursky in #9611
* Make createGraphicsDevice reject instead of hanging when no device can be created by @willeastcott in #9603
* Depth state fixes (depthTest, WebGPU depth bias, layer reset) by @mvaligursky in #9615
* Fix BlendState#setAttachment reviving overrides cleared by copy by @mvaligursky in #9620

## Examples

* Restyle the examples browser by @mvaligursky in #9629
* Slim the collapsed code pane and hide the code minimap by @mvaligursky in #9632
* Open the PlayCanvas Inspector in the examples browser by @mvaligursky in #9621
* Serve the inspector loader as is in the dev server by @mvaligursky in #9638
* Link a whole examples category by URL by @mvaligursky in #9303
* Add category sharing and expand-all controls by @mvaligursky in #9356
* Reveal current example when clearing search by @mvaligursky in #9354
* Rebuild the UI button, panel, masking and custom shader examples by @willeastcott in #9565
* Rebuild the UI text examples by @willeastcott in #9566
* Rebuild the UI layout, image and world examples by @willeastcott in #9569
* Add the UI input, common widgets and drag and drop examples by @willeastcott in #9571
* Add a hidden test example for UI element states by @mvaligursky in #9580
* Add rendering without entities example by @mvaligursky in #9377
* Add environment sources test example by @mvaligursky in #9360
* Give the render to image example a knight of its own by @willeastcott in #9583
* Refresh the MiniStats example scene by @mvaligursky in #9309
* Migrate examples to TextureRenderer by @mvaligursky in #9384
* Load the VAT character data as a binary asset in the vat-characters example by @mvaligursky in #9518
* Render gaussian splat crop and depth-of-field examples without MSAA by @mvaligursky in #9634
* Restore the depth prepass in the gaussian splat depth-of-field example by @mvaligursky in #9639
* Apply area-picker example material changes via update() by @mvaligursky in #9497
* Remove no-op bloom.enabled writes from examples by @mvaligursky in #9476
* Replace the Arial and Courier fonts of the examples and tests with Roboto by @willeastcott in #9577
* Ship the Roboto ExtraLight license with the XR examples' font by @willeastcott in #9576

## Documentation & Types

* Type Entity#addComponent options and result by component name by @willeastcott in #9320
* Type Asset#resource by asset type (Asset<K>, AssetMap, AssetType) by @willeastcott in #9403
* Type the script lookup APIs against Script and return the class you pass in by @willeastcott in #9472
* Type element input events with the browser objects they carry by @willeastcott in #9527
* Add alpha option to createGraphicsDevice typings by @willeastcott in #9317
* Fix JSDoc types that disagree with runtime values, salvaged from #5817 by @willeastcott in #9469
* Fix Debug.assert and Texture levels JSDoc types by @willeastcott in #9491
* Clear all API reference build warnings and treat warnings as errors by @willeastcott in #9412
* Add a Framework category and home the uncategorised public classes by @willeastcott in #9421
* Rewrite Asset-related class overviews and document every resource handler by @willeastcott in #9409
* Rewrite the Math category class overviews by @willeastcott in #9410
* Rewrite the scene graph and render pipeline class overviews by @willeastcott in #9422
* Rewrite the material class overviews by @willeastcott in #9424
* Make the modern input system the Input category and describe its classes by @willeastcott in #9427
* Describe the modern animation classes and mark the legacy ones by @willeastcott in #9430
* Describe the sound classes and template the thin component system pages by @willeastcott in #9432
* Describe the Gizmo and TransformGizmo base classes by @willeastcott in #9433
* Use American spelling in the Material and LitShaderOptions overviews by @willeastcott in #9431
* Document which asset types fire the progress event by @willeastcott in #9474
* Correct RigidBodyComponent group/mask defaults and raycastAll sort order by @willeastcott in #9305
* Fix physics docs for impulse points, kinematic velocity and contact results by @willeastcott in #9515
* Improve OutlineRenderer API documentation by @mvaligursky in #9587
* Correct typos and British spellings across the engine source by @willeastcott in #9547
* Correct typos and British spellings in examples, scripts and tests by @willeastcott in #9550
* Add a context7.json so Context7 indexes usage docs and agent rules by @willeastcott in #9478
* Point the Context7 rules at the llms.txt indexes by @willeastcott in #9503

## Engine Dependencies

* Update dependency sharp to v0.35.4 [SECURITY] in #9330
* Update all npm dependencies in #9434
* Update dependency mocha to v12 in #9435

### Contributors

* @AlexAPPi
* @dfattal
* @humairaambreen
* @mvaligursky
* @slimbuck
* @willeastcott

## New Contributors

* @dfattal made their first contribution in #9486
* @humairaambreen made their first contribution in #9543

**Full Changelog**: https://github.com/playcanvas/engine/compare/v2.22.6...v2.23.0

Related Repositories