docs/isl: Add detailed documentation about isl formats

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/11366>
This commit is contained in:
Jason Ekstrand 2021-06-14 23:44:05 -05:00 committed by Marge Bot
parent 3894e42590
commit 0f6ebd2b73
3 changed files with 339 additions and 6 deletions

228
docs/isl/formats.rst Normal file
View File

@ -0,0 +1,228 @@
Surface Formats
===============
A surface format describes the encoding of color information into the actual
data stored in memory. Surface formats in isl are specified via the
:cpp:enum:`isl_format` enum. A complete list of surface formats is included at
the end of this chapter.
In general, a surface format definition consists of two parts: encoding and
layout.
Data Encoding
-------------
There are several different ways that one can encode a number (or vector) into
a binary form, and each makes different trade-offs. By default, most color
values lie in the range [0, 1], so one of the most common encodings for color
data is unsigned normalized where the range of an unsigned integer of a
particular size is mapped linearly onto the interval [0, 1]. While normalized
is certainly the most common representation for color data, not all data is
color data, and not all values are nicely bounded. The possible data encodings
are specified by :cpp:enum:`isl_base_type`:
.. doxygenenum:: isl_base_type
Data Layout
-----------
The different data layouts fall into two categories: array and packed. When an
array layout is used, the components are stored sequentially in an array of the
given encoding. For instance, if the data is encoded in an 8-bit RGBA array
format the data is stored in an array of type :c:type:`uint8_t` where the blue
component of the :c:expr:`i`'th color value is accessed as:
.. code-block:: C
uint8_t r = ((uint8_t *)data)[i * 4 + 0];
uint8_t g = ((uint8_t *)data)[i * 4 + 1];
uint8_t b = ((uint8_t *)data)[i * 4 + 2];
uint8_t a = ((uint8_t *)data)[i * 4 + 3];
Array formats are popular because of their simplicity. However, they are
limited to formats where all components have the same size and fit in
a standard C data type.
Packed formats, on the other hand, are encoded with the entire color value
packed into a single 8, 16, or 32-bit value. The components are specified by
which bits they occupy within that value. For instance, with the popular
:c:expr:`RGB565` format, each :c:type:`vec3` takes up 16 bits and the
:c:expr:`i`'th color value is accessed as:
.. code-block:: C
uint8_t r = (*(uint16_t *)data >> 0) & 0x1f;
uint8_t g = (*(uint16_t *)data >> 5) & 0x3f;
uint8_t b = (*(uint16_t *)data >> 11) & 0x1f;
Packed formats are useful because they allow you to specify formats with uneven
component sizes such as :c:expr:`RGBA1010102` or where the components are
smaller than 8 bits such as :c:expr:`RGB565` discussed above. It does,
however, come with the restriction that the entire vector must fit within 8,
16, or 32 bits.
One has to be careful when reasoning about packed formats because it is easy to
get the color order wrong. With array formats, the channel ordering is usually
implied directly from the format name with :c:expr:`RGBA8888` storing the
formats as in the first example and :c:expr:`BGRA8888` storing them in the BGRA
ordering. Packed formats, however, are not as simple because some
specifications choose to use a MSB to LSB ordering and others LSB to MSB. One
must be careful to pay attention to the enum in question in order to avoid
getting them backwards.
From an API perspective, both types of formats are available. In Vulkan, the
formats that are of the form :c:enumerator:`VK_FORMAT_xxx_PACKEDn` are packed
formats where the entire color fits in :c:expr:`n` bits and formats without the
:c:expr:`_PACKEDn` suffix are array formats. In GL, if you specify one of the
base types such as :c:enumerator:`GL_FLOAT` you get an array format but if you
specify a packed type such as :c:enumerator:`GL_UNSIGNED_INT_8_8_8_8_REV` you
get a packed format.
The following table provides a summary of the bit orderings of different packed
format specifications. The bit ordering is relative to the reading of the enum
name from left to right.
===================== ==============
Component Left → Right
===================== ==============
GL MSB → LSB
Vulkan MSB → LSB
mesa_format LSB → MSB
Intel surface format LSB → MSB
===================== ==============
Understanding sRGB
------------------
The sRGB colorspace is one of the least tractable concepts in the entire world
of surfaces and formats. Most texture formats are stored in a linear
colorspace where the floating-point value corresponds linearly to intensity
values. The sRGB color space, on the other hand, is non-linear and provides
greater precision in the lower-intensity (darker) end of the spectrum. The
relationship between linear and sRGB is governed by the following continuous
bijection:
.. math::
c_l =
\begin{cases}
\frac{c_s}{12.92} &\text{if } c_s \le 0.04045 \\\\
\left(\frac{c_s + 0.055}{1.055}\right)^{2.4} &\text{if } c_s > 0.04045
\end{cases}
where :math:`c_l` is the linear color and :math:`c_s` is the color in sRGB.
It is important to note that, when an alpha channel is present, the alpha
channel is always stored in the linear colorspace.
The key to understanding sRGB is to think about it starting from the physical
display. All displays work natively in sRGB. On older displays, there isn't
so much a conversion operation as a fact of how the hardware works. All
display hardware has a natural gamma curve required to get from linear to the
signal level required to generate the correct color. On older CRT displays,
the gamma curve of your average CRT is approximately the sRGB curve. More
modern display hardware has support for additional gamma curves to try and get
accurate colors but, for the sake of compatibility, everything still operates
in sRGB. When an image is sent to the X server, X passes the pixels on to the
display verbatim without doing any conversions. (Fun fact: When dealing with
translucent windows, X blends in the wrong colorspace.) This means that the
image into which you are rendering will always be interpreted as if it were in
the sRGB colorspace.
When sampling from a texture, the value returned to the shader is in the linear
colorspace. The conversion from sRGB happens as part of sampling. In OpenGL,
thanks mostly to history, there are various knobs for determining when you
should or should not encode or decode sRGB. In 2007, GL_EXT_texture_sRGB added
support for sRGB texture formats and was included in OpenGL 2.1. In 2010,
GL_EXT_texture_sRGB_decode added a flag to allow you to disable texture
decoding so that the shader received the data still in the sRGB colorspace.
Then, in 2012, GL_ARB_texture_view came along and made
GL_EXT_texture_sRGB_decode` simultaneously obsolete and very confusing. Now,
thanks to the combination of extensions, you can upload a texture as linear,
create an sRGB view of it and ask that sRGB not be decoded. What format is it
in again?
The situation with render targets is a bit different. Historically, you got
your render target from the window system (which is always sRGB) and the spec
said nothing whatsoever about encoding. All render targets were sRGB because
that's how monitors worked and application writers were expected to understand
that their final rendering needed to be in sRGB. However, with the advent of
EXT_framebuffer_object this was no longer true. Also, sRGB was causing
problems with blending because GL was blind to the fact that the output was
sRGB and blending was occurring in the wrong colorspace. In 2006, a set of
EXT_framebuffer_sRGB extensions added support (on both the GL and window-system
sides) for detecting whether a particular framebuffer was in sRGB and
instructing GL to do the conversion into the sRGB colorspace as the final step
prior to writing out to the render target. Enabling sRGB also implied that
blending would occur in the linear colorspace prior to sRGB conversion and
would therefore be more accurate. When sRGB was added to the OpenGL ES spec in
3.1, they added the query for sRGB but did not add the flag to allow you to
turn it on and off.
In Vulkan, this is all much more straightforward. Your format is sRGB or it
isn't. If you have an sRGB image and you don't want sRGB decoding to happen
when you sample from it, you simply create a c:struct:`VkImageView` that has
the appropriate linear format and the data will be treated as linear and not
converted. Similarly for render targets, blending always happens in the same
colorspace as the shader output and you determine whether or not you want sRGB
conversion by the format of the c:struct:`VkImageView` used as the render
target.
Surface Format Introspection API
--------------------------------
ISL provides an API for introspecting the :cpp:enum:`isl_format` enum and
getting various bits of information about a format. ISL provides helpers for
introspecting both the data layout of an cpp:enum:`isl_format` and the
capabilities of that format for a particular piece of Intel hardware.
Format Layout Introspection
^^^^^^^^^^^^^^^^^^^^^^^^^^^
To get the layout of a given :cpp:enum:`isl_format`, call
:cpp:func:`isl_format_get_layout`:
.. doxygenfunction:: isl_format_get_layout
.. doxygenstruct:: isl_format_layout
:members:
.. doxygenstruct:: isl_channel_layout
:members:
There are also quite a few helpers for many of the common cases that allow you
to avoid using :cpp:struct:`isl_format_layout` manually. There are a lot of
them so we won't include a full list here. Look at isl.h for more details.
Hardware Format Support Introspection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is provided by means of a table located in isl_format.c. Looking at the
table directly is often useful for understanding HW support for various
formats. However, for the purposes of code cleanliness, the table is not
exposed directly and, instead, hardware support information is exposed via
a set of helper functions:
.. doxygenfunction:: isl_format_supports_rendering
.. doxygenfunction:: isl_format_supports_alpha_blending
.. doxygenfunction:: isl_format_supports_sampling
.. doxygenfunction:: isl_format_supports_filtering
.. doxygenfunction:: isl_format_supports_vertex_fetch
.. doxygenfunction:: isl_format_supports_typed_writes
.. doxygenfunction:: isl_format_supports_typed_reads
.. doxygenfunction:: isl_format_supports_ccs_d
.. doxygenfunction:: isl_format_supports_ccs_e
.. doxygenfunction:: isl_format_supports_multisampling
.. doxygenfunction:: isl_formats_are_ccs_e_compatible
Surface Format Enums
--------------------
Everything in ISL is done in terms of the :cpp:enum:`isl_format` enum. However,
for the sake of interacting with other parts of Mesa, we provide a helper for
converting a :cpp:enum:`pipe_format` to an :cpp:enum:`isl_format`:
.. doxygenfunction:: isl_format_for_pipe_format
The :cpp:enum:`isl_format` enum is as follows:
.. doxygenenum:: isl_format

View File

@ -10,6 +10,7 @@ Chery.
:maxdepth: 2
units
formats
The core representation of a surface in ISL is :cpp:struct:`isl_surf`.

View File

@ -415,17 +415,113 @@ enum isl_format {
* Numerical base type for channels of isl_format.
*/
enum PACKED isl_base_type {
/** Data which takes up space but is ignored */
ISL_VOID,
/** Data in a "raw" form and cannot be easily interpreted */
ISL_RAW,
/**
* Unsigned normalized data
*
* Though stored as an integer, the data is interpreted as a floating-point
* number in the range [0, 1] where the conversion from the in-memory
* representation to float is given by \f$\frac{x}{2^{bits} - 1}\f$.
*/
ISL_UNORM,
/**
* Signed normalized data
*
* Though stored as an integer, the data is interpreted as a floating-point
* number in the range [-1, 1] where the conversion from the in-memory
* representation to float is given by
* \f$max\left(\frac{x}{2^{bits - 1} - 1}, -1\right)\f$.
*/
ISL_SNORM,
/**
* Unsigned floating-point data
*
* Unlike the standard IEEE floating-point representation, unsigned
* floating-point data has no sign bit. This saves a bit of space which is
* important if more than one float is required to represent a color value.
* As with IEEE floats, the high bits are the exponent and the low bits are
* the mantissa. The available bit sizes for unsigned floats are as
* follows:
*
* \rst
* ===== ========= =========
* Bits Mantissa Exponent
* ===== ========= =========
* 11 6 5
* 10 5 5
* ===== ========= =========
* \endrst
*
* In particular, both unsigned floating-point formats are identical to
* IEEE float16 except that the sign bit and the bottom mantissa bits are
* removed.
*/
ISL_UFLOAT,
/** Signed floating-point data
*
* Signed floating-point data is represented as standard IEEE floats with
* the usual number of mantissa and exponent bits
*
* \rst
* ===== ========= =========
* Bits Mantissa Exponent
* ===== ========= =========
* 64 52 11
* 32 23 8
* 16 10 5
* ===== ========= =========
* \endrst
*/
ISL_SFLOAT,
/**
* Unsigned fixed-point data
*
* This is a 32-bit unsigned integer that is interpreted as a 16.16
* fixed-point value.
*/
ISL_UFIXED,
/**
* Signed fixed-point data
*
* This is a 32-bit signed integer that is interpreted as a 16.16
* fixed-point value.
*/
ISL_SFIXED,
/** Unsigned integer data */
ISL_UINT,
/** Signed integer data */
ISL_SINT,
/**
* Unsigned scaled data
*
* This is data which is stored as an unsigned integer but interpreted as a
* floating-point value by the hardware. The re-interpretation is done via
* a simple unsigned integer to float cast. This is typically used as a
* vertex format.
*/
ISL_USCALED,
/**
* Signed scaled data
*
* This is data which is stored as a signed integer but interpreted as a
* floating-point value by the hardware. The re-interpretation is done via
* a simple signed integer to float cast. This is typically used as a
* vertex format.
*/
ISL_SSCALED,
};
@ -1101,21 +1197,26 @@ struct isl_extent4d {
union { uint32_t a, array_len; };
};
/**
* Describes a single channel of an isl_format
*/
struct isl_channel_layout {
enum isl_base_type type;
enum isl_base_type type; /**< Channel data encoding */
uint8_t start_bit; /**< Bit at which this channel starts */
uint8_t bits; /**< Size in bits */
};
/**
* Describes the layout of an isl_format
*
* Each format has 3D block extent (width, height, depth). The block extent of
* compressed formats is that of the format's compression block. For example,
* the block extent of ISL_FORMAT_ETC2_RGB8 is (w=4, h=4, d=1). The block
* extent of uncompressed pixel formats, such as ISL_FORMAT_R8G8B8A8_UNORM, is
* is (w=1, h=1, d=1).
* the block extent of `ISL_FORMAT_ETC2_RGB8` is `(w=4, h=4, d=1)`. The block
* extent of uncompressed pixel formats, such as `ISL_FORMAT_R8G8B8A8_UNORM`,
* is `(w=1, h=1, d=1)`.
*/
struct isl_format_layout {
enum isl_format format;
enum isl_format format; /**< Format */
uint16_t bpb; /**< Bits per block */
uint8_t bw; /**< Block width, in pixels */
@ -1135,7 +1236,7 @@ struct isl_format_layout {
struct isl_channel_layout channels_array[7];
};
/** Set if all channels have the same isl_base_type. Otherwise, ISL_BASE_VOID. */
/** Set if all channels have the same isl_base_type. Otherwise, ISL_VOID. */
enum isl_base_type uniform_channel_type;
enum isl_colorspace colorspace;
@ -1569,6 +1670,9 @@ isl_device_init(struct isl_device *dev,
isl_sample_count_mask_t ATTRIBUTE_CONST
isl_device_get_sample_counts(struct isl_device *dev);
/**
* \return The isl_format_layout for the given isl_format
*/
static inline const struct isl_format_layout * ATTRIBUTE_CONST
isl_format_get_layout(enum isl_format fmt)
{