Units¶
Almost every variable, function parameter, or struct field in ISL that carries a numeric value has explicit units associated with it. The units used in ISL are as follows:
Pixels (px)
Samples (sa)
Elements (el)
Tiles (tl)
Bytes (B)
Rows of some other unit size (<unit>_rows)
These units are fundamental to ISL because they allow us to specify information
about a surface in a canonical way that isn’t dependent on hardware generation.
Each field in an ISL data structure that stores any sort of dimension has a
suffix that declares the units for that particular value: _el
for elements,
_sa
for samples, etc. If the units of the particular field aren’t quite
what is wanted by the hardware, we do the conversion when we emit
RENDER_SURFACE_STATE
.
This is one of the primary differences between ISL and the old miptree code and
one of the core design principles of ISL. In the old miptree code, we tried to
keep everything in the same units as the hardware expects but this lead to
unnecessary complications as the hardware evolved. One example of this
difference is QPitch which specifies the distance between array slices. On
Broadwell and earlier, QPitch field in RENDER_SURFACE_STATE
was in
rows of samples. For block-compressed images, this meant it had to be
a multiple of the block height. On Skylake, it changed to always being in rows
of elements so you have to divide the pitch in samples by the compression
block height. Since the old surface state code tries to store things in
hardware units, everyone who ever reads brw_mipmap_tree.qpitch
has
to change their interpretation based on hardware generation and whether or not
the surface was block-compressed. In ISL, we have
isl_surf.array_pitch_el_rows
which, as the name says, is in rows
of elements. On Broadwell and earlier, we have to multiply by the block size
of the texture when we finally fill out the hardware packet. However, the
consistency of always being in rows of elements makes any other users of the
field much simpler because they never have to look at hardware generation or
whether or not the image is block-compressed.
Pixels are the most straightforward unit and are where everything starts. A pixel simply corresponds to a single pixel (or texel if you prefer) in the surface. For multisampled surfaces, a pixel may contain one or more samples. For compressed textures, a compression block may contain one or more pixels. When initially creating a surface, everything passed to isl_surf_init is implicitly in terms of pixels because this is what all of the APIs use.
The next unit in ISL’s repertoire is samples. In a multisampled surface,
each pixel corresponds to some number of samples given by
isl_surf.samples
. The exact layout of the samples depends on
the value of isl_surf.msaa_layout
. If the layout is
isl_msaa_layout.ISL_MSAA_LAYOUT_ARRAY
then each logical array in
the surface corresponds to isl_surf.samples
actual slices
in the resulting surface, one per array slice. If the layout is
isl_msaa_layout.ISL_MSAA_LAYOUT_INTERLEAVED
then each pixel corresponds
to a 2x1, 2x2, 4x2, or 4x4 grid of samples. In order to aid in calculations, one of
the first things ISL does is to compute isl_surf.phys_level0_sa
which gives the dimensions of the base miplevel of the surface in samples. The
type of isl_surf.phys_level0_sa
is isl_extent4d
which allows us to express both the array and interleaved cases. Most of the
calculations of how the different miplevels and array slices are laid out is
done in terms of samples.
Next, we have surface elements. An element is the basic unit of actual surface memory. For multisampled textures, an element is equal to a single sample. For block compressed textures, an element corresponds to an entire compression block. The conversion from samples to elements is given by dividing by the block width and block height of the surface format. This is true regardless of whether or not the surface is multisampled; for multisampled compressed textures (these exist for certain auxiliary formats), the block width and block height are expressed in samples. Pixels cannot be converted directly to elements or vice versa; any conversion between pixels and elements must go through samples.
The final surface unit is tiles. A tile is a large rectangular block of surface data that all fits in a single contiguous block of memory (usually a 4K or 64K page, depending on tile format). Tiles are used to provide an arrangement of the data in memory that yields better cache performance. The size of a tile is always specified in surface elements.