#[repr(C)]pub struct Vertex {
pub position: [f32; 3],
pub uv: [f32; 2],
pub color: [f32; 4],
}Expand description
One vertex: where it is, and where it reads from.
§Why every vertex carries texture coordinates
Most geometry here does not need them — a solid fill and a gradient both locate themselves from the interpolated clip position. A glyph run does: a run is many quads reading different parts of one atlas, and a material is per draw, so coordinates carried in the paint would mean a draw per glyph. Text is the highest draw-count content there is, so that is the wrong place to spend.
The cost is eight bytes on every vertex, including the ones that ignore them. The alternative — a second vertex format and a second pipeline for text — spends more in pipeline state and in the code that has to decide which of two shapes a batch is in, to save memory on the geometry that is already the cheapest to store.
Fields§
§position: [f32; 3]Homogeneous clip position: the point as the recorder produced it, before the rasterizer divides.
w is one for everything an affine transform placed, which is nearly
everything, and the third float is what lets a transform with
perspective say anything at all — there is no two-component form of a
point that has been divided by a quantity varying across the triangle.
Carrying it undivided rather than dividing on the way here buys two
things beyond the mapping itself. The rasterizer clips against the plane
where w reaches zero, so geometry crossing the vanishing line is cut
there by the hardware instead of arriving as coordinates on both sides
of infinity. And every varying beside this one — texture coordinates
most of all — is then interpolated perspective-correctly, which is the
difference between a textured quad seen at an angle and the diagonal
seam that affine interpolation puts across it.
uv: [f32; 2]Where in a sampled texture this vertex reads, if the material samples one. Zero where it does not, which costs nothing to interpolate.
color: [f32; 4]A color multiplied into whatever the material produced, premultiplied.
Opaque white for everything but a mesh a caller colored, and white is the identity, so a fill pays for this in bandwidth rather than in a second path. Sixteen bytes per vertex: at fifty thousand vertices a frame, which is a great deal of two-dimensional geometry, that is under fifty megabytes a second against a tiler already spending ten times that on the framebuffer alone. A second vertex layout and a second pipeline would save it and cost a permanent split in the batch model, which is the wrong trade at this magnitude.
Premultiplied rather than straight because it is interpolated across a triangle, and interpolating straight color between vertices whose alpha differs gives a color no point on the edge actually has.
Implementations§
Source§impl Vertex
impl Vertex
pub const fn new(position: [f32; 2], uv: [f32; 2]) -> Self
Sourcepub const fn projected(position: [f32; 3], uv: [f32; 2]) -> Self
pub const fn projected(position: [f32; 3], uv: [f32; 2]) -> Self
A vertex whose position is already homogeneous.
The form a transform carrying perspective produces. Self::new is
this with a w of one, which is what an affine always gives, and is why
the ordinary constructors did not have to change when the third float
arrived.
Sourcepub const fn at_projected(position: [f32; 3]) -> Self
pub const fn at_projected(position: [f32; 3]) -> Self
A homogeneous vertex that samples nothing.
Sourcepub const fn with_color(self, color: [f32; 4]) -> Self
pub const fn with_color(self, color: [f32; 4]) -> Self
The same vertex, tinted.
color is premultiplied; see Self::color.