Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions examples/smoke_render/lib/smoke_scenes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,57 @@ final List<SmokeScene> kSmokeScenes = <SmokeScene>[
scene.add(node);
return (scene: scene, camera: _camera());
}),
// Two instanced meshes plus a plain mesh, all sharing a lit pipeline.
// Covers hardware instancing and, because the draws share a pipeline, the
// engine-lighting bind bookkeeping between instanced and non-instanced
// draws; a lost bind reads as black or missing geometry here.
SmokeScene('instanced_lighting', () {
final scene = Scene();
scene.add(
Node()..addComponent(
DirectionalLightComponent(
DirectionalLight(direction: vm.Vector3(-0.4, -1.0, -0.35)),
),
),
);
// Non-instanced receiver, drawn from the same pipeline as the instances.
scene.add(
Node(
mesh: Mesh(
PlaneGeometry(width: 4.0, depth: 4.0),
PhysicallyBasedMaterial()
..baseColorFactor = vm.Vector4(0.78, 0.78, 0.80, 1.0)
..metallicFactor = 0.0
..roughnessFactor = 0.9
..vertexColorWeight = 0.0,
),
)..localTransform = vm.Matrix4.translation(vm.Vector3(0, -0.6, 0)),
);
// Two separate instanced meshes, so the second one draws after a
// same-pipeline run has already started.
for (var mesh = 0; mesh < 2; mesh++) {
final instanced = InstancedMesh(
geometry: CuboidGeometry(vm.Vector3(0.5, 0.5, 0.5)),
material: PhysicallyBasedMaterial()
..baseColorFactor = mesh == 0
? vm.Vector4(0.85, 0.35, 0.20, 1.0)
: vm.Vector4(0.20, 0.55, 0.90, 1.0)
..metallicFactor = 0.1
..roughnessFactor = 0.45
..vertexColorWeight = 0.0,
);
for (var i = 0; i < 3; i++) {
instanced.addInstance(
vm.Matrix4.translation(
vm.Vector3((i - 1) * 0.85, mesh * 0.75, mesh * 0.6 - 0.3),
) *
vm.Matrix4.rotationY(0.5 + i * 0.3),
);
}
scene.add(Node()..addComponent(InstancedMeshComponent(instanced)));
}
return (scene: scene, camera: _camera());
}),
// A directional light casting a shadow from a floating cuboid onto a
// ground plane. Exercises the ShadowPass (a depth-only shadow-map pass)
// and the lit material's shadow sampling, which the other scenes don't.
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_scene/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.20.1

* Fixed instanced meshes losing their lighting, shadow, and fog bindings on GLES, which drew them (and any mesh sharing their pipeline) black.

## 0.20.0

* The `.fscene` document core (document model, stable ids, JSON/binary serialization, prefab composition, diffing) moved to the new pure-Dart `scene` package; `flutter_scene` re-exports it, so existing imports are unchanged.
Expand Down
37 changes: 31 additions & 6 deletions packages/flutter_scene/lib/src/gpu/web/render_pass.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ base class RenderPass {
/// through the VAO cache when the draw is issued: (view, slot, whether the
/// stream is instance-rate).
final List<(BufferView, int, bool)> _pendingVertexBindings = [];

/// Uniform-block binding points, and texture units mapped to the GL target
/// bound there, occupied since the last [clearBindings]. Recorded so the
/// clear can release exactly what it handed out.
final Set<int> _boundUniformBlocks = {};
final Map<int, int> _boundTextureUnits = {};
PrimitiveType _primitiveType = PrimitiveType.triangle;
BufferView? _inlineVertexBufferView;

Expand Down Expand Up @@ -582,6 +588,7 @@ base class RenderPass {
bufferView.offsetInBytes,
lengthInBytes,
);
_boundUniformBlocks.add(blockBinding);
return;
}

Expand Down Expand Up @@ -679,6 +686,7 @@ base class RenderPass {
final target = texture.glTarget;
gl.activeTexture(web.WebGL2RenderingContext.TEXTURE0 + unit);
gl.bindTexture(target, texture.glTexture);
_boundTextureUnits[unit] = target;
if (sampler != null) {
// Sampler parameters are per-texture-object GL state; skip the ones
// already applied to this texture. Per-draw texParameteri calls are
Expand Down Expand Up @@ -767,16 +775,33 @@ base class RenderPass {
}

void clearBindings() {
// Clears per-draw resource bindings (vertex/index buffers, uniforms,
// textures) but NOT the bound pipeline - matching flutter_gpu, where the
// pipeline persists until the next bindPipeline. flutter_scene's encoder
// relies on this: it caches the last pipeline and skips re-binding it
// across consecutive draws with the same material, so nulling it here
// would leave later draws with no pipeline.
// Drops every per-draw resource binding, leaving the bound pipeline in
// place (matching flutter_gpu, where the pipeline persists until the next
// bindPipeline; the encoder skips rebinding a pipeline it already bound,
// so nulling it here would leave later draws without one).
//
// Releasing the uniform blocks and texture units matters beyond tidiness.
// They are GL context state rather than per-draw descriptors, so leaving
// them attached lets a draw that should have rebound a slot read the
// previous draw's resource and render correctly, which hides bind
// lifetime bugs here that break on the native backends.
final gl = _gpuContext._gl;
if (_vao != null) {
gl.bindVertexArray(null);
}
for (final binding in _boundUniformBlocks) {
gl.bindBufferBase(
web.WebGL2RenderingContext.UNIFORM_BUFFER,
binding,
null,
);
}
_boundUniformBlocks.clear();
for (final entry in _boundTextureUnits.entries) {
gl.activeTexture(web.WebGL2RenderingContext.TEXTURE0 + entry.key);
gl.bindTexture(entry.value, null);
}
_boundTextureUnits.clear();
_inlineVertexBufferView = null;
_indexBufferView = null;
_pendingVertexBindings.clear();
Expand Down
14 changes: 11 additions & 3 deletions packages/flutter_scene/lib/src/scene_encoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ base class SceneEncoder {
_boundPipeline = pipeline;
}

// Drops the pass's bindings. The engine-lighting memo tracks what is
// already bound on the pass, so it has to be forgotten here or the next
// draw skips rebinding slots that were just wiped; never call
// `clearBindings` directly.
void _clearBindings() {
_renderPass.clearBindings();
EngineLightingUniforms.invalidateBindMemo();
}

void _encode(
gpu.RenderPipeline pipeline,
Matrix4 worldTransform,
Expand All @@ -376,8 +385,7 @@ base class SceneEncoder {
// FFI, and re-issuing the full set per item dominated main-thread frame
// time in draw-heavy scenes.
if (!identical(_boundPipeline, pipeline)) {
_renderPass.clearBindings();
EngineLightingUniforms.invalidateBindMemo();
_clearBindings();
}
_bindPipeline(pipeline);
// The material reads its cross-fade coverage from this transient field as
Expand Down Expand Up @@ -438,7 +446,7 @@ base class SceneEncoder {
bool windingFlipped,
double fade,
) {
_renderPass.clearBindings();
_clearBindings();
_bindPipeline(pipeline);
material.lodFade = fade;
final materialVertex = material.materialVertexShader(
Expand Down