Skip to content

flutter_scene 0.24.0

Choose a tag to compare

@bdero bdero released this 05 Oct 22:48
· 52 commits to master since this release
  • Bloom weights an isolated extreme pixel down against its neighbors before blurring (BloomSettings.fireflySuppression, on by default), so a mirror glint of a small intense light no longer floods the frame or strobes, while bright regions bloom in full.
  • BloomSettings.highlight (default 1000) softly compresses extreme pixels before they bloom, so ordinary highlights pass nearly unchanged but no pixel floods the frame. 0 turns it off.
  • PointLight.radius and SpotLight.radius give a light a physical size, so glossy reflections widen to the emitter instead of peaking at a point, and the near-field falloff stops at its surface.
  • Opaque materials no longer discard, so mobile GPUs keep early depth testing and hidden-surface removal on for every opaque draw (frames 2.5 to 6x faster on a Mali-G57). Alpha-masked and LOD cross-fading draws cut out through a depth pre-draw instead; translucent cross-fades fade by opacity.
  • An alpha-masked PhysicalMaterial without a base color texture masks its shadows and depth by its vertex and factor alpha.
  • Material.depthLayer (.fmat depth_layer:) orders surfaces that share a plane. A higher layer draws over a lower one at any distance, so signs, screens, and road paint laid on a surface stop flickering (z-fighting).
  • Scenes store reversed float depth where the device supports it (Metal, and WebGL2 with EXT_clip_control) and rasterize with a near plane fitted to visible content each frame, so distant surfaces centimetres apart stay distinct. Scene.reversedDepth and Scene.fitNearPlane turn each off; projections and picking keep the authored planes.
  • PerspectiveProjection and PerspectiveCamera accept an infinite far plane (.fscene infiniteFar).
  • Scene.probeDepthConflicts names each pair of nodes that trades pixels as the camera moves, and DebugOverlay.depthConflicts marks them live.
  • Scene.findCoplanarOverlaps lists faces that overlap in one plane, with the length that fixes a repeated piece; debug builds print it once the scene settles.
  • SurfaceDebugChannel.depthGap shows the gap two surfaces need at each pixel to keep their order.
  • Scene.coplanarTieBreak (experimental) gives unlayered exact overlaps a stable winner.
  • Custom ShaderMaterial vertex shaders that write clip depth themselves (a sky at z = w) need Scene.reversedDepth = false; ones that use camera_transform are unaffected.
  • loadScene and the async .fsceneb loaders parse on a background isolate; readFscenebAsync is exported from fscene.dart.
  • Generated mip chains upload in one submission instead of one per level.
  • Texture2D.fromImage(opaque: true) wraps the image's GPU texture instead of reading it back, when no mip chain is needed.
  • Scene.warmUp waits for the raster thread before drawing, so OpenGL ES pipeline compiles no longer stall the UI thread behind a swap.
  • Fixed shadows striping on half-precision GPUs (Mali, Adreno, software GL) since material shaders default to mediump; the shadow map and punctual light data samplers are declared highp again (#234).
  • Fixed lit materials writing NaN at sharp specular highlights viewed at grazing angles on half-precision GPUs, which reflections, depth of field and bloom spread into screen-filling flashes.
  • The bloom and depth of field prefilters map a NaN pixel to black instead of to the half-float maximum, so a stray NaN from any shader can no longer flood the screen with bloom.
  • The shaders behind PhysicallyBasedMaterial extensions and ShadowCatcherMaterial, and the SMAA tables, load on first use instead of delaying every scene's first frame; Scene.preload loads them up front.
  • Generated scenes, textures, and materials rebuild when the flutter_scene code that writes them changes, so an upgrade no longer reuses outputs from the old importer, encoder, or .fmat compiler.
  • Custom vertex attributes no longer have to match between mesh and material. A declared attribute the mesh lacks reads zero instead of crashing or reading garbage, and materials that do not declare a mesh's attribute draw it instead of skipping it (#440).
  • .fmat samplers declared with hint: default_black or hint: default_transparent sample a black or transparent placeholder until a texture is set; both used to sample white.
  • A draw the backend refuses is skipped instead of throwing out of paint, and a pipeline that keeps failing (an OpenGL ES driver that cannot compile its shader) is dropped and reported once (#444).
  • Fixed a crash on M3 and newer Macs and recent iPhones (macOS and iOS 26+) when drawing a lit material with directional shadows; the lit shaders no longer inline their lighting twice (#436).
  • Fixed bloom stamping a grid of square copies around bright highlights and popping as they moved. The first mip averages each texel's whole source footprint, the upsample tent no longer spreads its taps apart, BloomSettings.scatter weights the wider mips instead, and the resolve magnifies bloom with a B-spline.
  • Fixed a native crash on Impeller Vulkan when cached static shadows re-rendered after a resize, a render-target release, or from a second view (#431).
  • Runtime-imported glTF textures honor their sampler's per-axis wrap modes (CLAMP_TO_EDGE, MIRRORED_REPEAT) and NEAREST filters instead of always repeating.
  • ETC1S KTX2 textures (KHR_texture_basisu) transcode to BC or ETC2 blocks on devices that sample them, instead of decoding to rgba8.
  • buildTextures(encoding: TextureEncoding.etc1s(quality: ...)) cooks textures to standard ETC1S KTX2 with a pure-Dart encoder, several times smaller than .fstex; encodings sets it per source.
  • Fixed morph targets on unskinned meshes not deforming on the GPU path; their weights animated but the mesh drew in its base pose (#428).
  • Morphed mesh bounds grow to cover every weight a node draws with, including weights below 0 or above 1, so animated blend shapes are no longer culled while on screen.
  • Setting Node.highlightColor between a tick and the next draw no longer throws inside the selection outline pass; the outline draws white for that frame and takes the color on the next.
  • Punctual light data textures skip uploads that would not change them, saving a synchronous upload per texture per frame on drivers like Android Vulkan.
  • Scene.warmUp(sliceBudget:) compiles pipelines a slice at a time and yields between slices, so a slow device keeps answering input; SceneView(warmUp: true) now warms up this way.
  • Lit .fmat materials can define Light(), Ambient(), and Composite() hooks to shade each light with their own BRDF, adjust the image-based lighting inputs, and combine the lit lobes themselves; Light() also sees each light's table row and shadow slot.
  • Lit .fmat materials can set environment_lighting: false to compile out the engine's environment lighting and supply their own indirect light in Composite().
  • .fmat Vertex() hooks read vertex.model_transform, the object-to-world transform, so a vertex stage can build geometry in object space and place it itself.
  • Fixed image-based lighting adding the specular multiscatter energy to the diffuse lobe instead of subtracting it, which brightened rough surfaces, partly metallic ones most.
  • .fmat alpha_to_coverage: true turns an opaque material's alpha into MSAA coverage for crisp, sort-free cutouts such as foliage cards (a dithered cutout on OpenGL ES and WebGL2), and cuts its depth prepass and shadows by the same alpha.
  • SpotLight.shadowFieldOfView narrows a spot's shadow frustum inside its cone, spending the tile's texels on the core of a wide, soft-edged cone.
  • PointLight.falloffExponent and SpotLight.falloffExponent accept 0, which turns off distance falloff and leaves only the range window; they used to clamp to 0.1.
  • Custom render passes read each casting spot's shadow matrix and atlas tile from RenderPassContext.spotShadows (SpotShadowInfo).
  • RenderTexture(linearColor: true) captures the view's linear HDR scene color with no post-processing or tone mapping, for mirrors and probes composited in HDR; MSAA still applies when the view asks for it.
  • PlanarReflectionCamera is public, so an app can render its own mirror into a RenderTexture with the reflected camera and an oblique near plane at the mirror.
  • Node.renderOrder sorts a node's draws ahead of everything else in their pass, pinning the order of overlapping translucent surfaces that depth sorting gets wrong.
  • MeshPrimitive.drawSelector and InstancedMesh.drawSelector take a MeshDrawSelector that picks how many leading instances and which index range each color, depth, or shadow draw uses, for detail that depends on the pass or camera.
  • An InstancedMesh whose instances stop changing keeps its instance data in a device buffer, so its draws stop copying every instance each frame.
  • Double-sided materials cast shadows from both faces, so cards turned away from the light no longer drop out of the shadow map.
  • Fixed a Metal crash when a .fmat named a declared sampler without reading it (in a comment or code the compiler removes); every declared sampler now stays in the compiled shader.
  • buildScenes and buildMaterials rerun when a source is added or removed in a subdirectory, not only at the top of the discovery root.
  • Image and HDR sky backgrounds no longer draw a one-pixel line where the panorama wraps or a fan of wedges at the poles; the sky picks its mip level from the projection instead of screen derivatives.
  • .fscene documents keep data this build does not read through a load and save, and a property with an unknown tag loads as an inert UnknownValue (exported from fscene.dart) instead of failing the document.
  • Render textures share the screen view's spot and point shadow tiles when no directional light casts, so mirrors no longer re-render them. Their casters draw from the screen view's camera.
  • A .fmat vertex stage that reads custom or per-instance attributes runs in depth and shadow passes too, so geometry it moves casts the shadow it draws.
  • After-scene custom passes that read depth see translucent surfaces that write depth, so a depth-based fog or composite no longer paints over them.
  • .fmat effects_depth: true puts a translucent surface in the depth that depth of field and custom passes read, cut by its alpha, without writing depth in the color pass (fins, hair cards).
  • Scene.debug.splitView shows a second surface debug view left of Scene.debug.split, so a wipe can run between two channels instead of against the lit image.
  • Debug overlays follow Scene.debug.split, drawn only on the view side, and Scene.debug.splitOverlays draws overlays on the other side, so a wireframe can wipe in and out.
  • Depth of field spreads an out-of-focus foreground object's blur past its silhouette over its full blur radius, while the in-focus background beside it stays sharp.
  • SceneView.maxFrameRate caps rendering at an even cadence, every other refresh for 60 on a 120 Hz display.
  • Transient GPU buffers are recycled by idle age, so a loaded scene stops allocating per frame.
  • Geometry.uploadVertexStreams uploads vertex streams in a caller-defined format, and Geometry.setDepthOnlyVertex gives such a mesh a position-only shader for shadows and the depth prepass.
  • A geometry with a declared vertex layout draws depth through its own vertex shader instead of the engine's position-only one, which misread any first stream that was not a float position.
  • Integer vertex formats work on the web backend.
  • MeshGeometry attributes left out at construction bind shared default streams instead of uploading per-mesh defaults.
  • A light that turns by a few degrees refreshes cached shadow cascades one per frame instead of re-rendering all of them in one frame, and keeps their textures.
  • Encoding a frame allocates far less per draw and per node (uniform slots, pipeline lookups, sort depth, transient staging, uniform scratches).
  • A view keeps render targets for its two most recent sizes, so a render scale toggling between two resolutions no longer reallocates.
  • Lit .fmat materials can set directional_light: false to compile out the directional light.
  • Fixed unlit .fmat materials drawing nothing on Impeller Vulkan. Their shader carried an unread radiance block that collided with the vertex stage's frame block in the pipeline layout, which some drivers reject; the block is now compiled out where nothing reads it.
  • Display-referred surfaces. Material.displayReferred (settable on UnlitMaterial) marks a surface whose color is already final screen values, so it draws past the tone curve into its own layer and composites onto the resolved image with its colors unchanged. WidgetComponent turns it on for the material it owns, fixing captured widgets arriving dark and dark tones crushed (#382); pass displayReferred: false for a screen that should read as a lit object. The layer is still occluded by opaque geometry, but takes no exposure, grading, tone mapping, fog, bloom or depth of field, casts no shadow, and does not order against translucent geometry.
  • Web decodes glTF, .fscene, and asset textures in the browser and builds their mip chains on the GPU, so large images no longer render in pieces and no longer stall the main thread.
  • Texture2D.fromEncodedBytes uploads an encoded image. It, Texture2D.fromAsset, and the glTF loaders take a size cap that scales larger images down as they decode.
  • Web wasm builds now enable anisotropic filtering, which a mistyped limit query had silently disabled.
  • Web mesh uploads give vertex and index data a GL buffer each, written straight through, instead of one shared buffer mirrored in Dart and uploaded twice. Meshes outside a GeometryBufferArena upload about half the bytes, and far faster under wasm.
  • Geometry.uploadVertexData takes TypedData rather than ByteData for its vertices and indices, so an upload keeps the element type it was packed as. Subclasses overriding it must widen their parameters to match.
  • OrthographicCamera and OrthographicProjection add parallel projection, sized by OrthographicSize (full world extents with height, width, contain, cover, or stretch fitting, or a fixed pixelsPerUnit) plus zoom, offset, and a near that may be negative.
  • CameraProjection.getProjectionMatrixForViewport lets a projection size its volume from the view's logical size, which rendering and picking both resolve against.
  • Shadows, AO, SSR, TAA, depth of field, god rays, GI, planar reflections, froxel lighting, LOD, and custom-pass depth/normals all work under orthographic and custom projections.
  • #include <view_projection.glsl> reconstructs view positions for any projection; PostCameraInfo carries its axis offset and orthographic flag in the formerly unused w slots.
  • .fscene cameras serialize orthographic projections, the editor draws their view volume, and OrbitCameraController dollies an orthographic camera by zoom.
  • Gaussian splats sort back to front and evaluate view-dependent color under orthographic cameras.
  • BREAKING: Lighting takes projectionScaleX/Y, projectionOffsetX/Y, and orthographic in place of tanHalfFovX/Y, which remain as deprecated getters.
  • The vertex stage writes the world normal and tangent varyings at unit length, so a model authored at a small scale no longer lights black on GPUs that flush its tiny normals to zero in mediump varyings.
  • Screen-size LOD applies to every perspective camera, not only PerspectiveCamera.
  • Apps ship each engine shader bundle once and only for their own platform; a shared pub cache used to ship every platform's bundles, twice with data assets enabled.
  • dart run flutter_scene:init lists a directory per platform in the app's pubspec so the app's own generated shaders ship only for their platform too; rerun it in an existing app.
  • Scene.addTickListener runs a SceneTickListener at the start of every tick and before every fixed step, ahead of all components, for per-frame sampling such as input.
  • FlyCameraController.setMoveInput drives movement from a gamepad, touch controls, or an input system, summing with the keys and keeping analog magnitude.
  • Cascaded shadows skip casters that cannot shadow anything the camera shades, cutting shadow-pass draws with no change to the rendered image.
  • Skinned vertex shaders normalize the four joint weights, so a mesh exported with weights summing to slightly under 1 no longer stretches toward the origin as it moves away from it.
  • releaseTransientRenderTargets() drops the render graph's pooled attachments (shadow atlas, scene color, depth, the post-process chain) and returns the bytes released; they reallocate on the next frame that needs them.
  • Pooled render targets are released automatically on platform memory pressure (releaseRenderTargetsOnMemoryPressure turns that off), and takeMemoryReport() reports them as a render targets category.
  • Surface debug views. Scene.debug.view shows a resolved material channel (base color, roughness, metallic, every physical field), a geometry attribute (normals, tangents, UV sets, vertex color, face orientation, UV checkers), an identity color per object or material, or a validation flag (NaN/Inf, albedo range, non-binary metallic, missing tangents, UV range) in place of the lit result, on every material including .fmat ones, at runtime in any build. Scene.debug.split compares a view against the lit image, DebugView carries a range, gain, and out-of-range policy, Node.debugView overrides or excludes a subtree, and Scene.debug.overlays adds a wireframe drawn through each mesh's own vertex path. DebugViewRegistry lists the views by id for tools; a .fmat shows any value through material.debug and the custom channel.
  • GeometryBufferArena fills the first block with room instead of abandoning a block's tail once an upload does not fit, and its docs now say it never reclaims space.
  • DebugDraw.flushInto rebuilds one updatable line geometry in place each frame (DebugDraw.createGeometry makes it) instead of allocating a geometry per flush; the Kit example uses it.
  • Scene.renderStats reports every frame's draw, instance, vertex, culling, batching, and pipeline counters per view and per pass with CPU times, plus a bounded history; the render graph also emits a dart:developer timeline event per pass for DevTools.
  • ShaderReflection parses any loaded shader bundle into per-shader, per-backend reflection (inputs, uniform block layouts, texture bindings, entrypoint) and the compiled source for every backend it holds, maps shader objects back to their names, and decodes packed uniform bytes through the layout.
  • A render graph capture now records every draw call (CapturedDraw, with node, material, shader pair, pipeline, counts, batch size, why an opaque run ended, and the uniform blocks bound for it) and every skipped item with its reason.
  • RenderGraphCaptureResult.toJson and fromJson serialize a capture with PNG thumbnails and decoded uniforms, so a capture can be attached to a bug report and opened elsewhere.
  • .fmat compile failures expose parsed diagnostics (FmatCompileException.diagnostics) with line numbers, and shaderSourceWindow renders the marked source around one.
  • Projected box decals via DecalNode, an oriented projection volume that paints a .fmat material onto whatever opaque surfaces it intersects (scorch marks, splats), with no mesh work at the impact site.
  • Radial screen distortion pulses via Scene.screenDistortion, expanding shockwave rings that warp the composed image with optional chromatic fringing.
  • .fmat materials accept blending: additive alongside opaque/alpha, and depth_write/depth_test configure the translucent depth state.
  • Barycentric wireframe helpers, #include <wireframe.glsl> plus MeshData.unweld barycentric attributes, for wireframe and scan-line overlays on any mesh.
  • MaterialGroup pushes one parameter value to a heterogeneous set of materials, skipping names a member does not declare.
  • Unlit .fmat materials may declare engine_inputs; the screen accessors gain GetSceneWorldPosition, materials that read scene depth get it at full resolution, and scene_color_reach bounds a reader so disjoint readers share one scene-color capture.
  • A draw whose render pipeline the backend rejects is skipped with a one-time console message naming the material and geometry, instead of failing the whole frame from inside paint.
  • A render pipeline build that stalls a frame (8ms or more, always on its first draw) is reported in debug builds with the material and geometry that triggered it, so the fix (drawing it once behind a load screen) has a target.
  • Scene.shadowCasterOverflowCount and Scene.isShadowCasterGranted report the lights whose authored shadow is not being drawn, since the shared shadow atlas caps shadow-casting spots and point lights (four each) and the lights past the cap previously went dark silently.
  • Scene.globalIlluminationProbeGrid reports the probe lattice the irradiance field resolved this frame (origin, spacing, and probe counts as an IrradianceProbeGrid), so an editor or debug overlay can draw where the probes actually are instead of re-deriving a placement that depends on the volume mode, camera, and scene bounds.
  • Node.shadowCastingMode selects how a node's meshes cast: on, off (excluded from every shadow map while still drawing), doubleSided (casts from every face, closing light leaks through single-sided geometry), or shadowsOnly (casts while staying out of the color image, for proxy and off-screen occluders). Replaces Node.castsShadows, which stays as a deprecated two-state view of it, and is carried by .fscene documents and the editor's node inspector.
  • SpotLight.shadowCasterChannelMask and PointLight.shadowCasterChannelMask drop casters from one light's shadow map by light channel, matching DirectionalLight's, so a node can be lit by a light without casting into it.
  • Point lights cast shadows: PointLight.castsShadow plus per-light resolution, near, bias, softness, and caster-face controls. Six 90-degree cube faces render into the shared shadow atlas (four quarter-resolution faces per tile, two tiles per light) and the lit shader picks the face by the dominant axis of the light-to-fragment vector, so point shadows cost no new samplers or shader variants. A limited number of point casters render per frame; the rest shade unshadowed.
  • Scene.smaa (SmaaSettings) tunes SMAA's edge threshold, search steps, and corner rounding at runtime, previously compile-time constants.
  • .fscene stage effects now apply and serialize temporal anti-aliasing tuning and SMAA quality (the fields previously round-tripped through documents without reaching the scene).
  • The bundled agent skills cover this release (flutter_scene-idioms v14, flutter_scene-kit v7, flutter_scene-looks v5, flutter_scene-performance v3, flutter_scene-procedural v4, flutter_scene-verification-loop v4), including game input through flutter_scene_input. Run dart run flutter_scene:skills to update installed copies.
  • Switching Scene.antiAliasingMode from msaa to another mode on the OpenGL ES backend no longer renders without a depth test (far surfaces drew over near ones); pooled color targets now keep a separate texture per depth attachment setup.
  • Scene.renderQuality (RenderQualitySettings) puts the automatic settings on a quality ladder: AntiAliasingMode.auto and the scene color capture budget follow the tier (web and Linux desktop start at medium, everything else at high), and adaptive lowers the render scale and then the tier from measured frame periods when frames overrun targetFrameRate, recovering when they keep it. Scene.effectiveRenderQualityTier and Scene.adaptiveRenderScale report what is in effect.
  • Scene.sceneColorCaptureBatches caps how many scene color captures a frame opens for overlapping transmissive readers (1 makes them all share one snapshot), the biggest lever on tiled and low-end GPUs; null follows the quality tier.
  • Smooth transmission (zero roughness, or an index of refraction of 1) no longer builds the rough-transmission filter pyramid every capture, which the shader never sampled.
  • Material fragment shaders default to mediump with explicit highp on positions, coordinates, depth, and the HDR accumulators (see shaders/PRECISION.md), so GPUs that run half precision faster (Mali, Adreno, the web) shade the standard and physical materials at their fp16 rate. Metal is unaffected. A .fmat body inherits the default and should declare highp on positions or coordinates it computes itself; noise.glsl runs in highp regardless, since its parity with the Dart port is a float32 contract.
  • Scene.maxGpuFramesInFlight paces the GPU: once that many frames of GPU work are still running, a screen view presents its previous image instead of encoding a new frame, so a GPU-bound scene no longer stalls the UI thread on the Vulkan and Metal backends, which queue work from the calling thread. The default of 1 keeps the UI thread free for about a tenth of GPU throughput; 2 keeps the throughput but only halves the stall. Scene.pacedFrameCount counts the frames it held, and Scene.repaintRequested notifies when the held frame can be painted, so a view that repaints only on demand still shows it (SceneView listens).
  • ThirdPersonControllerComponent.rotatesToMovement keeps the node's authored rotation while still moving it, and yaw exposes the smoothed heading, now seeded from the node's rotation instead of snapping to zero on the first step.
  • DebugDraw.colliders wireframes every physics collider in a node subtree, posed the way the simulation sees it, and DebugDraw.shape draws one Shape at a given transform. Triggers draw in their own color.
  • SceneView measures onTick deltas with a wall clock instead of the ticker's frame-begin timestamps, whose deltas alternate between tiny and double-length values under GPU load and stagger any motion integrated against them. The new SceneView.clock injects a clock for tests and time-controlling drivers; the ambient package:clock clock (faked under flutter_test) is the default.
  • Scene.punctualLightOverflowCount reports how many drawable items dropped punctual lights last frame because more lights reached them than the per-object budget shades.
  • A -split<N> suffix on a glTF node name splits its mesh into grid-cell child meshes at import (whole triangles binned by world-space centroid into N-unit cells, children named Ground_x0_z3), so a level-spanning mesh culls and receives punctual lights per cell; the split reapplies on every re-import.
  • Punctual lights shade through per-view froxel clustering (screen tiles crossed with exponential depth slices, each shading only the lights that reach it), removing the per-object light cap, so a large mesh reached by many lights shades them all and no draw carries light state. Perspective views cluster automatically; orthographic views and frames using light channel masks keep the per-object lists, and Scene.punctualLightClustering disables clustering for comparison.
  • The punctual light loop is dynamically bounded (every compiled shader dialect is GLSL ES 3.00 or newer), so drivers cannot unroll it, and a froxel holds up to 255 lights.
  • Normals transform by the inverse-transpose of the model matrix, so a node under non-uniform scale or shear lights the same as the shape modelled at that size.
  • BREAKING for classes that implements rather than extends these types: Geometry gained uploadVertexStreams and setDepthOnlyVertex, Material gained depthLayer and displayReferred, Camera gained projectToScreenUv, CameraProjection gained getProjectionMatrixForViewport, and ComponentCodec gained writeLiveProperty, all with default implementations, alongside several internal engine members. IrradianceFieldBakeStepper takes a required pointShadowFrame, and its renderView callback receives it.
  • BREAKING: Scene.initializeStaticResources() completes with its error instead of normally, so a failed shader-bundle or material load now reaches the caller rather than only dart:developer log() (which web never showed). Awaiting it without a try/catch throws where it previously continued; wrap the call, or use a SceneView, which reports the failure and stays on its loadingBuilder. baseShaderLibrary names that cause too, rather than telling you to await the call that already failed.
  • A rejected collider names the node it was on and the degenerate cases that cause the rejection.
  • Batching comparators cache their identity keys, sceneSortDepth allocates nothing, and instance batch objects are pooled, cutting per-frame work that multiplies across shadow, depth-prepass, and reflection views.
  • Allow code_assets 2.x.
  • Fix vertex-attribute traffic on the web backend growing quadratically across same-pipeline draws.
  • On web, shader bundles and their generated JSON revalidate with the server on load, so a browser cache can no longer pair a previous build's bundle with new Dart code.
  • Spatial audio follows a SceneView's camera (or cameraBuilder) when no scene camera or AudioListener is set, instead of a listener stuck at the origin.
  • Fix the mip-sampling probe reporting the base-mip clamp on hardware that samples mip chains correctly, about one cold run in three on a Mali-G57. It read its render target back without waiting for the draw, which the OpenGL ES backend runs on the raster thread, so an unrendered target answered with whatever its memory held. A false reading dropped every uploaded mip chain for the life of the process and moved image-based lighting to the radiance atlas, silently.
  • The split-sum environment BRDF ships precomputed as assets/dfg.bin instead of being integrated at every cold start, taking Scene.initializeStaticResources() from 274ms to under 20ms on a Galaxy A16. The table is a constant, so the asset is bit-identical to the integration; an app that trims it still gets a correct table, built off the render isolate and with a message saying what it costs.
  • The CPU mip build is roughly twice as fast for color and normal textures: the content switch moved out of the per-texel loop, the normal filter's taps are unrolled, and the sRGB decode reads a 256-entry table instead of calling pow three times per texel. A 2048-square color chain drops from 141ms to 55ms.
  • Radiance prefiltering fills one roughness band per frame instead of submitting the whole atlas as a single draw. That draw was ~850ms of GPU work on a Mali-G57, and because it lands inside a Flutter frame the display froze until the driver's buffer-queue timeout; a cold start that built two environments spent 1.7 seconds of 3.1 frozen. The texture is returned immediately and sharpens over the following frames, and EnvironmentMap.radianceComplete waits for the finished result.
  • The radiance prefilter takes its mirror band directly rather than integrating a delta lobe 256 times, and generates its samples from a rank-1 lattice instead of a float-emulated radical inverse whose loop cost more than the texture fetches it fed. Together about half the prefilter's GPU cost, with no change to the rendered result.
  • Fixed animating glTF rigs with a joint named root, whose channels drove the import root instead, dropping the Z flip and leaving the joint still.
  • On web, EnvironmentMap.fromEquirectImageAsset and imageFromBytes(maxWidth:) no longer fail with "ImageDescriptor.width is not supported".
  • The Dart noise (FastNoiseLite, noiseCurl3, particle turbulence) now matches native exactly on web; 3D OpenSimplex2 previously returned huge values there, driving particles to NaN.
  • On web, an int .fmat parameter saved into an .fscene document keeps its int type instead of becoming a double.

Development funded by our sponsors, Primio (Silver).