frame method
Renders one frame.
Implementation
@override
Future<void> frame() async {
if (_isResizing || _inFrame || _camera == null) return;
final hasBackground = _backgroundTexture != null;
final atlas = _source.atlas;
final orderTexture = _source.orderTexture;
final quatTexture = _source.quatTexture;
final colorTexture = _source.colorTexture;
final shTexture = _source.shTexture;
final hasSplats = atlas != null &&
orderTexture != null &&
quatTexture != null &&
colorTexture != null &&
shTexture != null &&
_source.splatCount > 0;
if (!hasBackground && !hasSplats) {
return;
}
_inFrame = true;
final watch = Stopwatch()..start();
try {
_requestSort();
_frameUniforms.reset();
// SH resolve pass: evaluate per-splat SH colors into a texture the splat
// pass samples once, instead of 12 SH fetches + eval per vertex. It runs
// on its own command buffer (submitted first) because flutter_gpu allows
// only one render pass to encode per command buffer at a time.
_resolvedActive = false;
if (hasSplats && !_highQualitySH) {
_ensureResolvedTexture();
if (_shouldResolveNow()) {
final resolveBuffer = gpu.gpuContext.createCommandBuffer();
resolveBuffer.createRenderPass(
gpu.RenderTarget.singleColor(
gpu.ColorAttachment(
texture: _resolvedColorTexture!,
clearValue: vm.Vector4(0, 0, 0, 0),
),
),
)
..setViewport(
gpu.Viewport(
width: GsConst.splatsPerRow,
height: _resolvedHeight,
),
)
..setDepthWriteEnable(false)
..setColorBlendEnable(false)
..setCullMode(gpu.CullMode.none)
..bindPipeline(_shResolvePipeline)
..bindUniform(
_shResolveInfoSlot,
_frameUniforms.emplace(_packShResolveInfo()),
)
..bindTexture(_shResolveShSlot, shTexture, sampler: _nearestSampler)
..bindTexture(
_shResolveColorSlot,
colorTexture,
sampler: _nearestSampler,
)
..bindVertexBuffer(
gpu.BufferView(
_skyVertexBuffer,
offsetInBytes: 0,
lengthInBytes: _skyVertexBufferBytes,
),
_skyVertexCount,
)
..draw();
resolveBuffer.submit();
_lastResolvedDirection = _viewDirectionFor(_camera!);
_resolveDataDirty = false;
}
_resolvedActive = _resolvedColorTexture != null;
}
final commandBuffer = gpu.gpuContext.createCommandBuffer();
final renderPass = commandBuffer.createRenderPass(
gpu.RenderTarget.singleColor(
gpu.ColorAttachment(
texture: _target,
clearValue: vm.Vector4(0, 0, 0, 0),
),
),
)
..setViewport(
gpu.Viewport(width: _target.width, height: _target.height),
)
..setDepthWriteEnable(false)
..setColorBlendEquation(gpu.ColorBlendEquation())
..setCullMode(gpu.CullMode.none);
if (hasBackground) {
renderPass
..bindPipeline(_skyPipeline)
..setColorBlendEnable(false)
..bindUniform(_skyInfoSlot, _frameUniforms.emplace(_packSkyInfo()))
..bindTexture(
_skyBgSlot,
_backgroundTexture!,
sampler: _linearSampler,
)
..bindVertexBuffer(
gpu.BufferView(
_skyVertexBuffer,
offsetInBytes: 0,
lengthInBytes: _skyVertexBufferBytes,
),
_skyVertexCount,
)
..draw();
}
if (hasSplats) {
final frameInfo = _frameUniforms.emplace(_packFrameInfo());
renderPass
..bindPipeline(_pipeline)
..setColorBlendEnable(true)
..bindUniform(_frameInfoSlot, frameInfo)
..bindTexture(_atlasSlot, atlas, sampler: _nearestSampler)
..bindTexture(_orderSlot, orderTexture, sampler: _nearestSampler)
..bindTexture(_quatSlot, quatTexture, sampler: _nearestSampler)
..bindTexture(_colorSlot, colorTexture, sampler: _nearestSampler)
..bindTexture(_shSlot, shTexture, sampler: _nearestSampler)
// u_resolved_texture must always be bound; when high-quality SH is
// active it goes unsampled, so the color texture stands in for it.
..bindTexture(
_resolvedSlot,
_resolvedActive ? _resolvedColorTexture! : colorTexture,
sampler: _nearestSampler,
);
// Skip behind-camera splats, which the sorter parks in the order tail.
final fullCount = _source.splatCount;
final drawCount = _visibleCount < 0
? fullCount
: (_visibleCount < fullCount ? _visibleCount : fullCount);
for (var baseSplat = 0;
baseSplat < drawCount;
baseSplat += _splatsPerBatch) {
final remaining = drawCount - baseSplat;
final splatsInBatch =
remaining < _splatsPerBatch ? remaining : _splatsPerBatch;
renderPass
..bindUniform(
_batchInfoSlot,
_frameUniforms.emplace(_packBatchInfo(baseSplat)),
)
..bindVertexBuffer(
gpu.BufferView(
_quadVertexBuffer,
offsetInBytes: 0,
lengthInBytes: _quadVertexBufferBytes,
),
splatsInBatch * _verticesPerSplat,
)
..draw();
}
}
commandBuffer.submit();
final previousFrame = _lastFrame;
_lastFrame = _target.asImage();
previousFrame?.dispose();
_lastFrameTime = DateTime.timestamp();
} finally {
watch.stop();
_cpuMs.add(watch.elapsedMicroseconds / 1000);
_inFrame = false;
}
}