1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
|
Introduction to ISL
===================
All of the documentation here focuses around ISL, the **I**\ ntel
**S**\ urface **L**\ ayout library that was originally written by Chad
for doing surface layout in the Vulkan driver. When writing the Vulkan
driver we decided not to port the old code from ``intel_mipmap_tree``.
Instead, we did a complete rewrite of the surface layout code and the
result of that rewrite is ISL.
The best place to start with ISL is the ``isl_surf`` data structure:
.. code:: c
struct isl_surf {
enum isl_surf_dim dim;
enum isl_dim_layout dim_layout;
enum isl_msaa_layout msaa_layout;
enum isl_tiling tiling;
enum isl_format format;
/**
* Alignment of the upper-left sample of each subimage, in units of surface
* elements.
*/
struct isl_extent3d image_alignment_el;
/**
* Logical extent of the surface's base level, in units of pixels. This is
* identical to the extent defined in isl_surf_init_info.
*/
struct isl_extent4d logical_level0_px;
/**
* Physical extent of the surface's base level, in units of physical
* surface samples and aligned to the format's compression block.
*
* Consider isl_dim_layout as an operator that transforms a logical surface
* layout to a physical surface layout. Then
*
* logical_layout := (isl_surf::dim, isl_surf::logical_level0_px)
* isl_surf::phys_level0_sa := isl_surf::dim_layout * logical_layout
*/
struct isl_extent4d phys_level0_sa;
uint32_t levels;
uint32_t samples;
/** Total size of the surface, in bytes. */
uint32_t size;
/** Required alignment for the surface's base address. */
uint32_t alignment;
/**
* Pitch between vertically adjacent surface elements, in bytes.
*/
uint32_t row_pitch;
/**
* Pitch between physical array slices, in rows of surface elements.
*/
uint32_t array_pitch_el_rows;
enum isl_array_pitch_span array_pitch_span;
/** Copy of isl_surf_init_info::usage. */
isl_surf_usage_flags_t usage;
};
This data structure describes everything you need to know about a
surface in a canonical way. Everything in ISL has well-defined units and
things don't change meanings based on hardware generation. Units are
usually denoted by a suffix such as "``_el``" for elements or "``_sa``"
for samples. Understanding ISL's units is key to understanding how ISL
performs surface calculations; so important, in fact, that they are
given `their own section <#units#>`__.
Units
-----
Before we go any further, we should discuss the different units that ISL
uses. There are four of them:
- Pixels (px)
- Samples (sa)
- Elements (el)
- Tiles (tl)
**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_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_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 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. This means that you cannot convert directly from
pixels to elements or vice versa; any conversion between pixels and
elements *must* go through samples.
Finally, we have **tiles**. A tile is a large rectangular block of
surface data that all fits in a single contiguous block of memory
(usually a 4K page). 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.
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 in ideology between ISL and the
old miptree code which tried to keep everything in the same units as the
hardware expects. One example of this difference is QPitch which
specifies the distance between array slices. For compressed textures,
the QPitch field in ``RENDER_SURFACE_STATE`` was in compression blocks
on Broadwell but it changed to pixels on Sky Lake. Since the old surface
state code tries to store things in hardware units, everyone who ever
reads ``intel_mipmap_tree::qpitch`` has to change their interpretation
based on hardware generation. In ISL, we have ``array_pitch_el_rows``
which, as the name says, is in rows of elements. On Sky Lake and later,
we have to multiply by the block size of the texture when we finally
fill out the hardware packet, but it makes any other users of the field
much simpler because they know that it's always in elements.
Creating Surfaces
-----------------
Creating an ``isl_surf`` is done via the ``isl_surf_init_s`` function
which takes an ``isl_surf_init_info`` structure. There is also an
``isl_surf_init`` macro which uses a C99 designated initializer to
provide a function-like interface with named parameters.
.. code:: c
struct isl_surf_init_info {
enum isl_surf_dim dim;
enum isl_format format;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t levels;
uint32_t array_len;
uint32_t samples;
/** Lower bound for isl_surf::alignment, in bytes. */
uint32_t min_alignment;
/** Lower bound for isl_surf::pitch, in bytes. */
uint32_t min_pitch;
isl_surf_usage_flags_t usage;
/** Flags that alter how ISL selects isl_surf::tiling. */
isl_tiling_flags_t tiling_flags;
};
#define isl_surf_init(dev, surf, ...) \
isl_surf_init_s((dev), (surf), \
&(struct isl_surf_init_info) { __VA_ARGS__ });
bool
isl_surf_init_s(const struct isl_device *dev,
struct isl_surf *surf,
const struct isl_surf_init_info *restrict info);
The dimensionality of the surface is given by the ``isl_surf_dim`` enum:
.. code:: c
enum isl_surf_dim {
ISL_SURF_DIM_1D,
ISL_SURF_DIM_2D,
ISL_SURF_DIM_3D,
};
Not that ISL has no inherent concept of cube or array surfaces. All 1-D
or 2-D surfaces are potentially arrays. Cube surfaces are simply 2-D
surfaces with 6 array layers that have the ``ISL_SURF_USAGE_CUBE_BIT``
set (more on usage bits later).
Next we have an ``isl_format`` which specifies the nominal format of the
surface. The values in the ``isl_format`` enum are exactly the same
integer values as the hardware surface format enumerations. This allows
for zero-cost translations between ISL and the hardware. The format
specified in the ``isl_surf`` is used for surface layout calculations
but it is not necessarily the format that will be packed into the
``RENDER_SURFACE_STATE`` structure. When emitting a surface state, you
also provide an ``isl_view`` structure that provides array layer and
miplevel ranges as well as the final format.
Next we have 6 unsigned integer values that provide the size of the
surface in all possible dimensions. The ``width``, ``height``,
``depth``, and ``array_len`` fields are all in terms of surface
*pixels*. The ``array_len`` field is expected to be 6 for cubemap
surfaces and is specified in number of faces (not number of cubes) for
cube array surfaces. The ``levels`` and ``samples`` fields are fairly
self-explanatory.
The ``min_alignment`` and ``min_pitch`` fields allow some control over
the way the surface is laid out in memory. While the final alignment and
pitch are calculated by ISL in ``isl_init_surf_s``, these allow the
caller to specify a lower bound. For linear surfaces, these fields are
more-or-less respected with the exception that ISL may round up to the
size of an element.
The ``usage`` field is a bitwise OR of ``ISL_SURF_USAGE_*`` flags that
specify all of the possible ways the surface may be used. Correctly
specifying these flags is crucial to getting the correct results.
Because the hardware has no surface formats for depth of stencil
textures, the only way that ISL can know that a texture is expected to
be used for depth or stencil is by the usage flags. For instance, a
stencil texture should always have a format of ``ISL_FORMAT_R8_UINT``
and specify ``ISL_SURF_USAGE_STENCIL_BIT``. It is illegal to combine
depth or stencil bits with ``ISL_SURF_USAGE_RENDER_TARGET_BIT`` because
they have different layout requirements which may or may not be
renderable. The usage flags are also where you specify that a given
surface may be used as a cube map.
Finally, we have tiling flags. These specify the allowed tiling modes
for the given surface. Usually, this will be one of
``ISL_TILING_LINEAR_BIT``, ``ISL_TILING_NON_LINEAR_MASK`` or
``ISL_TILING_ANY_MASK``. Inside of ``isl_surf_init_s``, isl will
automatically filter the set of possible tilings based on hardware
generation, usage flags, etc. and produce choose the tiling format that
it thinks is the most appropriate. If, however, the calling code knows
exactly what tiling format it wants, then it can specify a single bit
and it will get that tiling format assuming it's supported.
|