pub struct BatchDraw {
pub first_index: u32,
pub index_count: u32,
pub material: Material,
pub filter: ColorFilter,
pub blend: BlendMode,
pub clip: Option<Scissor>,
pub stencil: ClipState,
pub tint_blend: BlendMode,
pub paint_at_texture_coords: bool,
}Expand description
One draw within a batch.
Fields§
§first_index: u32§index_count: u32§material: Material§filter: ColorFilterA function applied to the material’s color before the blend.
Beside the material rather than inside it, for the same reason the blend mode is: it applies to every kind of material equally and belongs to none of them. It is packed into the same uniform the material is, because the shader reads one block per draw.
blend: BlendMode§clip: Option<Scissor>The region of the target this draw may write to.
None is the whole target. It is distinct from a rectangle that happens
to cover the target so a backend can tell “this draw was never clipped”
from “this draw’s clip works out to everything”, and skip the state
change in the first case without having to know the target’s size.
Independent of Self::stencil, and both apply. An axis-aligned clip
stays here even where a stencil is already in play, because a scissor is
exact and costs nothing while a stencil pass costs a draw.
stencil: ClipStateWhat this draw does with the stencil buffer.
tint_blend: BlendModeHow a color the caller attached to a vertex or a sprite combines with what the material produced.
BlendMode::Modulate multiplies them, which is what every draw did
before this existed and is what a paint with no per-vertex color wants:
white is the identity under it. Distinct from Self::blend, which is
how the result then reaches the target – these two colors are both in
the shader, so this one needs no extension and every mode is available.
paint_at_texture_coords: boolRead the paint at this draw’s texture coordinates rather than at the position of the fragment.
A property of the geometry rather than of the material, which is why it is here: a mesh that states a coordinate per vertex has said where each one sits in the paint’s space, and there is nothing left to derive. An image already worked this way and had its own material for it; this is what lets a gradient or a caller’s program do the same.
False everywhere else, and it costs those draws nothing: the flag lands in a slot no material that could set it uses, and the shader’s select is one instruction on a value it has already computed.
Implementations§
Source§impl BatchDraw
impl BatchDraw
Sourcepub fn covered(
&self,
vertices: &[Vertex],
indices: &[u32],
extent: Extent2D,
) -> Option<Scissor>
pub fn covered( &self, vertices: &[Vertex], indices: &[u32], extent: Extent2D, ) -> Option<Scissor>
Whether this draw may be moved ahead of earlier draws it covers.
A draw that answers yes replaces every sample it touches, so nothing
underneath it can show through and the painter’s-order guarantee this
batch otherwise relies on does not apply to it. That is what makes an
opaque reordering possible: docs/non-parity.md 21 has the measurement,
and on both boards here the covered part of a frame’s background is
around forty per cent of the frame.
Conservative on purpose, and every condition below is load-bearing. A wrong yes is not a slow frame, it is a wrong picture – a background showing through where it should not, or showing when it should not – so each test is for a property that can be read off the draw rather than reasoned about, and anything this cannot prove answers no.
Material::Solidwith an opaque alpha, and nothing else. A solid fill takes its coverage from the rasterizer, so a sample is either inside the geometry or outside it and there is no partial result. The analytic materials are the case this exists to exclude:RoundedRect,EllipseandRoundedRectBlurcompute coverage in the shader and blend it, so an opaque color still leaves a soft edge, and writing depth there would hide the background behind a half-covered pixel. Gradients and images could be opaque and are refused anyway: proving it means reading every stop or every texel.- Alpha at or above one. Premultiplied and straight color agree there, so the form the material carries does not have to be known.
SrcorSrcOver. Both put an opaque source through unchanged. Every other mode reads the destination, which is the thing being reordered away.- No color filter. A matrix or a blend filter can take alpha below one after the material produced it.
Modulatetinting. It is the identity against the white a solid fill carries; another mode is a second color this cannot see.- Every vertex carrying white. A vertex color multiplies the
material, so a translucent one makes a translucent draw out of an
opaque material – and
Material::Solidis exactly the pairingdraw_verticesproduces for a caller’s mesh. This is why the vertex buffer is a parameter: the material cannot answer it, and the draw does not hold it. Refused for any non-white color rather than only a translucent one, since a colored-but-opaque vertex still has to be read to know that, and it costs nothing to say no. - Unclipped. A
NarroworWidendraw writes the stencil rather than color and is sequencing, not content. A clippedContentdraw writes color but depends on stencil state that the draws around it establish, so moving it past them would change what it is clipped to.
Antialiasing does not appear here, and that is the point rather than an
omission. It is multisampling in this renderer – Canvas::pass_samples
raises the whole pass’s sample count and no draw blends its own coverage
– so an opaque solid fill is binary at every sample whether the pass is
multisampled or not, which is exactly the case a depth test is built for.
A renderer that antialiased by blending coverage could not use this
predicate at all.
The whole pixels this draw certainly covers, where that is knowable exactly.
None unless the geometry is a quad standing on its own bounding box: four
vertices at the four corners, six indices forming two triangles that share the
quad’s diagonal. That is what an axis-aligned rectangle fill tessellates to, and
it is the one shape whose covered area is its bounding box rather than something
strictly inside it. Everything else – a rotated rectangle, a path, a stroke, a
glyph run – is refused rather than approximated, because the answer is used to
stop drawing something underneath and a rectangle too large leaves a hole in the
frame.
Every test here is discrete, with no tolerance anywhere. A quad one part in ten thousand short of its bounding box would pass an area comparison and leave a sub-pixel notch, and at four samples a notch is a visible seam. Exact corners or nothing.
Two triangles sharing a side rather than the diagonal are refused too. They have six indices over four vertices and cover half the box, so nothing short of looking at which pair is shared tells them apart.
The positions are homogeneous clip coordinates, so this converts. w must be
exactly one on all four vertices, which refuses perspective rather than dividing
by a quantity that varies across the quad, and the normalized range maps onto the
target with y running downward – the orientation Scissor fixes and that both
backends already agree on. Scissor::covered_device_bounds then rounds inward.
Sourcepub fn splits_safely(&self, vertices: &[Vertex], indices: &[u32]) -> bool
pub fn splits_safely(&self, vertices: &[Vertex], indices: &[u32]) -> bool
Whether writing this draw’s pixels twice gives what writing them once gives.
The condition for splitting a draw into several, and it is not the same question
as Self::occludes. That one asks whether a draw hides what is under it; this
asks whether it is safe to draw overlapping copies of it – which matters
because a driver may write a pixel outside the scissor it was given.
Measured, not hypothetical. lavapipe on Mesa 25.2.8 and 15.0.6 writes the
pixel to the left of a scissor at half coverage when the pass is multisampled;
26.1.7, RADV and PanVK are clean. public_api.rs probes for it. Where it happens,
two pieces of one draw overlap by a column – and a translucent draw blends there
twice, which reads 90 against 121 on a half-transparent wash under an opaque bar.
An opaque one writes the same color twice and cannot tell.
So a draw splits only where a second write is a no-op: Src replaces whatever the
alpha, and SrcOver replaces only where the source is opaque – which means the
material, the absence of a color filter, an identity tint, and the vertex colors
the tint multiplies in, all four.
pub fn occludes(&self, vertices: &[Vertex], indices: &[u32]) -> bool
Sourcepub fn to_uniform(&self, target: PixelFormat) -> [f32; 64]
pub fn to_uniform(&self, target: PixelFormat) -> [f32; 64]
The uniform block this draw’s shader reads.
The material and the filter are packed together because the shader
takes one block per draw, and separately here because they are separate
things: a filter applies to any material, and a material knows nothing
about being filtered.
target is the format this draw is about to be written into, which
only the backend knows: a recording is built without one, and the same
recording is drawn into an eight-bit surface and a float one. It decides
the dither, and nothing else here.