Repository navigation
flutter_scene 0.24.0
·
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.radiusandSpotLight.radiusgive 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
PhysicalMaterialwithout a base color texture masks its shadows and depth by its vertex and factor alpha. Material.depthLayer(.fmatdepth_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.reversedDepthandScene.fitNearPlaneturn each off; projections and picking keep the authored planes. PerspectiveProjectionandPerspectiveCameraaccept an infinite far plane (.fsceneinfiniteFar).Scene.probeDepthConflictsnames each pair of nodes that trades pixels as the camera moves, andDebugOverlay.depthConflictsmarks them live.Scene.findCoplanarOverlapslists faces that overlap in one plane, with the length that fixes a repeated piece; debug builds print it once the scene settles.SurfaceDebugChannel.depthGapshows the gap two surfaces need at each pixel to keep their order.Scene.coplanarTieBreak(experimental) gives unlayered exact overlaps a stable winner.- Custom
ShaderMaterialvertex shaders that write clip depth themselves (a sky atz = w) needScene.reversedDepth = false; ones that usecamera_transformare unaffected. loadSceneand the async.fscenebloaders parse on a background isolate;readFscenebAsyncis exported fromfscene.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.warmUpwaits 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
PhysicallyBasedMaterialextensions andShadowCatcherMaterial, and the SMAA tables, load on first use instead of delaying every scene's first frame;Scene.preloadloads 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
.fmatcompiler. - 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).
.fmatsamplers declared withhint: default_blackorhint: default_transparentsample 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.scatterweights 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) andNEARESTfilters 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;encodingssets 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.highlightColorbetween 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
.fmatmaterials can defineLight(),Ambient(), andComposite()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
.fmatmaterials can setenvironment_lighting: falseto compile out the engine's environment lighting and supply their own indirect light inComposite(). .fmatVertex()hooks readvertex.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.
.fmatalpha_to_coverage: trueturns 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.shadowFieldOfViewnarrows a spot's shadow frustum inside its cone, spending the tile's texels on the core of a wide, soft-edged cone.PointLight.falloffExponentandSpotLight.falloffExponentaccept 0, which turns off distance falloff and leaves only therangewindow; 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.PlanarReflectionCamerais public, so an app can render its own mirror into aRenderTexturewith the reflected camera and an oblique near plane at the mirror.Node.renderOrdersorts a node's draws ahead of everything else in their pass, pinning the order of overlapping translucent surfaces that depth sorting gets wrong.MeshPrimitive.drawSelectorandInstancedMesh.drawSelectortake aMeshDrawSelectorthat 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
InstancedMeshwhose 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
.fmatnamed a declared sampler without reading it (in a comment or code the compiler removes); every declared sampler now stays in the compiled shader. buildScenesandbuildMaterialsrerun 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.
.fscenedocuments keep data this build does not read through a load and save, and a property with an unknown tag loads as an inertUnknownValue(exported fromfscene.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
.fmatvertex 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.
.fmateffects_depth: trueputs 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.splitViewshows a second surface debug view left ofScene.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, andScene.debug.splitOverlaysdraws 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.maxFrameRatecaps 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.uploadVertexStreamsuploads vertex streams in a caller-defined format, andGeometry.setDepthOnlyVertexgives 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.
MeshGeometryattributes 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
.fmatmaterials can setdirectional_light: falseto compile out the directional light. - Fixed unlit
.fmatmaterials 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 onUnlitMaterial) 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.WidgetComponentturns it on for the material it owns, fixing captured widgets arriving dark and dark tones crushed (#382); passdisplayReferred: falsefor 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.fromEncodedBytesuploads 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
GeometryBufferArenaupload about half the bytes, and far faster under wasm. Geometry.uploadVertexDatatakesTypedDatarather thanByteDatafor its vertices and indices, so an upload keeps the element type it was packed as. Subclasses overriding it must widen their parameters to match.OrthographicCameraandOrthographicProjectionadd parallel projection, sized byOrthographicSize(full world extents with height, width, contain, cover, or stretch fitting, or a fixedpixelsPerUnit) pluszoom,offset, and anearthat may be negative.CameraProjection.getProjectionMatrixForViewportlets 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;PostCameraInfocarries its axis offset and orthographic flag in the formerly unusedwslots..fscenecameras serialize orthographic projections, the editor draws their view volume, andOrbitCameraControllerdollies an orthographic camera by zoom.- Gaussian splats sort back to front and evaluate view-dependent color under orthographic cameras.
- BREAKING:
LightingtakesprojectionScaleX/Y,projectionOffsetX/Y, andorthographicin place oftanHalfFovX/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:initlists 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.addTickListenerruns aSceneTickListenerat the start of every tick and before every fixed step, ahead of all components, for per-frame sampling such as input.FlyCameraController.setMoveInputdrives 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 (
releaseRenderTargetsOnMemoryPressureturns that off), andtakeMemoryReport()reports them as arender targetscategory. - Surface debug views.
Scene.debug.viewshows 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.fmatones, at runtime in any build.Scene.debug.splitcompares a view against the lit image,DebugViewcarries a range, gain, and out-of-range policy,Node.debugViewoverrides or excludes a subtree, andScene.debug.overlaysadds a wireframe drawn through each mesh's own vertex path.DebugViewRegistrylists the views by id for tools; a.fmatshows any value throughmaterial.debugand thecustomchannel. GeometryBufferArenafills 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.flushIntorebuilds one updatable line geometry in place each frame (DebugDraw.createGeometrymakes it) instead of allocating a geometry per flush; the Kit example uses it.Scene.renderStatsreports 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 adart:developertimeline event per pass for DevTools.ShaderReflectionparses 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.toJsonandfromJsonserialize a capture with PNG thumbnails and decoded uniforms, so a capture can be attached to a bug report and opened elsewhere..fmatcompile failures expose parsed diagnostics (FmatCompileException.diagnostics) with line numbers, andshaderSourceWindowrenders the marked source around one.- Projected box decals via
DecalNode, an oriented projection volume that paints a.fmatmaterial 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. .fmatmaterials acceptblending: additivealongsideopaque/alpha, anddepth_write/depth_testconfigure the translucent depth state.- Barycentric wireframe helpers,
#include <wireframe.glsl>plusMeshData.unweldbarycentric attributes, for wireframe and scan-line overlays on any mesh. MaterialGrouppushes one parameter value to a heterogeneous set of materials, skipping names a member does not declare.- Unlit
.fmatmaterials may declareengine_inputs; the screen accessors gainGetSceneWorldPosition, materials that read scene depth get it at full resolution, andscene_color_reachbounds 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.shadowCasterOverflowCountandScene.isShadowCasterGrantedreport 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.globalIlluminationProbeGridreports the probe lattice the irradiance field resolved this frame (origin, spacing, and probe counts as anIrradianceProbeGrid), 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.shadowCastingModeselects 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), orshadowsOnly(casts while staying out of the color image, for proxy and off-screen occluders). ReplacesNode.castsShadows, which stays as a deprecated two-state view of it, and is carried by.fscenedocuments and the editor's node inspector.SpotLight.shadowCasterChannelMaskandPointLight.shadowCasterChannelMaskdrop casters from one light's shadow map by light channel, matchingDirectionalLight's, so a node can be lit by a light without casting into it.- Point lights cast shadows:
PointLight.castsShadowplus 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..fscenestage 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-idiomsv14,flutter_scene-kitv7,flutter_scene-looksv5,flutter_scene-performancev3,flutter_scene-proceduralv4,flutter_scene-verification-loopv4), including game input throughflutter_scene_input. Rundart run flutter_scene:skillsto update installed copies. - Switching
Scene.antiAliasingModefrommsaato 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.autoand the scene color capture budget follow the tier (web and Linux desktop start at medium, everything else at high), andadaptivelowers the render scale and then the tier from measured frame periods when frames overruntargetFrameRate, recovering when they keep it.Scene.effectiveRenderQualityTierandScene.adaptiveRenderScalereport what is in effect.Scene.sceneColorCaptureBatchescaps 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.fmatbody inherits the default and should declarehighpon positions or coordinates it computes itself;noise.glslruns in highp regardless, since its parity with the Dart port is a float32 contract. Scene.maxGpuFramesInFlightpaces 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.pacedFrameCountcounts the frames it held, andScene.repaintRequestednotifies when the held frame can be painted, so a view that repaints only on demand still shows it (SceneViewlistens).ThirdPersonControllerComponent.rotatesToMovementkeeps the node's authored rotation while still moving it, andyawexposes the smoothed heading, now seeded from the node's rotation instead of snapping to zero on the first step.DebugDraw.colliderswireframes every physics collider in a node subtree, posed the way the simulation sees it, andDebugDraw.shapedraws oneShapeat a given transform. Triggers draw in their own color.SceneViewmeasuresonTickdeltas 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 newSceneView.clockinjects a clock for tests and time-controlling drivers; the ambientpackage:clockclock (faked underflutter_test) is the default.Scene.punctualLightOverflowCountreports 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 intoN-unit cells, children namedGround_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.punctualLightClusteringdisables 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
implementsrather than extends these types:GeometrygaineduploadVertexStreamsandsetDepthOnlyVertex,MaterialgaineddepthLayeranddisplayReferred,CameragainedprojectToScreenUv,CameraProjectiongainedgetProjectionMatrixForViewport, andComponentCodecgainedwriteLiveProperty, all with default implementations, alongside several internal engine members.IrradianceFieldBakeSteppertakes a requiredpointShadowFrame, and itsrenderViewcallback 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 onlydart:developerlog()(which web never showed). Awaiting it without atry/catchthrows where it previously continued; wrap the call, or use aSceneView, which reports the failure and stays on itsloadingBuilder.baseShaderLibrarynames 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,
sceneSortDepthallocates nothing, and instance batch objects are pooled, cutting per-frame work that multiplies across shadow, depth-prepass, and reflection views. - Allow
code_assets2.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 (orcameraBuilder) when no scene camera orAudioListeneris 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.bininstead of being integrated at every cold start, takingScene.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
powthree 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.radianceCompletewaits 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.fromEquirectImageAssetandimageFromBytes(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
.fmatparameter saved into an.fscenedocument keeps its int type instead of becoming a double.
Development funded by our sponsors, Primio (Silver).