draw method

  1. @override
void draw({
  1. int instanceCount = 1,
  2. int firstIndex = 0,
  3. int? indexCount,
})
override

Draws what is bound. Always indexed — there is no non-indexed path in this engine, which is why the debug overlay keeps an identity index sequence.

instanceCount draws the bound geometry that many times, with the vertex stage seeing an instance index and reading any attribute whose BufferLayout.stepMode is VertexStepMode.instance from a per-instance buffer. One means an ordinary draw and is the only value anything asked for before mesh particles.

The one count that belongs to the draw rather than to a binding. Every other count here travels with the thing it counts — vertices with the vertex buffer, indices with the index buffer — because that is where it is known. An instance count is not a property of any buffer: the same mesh and the same instance buffer can be drawn a hundred times or ten.

Zero draws nothing, which matters because the number usually comes from a live population: a particle system with nothing alive should not be a special case at every call site.

firstIndex and indexCount draw a window of the bound indices rather than all of them — P7. Null indexCount reads to the end of the binding, so leaving both out is the whole buffer, as it always was.

On the draw, not on the binding, for the reason instanceCount is. One buffer holding a model's every part is bound once and drawn a part at a time, each part with its own material; a window on the binding would mean binding the same buffer again for every part to say something only the draw knows. WebGPU and Metal take a first index on the draw for the same reason, and WebGL2 takes a byte offset there.

A window past the end of the binding is refused with a RangeError, on every backend, before anything reaches the driver — see indexWindow, which all four call. Left to the drivers it would be four different answers: WebGL2 reports INVALID_OPERATION and draws nothing, WebGPU invalidates the whole pass, Metal leaves it undefined, and the software rasteriser would read past its bytes and throw from somewhere that names none of them. Held by the conformance check a window of the index buffer draws that window.

A draw whose pipeline would not build is skipped, never thrown — every backend, and one from outside this repository owes the same. A driver that links lazily refuses here, at the first draw; the backend reports it to GraphicsDevice.pipelineRefusals and returns, and the draws that follow with a pipeline left refused do nothing. A throw here fails the frame, and a frame that fails for a driver's reason fails again at the next one.

Implementation

@override
void draw({int instanceCount = 1, int firstIndex = 0, int? indexCount}) {
  // A pipeline the device's driver would not link is skipped, not
  // recorded: the draw never happened, as on a real backend.
  final pipelineHandle = _pipeline;
  if (pipelineHandle != null && !(_links?.call(pipelineHandle) ?? true)) {
    return;
  }
  final window = switch (_boundIndices) {
    final bound? => indexWindow(
      bound,
      firstIndex: firstIndex,
      indexCount: indexCount,
    ),
    null => (first: firstIndex, count: indexCount),
  };
  commands.add(
    RecordedDraw(
      instanceCount: instanceCount,
      firstIndex: window.first,
      indexCount: indexCount == null ? null : window.count,
    ),
  );
  final bindings = stageBindings;
  final pipeline = _pipelineName;
  if (bindings == null || pipeline == null) return;
  // The fake names its pipelines 'Vertex+Fragment'; see FakeBackend.
  for (final stage in pipeline.split('+')) {
    final declared = bindings[stage];
    if (declared == null) continue;
    for (final block in declared.blocks) {
      if (_boundBlocks[stage]?.contains(block) ?? false) continue;
      violations.add('$pipeline: $stage declares block "$block", unbound');
    }
    for (final sampler in declared.samplers) {
      if (_boundSamplers[stage]?.contains(sampler) ?? false) continue;
      violations.add(
        '$pipeline: $stage declares sampler "$sampler", unbound',
      );
    }
  }
}