frame method

  1. @override
Future<void> frame()
override

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;
  }
}