mirror of
https://github.com/elwrcl/intel-hard-graphics.git
synced 2026-11-01 03:18:02 +03:00
init
This commit is contained in:
12511 files changed
+6156641
No files matched your search
@@ -0,0 +1,71 @@
|
||||
Benchmarking Panfrost
|
||||
=====================
|
||||
|
||||
Obtaining reproducible benchmark timings can sometimes be tricky on Mali
|
||||
based SoCs. There are a number of things that can be done to make the timings
|
||||
more predictable.
|
||||
|
||||
Make sure the device is actively cooled (to eliminate thermal throttling)
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
This isn't strictly speaking necessary, if you use low enough frequencies
|
||||
in the steps below, but it is recommended.
|
||||
|
||||
Fix the GPU frequency
|
||||
---------------------
|
||||
|
||||
If power management is allowed to vary the GPU frequency, this will lead to
|
||||
uncertainty in benchmark figures, particularly for shorter benchmark runs.
|
||||
|
||||
On most systems, the GPU frequency can be set to a fixed value via a command
|
||||
like the following (run it as root, sudo alone won't allow the redirection):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cat /sys/devices/platform/fb000000.gpu/devfreq/fb000000.gpu/min_freq > /sys/devices/platform/fb000000.gpu/devfreq/fb000000.gpu/max_freq
|
||||
|
||||
This forces the GPU to always use its minimum frequency. You could reverse
|
||||
`min_freq` and `max_freq` to fix it to its maximum frequency, if your system
|
||||
is adequately cooled.
|
||||
|
||||
Fix the CPU frequency
|
||||
---------------------
|
||||
|
||||
It is easy to overlook the role the CPU plays in driver performance. To set
|
||||
its frequency to a fixed value, install the `linux-cpupower` package and
|
||||
run something like:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sudo cpupower frequency-info
|
||||
sudo cpupower frequency-set -d 1.20
|
||||
sudo cpupower frequency-set -u 1.20
|
||||
sudo cpupower frequency-info
|
||||
|
||||
Adjust the numbers based on what the board actually supports (seen in
|
||||
the first `frequency-info`. It is best to choose fairly low settings
|
||||
unless you're confident of the board's cooling.
|
||||
|
||||
Pin the benchmark program to a particular core
|
||||
----------------------------------------------
|
||||
|
||||
This is especially important on devices with a big.LITTLE architecture, where
|
||||
having the benchmark be scheduled on a different kind of core can drastically
|
||||
change the performance. But even without this, variations in cache residency
|
||||
and scheduling of kernel tasks can lead to changes in benchmark timing.
|
||||
|
||||
Experience shows that using a little core for the benchmark program
|
||||
tends to give more reproducible results (probably because they have smaller
|
||||
caches, and also are less likely to be scheduled to by the kernel).
|
||||
However, forcing the benchmark to run on a little core could change the
|
||||
bottleneck from GPU to CPU, so watch out for that.
|
||||
|
||||
Pinning a task to a particular core is done with the `taskset` command. An
|
||||
example for the Rock5b (rk3588) board is:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
taskset 0x04 glmark2-es2-wayland
|
||||
|
||||
Replace `0x04` with an appropriate mask for your board, and
|
||||
`glmark2-es2-wayland` with the benchmark program to be used.
|
||||
@@ -0,0 +1,78 @@
|
||||
drm-shim
|
||||
========
|
||||
|
||||
Panfrost implements ``drm-shim``, stubbing out the Panfrost kernel interface.
|
||||
Use cases for this functionality include:
|
||||
|
||||
- Future hardware bring up
|
||||
- Running shader-db on non-Mali workstations
|
||||
- Reproducing compiler (and some driver) bugs without Mali hardware
|
||||
|
||||
Although Mali hardware is usually paired with an Arm CPU, Panfrost is portable C
|
||||
code and should work on any Linux machine. In particular, you can test the
|
||||
compiler on shader-db on an Intel desktop.
|
||||
|
||||
To build Mesa with Panfrost drm-shim, configure Meson with
|
||||
``-Dgallium-drivers=panfrost`` and ``-Dtools=drm-shim``. See the above
|
||||
building section for a full invocation. The drm-shim binary will be built to
|
||||
``build/src/panfrost/drm-shim/libpanfrost_noop_drm_shim.so``.
|
||||
|
||||
To use, set the ``LD_PRELOAD`` environment variable to the drm-shim binary.
|
||||
|
||||
By default, drm-shim mocks a Mali-G52 system. To select a specific Mali GPU,
|
||||
set the ``PAN_GPU_ID`` environment variable to the desired GPU ID:
|
||||
|
||||
========= ============= =======
|
||||
Product Architecture GPU ID
|
||||
========= ============= =======
|
||||
Mali-T720 Midgard (v4) 720
|
||||
Mali-T860 Midgard (v5) 860
|
||||
Mali-G72 Bifrost (v6) 6221
|
||||
Mali-G52 Bifrost (v7) 7212
|
||||
Mali-G57 Valhall (v9) 9093
|
||||
Mali-G610 Valhall (v10) a867
|
||||
========= ============= =======
|
||||
|
||||
Additional GPU IDs are enumerated in the ``pan_model_list`` list in
|
||||
``src/panfrost/model/pan_model.c``.
|
||||
|
||||
As an example: assuming Mesa is installed to a local path ``~/lib`` and Mesa's
|
||||
build directory is ``~/mesa/build``, a shader can be compiled for Mali-G52 as:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~/shader-db$ BIFROST_MESA_DEBUG=shaders \
|
||||
LD_PRELOAD=~/mesa/build/src/panfrost/drm-shim/libpanfrost_noop_drm_shim.so \
|
||||
PAN_GPU_ID=7212 \
|
||||
./run shaders/glmark/1-1.shader_test
|
||||
|
||||
The same shader can be compiled for Mali-T720 as:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~/shader-db$ MIDGARD_MESA_DEBUG=shaders \
|
||||
LD_PRELOAD=~/mesa/build/src/panfrost/drm-shim/libpanfrost_noop_drm_shim.so \
|
||||
PAN_GPU_ID=720 \
|
||||
./run shaders/glmark/1-1.shader_test
|
||||
|
||||
These examples set the compilers' ``shaders`` debug flags to dump the optimized
|
||||
NIR, backend IR after instruction selection, backend IR after register
|
||||
allocation and scheduling, and a disassembly of the final compiled binary.
|
||||
|
||||
As another example, this invocation runs a single dEQP test "on" Mali-G52,
|
||||
pretty-printing GPU data structures and disassembling all shaders
|
||||
(``PAN_MESA_DEBUG=trace``) as well as dumping raw GPU memory
|
||||
(``PAN_MESA_DEBUG=dump``). The ``EGL_PLATFORM=surfaceless`` environment variable
|
||||
and various flags to dEQP mimic the surfaceless environment that our
|
||||
continuous integration (CI) uses. This eliminates window system dependencies,
|
||||
although it requires a specially built CTS:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~/VK-GL-CTS/build/external/openglcts/modules$ PAN_MESA_DEBUG=trace,dump \
|
||||
LD_PRELOAD=~/mesa/build/src/panfrost/drm-shim/libpanfrost_noop_drm_shim.so \
|
||||
PAN_GPU_ID=7212 EGL_PLATFORM=surfaceless \
|
||||
./glcts --deqp-surface-type=pbuffer \
|
||||
--deqp-gl-config-name=rgba8888d24s8ms0 --deqp-surface-width=256 \
|
||||
--deqp-surface-height=256 -n \
|
||||
dEQP-GLES31.functional.shaders.builtin_functions.common.abs.float_highp_compute
|
||||
@@ -0,0 +1,117 @@
|
||||
Instancing
|
||||
==========
|
||||
|
||||
The attribute descriptor lets the attribute unit compute the address of an
|
||||
attribute given the vertex and instance ID. Unfortunately, the way this works is
|
||||
rather complicated when instancing is enabled.
|
||||
|
||||
To explain this, first we need to explain how compute and vertex threads are
|
||||
dispatched. When a quad is dispatched, it receives a single, linear index.
|
||||
However, we need to translate that index into a (vertex id, instance id) pair.
|
||||
One option would be to do:
|
||||
|
||||
.. math::
|
||||
\text{vertex id} = \text{linear id} \% \text{num vertices}
|
||||
|
||||
\text{instance id} = \text{linear id} / \text{num vertices}
|
||||
|
||||
but this involves a costly division and modulus by an arbitrary number.
|
||||
Instead, we could pad ``num_vertices``. We dispatch
|
||||
:math:`\text{padded_num_vertices} \cdot \text{num_instances}` threads instead
|
||||
of :math:`\text{num_vertices} \cdot \text{num_instances}`, which results
|
||||
in some "extra" threads with :math:`\text{vertex_id} \geq \text{num_vertices}`,
|
||||
which we have to discard. The more we pad ``num_vertices``, the more "wasted"
|
||||
threads we dispatch, but the division is potentially easier.
|
||||
|
||||
One straightforward choice is to pad ``num_vertices`` to the next power
|
||||
of two, which means that the division and modulus are just simple bit shifts
|
||||
and masking. But the actual algorithm is a bit more complicated. The thread
|
||||
dispatcher has special support for dividing by 3, 5, 7, and 9, in addition
|
||||
to dividing by a power of two. As a result, ``padded_num_vertices`` can
|
||||
be 1, 3, 5, 7, or 9 times a power of two. This results in less wasted threads,
|
||||
since we need less padding.
|
||||
|
||||
``padded_num_vertices`` is picked by the hardware. The driver just specifies
|
||||
the actual number of vertices. Note that ``padded_num_vertices`` is a multiple
|
||||
of four (presumably because threads are dispatched in groups of 4). Also,
|
||||
``padded_num_vertices`` is always at least one more than ``num_vertices``,
|
||||
which seems like a quirk of the hardware. For larger ``num_vertices``, the
|
||||
hardware uses the following algorithm: using the binary representation of
|
||||
``num_vertices``, we look at the most significant set bit as well as the
|
||||
following 3 bits. Let n be the number of bits after those 4 bits. Then we
|
||||
set ``padded_num_vertices`` according to the following table:
|
||||
|
||||
========== =======================
|
||||
high bits ``padded_num_vertices``
|
||||
========== =======================
|
||||
1000 :math:`9 \cdot 2^n`
|
||||
1001 :math:`5 \cdot 2^{n+1}`
|
||||
101x :math:`3 \cdot 2^{n+2}`
|
||||
110x :math:`7 \cdot 2^{n+1}`
|
||||
111x :math:`2^{n+4}`
|
||||
========== =======================
|
||||
|
||||
For example, if :math:`\text{num_vertices} = 70` is passed to
|
||||
:c:func:`glDraw()`, its binary representation is 1000110, so :math:`n = 3`
|
||||
and the high bits are 1000, and therefore
|
||||
:math:`\text{padded_num_vertices} = 9 \cdot 2^3 = 72`.
|
||||
|
||||
The attribute unit works in terms of the original ``linear_id``. if
|
||||
:math:`\text{num_instances} = 1`, then they are the same, and everything
|
||||
is simple. However, with instancing things get more complicated. There are
|
||||
four possible modes, two of them we can group together:
|
||||
|
||||
1. Use the ``linear_id`` directly. Only used when there is no instancing.
|
||||
|
||||
2. Use the ``linear_id`` modulo a constant. This is used for per-vertex
|
||||
attributes with instancing enabled by making the constant equal
|
||||
``padded_num_vertices``. Because the modulus is always ``padded_num_vertices``,
|
||||
this mode only supports a modulus that is a power of 2 times 1, 3, 5, 7,
|
||||
or 9. The shift field specifies the power of two, while the ``extra_flags``
|
||||
field specifies the odd number. If :math:`\text{shift} = n` and
|
||||
:math:`\text{extra_flags} = m`, then the modulus is
|
||||
:math:`(2m + 1) \cdot 2^n`. As an example, if
|
||||
:math:`\text{num_vertices} = 70`, then as computed above,
|
||||
:math:`\text{padded_num_vertices} = 9 \cdot 2^3`, so we should set
|
||||
:math:`\text{extra_flags} = 4` and :math:`\text{shift} = 3`. Note that we
|
||||
must exactly follow the hardware algorithm used to get ``padded_num_vertices``
|
||||
in order to correctly implement per-vertex attributes.
|
||||
|
||||
3. Divide the ``linear_id`` by a constant. In order to correctly implement
|
||||
instance divisors, we have to divide ``linear_id`` by ``padded_num_vertices``
|
||||
times to user-specified divisor. So first we compute ``padded_num_vertices``,
|
||||
again following the exact same algorithm that the hardware uses, then multiply
|
||||
it by the GL-level divisor to get the hardware-level divisor. This case is
|
||||
further divided into two more cases. If the hardware-level divisor is a
|
||||
power of two, then we just need to shift. The shift amount is specified by
|
||||
the shift field, so that the hardware-level divisor is just
|
||||
:math:`2^\text{shift}`.
|
||||
|
||||
If it isn't a power of two, then we have to divide by an arbitrary integer.
|
||||
For that, we use the well-known technique of multiplying by an approximation
|
||||
of the inverse. The driver must compute the magic multiplier and shift
|
||||
amount, and then the hardware does the multiplication and shift. The
|
||||
hardware and driver also use the "round-down" optimization as described in
|
||||
https://ridiculousfish.com/files/faster_unsigned_division_by_constants.pdf.
|
||||
The hardware further assumes the multiplier is between :math:`2^{31}` and
|
||||
:math:`2^{32}`, so the high bit is implicitly set to 1 even though it is set
|
||||
to 0 by the driver -- presumably this simplifies the hardware multiplier a
|
||||
little. The hardware first multiplies ``linear_id`` by the multiplier and
|
||||
takes the high 32 bits, then applies the round-down correction if
|
||||
:math:`\text{extra_flags} = 1`, then finally shifts right by the shift field.
|
||||
|
||||
There are some differences between ridiculousfish's algorithm and the Mali
|
||||
hardware algorithm, which means that the reference code from ridiculousfish
|
||||
doesn't always produce the right constants. Mali does not use the pre-shift
|
||||
optimization, since that would make a hardware implementation slower (it
|
||||
would have to always do the pre-shift, multiply, and post-shift operations).
|
||||
It also forces the multiplier to be at least :math:`2^{31}`, which means
|
||||
that the exponent is entirely fixed, so there is no trial-and-error.
|
||||
Altogether, given the divisor d, the algorithm the driver must follow is:
|
||||
|
||||
1. Set :math:`\text{shift} = \lfloor \log_2(d) \rfloor`.
|
||||
2. Compute :math:`m = \lceil 2^{shift + 32} / d \rceil` and :math:`e = 2^{shift + 32} % d`.
|
||||
3. If :math:`e \leq 2^{shift}`, then we need to use the round-down algorithm.
|
||||
Set :math:`\text{magic_divisor} = m - 1` and :math:`\text{extra_flags} = 1`.
|
||||
4. Otherwise, set :math:`\text{magic_divisor} = m` and
|
||||
:math:`\text{extra_flags} = 0`.
|
||||
@@ -0,0 +1,17 @@
|
||||
Compressed texture support
|
||||
==========================
|
||||
|
||||
In the driver, Panfrost supports ASTC, ETC, and all BCn formats (e.g. RGTC,
|
||||
S3TC, etc.) However, Panfrost depends on the hardware to support these formats
|
||||
efficiently. All supported Mali architectures support these formats, but not
|
||||
every system-on-chip with a Mali GPU support all these formats. Many lower-end
|
||||
systems lack support for some BCn formats, which can cause problems when playing
|
||||
desktop games with Panfrost. To check whether this issue applies to your
|
||||
system-on-chip, Panfrost includes a ``panfrost_texfeatures`` tool to query
|
||||
supported formats.
|
||||
|
||||
To use this tool, include the option ``-Dtools=panfrost`` when configuring Mesa.
|
||||
Then inside your Mesa build directory, the tool is located at
|
||||
``src/panfrost/tools/panfrost_texfeatures``. Copy it to your target device,
|
||||
set as executable as necessary, and run on the target device. A table of
|
||||
supported formats will be printed to standard output.
|
||||
@@ -0,0 +1,37 @@
|
||||
U-interleaved tiling
|
||||
====================
|
||||
|
||||
Panfrost supports u-interleaved tiling. U-interleaved tiling is
|
||||
indicated by the ``DRM_FORMAT_MOD_ARM_16X16_BLOCK_U_INTERLEAVED`` modifier.
|
||||
|
||||
The tiling reorders whole pixels (blocks). It does not compress or modify the
|
||||
pixels themselves, so it can be used for any image format. Internally, images
|
||||
are divided into tiles. Tiles occur in source order, but pixels (blocks) within
|
||||
each tile are reordered according to a space-filling curve.
|
||||
|
||||
For regular formats, 16x16 tiles are used. This harmonizes with the default tile
|
||||
size for binning and CRCs (transaction elimination). It also means a single line
|
||||
(16 pixels) at 4 bytes per pixel equals a single 64-byte cache line.
|
||||
|
||||
For formats that are already block compressed (S3TC, RGTC, etc), 4x4 tiles are
|
||||
used, where entire blocks are reorder. Most of these formats compress 4x4
|
||||
blocks, so this gives an effective 16x16 tiling. This justifies the tile size
|
||||
intuitively, though it's not a rule: ASTC may uses larger blocks.
|
||||
|
||||
Within a tile, the X and Y bits are interleaved (like Morton order), but with a
|
||||
twist: adjacent bit pairs are XORed. The reason to add XORs is not obvious.
|
||||
Visually, addresses take the form::
|
||||
|
||||
| y3 | (x3 ^ y3) | y2 | (y2 ^ x2) | y1 | (y1 ^ x1) | y0 | (y0 ^ x0) |
|
||||
|
||||
Reference routines to encode/decode u-interleaved images are available in
|
||||
``src/panfrost/shared/test/test-tiling.cpp``, which documents the space-filling
|
||||
curve. This reference implementation is used to unit test the optimized
|
||||
implementation used in production. The optimized implementation is available in
|
||||
``src/panfrost/shared/pan_tiling.c``.
|
||||
|
||||
Although these routines are part of Panfrost, they are also used by Lima, as Arm
|
||||
introduced the format with Utgard. It is the only tiling supported on Utgard. On
|
||||
Mali-T760 and newer, Arm Framebuffer Compression (AFBC) is more efficient and
|
||||
should be used instead where possible. However, not all formats are
|
||||
compressible, so u-interleaved tiling remains an important fallback on Panfrost.
|
||||
@@ -0,0 +1,108 @@
|
||||
Varyings
|
||||
========
|
||||
|
||||
This is the naming convention followed by the hardware (slightly different
|
||||
from Vulkan naming):
|
||||
|
||||
- "Attribute": per-vertex data
|
||||
- "Varying": per-fragment data
|
||||
|
||||
Thus vertex shaders always interact with attributes and fragment/pixel shaders
|
||||
only interact with varyings (commonly shortened to "VAR").
|
||||
|
||||
|
||||
Hardware descriptors
|
||||
--------------------
|
||||
|
||||
Before Valhall there were two types of hardware descriptors:
|
||||
|
||||
- ``AttributeDescriptor``: Descriptor for a single attribute in a larger buffer,
|
||||
contains offset, stride and the in-memory format.
|
||||
- ``BufferDescriptor``: Buffer, when used for attributes and varyings it
|
||||
contains: pointer, size and stride
|
||||
|
||||
Midgard (v5)
|
||||
------------
|
||||
Attributes are loaded/stored using ``LD_ATTR``/``ST_ATTR``, those query the
|
||||
attribute descriptors and buffer descriptors to find the real global memory
|
||||
offset before loading/storing.
|
||||
All varyings are loaded using ``LD_VAR``, including flat attributes (using a
|
||||
modifier), and special attributes (position, psiz, ecc...). Special attributes
|
||||
require a special descriptor, that is not backed by any buffer, it generates
|
||||
data on-the-fly.
|
||||
There is a concept of in-memory format and register format, the first is written
|
||||
inside of the ``AttributeDescriptor`` while the register format is a modifier of
|
||||
the instructions, when those two disagree the hardware will try to convert them,
|
||||
this is only supported between int-int or float-float operations.
|
||||
|
||||
|
||||
Bifrost (v6)
|
||||
------------
|
||||
Changes:
|
||||
|
||||
- ``ST_ATTR`` is replaced by ``LEA_ATTR`` and ``ST_CVT`` pairs.
|
||||
|
||||
- ``LEA_ATTR`` queries memory address and format from the appropriate
|
||||
descriptor.
|
||||
- ``ST_CVT`` performs the actual conversion and memory store.
|
||||
- A new ``LD_VAR_SPECIAL`` is introduced, now special attributes do not need
|
||||
special descriptors.
|
||||
- ``LD_VAR`` only handles interpolated varyings, flat varyings are now handled
|
||||
by ``LD_VAR_FLAT``.
|
||||
|
||||
Valhall (v9)
|
||||
------------
|
||||
Valhall introduced layout-specific instructions, ``LEA_BUF`` and ``LD_VAR_BUF``,
|
||||
those do not use ``AttributeDescriptor`` but read ``BufferDescriptor`` directly.
|
||||
These new instructions bake the offset and in-memory format of the attribute
|
||||
inside of the shader code itself, saving a lookup. If all shaders in a program
|
||||
use those instructions, we can remove all ``AttributeDescriptor``. If the
|
||||
shaders aren't compiled together, one of the shaders cannot know the data layout,
|
||||
and it needs to fall back to the old ``AttributeDescriptor``-based path. We
|
||||
also have to fall back to ``AttributeDescriptor`` completely if the attribute
|
||||
offset overflows the immediate field.
|
||||
|
||||
``LD_VAR_BUF`` only supports ``.f16`` and ``.f32`` as register modifiers, we can
|
||||
also load (flat) ints with those. We need to specify no conversion
|
||||
(``.f16.src_flat16`` or ``.f32.src_flat32``), but with those we can load both
|
||||
16-bit and 32-bit integers.
|
||||
|
||||
In this architecture we assume the VS always uses IDVS, emitting attribute data
|
||||
with ``LEA_BUF`` (instead of ``LEA_ATTR``), and never emit varying
|
||||
``AttributeDescriptor`` for VS.
|
||||
|
||||
Challenges
|
||||
----------
|
||||
Theoretically, the output types from the VS and input types from the FS should
|
||||
always agree. In practice we cannot trust the varyings types given to us,
|
||||
here are some challenging examples:
|
||||
|
||||
* Shaders are allowed to use the same types with different precision modifiers.
|
||||
* ``nir_opt_varying`` erases highp flat types with "float32" and packs them
|
||||
together.
|
||||
* Some games like to play with ``intBitsToFloat`` / ``floatBitsToInt``, so it's
|
||||
possible to have VS write int and FS read float (in that case, we treat the
|
||||
underlying bits as float, allowing for NaN normalization)
|
||||
|
||||
In practice you have to follow these rules:
|
||||
|
||||
* Only FS knows a varying smoothness, VS sees everything as flat.
|
||||
* When linked together, you cannot trust highp flat types, but you can trust
|
||||
precision
|
||||
* While loading/storing data, mistaking a float for an int is ok, mistaking an
|
||||
int for a float seems to be lossy even without bit-size conversions. Be
|
||||
conservative on what you say is a float.
|
||||
* You cannot lower flat varyings after they passed through ``nir_opt_varying``
|
||||
|
||||
Current implementation rules:
|
||||
|
||||
* VS decides the memory layout
|
||||
* Descriptor formats can mismatch between stages (eg. VS int - FS float)
|
||||
* Descriptor formats always match what the instructions are using
|
||||
|
||||
|
||||
To regain the type information:
|
||||
|
||||
* in VS, every highp varying is written as U32
|
||||
* in FS, highp flat varyings are read as U32, highp smooth are read as F32
|
||||
* for mediump, we trust the original type
|
||||
Reference in new issue
Block a user