draw method

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

Draws what is bound: indexed when an index buffer is bound, and the vertices in order otherwise. drawNonIndexed is the 1.0 call for the second, with a first vertex and a first instance.

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 => null,
  };
  commands.add(
    RecordedDraw(
      instanceCount: instanceCount,
      firstIndex: window?.first ?? firstIndex,
      indexCount: indexCount == null ? null : window?.count ?? indexCount,
    ),
  );
  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',
      );
    }
  }
}