v2.22.0
playcanvas/enginev2.22.0Sep 4, 2026by mvaligursky
AI Summary
v2.22.0 introduces major WebGPU enhancements including full multisampled texture support and depth resolve modes, alongside improvements to Gaussian Splatting and advanced rendering features like dual-source blending and flat shading. The release also removes legacy APIs and introduces new physics controls.
Key Highlights
- WebGPU Multisampling: Full support for multisampled textures and depth buffers with configurable resolve modes.
- Advanced Rendering: Adds dual-source blending, flat shading, off-center projection, and independent blending for multiple render targets.
- Gaussian Splatting: Enhances performance with error-driven LOD selection and depth writing capabilities.
- Physics Control: Introduces timeScale and explicit backend selection for the physics system.
Breaking Changes
- Store frustum planes in a typed array.
- Remove wire drawing methods from AppBase.
New Features
- Camera projection offset for off-center projections.
- SceneDepthReader for reading scene depth to the CPU.
- Parallax occlusion mapping support.
- WireRenderer for debug shapes.
- Justified text support.
- Multisampled texture options for WebGPU.
- AppOptions.physicsWorld for backend selection.
- RenderTarget.depthResolveMode for multisampled depth.
Full Release Notes
## Breaking Changes * [BREAKING] Store frustum planes in a typed array by @mvaligursky in #9196 * [BREAKING] Remove wire drawing methods from `AppBase` by @mvaligursky in #9289 ## Changes * Remove `Scene#forcePassThroughSpecular` compatibility flag by @mvaligursky in #9250 * Remove passive event listener detection and `platform.passiveEvents` by @willeastcott in #9252 * Remove unused `BatchManager#clone` method by @mvaligursky in #9106 * Add dual-source blending support by @mvaligursky in #9096 * Add `Camera.projectionOffset` for off-center (shift lens) projection by @mvaligursky in #9098 * Add `RenderTarget` origin option, deprecate `flipY` by @mvaligursky in #9101 * Expose Bayer 2/4 dither modes and add Bayer 16 dither mode by @mvaligursky in #9117 * Add shadow pass support for `ShaderMaterial` using `#include` by @mvaligursky in #9116 * Cull omni shadow casters against all six faces in a single pass by @mvaligursky in #9194 * Cull mesh instances by their bounding box instead of bounding sphere by @mvaligursky in #9197 * Add flat shading support by @mvaligursky in #9191 * Flat shade glTF primitives without vertex normals by @mvaligursky in #9193 * Add `shadowCascadeMask` to the render component by @mvaligursky in #9195 * Add `shadowCascadeMask` in batch by @AlexAPPi in #9104 * Configurable maximum number of clustered lights by @mvaligursky in #9188 * Add volumetric fog support for clustered omni and spot lights by @mvaligursky in #9137 * Support refraction without the metalness workflow by @mvaligursky in #9147 * Add independent blending for multiple render targets by @mvaligursky in #9140 * Add per-attachment clear colors for multiple render targets by @mvaligursky in #9142 * Add clear support for integer format color attachments by @mvaligursky in #9143 * Render the scene depth from the scene pass, replacing the depth prepass where possible by @mvaligursky in #9174 * Support writing the scene textures from the forward pass by @mvaligursky in #9166 * Add `SceneDepthReader` for reading a camera's scene depth to the CPU by @mvaligursky in #9274 * Add a scene depth mode to `CameraFrame` debug rendering by @mvaligursky in #9182 * Wire `alphaToCoverage` into the WebGPU render pipeline state by @mvaligursky in #9208 * Add `sample_mask` builtin support to WGSL fragment shaders by @mvaligursky in #9222 * Add `samples` option to `Texture` for multisampled textures on WebGPU by @mvaligursky in #9217 * Support multisampled texture bindings in WGSL reflection and bind groups by @SashaRX in #9167 * Support multisampled color buffers and custom resolves on `RenderTarget` by @mvaligursky in #9221 * Support multisampled depth buffers and depth resolve targets on `RenderTarget` by @mvaligursky in #9230 * Add `RenderTarget.depthResolveMode` controlling multisampled depth resolve by @mvaligursky in #9226 * Support blendable and R/RG float formats in `getRenderableHdrFormat` by @mvaligursky in #9164 * Expose `textureFloatBlendable` capability on all devices by @mvaligursky in #9160 * Support the `RG32F` texture format on WebGL by @mvaligursky in #9218 * Add readonly detection for the WebGPU subgroup-size-control feature by @mvaligursky in #9158 * Fix parallax mapping correctness and promote to public by @mvaligursky in #9207 * Add parallax occlusion mapping as an opt-in parallax mode by @mvaligursky in #9213 * Add soft self shadowing to parallax occlusion mapping by @mvaligursky in #9225 * Add `heightMapBase` to unify the parallax zero level across modes by @mvaligursky in #9245 * Add `WireRenderer` for wireframe debug shapes and a debug examples category by @mvaligursky in #9288 * Add `AppOptions.physicsWorld` for explicit physics backend selection by @willeastcott in #9266 * Add `timeScale` and public `step()` to `RigidBodyComponentSystem` by @willeastcott in #9300 * Wake constrained bodies when a joint constraint is created or destroyed by @willeastcott in #9177 * Add justified text support to text elements by @mvaligursky in #9185 * Allow typed arrays in geometry and mesh data APIs by @mvaligursky in #9121 * Add support for transform feedback separate buffer mode by @AlexAPPi in #9109 * Support multiple transform feedback buffers by @mvaligursky in #9131 * Add rotation attribute to the `ProceduralSky` script by @mvaligursky in #9150 * Detach element component graph nodes on destroy by @mvaligursky in #9183 * Release application references on destroy by @mvaligursky in #9190 * Destroy the asset registry with the application by @mvaligursky in #9189 * Relocate deprecated API shims so the ESM engine tree-shakes by @willeastcott in #9251 * Remove WebGL1-era POT checks from texture mipmap upload and `gpuSize` by @willeastcott in #9262 * Use `Geometry#calculateTangents` in procedural geometry subclasses by @willeastcott in #9253 * Stop the `Shader` constructor modifying the supplied definition by @willeastcott in #9281 ## Gaussian Splatting * Error-driven GSplat LOD selection with a derived fallback by @mvaligursky in #9233 * Add a LOD mode and per-splat detail falloff for streamed GSplats by @mvaligursky in #9267 * Gaussian splats write the scene depth by @mvaligursky in #9175 * Add `scene.gsplat.useTonemap` to opt GSplats out of tonemapping and exposure by @mvaligursky in #9229 * Use `shadowCasterPS` chunk for gsplat shadow output by @mvaligursky in #9119 * Fix unified CPU splat sort for non-uniformly scaled splats by @AriaLian in #9268 * Fix stale gsplat projector pipeline after a `dataFormat` swap by @mvaligursky in #9269 * Fix gsplat rendering on Firefox/Windows WebGPU by @mvaligursky in #9148 * Fix SOG parsers throwing when a load completes after app teardown by @mvaligursky in #9232 * Fix radix sort value buffer rotation with initial values by @slimbuck in #9276 * Fix blending disabled when generating SOG centers into an integer render target by @slimbuck in #9224 ## Fixes * Fix inverted normal mapping on WebGPU (derivative TBN) by @mvaligursky in #9099 * Fix MSDF coverage ramp exceeding field range at small sizes by @willeastcott in #9100 * Fix reported WebGPU sampled texture limit by @mvaligursky in #9103 * Discard out of range depth in VSM shadow casters by @mvaligursky in #9124 * Fix `textureBias` redefinition in tiled nine-slice shaders by @mvaligursky in #9129 * Fix the bright outline around MSDF text glyphs by @mvaligursky in #9139 * Fix vertex array object cache key collisions by @mvaligursky in #9127 * Fix instancing/draw-commands mismatch skipping indirect draws by @mvaligursky in #9159 * Fix phantom sampler binding for a trailing texture in WGSL reflection by @mvaligursky in #9198 * Fix `ScriptComponent` clone dropping scripts awaiting their script type by @mvaligursky in #9200 #9201 #9202 #9203 #9204 * Fix `AnimationComponent` leaking asset event handlers by @mvaligursky in #9141 * Fix component event leak when removing an enabled component by @mvaligursky in #9145 * Fix layer event leak when the scene layer composition is replaced by @mvaligursky in #9146 * Fix `XrMenu` rendering outside of XR sessions by @willeastcott in #9155 * Fix looping sounds that specify a start time and duration by @willeastcott in #9231 * Correct sound duration and playback timing by @mvaligursky in #9249 * Fix inverted entity name guard in `AnimComponentBinder` by @willeastcott in #9187 * Fix volumetric fog missing for one frame after a canvas resize by @mvaligursky in #9283 * Fix `'set:outline'` event reporting the text color by @willeastcott in #9260 * Handle `mapAsync` rejection when the WebGPU device is lost by @mvaligursky in #9115 * Fix `ComponentSystemRegistry.remove` leaving the system in the list by @willeastcott in #9280 * Settle the WebGL texture read when the device is destroyed by @mvaligursky in #9275 * Recalculate particle local bounds when the spawn volume changes by @mvaligursky in #9192 * Anchor the AppleWebKit 15.4 antialias workaround UA match by @willeastcott in #9265 ## Examples * Add VAT character crowd rendering scripts and example by @mvaligursky in #9123 * Add catapult siege demo example by @mvaligursky in #9130 * Add a parallax terrain example by @mvaligursky in #9223 * Support multiple transform feedback buffers and add a visual example by @mvaligursky in #9131 * Point the downtown example at a single full-scene streamed SOG bundle by @mvaligursky in #9290 * Make the examples MiniStats toggle drive example-owned instances by @mvaligursky in #9292 * Fix painter example render target orientation across APIs by @mvaligursky in #9108 ## Engine Dependencies * Update all npm dependencies (major) by @renovate[bot] in #9056 ### Contributors * @AlexAPPi * @AriaLian * @kpal81xd * @mvaligursky * @SashaRX * @slimbuck * @willeastcott ## New Contributors * @SashaRX made their first contribution in #9167 * @AriaLian made their first contribution in #9268 **Full Changelog**: https://github.com/playcanvas/engine/compare/v2.21.4...v2.22.0