mirror of
https://github.com/elwrcl/intel-hard-graphics.git
synced 2026-10-31 10:58:03 +03:00
init
This commit is contained in:
12511 files changed
+6156641
No files matched your search
@@ -0,0 +1,173 @@
|
||||
:orphan:
|
||||
|
||||
.. _aco-fn-calls:
|
||||
|
||||
Function call support in RADV/ACO
|
||||
=================================
|
||||
|
||||
ACO supports function calls inside shaders - given a function signature and ABI, shaders can call
|
||||
an arbitrary function, even via only a function pointer (i.e. with an unknown function definition).
|
||||
|
||||
This function call support is useful for implementing ray tracing pipelines (by representing individual RT shaders
|
||||
as callable functions), but it also has potential use cases in GPGPU/Compute workloads.
|
||||
|
||||
This page serves to document the concepts involved in implementing function calls as well as an overview of the
|
||||
implementation components.
|
||||
|
||||
Function call representation
|
||||
----------------------------
|
||||
|
||||
In NIR, function calls are represented by a `nir_call_instr`. The instruction takes a `nir_function` representing
|
||||
the function being called, as well as SSA defs for each call parameter.
|
||||
NIR can also represent "indirect calls", i.e. calls where the function being called is
|
||||
unknown - instead, the instruction takes an SSA def containing a function pointer to the callee. In this case, the
|
||||
`nir_function` only serves to provide information about the function signature, i.e. how many and which parameters
|
||||
the function takes.
|
||||
|
||||
Call instructions do not have return values - instead, return values are represented by so-called "return parameters".
|
||||
Instead of an SSA value, these parameters are derefs, and the return value is written into the deref when the callee
|
||||
returns. Return parameters can double as input parameters, too - the callee can read the previous value of the deref
|
||||
before (potentially) overwriting it with a new value.
|
||||
|
||||
ACO's representation of function calls follows this very closely. Calls are described by the `p_call` pseudo-instruction.
|
||||
The operands to this instruction are a function pointer (i.e. the address of the callee), followed by the call
|
||||
parameters. Return parameters are handled differently, though: While the initial value of the return parameter is passed
|
||||
as an operand, the call instruction produces new definitions that refer to the SSA values of the return parameters after
|
||||
the function call returns. There is a special NIR intrinsic ``load_return_param_amd`` that can be used to access these
|
||||
new definitions when lowering return parameter derefs to SSA form.
|
||||
|
||||
.. _div-calls:
|
||||
|
||||
Divergent calls
|
||||
---------------
|
||||
|
||||
On CPUs, a call instruction will only ever jump to a single address. However, GPUs are SIMT, and the value of a function
|
||||
pointer may be divergent, i.e. different threads try calling different functions within the same call instruction. AMD
|
||||
hardware executes one instruction for all threads in lockstep, so the multiple callees have to be executed one after
|
||||
the other.
|
||||
|
||||
This is handled by RADV in ``radv_nir_lower_call_abi``. In addition to the (non-divergent) function pointer to jump to,
|
||||
``radv_nir_lower_call_abi`` prepends another parameter representing the (potentially divergent) function pointer for all
|
||||
lanes. For callable functions, ``radv_nir_lower_call_abi`` wraps the function body in a condition that verifies that the
|
||||
current thread's (divergent) pointer matches the (non-divergent) pointer that is currently being executed. This serves
|
||||
to "mask off" all threads that wanted to jump to a different function than what is currently executing. At the very end,
|
||||
``radv_nir_lower_call_abi`` inserts some code deciding whether to jump to the next callee or to return.
|
||||
|
||||
.. _stack:
|
||||
|
||||
Stack
|
||||
-----
|
||||
|
||||
Supporting arbitrary function calls also means supporting recursion, and recursive functions need a stack.
|
||||
AMD hardware provides instructions for accessing a per-thread scratch memory area in VRAM, and ACO uses this per-thread
|
||||
scratch memory to set up its stack.
|
||||
|
||||
The stack frame for a function consists of all scratch memory allocated for this function in NIR, as well as space to
|
||||
spill VGPRs if that is required. ACO adds a stack pointer as a parameter to every function - this stack pointer is added
|
||||
to the offset inside the scratch space for all scratch loads/stores to make sure they don't overwrite stack frames of
|
||||
caller functions.
|
||||
|
||||
ACO's call instructions take two stack-related operands: The current (caller) stack pointer and the caller's stack size.
|
||||
When converting the call instruction to hardware instructions, ACO will add the caller stack size to the stack pointer
|
||||
for the duration of the call (and subtract it again afterwards). This allows us to re-use the same stack pointer after
|
||||
the call.
|
||||
|
||||
Implicit/System Parameters
|
||||
--------------------------
|
||||
|
||||
In addition to parameters defined by the function signature, both RADV and ACO will insert additional parameters while
|
||||
lowering calls. This is an overview of which lowering passes add which parameters.
|
||||
|
||||
Parameters added by ``radv_nir_lower_call_abi`` (see :ref:`Divergent calls <div-calls>`):
|
||||
- "Uniform"/Non-divergent callee pointer
|
||||
- Divergent function pointer
|
||||
|
||||
Parameters added by ACO: (see :ref:`Stack <stack>`)
|
||||
- Stack pointer (uniform)
|
||||
|
||||
ABI Definition
|
||||
--------------
|
||||
|
||||
The ABI (Application Binary Interface) defines specifics about the interaction between the function caller and the
|
||||
callee (e.g. assignment of registers to parameters or register preservation). In ACO, the primary purpose of the ABI is
|
||||
to define which register ranges are "preserved" (i.e. never overwritten by the callee) or "clobbered" (i.e. potentially
|
||||
overwritten by the callee).
|
||||
|
||||
The caller can use preserved register ranges to store temporaries that are live across a call, and the callee can use
|
||||
clobbered register ranges to store its own temporaries. If the callee wants to use registers from a preserved range,
|
||||
then it needs to back up the value contained in the preserved register beforehand, and restore it when it's done using
|
||||
the preserved register. Similarly, if there are not enough preserved registers for the caller to store all its
|
||||
temporaries, the caller will need to spill excess temporaries to the stack.
|
||||
|
||||
ACO has to cater to different needs when defining ABIs: On one side, ray tracing traversal shaders demand to free up
|
||||
the entire register file for the callee (Ray traversal is a really hot loop, so we don't want to spill anything at all).
|
||||
Besides some parameters like the invocation ID, these shaders should be able to overwrite almost anything. On the other
|
||||
side, RT traversal shaders should not be required to free up the register file when calling any-hit/intersection shaders
|
||||
as this would also cause spilling during traversal. GPGPU compute workloads could fall anywhere between these extremes,
|
||||
so a middle-ground solution is desirable for these.
|
||||
|
||||
ACO's way of defining an ABI divides the register file into "blocks" (``struct aco::ABI::RegisterBlock``). Each block
|
||||
consists of a fixed number of preserved and clobbered registers, and a boolean determining whether the preserved or
|
||||
clobbered registers come first in the block. Preserved and clobbered register ranges are defined by
|
||||
repeating these blocks for as long as there are unassigned registers.
|
||||
|
||||
Some examples of preserved/clobbered register ranges using this approach::
|
||||
|
||||
For all examples, there are 108 SGPRs and 128 VGPRs to assign.
|
||||
|
||||
RegisterBlock:
|
||||
clobbered_size: {16 sgpr, 16 vgpr}
|
||||
preserved_size: {16 sgpr, 16 vgpr}
|
||||
clobbered_first: false
|
||||
results in:
|
||||
v0-v15: preserved
|
||||
v16-v31: clobbered
|
||||
v32-v47: preserved
|
||||
v48-v63: clobbered
|
||||
v64-v79: preserved
|
||||
v80-v95: clobbered
|
||||
v96-v111: preserved
|
||||
v112-v127: clobbered
|
||||
|
||||
s0-s15: preserved
|
||||
s16-s31: clobbered
|
||||
s32-s47: preserved
|
||||
s48-s63: clobbered
|
||||
s64-s79: preserved
|
||||
s80-s95: clobbered
|
||||
s96-s108: preserved
|
||||
|
||||
RegisterBlock:
|
||||
clobbered_size: {128 sgpr, 256 vgpr}
|
||||
preserved_size: {80 sgpr, 80 vgpr}
|
||||
clobbered_first: false
|
||||
results in:
|
||||
v0-v79: preserved
|
||||
v80-v127: clobbered
|
||||
|
||||
s0-s79: preserved
|
||||
s80-s108: clobbered
|
||||
|
||||
An alternating preserved-clobbered-preserved pattern is useful for generic compute workloads, because the ratio of
|
||||
preserved to clobbered registers is roughly the same, no matter how many registers are used by the shaders.
|
||||
|
||||
The latter example where the lower part of the register file is preserved and only some registers high up in the
|
||||
register file are clobbered is suitable for any-hit/intersection shaders - traversal shader temporaries can live in the
|
||||
preserved part low in the register file.
|
||||
|
||||
This block assignment is optional - if no ``RegisterBlock`` is given, the ABI defines the entire register range as
|
||||
clobbered-by-default, although parameters that are not marked as clobbered via ``ACO_NIR_PARAM_ATTRIB_DISCARDABLE``
|
||||
will continue being preserved.
|
||||
|
||||
Parameter Register Assignment
|
||||
-----------------------------
|
||||
|
||||
If a ``RegisterBlock`` defines preserved and clobbered ranges, then parameters are assigned registers from either range
|
||||
depending on ``ACO_NIR_PARAM_ATTRIB_DISCARDABLE`` - if parameters are marked as clobbered with this attribute, then they
|
||||
are assigned a register in a clobbered range, otherwise they are assigned in a register in a preserved range. The order
|
||||
of the parameters in the register file is not necessarily the same order as in the function signature - they may get
|
||||
reordered if it's beneficial to fill gaps or for alignment.
|
||||
|
||||
If there is no ``RegisterBlock``, then registers will be assigned based on alignment only.
|
||||
|
||||
If there is no more space for a parameter in any of its corresponding register ranges, it will be moved to the stack.
|
||||
@@ -0,0 +1,85 @@
|
||||
:orphan:
|
||||
|
||||
.. _radv-debug-hang:
|
||||
|
||||
Debugging GPU hangs with RADV
|
||||
=============================
|
||||
|
||||
UMR (optional)
|
||||
--------------
|
||||
|
||||
UMR is needed for dumping a lot of useful information. Clone, build and install
|
||||
`UMR <https://gitlab.freedesktop.org/tomstdenis/umr>`__. Do not forget to run
|
||||
``chmod +s $(which umr)`` so RADV can actually access UMR.
|
||||
|
||||
UMR needs to access some kernel debug interfaces:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
chmod 777 /sys/kernel/debug
|
||||
chmod -R 777 /sys/kernel/debug/dri
|
||||
|
||||
Secure boot has to be disabled as well.
|
||||
|
||||
Generating and analyzing hang reports
|
||||
-------------------------------------
|
||||
|
||||
With UMR installed, you can now set ``RADV_DEBUG=hang`` which makes RADV insert
|
||||
trace markers and synchronization and check for hangs. The hang report will be
|
||||
saved to ``~/radv_dumps_<pid>_<time>``. Inside the directory of the hang report,
|
||||
there are a couple of files:
|
||||
|
||||
* ``*.spv``: SPIR-V binaries of the pipeline that was bound when the hang
|
||||
occurred.
|
||||
* ``addr_binding_report.log``: VK_EXT_address_binding_report logs.
|
||||
* ``app_info.log``: ``VkApplicationInfo`` fields.
|
||||
* ``bo_history.log``: A list of every GPU memory allocation and deallocation.
|
||||
If the GPU hang was caused by a page fault, you can use
|
||||
`radv_check_va.py <https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/tools/radv_check_va.py>`__
|
||||
to figure out if address is invalid or used after the memory was deallocated.
|
||||
* ``bo_ranges.log``: Address ranges that were valid at the time of submission.
|
||||
* ``dmesg.log``: Output of ``dmesg``, if available.
|
||||
* ``gpu_info.log``: Fields of ``radeon_info``.
|
||||
* ``pipeline.log``: IR of the shaders that were bound during the hang as well as
|
||||
programm counters of waves executing said shaders and bound descriptors.
|
||||
* ``registers.log``: Various GPU state registers.
|
||||
* ``trace.log``: An annotated list of the command stream that caused the hang.
|
||||
the commands that hung come after
|
||||
``!!!!! This is the last trace point that was reached by the CP !!!!!``.
|
||||
* ``umr_ring.log``: Similar to ``trace.log``.
|
||||
* ``umr_waves.log``: A list of waves that were active at the time of the hang,
|
||||
including register values.
|
||||
* ``vm_fault.log``: The page fault address if a page fault occurred.
|
||||
|
||||
Note: By default, the backend IR (ACO or LLVM) and the disassembly should be
|
||||
dumped to ``pipeline.log``. But due to shaders caching, you might need
|
||||
``RADV_DEBUG=hang,nocache`` to get SPIR-V and NIR in the GPU hang report.
|
||||
|
||||
Debugging Steam games
|
||||
---------------------
|
||||
|
||||
Steam games require a bit more work so RADV can access UMR: In your Steam library,
|
||||
make sure **Tools** is checked and search for **Steam Linux Runtime**.
|
||||
Under **Properties** -> **Installed Files**, click **Browse**, open
|
||||
``_v2-entry-point`` and add
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
shift 2
|
||||
exec "$@"
|
||||
|
||||
at the top of the file. Hang debugging can be enabled by selecting the faulting
|
||||
game and adding ``RADV_DEBUG=hang %command%`` under **Properties** -> **General**
|
||||
-> **LAUNCH OPTIONS**.
|
||||
|
||||
Debugging hangs without RADV_DEBUG=hang
|
||||
---------------------------------------
|
||||
|
||||
In some situations, ``RADV_DEBUG=hang`` wouldn't be able to generate a GPU hang
|
||||
report, like for synchronization issues (because it enables
|
||||
``RADV_DEBUG=syncshaders`` behind the scene). An alternative solution is to
|
||||
disable GPU recovery by adding ``amdgpu.gpu_recovery=0`` to your kernel command
|
||||
line options. And then invoke UMR manually with
|
||||
``umr --by-pci <pci_id> -O bits,halt_waves -go 0 -wa <ring> -go 1 2>&1`` for
|
||||
dumping the waves and ``umr --by-pci <pci_id> -RS <ring> 2>&1`` for dumping the
|
||||
rings once the GPU hang occurred.
|
||||
@@ -0,0 +1,477 @@
|
||||
Primitive Ordered Pixel Shading
|
||||
===============================
|
||||
|
||||
Primitive Ordered Pixel Shading (POPS) is the feature available starting from
|
||||
GFX9 that provides the Fragment Shader Interlock or Fragment Shader Ordering
|
||||
functionality.
|
||||
|
||||
It allows a part of a fragment shader — an ordered section (or a critical
|
||||
section) — to be executed sequentially in rasterization order for different
|
||||
invocations covering the same pixel position.
|
||||
|
||||
This article describes how POPS is set up in shader code and the registers. The
|
||||
information here is currently provided for architecture generations up to GFX11.
|
||||
|
||||
Note that the information in this article is **not official** and may contain
|
||||
inaccuracies, as well as incomplete or incorrect assumptions. It is based on the
|
||||
shader code output of the Radeon GPU Analyzer for Rasterizer Ordered View usage
|
||||
in Direct3D shaders, AMD's Platform Abstraction Library (PAL), ISA references,
|
||||
and experimentation with the hardware.
|
||||
|
||||
Shader code
|
||||
-----------
|
||||
|
||||
With POPS, a wave can dynamically execute up to one ordered section. It is fine
|
||||
for a wave not to enter an ordered section at all if it doesn't need ordering on
|
||||
its execution path, however.
|
||||
|
||||
The setup of the ordered section consists of three parts:
|
||||
|
||||
1. Entering the ordered section in the current wave — awaiting the completion of
|
||||
ordered sections in overlapped waves.
|
||||
2. Resolving overlap within the current wave — intrawave collisions (optional
|
||||
and GFX9–10.3 only).
|
||||
3. Exiting the ordered section — resuming overlapping waves trying to enter
|
||||
their ordered sections.
|
||||
|
||||
GFX9–10.3: Entering the ordered section in the wave
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Awaiting the completion of ordered sections in overlapped waves is performed by
|
||||
setting the POPS packer hardware register, and then polling the volatile
|
||||
``pops_exiting_wave_id`` ALU operand source until its value exceeds the newest
|
||||
overlapped wave ID for the current wave.
|
||||
|
||||
The information needed for the wave to perform the waiting is provided to it via
|
||||
the SGPR argument ``COLLISION_WAVEID``. Its loading needs to be enabled in the
|
||||
``SPI_SHADER_PGM_RSRC2_PS`` and ``PA_SC_SHADER_CONTROL`` registers (note that
|
||||
the POPS arguments specifically need to be enabled not only in ``RSRC`` unlike
|
||||
various other arguments, but in ``PA_SC_SHADER_CONTROL`` as well).
|
||||
|
||||
The collision wave ID argument contains the following unsigned values:
|
||||
|
||||
* [31]: Whether overlap has occurred.
|
||||
* [29:28] (GFX10+) / [28] (GFX9): ID of the packer the wave should be associated
|
||||
with.
|
||||
* [25:16]: Newest overlapped wave ID.
|
||||
* [9:0]: Current wave ID.
|
||||
|
||||
The 2020 RDNA and RDNA 2 ISA references contain incorrect offsets and widths of
|
||||
the fields, possibly from an early development iteration, but the meanings of
|
||||
them are accurate there.
|
||||
|
||||
The wait must not be performed if the "did overlap" bit 31 is set to 0,
|
||||
otherwise it will result in a hang. Also, the bit being set to 0 indicates that
|
||||
there are *both* no wave overlap *and no intrawave collisions* for the current
|
||||
wave — so if the bit is 0, it's safe for the wave to skip all of the POPS logic
|
||||
completely and execute the contents of the ordered section simply as usual with
|
||||
unordered access as a potential additional optimization. The packer hardware
|
||||
register, however, may be set even without overlap safely — it's the wait loop
|
||||
itself that must not be executed if it was reported that there was no overlap.
|
||||
|
||||
The packer ID needs to be passed to the packer hardware register using
|
||||
``s_setreg_b32`` so the wave can poll ``pops_exiting_wave_id`` on that packer.
|
||||
|
||||
On GFX9, the ``MODE`` (1) hardware register has two bits specifying which packer
|
||||
the wave is associated with:
|
||||
|
||||
* [25]: The wave is associated with packer 1.
|
||||
* [24]: The wave is associated with packer 0.
|
||||
|
||||
Initially, both of these bits are set 0, meaning that POPS is disabled for the
|
||||
wave. If the wave needs to enter the ordered section, it must set bit 24 to 1 if
|
||||
the packer ID in ``COLLISION_WAVEID`` is 0, or set bit 25 to 1 if the packer ID
|
||||
is 1.
|
||||
|
||||
Starting from GFX10, the ``POPS_PACKER`` (25) hardware register is used instead,
|
||||
containing the following fields:
|
||||
|
||||
* [2:1]: Packer ID.
|
||||
* [0]: POPS enabled for the wave.
|
||||
|
||||
Initially, POPS is disabled for a wave. To start entering the ordered section,
|
||||
bits 2:1 must be set to the packer ID from ``COLLISION_WAVEID``, and bit 0 needs
|
||||
to be set to 1.
|
||||
|
||||
The wave IDs, both in ``COLLISION_WAVEID`` and ``pops_exiting_wave_id``, are
|
||||
10-bit values wrapping around on overflow — consecutive waves are numbered 1022,
|
||||
1023, 0, 1… This wraparound needs to be taken into account when comparing the
|
||||
exiting wave ID and the newest overlapped wave ID.
|
||||
|
||||
Specifically, until the current wave exits the ordered section, its ID can't be
|
||||
smaller than the newest overlapped wave ID or the exiting wave ID. So
|
||||
``current_wave_id + 1`` can be subtracted from 10-bit wave IDs to remap them to
|
||||
monotonically increasing unsigned values. In this case, the largest value,
|
||||
0xFFFFFFFF, will correspond to the current wave, 10-bit values up to the current
|
||||
wave ID will be in a range near 0xFFFFFFFF growing towards it, and wave IDs from
|
||||
before the last wraparound will be near 0 increasing away from it. Subtracting
|
||||
``current_wave_id + 1`` is equivalent to adding ``~current_wave_id``.
|
||||
|
||||
GFX9 has an off-by-one error in the newest overlapped wave ID: if the 10-bit
|
||||
newest overlapped wave ID is greater than the 10-bit current wave ID (meaning
|
||||
that it's behind the last wraparound point), 1 needs to be added to the newest
|
||||
overlapped wave ID before using it in the comparison. This was corrected in
|
||||
GFX10.
|
||||
|
||||
The exiting wave ID (not to be confused with "exited" — the exiting wave ID is
|
||||
the wave that will exit the ordered section next) is queried via the
|
||||
``pops_exiting_wave_id`` ALU operand source, numbered 239. Normally, it will be
|
||||
one of the arguments of ``s_add_i32`` that remaps it from a wrapping 10-bit wave
|
||||
ID to monotonically increasing one.
|
||||
|
||||
It's a volatile operand, and it needs to be read in a loop until its value
|
||||
becomes greater than the newest overlapped wave ID (after remapping both to
|
||||
monotonic). However, if it's too early for the current wave to enter the ordered
|
||||
section, it needs to yield execution to other waves that may potentially be
|
||||
overlapped — via ``s_sleep``. GFX9 requires a finite amount of delay to be
|
||||
specified, AMD uses 3. Starting from GFX10, exiting the ordered section wakes up
|
||||
the waiting waves, so the maximum delay of 0xFFFF can be used.
|
||||
|
||||
In pseudocode, the entering logic would look like this::
|
||||
|
||||
bool did_overlap = collision_wave_id[31];
|
||||
if (did_overlap) {
|
||||
if (gfx_level >= GFX10) {
|
||||
uint packer_id = collision_wave_id[29:28];
|
||||
s_setreg_b32(HW_REG_POPS_PACKER[2:0], 1 | (packer_id << 1));
|
||||
} else {
|
||||
uint packer_id = collision_wave_id[28];
|
||||
s_setreg_b32(HW_REG_MODE[25:24], packer_id ? 0b10 : 0b01);
|
||||
}
|
||||
|
||||
uint current_10bit_wave_id = collision_wave_id[9:0];
|
||||
// Or -(current_10bit_wave_id + 1).
|
||||
uint wave_id_remap_offset = ~current_10bit_wave_id;
|
||||
|
||||
uint newest_overlapped_10bit_wave_id = collision_wave_id[25:16];
|
||||
if (gfx_level < GFX10 &&
|
||||
newest_overlapped_10bit_wave_id > current_10bit_wave_id) {
|
||||
++newest_overlapped_10bit_wave_id;
|
||||
}
|
||||
uint newest_overlapped_wave_id =
|
||||
newest_overlapped_10bit_wave_id + wave_id_remap_offset;
|
||||
|
||||
while (!(src_pops_exiting_wave_id + wave_id_remap_offset >
|
||||
newest_overlapped_wave_id)) {
|
||||
s_sleep(gfx_level >= GFX10 ? 0xFFFF : 3);
|
||||
}
|
||||
}
|
||||
|
||||
The SPIR-V fragment shader interlock specification requires an invocation — an
|
||||
individual invocation, not the whole subgroup — to execute
|
||||
``OpBeginInvocationInterlockEXT`` exactly once. However, if there are multiple
|
||||
begin instructions, or even multiple begin/end pairs, under divergent
|
||||
conditions, a wave may end up waiting for the overlapped waves multiple times.
|
||||
Thankfully, it's safe to set the POPS packer hardware register to the same
|
||||
value, or to run the wait loop, multiple times during the wave's execution, as
|
||||
long as the ordered section isn't exited in between by the wave.
|
||||
|
||||
GFX11: Entering the ordered section in the wave
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Instead of exposing wave IDs to shaders, GFX11 uses the "export ready" wave
|
||||
status flag to report that the wave may enter the ordered section. It's awaited
|
||||
by the ``s_wait_event`` instruction, with the bit 0 ("don't wait for
|
||||
``export_ready``") of the immediate operand set to 0. On GFX11 specifically, AMD
|
||||
passes 0 as the whole immediate operand.
|
||||
|
||||
The "export ready" wait can be done multiple times safely.
|
||||
|
||||
GFX9–10.3: Resolving intrawave collisions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
On GFX9–10.3, it's possible for overlapping fragment shader invocations to be
|
||||
placed not only in different waves, but also in the same wave, with the shader
|
||||
code making sure that the ordered section is executed for overlapping
|
||||
invocations in order.
|
||||
|
||||
This functionality is optional — it can be activated by enabling loading of the
|
||||
``INTRAWAVE_COLLISION`` SGPR argument in ``SPI_SHADER_PGM_RSRC2_PS`` and
|
||||
``PA_SC_SHADER_CONTROL``.
|
||||
|
||||
The lower 8 or 16 (depending on the wave size) bits of ``INTRAWAVE_COLLISION``
|
||||
contain the mask of whether each quad in the wave starts a new layer of
|
||||
overlapping invocations, and thus the ordered section code for them needs to be
|
||||
executed after running it for all lanes with indices preceding that quad index
|
||||
multiplied by 4. The rest of the bits in the argument need to be ignored — AMD
|
||||
explicitly masks them out in shader code (although this is not necessary if the
|
||||
shader uses "find first 1" to obtain the start of the next set of overlapping
|
||||
quads or expands this quad mask into a lane mask).
|
||||
|
||||
For example, if the intrawave collision mask is 0b0000001110000100, or
|
||||
``(1 << 2) | (1 << 7) | (1 << 8) | (1 << 9)``, the code of the ordered section
|
||||
needs to be executed first only for quads 1:0 (lanes 7:0), then only for quads
|
||||
6:2 (lanes 27:8), then for quad 7 (lanes 31:28), then for quad 8 (lanes 35:32),
|
||||
and then for the remaining quads 15:9 (lanes 63:36).
|
||||
|
||||
This effectively causes the ordered section to be executed as smaller
|
||||
"sub-subgroups" within the original subgroup.
|
||||
|
||||
However, this is not always compatible with the execution model of SPIR-V or
|
||||
GLSL fragment shaders, so enabling intrawave collisions and wrapping a part of
|
||||
the shader in a loop may be unsafe in some cases. One particular example is when
|
||||
the shader uses subgroup operations influenced by lanes outside the current
|
||||
quad. In this case, the code outside and inside the ordered section may be
|
||||
executed with different sets of active invocations, affecting the results of
|
||||
subgroup operations. But in SPIR-V and GLSL, fragment shader interlock is not
|
||||
supposed to modify the set of active invocations in any way. So the intrawave
|
||||
collision loop may break the results of subgroup operations in unpredictable
|
||||
ways, even outside the driver's compiler infrastructure. Even if the driver
|
||||
splits the subgroup exactly at ``OpBeginInvocationInterlockEXT`` and makes the
|
||||
lane subsets rejoin exactly at ``OpEndInvocationInterlockEXT``, the application
|
||||
and the compilers that created the source shader are still not aware of that
|
||||
happening — the input SPIR-V or GLSL shader might have already gone through
|
||||
various optimizations, such as common subexpression elimination which might
|
||||
have considered a subgroup operation before ``OpBeginInvocationInterlockEXT``
|
||||
and one after it equivalent.
|
||||
|
||||
The idea behind reporting intrawave collisions to shaders is to reduce the
|
||||
impact on the parallelism of the part of the shader that doesn't depend on the
|
||||
ordering, to avoid wasting lanes in the wave and to allow the code outside the
|
||||
ordered section in different invocations to run in parallel lanes as usual. This
|
||||
may be especially helpful if the ordered section is small compared to the rest
|
||||
of the shader — for instance, a custom blending equation in the end of the usual
|
||||
fragment shader for a surface in the world.
|
||||
|
||||
However, whether handling intrawave collisions is preferred is not a question
|
||||
with one universal answer. Intrawave collisions are pretty uncommon without
|
||||
multisampling, or when using sample interlock with multisampling, although
|
||||
they're highly frequent with pixel interlock with multisampling, when adjacent
|
||||
primitives cover the same pixels along the shared edge (though that's an
|
||||
extremely expensive situation in general). But resolving intrawave collisions
|
||||
adds some overhead costs to the shader. If intrawave overlap is unlikely to
|
||||
happen often, or even more importantly, if the majority of the shader is inside
|
||||
the ordered section, handling it in the shader may cause more harm than good.
|
||||
|
||||
GFX11 removes this concept entirely, instead overlapping invocations are always
|
||||
placed in different waves.
|
||||
|
||||
GFX9–10.3: Exiting the ordered section in the wave
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To exit the ordered section and let overlapping waves resume execution and enter
|
||||
their ordered sections, the wave needs to send the ``ORDERED_PS_DONE`` message
|
||||
(7) using ``s_sendmsg``.
|
||||
|
||||
If the wave has enabled POPS by setting the packer hardware register, it *must
|
||||
not* execute ``s_endpgm`` without having sent ``ORDERED_PS_DONE`` once, so the
|
||||
message must be sent on all execution paths after the packer register setup.
|
||||
However, if the wave exits before having configured the packer register, sending
|
||||
the message is not required, though it's still fine to send it regardless of
|
||||
that.
|
||||
|
||||
Note that if the shader has multiple ``OpEndInvocationInterlockEXT``
|
||||
instructions executed in the same wave (depending on a divergent condition, for
|
||||
example), it must still be ensured that ``ORDERED_PS_DONE`` is sent by the wave
|
||||
only once, and especially not before any awaiting of overlapped waves.
|
||||
|
||||
Before the message is sent, all counters for memory accesses that need to be
|
||||
primitive-ordered, both writes and (in case something after the ordered section
|
||||
depends on the per-pixel data, for instance, the tail blending fallback in
|
||||
order-independent transparency) reads, must be awaited. Those may include
|
||||
``vm``, ``vs``, and in some cases ``lgkm`` (though normally primitive-ordered
|
||||
memory accesses will be done through VMEM with divergent addresses, not SMEM, as
|
||||
there's no synchronization between fragments at different pixel coordinates, but
|
||||
it's still technically possible for a shader, even though pointless and
|
||||
nonoptimal, to explicitly perform them in a waterfall loop, for instance, and
|
||||
that must work correctly too). Without that, a race condition will occur when
|
||||
the newly resumed waves start accessing the memory locations to which there
|
||||
still are outstanding accesses in the current wave.
|
||||
|
||||
Another option for exiting is the ``s_endpgm_ordered_ps_done`` instruction,
|
||||
which combines waiting for all the counters, sending the ``ORDERED_PS_DONE``
|
||||
message, and ending the program. Generally, however, it's desirable to resume
|
||||
overlapping waves as early as possible, including before the export, as it may
|
||||
stall the wave for some time too.
|
||||
|
||||
GFX11: Exiting the ordered section in the wave
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The overlapping waves are resumed when the wave performs the last export (with
|
||||
the ``done`` flag).
|
||||
|
||||
The same requirements for awaiting the memory access counters as on GFX9–10.3
|
||||
still apply.
|
||||
|
||||
Memory access requirements
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The compiler needs to ensure that entering the ordered section implements
|
||||
acquire semantics, and exiting it implements release semantics, in the fragment
|
||||
interlock memory scope for ``UniformMemory`` and ``ImageMemory`` SPIR-V storage
|
||||
classes.
|
||||
|
||||
A fragment interlock memory scope instance includes overlapping fragment shader
|
||||
invocations executed by commands inside a single subpass. It may be considered a
|
||||
subset of a queue family memory scope instance from the perspective of memory
|
||||
barriers.
|
||||
|
||||
Fragment shader interlock doesn't perform implicit memory availability or
|
||||
visibility operations. Shaders must do them by themselves for accesses requiring
|
||||
primitive ordering, such as via ``coherent`` (``queuefamilycoherent``) in GLSL
|
||||
or ``MakeAvailable`` and ``MakeVisible`` in at least the ``QueueFamily`` scope
|
||||
in SPIR-V.
|
||||
|
||||
On AMD hardware, this means that the accessed memory locations must be made
|
||||
available or visible between waves that may be executed on any compute unit — so
|
||||
accesses must go directly to the global L2 cache, bypassing L0$ via the GLC flag
|
||||
and L1$ via DLC.
|
||||
|
||||
However, it should be noted that memory accesses in the ordered section may be
|
||||
expected by the application to be done in primitive order even if they don't
|
||||
have the GLC and DLC flags. Coherent access not only bypasses, but also
|
||||
invalidates the lower-level caches for the accessed memory locations. Thus,
|
||||
considering that normally per-pixel data is accessed exclusively by the
|
||||
invocation executing the ordered section, it's not necessary to make all reads
|
||||
or writes in the ordered section for one memory location to be GLC/DLC — just
|
||||
the first read and the last write: it doesn't matter if per-pixel data is cached
|
||||
in L0/L1 in the middle of a dependency chain in the ordered section, as long as
|
||||
it's invalidated in them in the beginning and flushed to L2 in the end.
|
||||
Therefore, optimizations in the compiler must not simply assume that only
|
||||
coherent accesses need primitive ordering — and moreover, the compiler must also
|
||||
take into account that the same data may be accessed through different bindings.
|
||||
|
||||
Export requirements
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
With POPS, on all hardware generations, the shader must have at least one
|
||||
export, though it can be a null or an ``off, off, off, off`` one.
|
||||
|
||||
Also, even if the shader doesn't need to export any real data, the export
|
||||
skipping that was added in GFX10 must not be used, and some space must be
|
||||
allocated in the export buffer, such as by setting ``SPI_SHADER_COL_FORMAT`` for
|
||||
some color output to ``SPI_SHADER_32_R``.
|
||||
|
||||
Without this, the shader will be executed without the needed synchronization on
|
||||
GFX10, and will hang on GFX11.
|
||||
|
||||
Drawing context setup
|
||||
---------------------
|
||||
|
||||
Configuring POPS
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Most of the configuration is performed via the ``DB_SHADER_CONTROL`` register.
|
||||
|
||||
To enable POPS for the draw,
|
||||
``DB_SHADER_CONTROL.PRIMITIVE_ORDERED_PIXEL_SHADER`` should be set to 1.
|
||||
|
||||
On GFX9–10.3, ``DB_SHADER_CONTROL.POPS_OVERLAP_NUM_SAMPLES`` controls which
|
||||
fragment shader invocations are considered overlapping:
|
||||
|
||||
* For pixel interlock, it must be set to 0 (1 sample).
|
||||
* If sample interlock is sufficient (only synchronizing between invocations that
|
||||
have any common sample mask bits), it may be set to
|
||||
``PA_SC_AA_CONFIG.MSAA_EXPOSED_SAMPLES`` — the number of sample coverage mask
|
||||
bits passed to the shader which is expected to use the sample mask to
|
||||
determine whether it's allowed to access the data for each of the samples. As
|
||||
of April 2023, PAL for some reason doesn't use non-1x
|
||||
``POPS_OVERLAP_NUM_SAMPLES`` at all, even when using Direct3D Rasterizer
|
||||
Ordered Views or ``GL_INTEL_fragment_shader_ordering`` with sample shading
|
||||
(those APIs tie the interlock granularity to the shading frequency — Vulkan
|
||||
and OpenGL fragment shader interlock, however, allows specifying the interlock
|
||||
granularity independently of it, making it possible both to ask for finer
|
||||
synchronization guarantees and to require stronger ones than Direct3D ROVs can
|
||||
provide). However, with MSAA, on AMD hardware, pixel interlock generally
|
||||
performs *massively*, sometimes prohibitively, slower than sample interlock,
|
||||
because it causes fragment shader invocations along the common edge of
|
||||
adjacent primitives to be ordered as they cover the same pixels (even though
|
||||
they don't cover any common samples). So it's highly desirable for the driver
|
||||
to provide sample interlock, and to set ``POPS_OVERLAP_NUM_SAMPLES``
|
||||
accordingly, if the shader declares that it's enough for it via the execution
|
||||
mode.
|
||||
|
||||
On GFX11, when POPS is enabled, ``DB_SHADER_CONTROL.OVERRIDE_INTRINSIC_RATE`` is
|
||||
used in place of ``DB_SHADER_CONTROL.POPS_OVERLAP_NUM_SAMPLES`` from the earlier
|
||||
architecture generations (and has a different bit offset in the register), and
|
||||
``DB_SHADER_CONTROL.OVERRIDE_INTRINSIC_RATE_ENABLE`` must be set to 1. The GFX11
|
||||
blending performance workaround overriding the intrinsic rate must not be
|
||||
applied if POPS is used in the draw — the intrinsic rate override must be used
|
||||
solely to control the interlock granularity in this case.
|
||||
|
||||
No explicit flushes/synchronization are needed when changing the pipeline state
|
||||
variables that may be involved in POPS, such as the rasterization sample count.
|
||||
POPS automatically keeps synchronizing invocations even between draws with
|
||||
different sample counts (invocations with common coverage mask bits are
|
||||
considered overlapping by the hardware, regardless of what those samples
|
||||
actually are — only the indices are important).
|
||||
|
||||
Also, on GFX11, POPS uses ``DB_Z_INFO.NUM_SAMPLES`` to determine the coverage
|
||||
sample count, and it must be equal to ``PA_SC_AA_CONFIG.MSAA_EXPOSED_SAMPLES``
|
||||
even if there's no depth/stencil target.
|
||||
|
||||
Hardware bug workarounds
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Early revisions of GFX9 — ``CHIP_VEGA10`` and ``CHIP_RAVEN`` — contain a
|
||||
hardware bug that may result in a hang, and need a workaround to be enabled.
|
||||
Specifically, if POPS is used with 8 or more rasterization samples, or with 8 or
|
||||
more depth/stencil target samples, ``DB_DFSM_CONTROL.POPS_DRAIN_PS_ON_OVERLAP``
|
||||
must be set to 1 for draws that satisfy this condition. In PAL, this is the
|
||||
``waMiscPopsMissedOverlap`` workaround. It results in slightly lower performance
|
||||
in those cases, increasing the frame time by around 1.5 to 2 times in
|
||||
`nvpro-samples/vk_order_independent_transparency <https://github.com/nvpro-samples/vk_order_independent_transparency>`_
|
||||
on the RX Vega 10, but it's required in a pretty rare case (8x+ MSAA) and is
|
||||
mandatory to ensure stability.
|
||||
|
||||
Also, even though ``DB_DFSM_CONTROL.POPS_DRAIN_PS_ON_OVERLAP`` is not required
|
||||
on chips other than the ``CHIP_VEGA10`` and ``CHIP_RAVEN`` GFX9 revisions, if
|
||||
it's enabled for some reason on GFX10.1 (``CHIP_NAVI10``, ``CHIP_NAVI12``,
|
||||
``CHIP_NAVI14``), and the draw uses POPS,
|
||||
``DB_RENDER_OVERRIDE2.PARTIAL_SQUAD_LAUNCH_CONTROL`` must be set to
|
||||
``PSLC_ON_HANG_ONLY`` to avoid a hang (see ``waStalledPopsMode`` in PAL).
|
||||
|
||||
Out-of-order rasterization interaction
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This is a largely unresearched topic currently. However, considering that POPS
|
||||
is primarily the functionality of the Depth Block, similarity to the behavior of
|
||||
out-of-order rasterization in depth/stencil testing may possibly be expected.
|
||||
|
||||
If the shader specifies an ordered interlock execution mode, out-of-order
|
||||
rasterization likely must not be enabled implicitly.
|
||||
|
||||
As of April 2023, PAL doesn't have any rules specifically for POPS in the logic
|
||||
determining whether out-of-order rasterization can be enabled automatically.
|
||||
Some of the POPS usage cases may possibly be covered by the rule that always
|
||||
disables out-of-order rasterization if the shader writes to Unordered Access
|
||||
Views (storage resources), though fragment shader interlock can be used for
|
||||
read-only purposes too (for ordering between draws that only read per-pixel data
|
||||
and draws that may write it), so that may be an oversight.
|
||||
|
||||
Explicitly enabled relaxed rasterization order modifies the concept of
|
||||
rasterization order itself in Vulkan, so from the point of view of the
|
||||
specification of fragment shader interlock, relaxed rasterization order should
|
||||
still be applicable regardless of whether the shader requests ordered interlock.
|
||||
PAL also doesn't make any POPS-specific exceptions here as of April 2023.
|
||||
|
||||
Variable-rate shading interaction
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
On GFX10.3, enabling ``DB_SHADER_CONTROL.PRIMITIVE_ORDERED_PIXEL_SHADER`` forces
|
||||
the shading rate to be 1x1, thus the
|
||||
``fragmentShadingRateWithFragmentShaderInterlock`` Vulkan device property must
|
||||
be false.
|
||||
|
||||
On GFX11, by default, POPS itself can work with non-1x1 shading rates, and the
|
||||
``fragmentShadingRateWithFragmentShaderInterlock`` property must be true.
|
||||
However, if ``PA_SC_VRS_SURFACE_CNTL_1.FORCE_SC_VRS_RATE_FINE_POPS`` is set,
|
||||
enabling POPS will force 1x1 shading rate.
|
||||
|
||||
The widest interlock granularity available on GFX11 — with the lowest possible
|
||||
Depth Block intrinsic rate, 1x — is per-fine-pixel, however. There's no
|
||||
synchronization between coarse fragment shader invocations if they don't cover
|
||||
common fine pixels, so the ``fragmentShaderShadingRateInterlock`` Vulkan device
|
||||
feature is not available.
|
||||
|
||||
Additional configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These are some largely unresearched options found in the register declarations.
|
||||
PAL doesn't use them, so it's unknown if they make any significant difference.
|
||||
No effect was found in `nvpro-samples/vk_order_independent_transparency <https://github.com/nvpro-samples/vk_order_independent_transparency>`_
|
||||
during testing on GFX9 ``CHIP_RAVEN`` and GFX11 ``CHIP_NAVI31``.
|
||||
|
||||
* ``DB_SHADER_CONTROL.EXEC_IF_OVERLAPPED`` on GFX9–10.3.
|
||||
* ``PA_SC_BINNER_CNTL_0.BIN_MAPPING_MODE = BIN_MAP_MODE_POPS`` on GFX10-11.5.
|
||||
This field is reserved on GFX12+ and should be set to 0.
|
||||
@@ -0,0 +1,164 @@
|
||||
:orphan:
|
||||
|
||||
.. _aco-live-var-analysis:
|
||||
|
||||
Description of live variable analysis in ACO
|
||||
============================================
|
||||
|
||||
Operand flags
|
||||
-------------
|
||||
|
||||
Operands can have several different flags which are set by live variable analysis:
|
||||
|
||||
* ``isKill``: this operand is not live after the instruction
|
||||
* ``isFirstKill``: this is the first instance of a temporary in the instruction with ``isKill=true``
|
||||
* ``isLateKill``: this operand cannot use the same registers as any definition
|
||||
* ``isClobbered``: this operand will use some of the same registers as a definition
|
||||
* ``isCopyKill``: this operand must use a different register than earlier instances of the same temporary in the
|
||||
instruction's operand list
|
||||
|
||||
Note that:
|
||||
|
||||
* ``isFirstKill=true`` requires that the operand is also ``isKill=true``.
|
||||
* If ``isKill=true``, then ``isLateKill=true`` requires that the operand is also ``isFirstKill=true`` or
|
||||
``isCopyKill=true``.
|
||||
* ``isCopyKill=true`` is incompatible with ``isFirstKill=true`` in the same operand.
|
||||
* ``isLateKill=true`` cannot be used with ``isClobbered=true`` for the same temporary in an instruction.
|
||||
* If ``isCopyKill=true``, then the operand will also have ``isKill=true``, even if the temporary is live after the
|
||||
instruction.
|
||||
|
||||
Operands and definitions can be "tied", indicated by ``get_tied_defs()``, meaning that the must use the same registers:
|
||||
|
||||
* Operands tied to definitions have ``isClobbered=true``.
|
||||
* If a clobbered operand has ``isKill=false``, it will be moved to a different register.
|
||||
* If two operands of the same temporary are tied to different definitions of the same instruction, the second of those
|
||||
operands will have ``isCopyKill=true``.
|
||||
|
||||
There also exists the ``isVectorAligned`` flag. This can be used to define a vector of operands, starting with
|
||||
``isVectorAligned=true`` and ending with ``isVectorAligned=false``, which must be placed in consecutive registers.
|
||||
|
||||
To prevent issues with register allocation, an operand being part of a vector means:
|
||||
|
||||
* Unless a vector is tied to a definition, it will also have ``isLateKill=true``, so that a partially killed vectors
|
||||
are not required to share the same space with definitions.
|
||||
* If two operands of the same temporary are both part of vectors in the same instruction, the second of those operands
|
||||
will have ``isCopyKill=true``.
|
||||
|
||||
Register demand calculation
|
||||
---------------------------
|
||||
|
||||
``live_in``
|
||||
temporaries live before the instruction
|
||||
|
||||
``live_out``
|
||||
temporaries live after the instruction
|
||||
|
||||
``live_through``
|
||||
temporaries live both before and after the instruction
|
||||
|
||||
``live_definitions``
|
||||
temporaries defined and used later (definitions where ``isKill=false``)
|
||||
|
||||
``dead_definitions``
|
||||
temporaries defined and not used later (definitions where ``isKill=true``)
|
||||
|
||||
``early_kill_operands``
|
||||
temporaries killed which are not marked late kill (operands where ``isFirstKill=true && isLateKill=false``)
|
||||
|
||||
``late_kill_operands``
|
||||
temporaries killed which are marked late kill (operands where ``isFirstKill=true && isLateKill=true``)
|
||||
|
||||
``first_kill_operands``
|
||||
temporaries killed by the instruction (operands where ``isFirstKill=true``)
|
||||
|
||||
``early_kill_operands + late_kill_operands``
|
||||
|
||||
``copied_operands``
|
||||
operands which are either clobbered but not killed, or copy-kill (operands where
|
||||
``isCopyKill=true || (isClobbered=true && isKill=false)``)
|
||||
|
||||
``early_kill_copies``
|
||||
``copied_operands`` which are not marked late kill (operands where
|
||||
``(isCopyKill=true && isLateKill=false) || (isClobbered=true && isKill=false)``)
|
||||
|
||||
``late_kill_copies``
|
||||
``copied_operands`` which are marked late kill (operands where ``(isCopyKill=true && isLateKill=true)``)
|
||||
|
||||
``live_out``
|
||||
``live_through + live_definitions``
|
||||
|
||||
``live_in - first_kill_operands + live_definitions``
|
||||
|
||||
``live_in``
|
||||
``live_out - live_definitions + first_kill_operands``
|
||||
|
||||
``live_through + first_kill_operands``
|
||||
|
||||
``live_through``
|
||||
``live_out - live_definitions``
|
||||
|
||||
``live_in - first_kill_operands``
|
||||
|
||||
Breakdown of register demand stages using ``live_in``:
|
||||
|
||||
* stage 0: before instruction: ``live_in``
|
||||
* stage 1: setup operands: ``live_in + early_kill_copies + late_kill_copies``
|
||||
* stage 2: during instruction: ``live_in - early_kill_operands + late_kill_copies``
|
||||
* stage 3: write definitions: ``live_in - early_kill_operands + late_kill_copies + live_definitions + dead_definitions``
|
||||
* stage 4: after instruction: ``live_in - early_kill_operands - late_kill_operands + live_definitions``
|
||||
|
||||
Breakdown of register demand stages using ``live_through``:
|
||||
|
||||
* stage 0: before instruction: ``live_through + late_kill_operands + early_kill_operands``
|
||||
* stage 1: setup operands: ``live_through + late_kill_operands + early_kill_operands + early_kill_copies + late_kill_copies``
|
||||
* stage 2: during instruction: ``live_through + late_kill_operands + late_kill_copies``
|
||||
* stage 3: write definitions: ``live_through + late_kill_operands + late_kill_copies + live_definitions + dead_definitions``
|
||||
* stage 4: after instruction: ``live_through + live_definitions``
|
||||
|
||||
Breakdown of register demand stages using ``live_out``:
|
||||
|
||||
* stage 0: before instruction: ``live_out - live_definitions + late_kill_operands + early_kill_operands``
|
||||
* stage 1: setup operands: ``live_out - live_definitions + late_kill_operands + early_kill_operands + early_kill_copies + late_kill_copies``
|
||||
* stage 2: during instruction: ``live_out - live_definitions + late_kill_operands + late_kill_copies``
|
||||
* stage 3: write definitions: ``live_out + dead_definitions + late_kill_operands + late_kill_copies``
|
||||
* stage 4: after instruction: ``live_out``
|
||||
|
||||
If instruction B immediately follows instruction A, then stage 0 of instruction B equals stage 4 of instruction A.
|
||||
``Instruction::register_demand`` is ``max(stage1, stage3)``, which is equal to the maximum of all stages.
|
||||
|
||||
There are a few helper functions for examining how an instruction changes register demand:
|
||||
|
||||
``get_live_changes()``
|
||||
This is the register demand change from killed temporaries and live definitions.
|
||||
|
||||
``live_definitions - first_kill_operands``
|
||||
|
||||
equal to ``live_out - live_in``
|
||||
|
||||
``get_temp_registers()``
|
||||
This is the temporary increase in register demand needed for copy-kill operands, late-kill operands, clobbered
|
||||
operands, and dead definitions.
|
||||
|
||||
``max(early_kill_operands + late_kill_operands + early_kill_copies + late_kill_copies - live_definitions, late_kill_operands + late_kill_copies + dead_definitions)``
|
||||
|
||||
equal to ``register_demand - live_out``
|
||||
|
||||
``get_temp_reg_changes()``
|
||||
Since ``register_demand`` is ``max(stage1, stage3)``, this can be used to know what's the effect of marking an
|
||||
operand killed will be.
|
||||
|
||||
``live_definitions + dead_definitions - early_kill_operands - early_kill_copies``
|
||||
|
||||
equal to ``stage3 - stage1``
|
||||
|
||||
They can be used as follows:
|
||||
|
||||
* ``register_demand(a) = live_in(a) + live_changes(a) + temp_registers(a)``
|
||||
* ``register_demand(a) = live_out(a) + temp_registers(a)``
|
||||
|
||||
Assuming ``stage4(a)==stage0(b)``:
|
||||
|
||||
* ``register_demand(a) = register_demand(b) - temp_registers(b) - live_changes(b) + temp_registers(a)``
|
||||
* ``register_demand(b) = register_demand(a) - temp_registers(a) + live_changes(b) + temp_registers(b)``
|
||||
|
||||
(note that ``max(a + b, a + c) - max(b, c) = a`` and ``a + max(b, c) = max(a + b, a + c)``)
|
||||
@@ -0,0 +1,437 @@
|
||||
ANV
|
||||
===
|
||||
|
||||
Experimental features
|
||||
---------------------
|
||||
|
||||
.. _`Bindless model`:
|
||||
|
||||
Binding Model
|
||||
-------------
|
||||
|
||||
Here is the ANV bindless binding model that was implemented for the
|
||||
descriptor indexing feature of Vulkan 1.2 :
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph G {
|
||||
fontcolor="black";
|
||||
compound=true;
|
||||
|
||||
subgraph cluster_1 {
|
||||
label = "Binding Table (HW)";
|
||||
|
||||
bgcolor="cornflowerblue";
|
||||
|
||||
node [ style=filled,shape="record",fillcolor="white",
|
||||
label="RT0" ] n0;
|
||||
node [ label="RT1" ] n1;
|
||||
node [ label="dynbuf0"] n2;
|
||||
node [ label="set0" ] n3;
|
||||
node [ label="set1" ] n4;
|
||||
node [ label="set2" ] n5;
|
||||
|
||||
n0 -> n1 -> n2 -> n3 -> n4 -> n5 [style=invis];
|
||||
}
|
||||
subgraph cluster_2 {
|
||||
label = "Descriptor Set 0";
|
||||
|
||||
bgcolor="burlywood3";
|
||||
fixedsize = true;
|
||||
|
||||
node [ style=filled,shape="record",fillcolor="white", fixedsize = true, width=4,
|
||||
label="binding 0 - STORAGE_IMAGE\n anv_storage_image_descriptor" ] n8;
|
||||
node [ label="binding 1 - COMBINED_IMAGE_SAMPLER\n anv_sampled_image_descriptor" ] n9;
|
||||
node [ label="binding 2 - UNIFORM_BUFFER\n anv_address_range_descriptor" ] n10;
|
||||
node [ label="binding 3 - UNIFORM_TEXEL_BUFFER\n anv_storage_image_descriptor" ] n11;
|
||||
|
||||
n8 -> n9 -> n10 -> n11 [style=invis];
|
||||
}
|
||||
subgraph cluster_5 {
|
||||
label = "Vulkan Objects"
|
||||
|
||||
fontcolor="black";
|
||||
bgcolor="darkolivegreen4";
|
||||
|
||||
subgraph cluster_6 {
|
||||
label = "VkImageView";
|
||||
|
||||
bgcolor=darkolivegreen3;
|
||||
node [ style=filled,shape="box",fillcolor="white", fixedsize = true, width=2,
|
||||
label="surface_state" ] n12;
|
||||
}
|
||||
subgraph cluster_7 {
|
||||
label = "VkSampler";
|
||||
|
||||
bgcolor=darkolivegreen3;
|
||||
node [ style=filled,shape="box",fillcolor="white", fixedsize = true, width=2,
|
||||
label="sample_state" ] n13;
|
||||
}
|
||||
subgraph cluster_8 {
|
||||
label = "VkImageView";
|
||||
bgcolor="darkolivegreen3";
|
||||
|
||||
node [ style=filled,shape="box",fillcolor="white", fixedsize = true, width=2,
|
||||
label="surface_state" ] n14;
|
||||
}
|
||||
subgraph cluster_9 {
|
||||
label = "VkBuffer";
|
||||
bgcolor=darkolivegreen3;
|
||||
|
||||
node [ style=filled,shape="box",fillcolor="white", fixedsize = true, width=2,
|
||||
label="address" ] n15;
|
||||
}
|
||||
subgraph cluster_10 {
|
||||
label = "VkBufferView";
|
||||
|
||||
bgcolor=darkolivegreen3;
|
||||
node [ style=filled,shape="box",fillcolor="white", fixedsize = true, width=2,
|
||||
label="surface_state" ] n16;
|
||||
}
|
||||
|
||||
n12 -> n13 -> n14 -> n15 -> n16 [style=invis];
|
||||
}
|
||||
|
||||
subgraph cluster_11 {
|
||||
subgraph cluster_12 {
|
||||
label = "CommandBuffer state stream";
|
||||
|
||||
bgcolor="gold3";
|
||||
node [ style=filled,shape="box",fillcolor="white", fixedsize = true, width=2,
|
||||
label="surface_state" ] n17;
|
||||
node [ label="surface_state" ] n18;
|
||||
node [ label="surface_state" ] n19;
|
||||
|
||||
n17 -> n18 -> n19 [style=invis];
|
||||
}
|
||||
}
|
||||
|
||||
n3 -> n8 [lhead=cluster_2];
|
||||
|
||||
n8 -> n12;
|
||||
n9 -> n13;
|
||||
n9 -> n14;
|
||||
n10 -> n15;
|
||||
n11 -> n16;
|
||||
|
||||
n0 -> n17;
|
||||
n1 -> n18;
|
||||
n2 -> n19;
|
||||
}
|
||||
|
||||
|
||||
|
||||
The HW binding table is generated when the draw or dispatch commands
|
||||
are emitted. Here are the types of entries one can find in the binding
|
||||
table :
|
||||
|
||||
- The currently bound descriptor sets, one entry per descriptor set
|
||||
(our limit is 8).
|
||||
|
||||
- For dynamic buffers, one entry per dynamic buffer.
|
||||
|
||||
- For draw commands, render target entries if needed.
|
||||
|
||||
The entries of the HW binding table for descriptor sets are
|
||||
RENDER_SURFACE_STATE similar to what you would have for a normal
|
||||
uniform buffer. The shader will emit reads this buffer first to get
|
||||
the information it needs to access a surface/sampler/etc... and then
|
||||
emits the appropriate message using the information gathered from the
|
||||
descriptor set buffer.
|
||||
|
||||
Each binding type entry gets an associated structure in memory
|
||||
(``anv_storage_image_descriptor``, ``anv_sampled_image_descriptor``,
|
||||
``anv_address_range_descriptor``, ``anv_storage_image_descriptor``).
|
||||
This is the information read by the shader.
|
||||
|
||||
|
||||
.. _`Binding tables`:
|
||||
|
||||
Binding Tables
|
||||
--------------
|
||||
|
||||
Binding tables are arrays of 32bit offset entries referencing surface
|
||||
states. This is how shaders can refer to binding table entry to read
|
||||
or write a surface. For example fragment shaders will often refer to
|
||||
entry 0 as the first render target.
|
||||
|
||||
The way binding tables are managed is fairly awkward.
|
||||
|
||||
Each shader stage must have its binding table programmed through
|
||||
a corresponding instruction
|
||||
``3DSTATE_BINDING_TABLE_POINTERS_*`` (each stage has its own).
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph structs {
|
||||
node [shape=record];
|
||||
struct3 [label="{ binding tables\n area | { <bt4> BT4 | <bt3> BT3 | ... | <bt0> BT0 } }|{ surface state\n area |{<ss0> ss0|<ss1> ss1|<ss2> ss2|...}}"];
|
||||
struct3:bt0 -> struct3:ss0;
|
||||
struct3:bt0 -> struct3:ss1;
|
||||
}
|
||||
|
||||
|
||||
The value programmed in the ``3DSTATE_BINDING_TABLE_POINTERS_*``
|
||||
instructions is not a 64bit pointer but an offset from the address
|
||||
programmed in ``STATE_BASE_ADDRESS::Surface State Base Address`` or
|
||||
``3DSTATE_BINDING_TABLE_POOL_ALLOC::Binding Table Pool Base Address``
|
||||
(available on Gfx11+). The offset value in
|
||||
``3DSTATE_BINDING_TABLE_POINTERS_*`` is also limited to a few bits
|
||||
(not a full 32bit value), meaning that as we use more and more binding
|
||||
tables we need to reposition ``STATE_BASE_ADDRESS::Surface State Base
|
||||
Address`` to make space for new binding table arrays.
|
||||
|
||||
To make things even more awkward, the binding table entries are also
|
||||
relative to ``STATE_BASE_ADDRESS::Surface State Base Address`` so as
|
||||
we change ``STATE_BASE_ADDRESS::Surface State Base Address`` we need
|
||||
add that offsets to the binding table entries.
|
||||
|
||||
The way with deal with this is that we allocate 4Gb of address space
|
||||
(since the binding table entries can address 4Gb of surface state
|
||||
elements). We reserve the first gigabyte exclusively to binding
|
||||
tables, so that anywhere we position our binding table in that first
|
||||
gigabyte, it can always refer to the surface states in the next 3Gb.
|
||||
|
||||
|
||||
.. _`Descriptor Set Memory Layout`:
|
||||
|
||||
Descriptor Set Memory Layout
|
||||
----------------------------
|
||||
|
||||
Here is a representation of how the descriptor set bindings, with each
|
||||
elements in each binding is mapped to a the descriptor set memory :
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph structs {
|
||||
node [shape=record];
|
||||
rankdir=LR;
|
||||
|
||||
struct1 [label="Descriptor Set | \
|
||||
<b0> binding 0\n STORAGE_IMAGE \n (array_length=3) | \
|
||||
<b1> binding 1\n COMBINED_IMAGE_SAMPLER \n (array_length=2) | \
|
||||
<b2> binding 2\n UNIFORM_BUFFER \n (array_length=1) | \
|
||||
<b3> binding 3\n UNIFORM_TEXEL_BUFFER \n (array_length=1)"];
|
||||
struct2 [label="Descriptor Set Memory | \
|
||||
<b0e0> anv_storage_image_descriptor|\
|
||||
<b0e1> anv_storage_image_descriptor|\
|
||||
<b0e2> anv_storage_image_descriptor|\
|
||||
<b1e0> anv_sampled_image_descriptor|\
|
||||
<b1e1> anv_sampled_image_descriptor|\
|
||||
<b2e0> anv_address_range_descriptor|\
|
||||
<b3e0> anv_storage_image_descriptor"];
|
||||
|
||||
struct1:b0 -> struct2:b0e0;
|
||||
struct1:b0 -> struct2:b0e1;
|
||||
struct1:b0 -> struct2:b0e2;
|
||||
struct1:b1 -> struct2:b1e0;
|
||||
struct1:b1 -> struct2:b1e1;
|
||||
struct1:b2 -> struct2:b2e0;
|
||||
struct1:b3 -> struct2:b3e0;
|
||||
}
|
||||
|
||||
Each Binding in the descriptor set is allocated an array of
|
||||
``anv_*_descriptor`` data structure. The type of ``anv_*_descriptor``
|
||||
used for a binding is selected based on the ``VkDescriptorType`` of
|
||||
the bindings.
|
||||
|
||||
The value of ``anv_descriptor_set_binding_layout::descriptor_offset``
|
||||
is a byte offset from the descriptor set memory to the associated
|
||||
binding. ``anv_descriptor_set_binding_layout::array_size`` is the
|
||||
number of ``anv_*_descriptor`` elements in the descriptor set memory
|
||||
from that offset for the binding.
|
||||
|
||||
|
||||
Pipeline state emission
|
||||
-----------------------
|
||||
|
||||
Vulkan initially started by baking as much state as possible in
|
||||
pipelines. But extension after extension, more and more state has
|
||||
become potentially dynamic.
|
||||
|
||||
ANV tries to limit the amount of time an instruction has to be packed
|
||||
to reprogram part of the 3D pipeline state. The packing is happening
|
||||
in 2 places :
|
||||
|
||||
- ``genX_pipeline.c`` where the non dynamic state is emitted in the
|
||||
pipeline batch. Chunks of the batches are copied into the command
|
||||
buffer as a result of calling ``vkCmdBindPipeline()``, depending on
|
||||
what changes from the previously bound graphics pipeline
|
||||
|
||||
- ``genX_gfx_state.c`` where the dynamic state is added to already
|
||||
packed instructions from ``genX_pipeline.c``
|
||||
|
||||
The rule to know where to emit an instruction programming the 3D
|
||||
pipeline is as follow :
|
||||
|
||||
- If any field of the instruction can be made dynamic, it should be
|
||||
emitted in ``genX_gfx_state.c``
|
||||
|
||||
- Otherwise, the instruction can be emitted in ``genX_pipeline.c``
|
||||
|
||||
When a piece of state programming is dynamic, it should have a
|
||||
corresponding field in ``anv_gfx_dynamic_state`` and the
|
||||
``genX(cmd_buffer_flush_gfx_runtime_state)`` function should be
|
||||
updated to ensure we minimize the amount of time an instruction should
|
||||
be emitted. Each instruction should have a associated
|
||||
``ANV_GFX_STATE_*`` mask so that the dynamic emission code can tell
|
||||
when to re-emit an instruction.
|
||||
|
||||
|
||||
Generated indirect draws optimization
|
||||
-------------------------------------
|
||||
|
||||
Indirect draws have traditionally been implemented on Intel HW by
|
||||
loading the indirect parameters from memory into HW registers using
|
||||
the command streamer's ``MI_LOAD_REGISTER_MEM`` instruction before
|
||||
dispatching a draw call to the 3D pipeline.
|
||||
|
||||
On recent products, it was found that the command streamer is showing
|
||||
as performance bottleneck, because it cannot dispatch draw calls fast
|
||||
enough to keep the 3D pipeline busy.
|
||||
|
||||
The solution to this problem is to change the way we deal with
|
||||
indirect draws. Instead of loading HW registers with values using the
|
||||
command streamer, we generate entire set of ``3DPRIMITIVE``
|
||||
instructions using a shader. The generated instructions contain the
|
||||
entire draw call parameters. This way the command streamer executes
|
||||
only ``3DPRIMITIVE`` instructions and doesn't do any data loading from
|
||||
memory or touch HW registers, feeding the 3D pipeline as fast as it
|
||||
can.
|
||||
|
||||
In ANV this implemented in 2 different ways :
|
||||
|
||||
By generating instructions directly into the command stream using a
|
||||
side batch buffer. When ANV encounters the first indirect draws, it
|
||||
generates a jump into the side batch, the side batch contains a draw
|
||||
call using a generation shader for each indirect draw. We keep adding
|
||||
on more generation draws into the batch until we have to stop due to
|
||||
command buffer end, secondary command buffer calls or a barrier
|
||||
containing the access flag ``VK_ACCESS_INDIRECT_COMMAND_READ_BIT``.
|
||||
The side batch buffer jump back right after the instruction where it
|
||||
was called. Here is a high level diagram showing how the generation
|
||||
batch buffer writes in the main command buffer :
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph commands_mode {
|
||||
rankdir = "LR"
|
||||
"main-command-buffer" [
|
||||
label = "main command buffer|...|draw indirect0 start|<f0>jump to\ngeneration batch|<f1>|<f2>empty instruction0|<f3>empty instruction1|...|draw indirect0 end|...|draw indirect1 start|<f4>empty instruction0|<f5>empty instruction1|...|<f6>draw indirect1 end|..."
|
||||
shape = "record"
|
||||
];
|
||||
"generation-command-buffer" [
|
||||
label = "generation command buffer|<f0>|<f1>write draw indirect0|<f2>write draw indirect1|...|<f3>exit jump"
|
||||
shape = "record"
|
||||
];
|
||||
"main-command-buffer":f0 -> "generation-command-buffer":f0;
|
||||
"generation-command-buffer":f1 -> "main-command-buffer":f2 [color="#0000ff"];
|
||||
"generation-command-buffer":f1 -> "main-command-buffer":f3 [color="#0000ff"];
|
||||
"generation-command-buffer":f2 -> "main-command-buffer":f4 [color="#0000ff"];
|
||||
"generation-command-buffer":f2 -> "main-command-buffer":f5 [color="#0000ff"];
|
||||
"generation-command-buffer":f3 -> "main-command-buffer":f1;
|
||||
}
|
||||
|
||||
By generating instructions into a ring buffer of commands, when the
|
||||
draw count number is high. This solution allows smaller batches to be
|
||||
emitted. Here is a high level diagram showing how things are
|
||||
executed :
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph ring_mode {
|
||||
rankdir=LR;
|
||||
"main-command-buffer" [
|
||||
label = "main command buffer|...| draw indirect |<f1>generation shader|<f2> jump to ring|<f3> increment\ndraw_base|<f4>..."
|
||||
shape = "record"
|
||||
];
|
||||
"ring-buffer" [
|
||||
label = "ring buffer|<f0>generated draw0|<f1>generated draw1|<f2>generated draw2|...|<f3>exit jump"
|
||||
shape = "record"
|
||||
];
|
||||
"main-command-buffer":f2 -> "ring-buffer":f0;
|
||||
"ring-buffer":f3 -> "main-command-buffer":f3;
|
||||
"ring-buffer":f3 -> "main-command-buffer":f4;
|
||||
"main-command-buffer":f3 -> "main-command-buffer":f1;
|
||||
"main-command-buffer":f1 -> "ring-buffer":f1 [color="#0000ff"];
|
||||
"main-command-buffer":f1 -> "ring-buffer":f2 [color="#0000ff"];
|
||||
}
|
||||
|
||||
Runtime dependencies
|
||||
--------------------
|
||||
|
||||
Starting with Intel 12th generation/Alder Lake-P and Intel Arc Alchemist, the Intel 3D driver stack requires GuC firmware for proper operation. You have two options to install the firmware:
|
||||
|
||||
- Distro package: Install the pre-packaged firmware included in your Linux distribution's repositories.
|
||||
- Manual download: You can download the firmware from the official repository: https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/i915. Place the downloaded files in the /lib/firmware/i915 directory.
|
||||
|
||||
Important: For optimal performance, we recommend updating the GuC firmware to version 70.6.3 or later.
|
||||
|
||||
Debugging tips
|
||||
--------------
|
||||
|
||||
When running into rendering/hang issues here are a few things that can
|
||||
be experimented with to try to narrow down the issue :
|
||||
|
||||
- Run with ``INTEL_DEBUG=stall`` : stall execution, flush and
|
||||
invalidate all caches between draw/dispatch/trace operations,
|
||||
helpful to detect synchronization issues between draw/dispatch/trace
|
||||
operations
|
||||
|
||||
- Run with ``INTEL_DEBUG=sync`` : wait for completion of previous
|
||||
command buffers before submitting new ones, helpful to detect
|
||||
synchronization between command buffers
|
||||
|
||||
- Run with ``INTEL_DEBUG=noccs`` : disable compression, helpful to
|
||||
detect compressed data handling issues in the driver
|
||||
|
||||
- Run with ``ANV_QUEUE_OVERRIDE=c=0,b=0`` : disable the async compute
|
||||
queue and transfer queue, helpful to identify synchronization issues
|
||||
between queues
|
||||
|
||||
- Run with ``INTEL_DEBUG=noccs-modifier`` : disable compressed
|
||||
modifiers, helpful to identify compression data handling issues
|
||||
between applications (application and compositor)
|
||||
|
||||
- Run with ``INTEL_DEBUG=no-resource-barrier`` : disable use of the
|
||||
new RESOURCE_BARRIER instruction on Xe2+, helpful to identify
|
||||
synchronization issues associated to this instruction
|
||||
|
||||
A combinaison of those can also be tried if the issue has multiple
|
||||
causes, for example : ``INTEL_DEBUG=stall,sync``
|
||||
|
||||
Shader performance analysis
|
||||
---------------------------
|
||||
|
||||
On Xe2+ GPUs a new feature call EU monitor is available to analyze hot
|
||||
paths in the shaders. To make use of this feature :
|
||||
|
||||
1. Run the EU monitor tools in a terminal :
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mesa % intel_monitor -e > eustall.csv
|
||||
|
||||
|
||||
2. Launch you application with :
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mesa % ANV_DEBUG=shader-dump my_app
|
||||
|
||||
3. Exit the application once enough data is captured
|
||||
|
||||
4. Untar the ``anv-shaders.mda.tar`` file created in the ``NAME_PID_mda``
|
||||
subdirectory, where ``NAME`` is the process name and ``PID`` is the process
|
||||
ID. Set ``MDA_OUTPUT_DIR=.`` to create the file in the directory the
|
||||
application was launched instead.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mesa % tar xfv my_app_12345_mda/anv-shaders.mda.tar
|
||||
|
||||
5. Look at the hot spots with ``intel_eu_stall_viewer`` :
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mesa % intel_eu_stall_viewer -s /path/to/untar/anv-shaders.mda -c /path/to/eustall.csv
|
||||
@@ -0,0 +1,427 @@
|
||||
Asahi
|
||||
=====
|
||||
|
||||
The Asahi driver aims to provide an OpenGL implementation for the Apple M1.
|
||||
|
||||
Wrap (macOS only)
|
||||
-----------------
|
||||
|
||||
Mesa includes a library that wraps the key IOKit entrypoints used in the macOS
|
||||
UABI for AGX. The wrapped routines print information about the kernel calls made
|
||||
and dump work submitted to the GPU using agxdecode. This facilitates
|
||||
reverse-engineering the hardware, as glue to get at the "interesting" GPU
|
||||
memory.
|
||||
|
||||
The library is only built if ``-Dtools=asahi`` is passed. It builds a single
|
||||
``wrap.dylib`` file, which should be inserted into a process with the
|
||||
``DYLD_INSERT_LIBRARIES`` environment variable.
|
||||
|
||||
For example, to trace an app ``./app``, run:
|
||||
|
||||
DYLD_INSERT_LIBRARIES=~/mesa/build/src/asahi/lib/libwrap.dylib ./app
|
||||
|
||||
Hardware varyings
|
||||
-----------------
|
||||
|
||||
At an API level, vertex shader outputs need to be interpolated to become
|
||||
fragment shader inputs. This process is logically pipelined in AGX, with a value
|
||||
traveling from a vertex shader to remapping hardware to coefficient register
|
||||
setup to the fragment shader to the iterator hardware. Each stage is described
|
||||
below.
|
||||
|
||||
Vertex shader
|
||||
`````````````
|
||||
|
||||
A vertex shader (running on the :term:`Unified Shader Cores`) outputs varyings with the
|
||||
``st_var`` instruction. ``st_var`` takes a *vertex output index* and a 32-bit
|
||||
value. The maximum number of *vertex outputs* is specified as the "output count"
|
||||
of the shader in the "VDM State Vertex Outputs" structure. The value may be interpreted
|
||||
consist of a single 32-bit value or an aligned 16-bit register pair, depending
|
||||
on whether interpolation should happen at 32-bit or 16-bit. Vertex outputs are
|
||||
indexed starting from 0, with the *vertex position* always coming first, the
|
||||
32-bit user varyings coming next with perspective, flat, and linear interpolated
|
||||
varyings grouped in that order, then 16-bit user varyings with the same groupings,
|
||||
and finally *point size*, *layer/viewport*, and *clip distances* at the end if present. Note that
|
||||
*clip distances* are not accessible from the fragment shader; if the fragment
|
||||
shader needs to read the interpolated clip distance, the vertex shader must
|
||||
*also* write the clip distance values to a user varying for the fragment shader
|
||||
to interpolate. Also note there is no clip plane enable mask anywhere; that must
|
||||
lowered for APIs that require this (OpenGL but not Vulkan).
|
||||
|
||||
.. list-table:: Ordering of vertex outputs with all outputs used
|
||||
:widths: 25 75
|
||||
:header-rows: 1
|
||||
|
||||
* - Size (words)
|
||||
- Value
|
||||
* - 4
|
||||
- Vertex position
|
||||
* - 1
|
||||
- 32-bit smooth varying 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- 32-bit smooth varying m
|
||||
* - 1
|
||||
- 32-bit flat varying 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- 32-bit flat varying n
|
||||
* - 1
|
||||
- 32-bit linear varying 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- 32-bit linear varying o
|
||||
* - 1
|
||||
- Packed pair of 16-bit smooth varyings 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- Packed pair of 16-bit smooth varyings p
|
||||
* - 1
|
||||
- Packed pair of 16-bit flat varyings 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- Packed pair of 16-bit flat varyings q
|
||||
* - 1
|
||||
- Packed pair of 16-bit linear varyings 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- Packed pair of 16-bit linear varyings r
|
||||
* - 1
|
||||
- Point size
|
||||
* - 1
|
||||
- Layer/viewport
|
||||
* - 1
|
||||
- Clip distance for plane 0
|
||||
* -
|
||||
- ...
|
||||
* - 1
|
||||
- Clip distance for plane 16
|
||||
|
||||
Remapping
|
||||
`````````
|
||||
|
||||
Vertex outputs are remapped to varying slots to be interpolated.
|
||||
The output of remapping consists of the following items: the *W* fragment
|
||||
coordinate, the *Z* fragment coordinate, user varyings in the vertex
|
||||
output order. *Z* may be omitted, but *W* may not be. This remapping is
|
||||
configured by the "Output select" word.
|
||||
|
||||
.. list-table:: Ordering of remapped slots
|
||||
:widths: 25 75
|
||||
:header-rows: 1
|
||||
|
||||
* - Index
|
||||
- Value
|
||||
* - 0
|
||||
- Fragment coord W
|
||||
* - 1
|
||||
- Fragment coord Z
|
||||
* - 2
|
||||
- 32-bit varying 0
|
||||
* -
|
||||
- ...
|
||||
* - 2 + m
|
||||
- 32-bit varying m
|
||||
* - 2 + m + 1
|
||||
- Packed pair of 16-bit varyings 0
|
||||
* -
|
||||
- ...
|
||||
* - 2 + m + n + 1
|
||||
- Packed pair of 16-bit varyings n
|
||||
|
||||
Coefficient registers
|
||||
`````````````````````
|
||||
|
||||
The fragment shader does not see the physical slots.
|
||||
Instead, it references varyings through *coefficient registers*. A coefficient
|
||||
register is a register allocated constant for all fragment shader invocations in
|
||||
a given polygon. Physically, it contains the values output by the vertex shader
|
||||
for each vertex of the polygon. Coefficient registers are preloaded with values
|
||||
from varying slots. This preloading appears to occur in fixed function hardware,
|
||||
a simplification from PowerVR which requires a specialized program for the
|
||||
programmable data sequencer to do the preload.
|
||||
|
||||
The "Fragment Shader" structure points to coefficient register bindings,
|
||||
preceded by a header. The header contains the number of 32-bit varying slots. As
|
||||
the *W* slot is always present, this field is always nonzero. Slots whose index
|
||||
is below this count are treated as 32-bit. The remaining slots are treated as
|
||||
16-bits.
|
||||
|
||||
The header also contains the total number of coefficient registers bound.
|
||||
|
||||
Each binding that follows maps a (vector of) varying slots to a (consecutive)
|
||||
coefficient registers. Some details about the varying (perspective
|
||||
interpolation, flat shading, point sprites) are configured here.
|
||||
|
||||
Coefficient registers may be ordered the same as the internal varying slots.
|
||||
However, this may be inconvenient for some APIs that require a separable shader
|
||||
model. For these APIs, the flexibility to mix-and-match slots and coefficient
|
||||
registers allows mixing shaders without shader variants. In that case, the
|
||||
bindings should be generated outside of the compiler. For simple APIs where the
|
||||
bindings are fixed and known at compile-time, the bindings could be generated
|
||||
within the compiler.
|
||||
|
||||
Mathematically, the value of the coefficient register is a vector in
|
||||
:math:`\mathbb{R}^3`. The X and Y components are the screen-space partial
|
||||
derivatives of the varying with respect to X and Y. The Z component is the
|
||||
interpolated value of the varying at the upper-left pixel in the 32x32 tile that
|
||||
the pixel belongs to.
|
||||
|
||||
Fragment shader
|
||||
```````````````
|
||||
|
||||
In the fragment shader, coefficient registers, identified by the prefix ``cf``
|
||||
followed by a decimal index, act as opaque handles to varyings. For flat
|
||||
shading, coefficient registers may be loaded into general registers with the
|
||||
``ldcf`` instruction. For smooth shading, the coefficient register corresponding
|
||||
to the desired varying is passed as an argument to the "iterate" instruction
|
||||
``iter`` in order to "iterate" (interpolate) a varying. As perspective correct
|
||||
interpolation also requires the W component of the fragment coordinate, the
|
||||
coefficient register for W is passed as a second argument. As an example, if
|
||||
there's a single varying to interpolate, an instruction like ``iter r0, cf1, cf0``
|
||||
is used.
|
||||
|
||||
It is occassionally useful to manipulate the raw coefficient registers, for
|
||||
example to implement interpolation modes not natively supported by the hardware.
|
||||
``ldcf`` is used for this purpose.
|
||||
|
||||
Iterator
|
||||
````````
|
||||
|
||||
To actually interpolate varyings, AGX provides fixed-function iteration hardware
|
||||
to multiply the specified coefficient registers with the required barycentrics,
|
||||
producing an interpolated value, hence the name "coefficient register". This
|
||||
operation is purely mathematical and does not require any memory access, as
|
||||
the required coefficients are preloaded before the shader begins execution.
|
||||
That means the iterate instruction executes in constant time, does not signal
|
||||
a data fence, and does not require the shader to wait on a data fence before
|
||||
using the value.
|
||||
|
||||
Image layouts
|
||||
-------------
|
||||
|
||||
AGX supports several image layouts, described here. To work with image layouts
|
||||
in the drivers, use the ail library, located in ``src/asahi/layout``.
|
||||
|
||||
Strided linear
|
||||
``````````````
|
||||
|
||||
The simplest layout is **strided linear**. Pixels are stored in raster-order in
|
||||
memory with a software-controlled stride. Strided linear images are useful for
|
||||
working with modifier-unaware window systems, however performance will suffer.
|
||||
Strided linear images have numerous limitations:
|
||||
|
||||
- Strides must be a multiple of 16 bytes.
|
||||
- Strides must be nonzero. For 1D images where the stride is logically
|
||||
irrelevant, ail will internally select the minimal stride.
|
||||
- Only 1D, 2D, and 2D Array images may be linear. In particular, no 3D or cubemaps.
|
||||
- 2D images must not be mipmapped.
|
||||
- Block-compressed formats and multisampled images are unsupported. Elements of
|
||||
a strided linear image are simply pixels.
|
||||
|
||||
With these limitations, addressing into a strided linear image is as simple as
|
||||
|
||||
.. math::
|
||||
|
||||
\text{address} = (y \cdot \text{stride}) + (x \cdot \text{bytes per pixel})
|
||||
|
||||
In practice, this suffices for window system integration and little else.
|
||||
|
||||
GPU-tiled
|
||||
`````````
|
||||
|
||||
The most common uncompressed layout is **GPU-tiled**. The image is divided into
|
||||
power-of-two sized tiles. The tiles themselves are stored in raster-order.
|
||||
Within each tile, elements (pixels/blocks) are stored in Morton (Z) order.
|
||||
|
||||
The tile size used depends on both the image size and the block size of the
|
||||
image format. For large images, :math:`n \times n` or :math:`2n \times n` tiles
|
||||
are used (:math:`n` power-of-two). :math:`n` is such that each page contains
|
||||
exactly one tile. Only power-of-two block sizes are supported in hardware,
|
||||
ensuring such a tile size always exists. The hardware uses 16 KiB pages, so tile
|
||||
sizes are as follows:
|
||||
|
||||
.. list-table:: Tile sizes for large images
|
||||
:widths: 50 50
|
||||
:header-rows: 1
|
||||
|
||||
* - Bytes per block
|
||||
- Tile size
|
||||
* - 1
|
||||
- 128 x 128
|
||||
* - 2
|
||||
- 128 x 64
|
||||
* - 4
|
||||
- 64 x 64
|
||||
* - 8
|
||||
- 64 x 32
|
||||
* - 16
|
||||
- 32 x 32
|
||||
|
||||
The dimensions of large images are rounded up to be multiples of the tile size.
|
||||
In addition, non-power-of-two large images have extra padding tiles when
|
||||
mipmapping is used, see below.
|
||||
|
||||
That rounding would waste a great deal of memory for small images. If
|
||||
an image is smaller than this tile size, a smaller tile size is used to reduce
|
||||
the memory footprint. For small images, the tile size is :math:`m \times m`
|
||||
where
|
||||
|
||||
.. math::
|
||||
|
||||
m = 2^{\lceil \log_2( \min \{ \text{width}, \text{ height} \}) \rceil}
|
||||
|
||||
In other words, small images use the smallest square power-of-two tile such that
|
||||
the image's minor axis fits in one tile.
|
||||
|
||||
For mipmapped images, tile sizes are determined independently for each level.
|
||||
Typically, the first levels of an image are "large" and the remaining levels are
|
||||
"small". This scheme reduces the memory footprint of mipmapping, compared to a
|
||||
fixed tile size for the whole image. Each mip level are padded to fill at least
|
||||
one cache line (128 bytes), ensure no cache line contains multiple mip levels.
|
||||
|
||||
There is a wrinkle: the dimensions of large mip levels in tiles are determined
|
||||
by the dimensions of level 0. For power-of-two images, the two calculations are
|
||||
equivalent. However, they differ subtly for non-power-of-two images. To
|
||||
determine the number of tiles to allocate for level :math:`l`, the number of
|
||||
tiles for level 0 should be right-shifted by :math:`2l`. That appears to divide
|
||||
by :math:`2^l` in both width and height, matching the definition of mipmapping,
|
||||
however it rounds down incorrectly. To compensate, the level contains one extra
|
||||
row, column, or both (with the corner) as required if any of the first :math:`l`
|
||||
levels were rounded down. This hurt the memory footprint. However, it means
|
||||
non-power-of-two integer multiplication is only required for level 0.
|
||||
Calculating the sizes for subsequent levels requires only addition and bitwise
|
||||
math. That simplifies the hardware (but complicates software).
|
||||
|
||||
A 2D image consists of a full miptree (constructed as above) rounded up to the
|
||||
page size (16 KiB).
|
||||
|
||||
3D images consist simply of an array of 2D layers (constructed as above). That
|
||||
means cube maps, 2D arrays, cube map arrays, and 3D images all use the same
|
||||
layout. The only difference is the number of layers. Notably, 3D images (like
|
||||
``GL_TEXTURE_3D``) reserve space even for mip levels that do not exist
|
||||
logically. These extra levels pad out layers of 3D images to the size of the
|
||||
first layer, simplifying layout calculations for both software and hardware.
|
||||
Although the padding is logically unnecessary, it wastes little space compared
|
||||
to the sizes of large mipmapped 3D textures.
|
||||
|
||||
Twiddled
|
||||
````````
|
||||
|
||||
In addition to GPU-tiled images, AGX also has a fully **twiddled** layout. The
|
||||
image is rounded up to power-of-two dimensions, then all elements are stored in
|
||||
Morton (Z) order.
|
||||
|
||||
A twiddled image is equivalent to a GPU-tiled image with a single square
|
||||
power-of-two tile spanning the entire image. That means GPU-tiled and twiddled
|
||||
images may share address calculation code, as long as everything is parametrized
|
||||
in terms of the tile size.
|
||||
|
||||
In general, twiddled images require more memory than GPU-tiled images due to the
|
||||
extra padding required. Because GPU tiles are page-sized, twiddling beyond that
|
||||
does not offer any cache locality benefit either. The twiddled layout is
|
||||
mostly vestigial at this point, but the PBE requires it for sparse mapping.
|
||||
|
||||
Sparse page tables
|
||||
``````````````````
|
||||
|
||||
The hardware has native support for sparse images. If an texture/PBE descriptor
|
||||
is configured in sparse mode, the specified address does not point to the image
|
||||
itself. Rather, it points to a **sparse page table**. The extra indirection
|
||||
enables on-device sparse binding (e.g. by updating the page table from a compute
|
||||
kernel).
|
||||
|
||||
At the top level, the sparse page table is an array of **folios**. Each folio
|
||||
describes 256 pages. The folio itself has two halves. The first half is the page
|
||||
table itself, containing 4-byte "Sparse Block" data structures mapping image
|
||||
pages to GPU virtual addresses. The second half is probably sparse texture
|
||||
counters, again 4-bytes per page. Each folio therefore consumes :math:`256 \cdot
|
||||
4 \cdot 2 = 2 \mathrm{KiB}` in order to describe :math:`256 \cdot 16384 =
|
||||
4 \mathrm{MiB}`.
|
||||
|
||||
In a layered (array or 3D) image, a given folio only describes a single layer.
|
||||
That implies extra padding between layers.
|
||||
|
||||
Within a layer, the table works purely at an address level. It maps pages to
|
||||
pages, rather than tiles to tiles. It is not a spatial data structure in itself.
|
||||
Rather, it inherits the tiling of the image by virtue of the addresses mapped.
|
||||
This presumably simplifies the hardware implementation.
|
||||
|
||||
drm-shim (Linux only)
|
||||
---------------------
|
||||
|
||||
Mesa includes a library that mocks out the DRM UABI used by the Asahi driver
|
||||
stack, allowing the Mesa driver to run on non-M1 Linux hardware. This can be
|
||||
useful for exercising the compiler. To build, use options:
|
||||
|
||||
::
|
||||
|
||||
-Dgallium-drivers=asahi -Dtools=drm-shim
|
||||
|
||||
Then run an OpenGL workload with environment variable:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
LD_PRELOAD=~/mesa/build/src/asahi/drm-shim/libasahi_noop_drm_shim.so
|
||||
|
||||
For example to compile a shader with shaderdb and print some statistics along
|
||||
with the IR:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~/shader-db$ AGX_MESA_DEBUG=shaders,shaderdb ASAHI_MESA_DEBUG=precompile LD_PRELOAD=~/mesa/build/src/asahi/drm-shim/libasahi_noop_drm_shim.so ./run shaders/glmark/1-12.shader_test
|
||||
|
||||
The drm-shim implementation for Asahi is located in ``src/asahi/drm-shim``. The
|
||||
drm-shim implementation there should be updated as new UABI is added.
|
||||
|
||||
Hardware glossary
|
||||
-----------------
|
||||
|
||||
AGX is a tiled renderer descended from the PowerVR architecture. Some hardware
|
||||
concepts used in PowerVR GPUs appear in AGX.
|
||||
|
||||
.. glossary:: :sorted:
|
||||
|
||||
VDM
|
||||
Vertex Data Master
|
||||
Dispatches vertex shaders.
|
||||
|
||||
PDM
|
||||
Pixel Data Master
|
||||
Dispatches pixel shaders.
|
||||
|
||||
CDM
|
||||
Compute Data Master
|
||||
Dispatches compute kernels.
|
||||
|
||||
USC
|
||||
Unified Shader Cores
|
||||
A unified shader core is a small CPU that runs shader code. The core is
|
||||
unified because a single ISA is used for vertex, pixel and compute
|
||||
shaders. This differs from older GPUs where the vertex, fragment and
|
||||
compute have separate ISAs for shader stages.
|
||||
|
||||
PPP
|
||||
Primitive Processing Pipeline
|
||||
The Primitive Processing Pipeline is a hardware unit that does primitive
|
||||
assembly. The PPP is between the :term:`VDM` and :term:`ISP`.
|
||||
|
||||
ISP
|
||||
Image Synthesis Processor
|
||||
The Image Synthesis Processor is responsible for the rasterization stage
|
||||
of the rendering pipeline.
|
||||
|
||||
PBE
|
||||
Pixel BackEnd
|
||||
Hardware unit which writes to color attachments and images. Also the
|
||||
name for a descriptor passed to :term:`PBE` instructions.
|
||||
|
||||
UVS
|
||||
Unified Vertex Store
|
||||
Hardware unit which buffers the outputs of the vertex shader (varyings).
|
||||
@@ -0,0 +1,65 @@
|
||||
D3D12
|
||||
=====
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The D3D12 driver is a Gallium driver that emits API calls for Microsoft's
|
||||
:abbr:`D3D12 (Direct3D 12)` API instead of targeting a specific GPU
|
||||
architecture. This can be used to get full desktop OpenGL 3.3 support on
|
||||
devices that only support D3D12.
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
There's a few tools that are useful for debugging D3D12, such as these
|
||||
environment variables:
|
||||
|
||||
.. envvar:: D3D12_DEBUG
|
||||
|
||||
Accepts the following comma-separated list of flags:
|
||||
|
||||
``verbose``
|
||||
Enable verbose output to stdout
|
||||
``blit``
|
||||
Trace blit and copy resource calls
|
||||
``experimental``
|
||||
Enable experimental shader models feature
|
||||
``dxil``
|
||||
Dump DXIL during program compile
|
||||
``disass``
|
||||
Dump disassambly of created DXIL shader
|
||||
``res``
|
||||
Debug resources
|
||||
``debuglayer``
|
||||
Enable `debug layer`_
|
||||
``gpuvalidator``
|
||||
Enable `GPU validator`_
|
||||
|
||||
.. envvar:: DXIL_DEBUG
|
||||
|
||||
Accepts the following comma-separated list of flags:
|
||||
|
||||
``verbose``
|
||||
Enable verbose output to stdout
|
||||
``dump_blob``
|
||||
Write shader blobs
|
||||
``trace``
|
||||
Trace instruction conversion
|
||||
``dump_module``
|
||||
dump module tree to stderr
|
||||
|
||||
.. _debug layer: https://learn.microsoft.com/en-us/windows/win32/direct3d12/understanding-the-d3d12-debug-layer
|
||||
.. _GPU validator: https://learn.microsoft.com/en-us/windows/win32/direct3d12/using-d3d12-debug-layer-gpu-based-validation
|
||||
|
||||
Utilities
|
||||
---------
|
||||
|
||||
Environment variables that control the behavior of the D3D12 driver.
|
||||
|
||||
.. envvar:: MESA_D3D12_DEFAULT_ADAPTER_NAME
|
||||
|
||||
Specifies a substring to search for when choosing a default adapter to
|
||||
run on. The first adapter matching the substring is chosen. The substring
|
||||
is not case sensitive.
|
||||
|
||||
@@ -0,0 +1,746 @@
|
||||
Freedreno
|
||||
=========
|
||||
|
||||
Freedreno GLES and GL driver for Adreno 2xx-6xx GPUs. It implements up to
|
||||
OpenGL ES 3.2 and desktop OpenGL 4.5.
|
||||
|
||||
See the `Freedreno Wiki
|
||||
<https://gitlab.freedesktop.org/freedreno/freedreno/-/wikis/home>`__ for more
|
||||
details.
|
||||
|
||||
Turnip
|
||||
------
|
||||
|
||||
Turnip is a Vulkan 1.3 driver for Adreno 6xx GPUs.
|
||||
|
||||
The current set of specific chip versions supported can be found in
|
||||
:file:`src/freedreno/common/freedreno_devices.py`. The current set of features
|
||||
supported can be found rendered at `Mesa Matrix <https://mesamatrix.net/>`__.
|
||||
There are no plans to port to a5xx or earlier GPUs.
|
||||
|
||||
Hardware architecture
|
||||
---------------------
|
||||
|
||||
Adreno is a mostly tile-mode renderer, but with the option to bypass tiling
|
||||
("gmem") and render directly to system memory ("sysmem"). It is UMA, using
|
||||
mostly write combined memory but with the ability to map some buffers as cache
|
||||
coherent with the CPU.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
freedreno/hw/*
|
||||
|
||||
Hardware acronyms
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. glossary::
|
||||
|
||||
Cluster
|
||||
A group of hardware registers, often with multiple copies to allow
|
||||
pipelining. There is an M:N relationship between hardware blocks that do
|
||||
work and the clusters of registers for the state that hardware blocks use.
|
||||
|
||||
CP
|
||||
Command Processor. Reads the stream of state changes and draw commands
|
||||
generated by the driver.
|
||||
|
||||
PFP
|
||||
Prefetch Parser. Adreno 2xx-4xx CP component.
|
||||
|
||||
ME
|
||||
Micro Engine. Adreno 2xx-4xx CP component after PFP, handles most PM4 commands.
|
||||
|
||||
SQE
|
||||
a6xx+ replacement for PFP/ME. This is the microcontroller that runs the
|
||||
microcode (loaded from Linux) which actually processes the command stream
|
||||
and writes to the hardware registers. See `qrisc
|
||||
<https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/freedreno/qrisc/README.rst>`__.
|
||||
|
||||
ROQ
|
||||
DMA engine used by the SQE for reading memory, with some prefetch buffering.
|
||||
Mostly reads in the command stream, but also serves for
|
||||
``CP_MEMCPY``/``CP_MEM_TO_REG`` and visibility stream reads.
|
||||
|
||||
SP
|
||||
Shader Processor. Unified, scalar shader engine. One or more, depending on
|
||||
GPU and tier.
|
||||
|
||||
TP
|
||||
Texture Processor.
|
||||
|
||||
UCHE
|
||||
Unified L2 Cache. 32KB on A330, unclear how big now.
|
||||
|
||||
CCU
|
||||
Color Cache Unit.
|
||||
|
||||
VSC
|
||||
Visibility Stream Compressor
|
||||
|
||||
PVS
|
||||
Primitive Visibility Stream
|
||||
|
||||
FE
|
||||
Front End? Index buffer and vertex attribute fetch cluster. Includes PC,
|
||||
VFD, VPC.
|
||||
|
||||
VFD
|
||||
Vertex Fetch and Decode
|
||||
|
||||
VPC
|
||||
Varying/Position Cache? Hardware block that stores shaded vertex data for
|
||||
primitive assembly.
|
||||
|
||||
HLSQ
|
||||
High Level Sequencer. Manages state for the SPs, batches up PS invocations
|
||||
between primitives, is involved in preemption.
|
||||
|
||||
PC_VS
|
||||
Cluster where varyings are read from VPC and assembled into primitives to
|
||||
feed GRAS.
|
||||
|
||||
VS
|
||||
Vertex Shader. Responsible for generating VS/GS/tess invocations
|
||||
|
||||
GRAS
|
||||
Rasterizer. Responsible for generating PS invocations from primitives, also
|
||||
does LRZ
|
||||
|
||||
PS
|
||||
Pixel Shader.
|
||||
|
||||
RB
|
||||
Render Backend. Performs both early and late Z testing, blending, and
|
||||
attachment stores of output of the PS.
|
||||
|
||||
GMEM
|
||||
Roughly 128KB-1MB of memory on the GPU (SKU-dependent), used to store
|
||||
attachments during tiled rendering
|
||||
|
||||
LRZ
|
||||
Low Resolution Z. A low resolution area of the depth buffer that can be
|
||||
initialized during the binning pass to contain the worst-case (farthest) Z
|
||||
values in a block, and then used to early reject fragments during
|
||||
rasterization.
|
||||
|
||||
Cache hierarchy
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The a6xx GPUs have two main caches: CCU and UCHE.
|
||||
|
||||
UCHE (Unified L2 Cache) is the cache behind the vertex fetch, VSC writes,
|
||||
texture L1, LRZ, and storage image accesses (``ldib``/``stib``). Misses and
|
||||
flushes access system memory.
|
||||
|
||||
The CCU is the separate cache used by 2D blits and sysmem render target access
|
||||
(and also for resolves to system memory when in GMEM mode). Its memory comes
|
||||
from a carveout of GMEM controlled by ``RB_CCU_CNTL``, with a varying amount
|
||||
reserved based on whether we're in a render pass using GMEM for attachment
|
||||
storage, or we're doing sysmem rendering. Cache entries have the attachment
|
||||
number and layer mixed into the cache tag in some way, likely so that a
|
||||
fragment's access is spread through the cache even if the attachments are the
|
||||
same size and alignments in address space. This means that the cache must be
|
||||
flushed and invalidated between memory being used for one attachment and another
|
||||
(notably depth vs color, but also MRT color).
|
||||
|
||||
The Texture Processors (TP) additionally have a small L1 cache (1KB on A330,
|
||||
unclear how big now) before accessing UCHE. This cache is used for normal
|
||||
sampling like ``sam``` and ``isam`` (and the compiler will make read-only
|
||||
storage image access through it as well). It is not coherent with UCHE (may get
|
||||
stale results when you ``sam`` after ``stib``), but must get flushed per draw or
|
||||
something because you don't need a manual invalidate between draws storing to an
|
||||
image and draws sampling from a texture.
|
||||
|
||||
The command processor (CP) does not read from either of these caches, and
|
||||
instead uses FIFOs in the ROQ to avoid stalls reading from system memory.
|
||||
|
||||
Draw states
|
||||
^^^^^^^^^^^
|
||||
|
||||
Since the SQE is not a fast processor, and tiled rendering means that many draws
|
||||
won't even be used in many bins, since a5xx state updates can be batched up into
|
||||
"draw states" that point to a fragment of CP packets. At draw time, if the draw
|
||||
call is going to actually execute (some primitive is visible in the current
|
||||
tile), the SQE goes through the ``GROUP_ID``\s and for any with an update since
|
||||
the last time they were executed, it executes the corresponding fragment.
|
||||
|
||||
Starting with a6xx, states can be tagged with whether they should be executed
|
||||
at draw time for any of sysmem, binning, or tile rendering. This allows a
|
||||
single command stream to be generated which can be executed in any of the modes,
|
||||
unlike pre-a6xx where we had to generate separate command lists for the binning
|
||||
and rendering phases.
|
||||
|
||||
Note that this means that the generated draw state has to always update all of
|
||||
the state you have chosen to pack into that ``GROUP_ID``, since any of your
|
||||
previous state changes in a previous draw state command may have been skipped.
|
||||
|
||||
Pipelining (a6xx+)
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Most CP commands write to registers. In a6xx+, the registers are located in
|
||||
clusters corresponding to the stage of the pipeline they are used from (see
|
||||
``enum tu_stage`` for a list). To pipeline state updates and drawing, registers
|
||||
generally have two copies ("contexts") in their cluster, so previous draws can
|
||||
be working on the previous set of register state while the next draw's state is
|
||||
being set up. You can find what registers go into which clusters by looking at
|
||||
:command:`crashdec` output in the ``regs-name: CP_MEMPOOL`` section.
|
||||
|
||||
As SQE processes register writes in the command stream, it sends them into a
|
||||
per-cluster queue stored in ``CP_MEMPOOL``. This allows the pipeline stages to
|
||||
process their stream of register updates and events independent of each other
|
||||
(so even with just 2 contexts in a stage, earlier stages can proceed on to later
|
||||
draws before later stages have caught up).
|
||||
|
||||
Each cluster has a per-context bit indicating that the context is done/free.
|
||||
Register writes will stall on the context being done.
|
||||
|
||||
During a 3D draw command, SQE generates several internal events flow through the
|
||||
pipeline:
|
||||
|
||||
- ``CP_EVENT_START`` clears the done bit for the context when written to the
|
||||
cluster
|
||||
- ``PC_EVENT_CMD``/``PC_DRAW_CMD``/``HLSQ_EVENT_CMD``/``HLSQ_DRAW_CMD`` kick off
|
||||
the actual event/drawing.
|
||||
- ``CONTEXT_DONE`` event completes after the event/draw is complete and sets the
|
||||
done flag.
|
||||
- ``CP_EVENT_END`` waits for the done flag on the next context, then copies all
|
||||
the registers that were dirtied in this context to that one.
|
||||
|
||||
The 2D blit engine has its own ``CP_2D_EVENT_START``, ``CP_2D_EVENT_END``,
|
||||
``CONTEXT_DONE_2D``, so 2D and 3D register contexts can do separate context
|
||||
rollover.
|
||||
|
||||
Because the clusters proceed independently of each other even across draws, if
|
||||
you need to synchronize an earlier cluster to the output of a later one, then
|
||||
you will need to ``CP_WAIT_FOR_IDLE`` after flushing and invalidating any
|
||||
necessary caches.
|
||||
|
||||
Also, note that some registers are not banked at all, and will require a
|
||||
``CP_WAIT_FOR_IDLE`` for any previous usage of the register to complete.
|
||||
|
||||
In a2xx-a4xx, there weren't per-stage clusters, and instead there were two
|
||||
register banks that were flipped between per draw.
|
||||
|
||||
Bindless/Bindful Descriptors (a6xx+)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Starting with a6xx++, cat5 (texture) and cat6 (image/SSBO/UBO) instructions are
|
||||
extended to support bindless descriptors.
|
||||
|
||||
In the old bindful model, descriptors are separate for textures, samplers,
|
||||
UBOs, and IBOs (combined descriptor for images and SSBOs), with separate
|
||||
registers for the memory containing the array of descriptors, and/or different
|
||||
``STATE_TYPE`` and ``STATE_BLOCK`` for ``CP_LOAD_STATE``/``_FRAG``/``_GEOM``
|
||||
to pre-load the descriptors into cache.
|
||||
|
||||
- textures - per-shader-stage
|
||||
- registers: ``SP_xS_TEX_CONST``/``SP_xS_TEX_COUNT``
|
||||
- state-type: ``ST6_CONSTANTS``
|
||||
- state-block: ``SB6_xS_TEX``
|
||||
- samplers - per-shader-stage
|
||||
- registers: ``SP_xS_TEX_SAMP``
|
||||
- state-type: ``ST6_SHADER``
|
||||
- state-block: ``SB6_xS_TEX``
|
||||
- UBOs - per-shader-stage
|
||||
- registers: none
|
||||
- state-type: ``ST6_UBO``
|
||||
- state-block: ``SB6_xS_SHADER``
|
||||
- IBOs - global across shader 3d stages, separate for compute shader
|
||||
- registers: ``SP_IBO``/``SP_IBO_COUNT`` or ``SP_CS_IBO``/``SP_CS_IBO_COUNT``
|
||||
- state-type: ``ST6_SHADER``
|
||||
- state-block: ``ST6_IBO`` or ``ST6_CS_IBO`` for compute shaders
|
||||
- Note, unlike per-shader-stage descriptors, ``CP_LOAD_STATE6`` is used,
|
||||
as opposed to ``CP_LOAD_STATE6_GEOM`` or ``CP_LOAD_STATE6_FRAG``
|
||||
depending on shader stage.
|
||||
|
||||
.. note::
|
||||
For the per-shader-stage registers and state-blocks the ``xS`` notation
|
||||
refers to per-shader-stage names, ex. ``SP_FS_TEX_CONST`` or ``SB6_DS_TEX``
|
||||
|
||||
Textures and IBOs (images) use *basically* the same 64byte descriptor format
|
||||
with some exceptions (for ex, for IBOs cubemaps are handles as 2d array).
|
||||
SSBOs are just untyped buffers, but otherwise use the same descriptors and
|
||||
instructions as images. Samplers use a 16byte descriptor, and UBOs use an
|
||||
8byte descriptor which packs the size in the upper 15 bits of the UBO address.
|
||||
|
||||
In the bindless model, descriptors are split into 5 descriptor sets, which are
|
||||
global across shader stages (but as with bindful IBO descriptors, separate for
|
||||
3d stages vs compute stage). Each HW descriptor is an array of descriptors
|
||||
of configurable size (each descriptor set can be configured for a descriptor
|
||||
pitch of 8bytes or 64bytes). Each descriptor can be of arbitrary format (ie.
|
||||
UBOs/IBOs/textures/samplers interleaved), it's interpretation by the HW is
|
||||
determined by the instruction that references the descriptor. Each descriptor
|
||||
set can contain at least 2^^16 descriptors.
|
||||
|
||||
The HW is configured with the base address of the descriptor set via an array
|
||||
of "BINDLESS_BASE" registers, ie ``SP_BINDLESS_BASE[n]``/``HLSQ_BINDLESS_BASE[n]``
|
||||
for 3d shader stages, or ``SP_CS_BINDLESS_BASE[n]``/``HLSQ_CS_BINDLESS_BASE[n]``
|
||||
for compute shaders, with the descriptor pitch encoded in the low bits.
|
||||
Which of the descriptor sets is referenced is encoded via three bits in the
|
||||
instruction. The address of the descriptor is calculated as::
|
||||
|
||||
descriptor_addr = (BINDLESS_BASE[n] & ~0x3) +
|
||||
(idx * 4 * (2 << BINDLESS_BASE[n] & 0x3))
|
||||
|
||||
|
||||
.. note::
|
||||
Turnip reserves one descriptor set for internal use and exposes the other
|
||||
four for the application via the Vulkan API.
|
||||
|
||||
Software Architecture
|
||||
---------------------
|
||||
|
||||
Freedreno and Turnip use a shared core for shader compiler, image layout, and
|
||||
register and command stream definitions. They implement separate state
|
||||
management and command stream generation.
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
freedreno/*
|
||||
|
||||
.. _freedreno-gpu-devcoredump:
|
||||
|
||||
GPU devcoredump
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A kernel message from DRM of "gpu fault" can mean any sort of error reported by
|
||||
the GPU (including its internal hang detection). If a fault in GPU address
|
||||
space happened, you should expect to find a message from the iommu, with the
|
||||
faulting address and a hardware unit involved:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
*** gpu fault: ttbr0=000000001c941000 iova=000000010066a000 dir=READ type=TRANSLATION source=TP|VFD (0,0,0,1)
|
||||
|
||||
On a GPU fault or hang, a GPU core dump is taken by the DRM driver and saved to
|
||||
``/sys/devices/virtual/devcoredump/**/data``. You can cp that file to a
|
||||
:file:`crash.devcore` to save it, otherwise the kernel will expire it
|
||||
eventually. Echo 1 to the file to free the core early, as another core won't be
|
||||
taken until then.
|
||||
|
||||
Once you have your core file, you can use :command:`crashdec -f crash.devcore`
|
||||
to decode it. The output will have ``ESTIMATED CRASH LOCATION`` where we
|
||||
estimate the CP to have stopped. Note that it is expected that this will be
|
||||
some distance past whatever state triggered the fault, given GPU pipelining, and
|
||||
will often be at some ``CP_REG_TO_MEM`` (which waits on previous WFIs) or
|
||||
``CP_WAIT_FOR_ME`` (which waits for all register writes to land) or similar
|
||||
event. You can try running the workload with ``TU_DEBUG=flushall`` or
|
||||
``FD_MESA_DEBUG=flush`` to try to close in on the failing commands.
|
||||
|
||||
You can also find what commands were queued up to each cluster in the
|
||||
``regs-name: CP_MEMPOOL`` section.
|
||||
|
||||
If ``ESTIMATED CRASH LOCATION`` doesn't exist you could find ``CP_SQE_STAT``,
|
||||
though going here is the last resort and likely won't be helpful.
|
||||
|
||||
.. code-block::
|
||||
|
||||
indexed-registers:
|
||||
- regs-name: CP_SQE_STAT
|
||||
dwords: 51
|
||||
PC: 00d7 <-------------
|
||||
PKT: CP_LOAD_STATE6_FRAG
|
||||
$01: 70348003 $11: 00000000
|
||||
$02: 20000000 $12: 00000022
|
||||
|
||||
The ``PC`` value is an instruction address in the current firmware.
|
||||
You would need to disassemble the firmware (/lib/firmware/qcom/aXXX_sqe.fw) via:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
qrisc-disasm -v a650_sqe.fw > a650_sqe.fw.disasm
|
||||
|
||||
Now you should search for PC value in the disassembly, e.g.:
|
||||
|
||||
.. code-block::
|
||||
|
||||
l018: 00d1: 08dd0001 add $addr, $06, 0x0001
|
||||
00d2: 981ff806 mov $data, $data
|
||||
00d3: 8a080001 mov $08, 0x0001 << 16
|
||||
00d4: 3108ffff or $08, $08, 0xffff
|
||||
00d5: 9be8f805 and $data, $data, $08
|
||||
00d6: 9806e806 mov $addr, $06
|
||||
00d7: 9803f806 mov $data, $03 <------------- HERE
|
||||
00d8: d8000000 waitin
|
||||
00d9: 981f0806 mov $01, $data
|
||||
|
||||
|
||||
Command Stream Capture
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
During Mesa development, it's often useful to look at the command streams we
|
||||
send to the kernel. We have an interface for the kernel to capture all
|
||||
submitted command streams:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cat /sys/kernel/debug/dri/0/rd > cmdstream &
|
||||
|
||||
By default, command stream capture does not capture texture/vertex/etc. data.
|
||||
You can enable capturing all the BOs with:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
echo Y > /sys/module/msm/parameters/rd_full
|
||||
|
||||
Note that, since all command streams get captured, it is easy to run the system
|
||||
out of memory doing this, so you probably don't want to enable it during play of
|
||||
a heavyweight game. Instead, to capture a command stream within a game, you
|
||||
probably want to cause a crash in the GPU during a frame of interest so that a
|
||||
single GPU core dump is generated. Emitting ``0xdeadbeef`` in the CS should be
|
||||
enough to cause a fault.
|
||||
|
||||
``fd_rd_output`` facilities provide support for generating the command stream
|
||||
capture from inside Mesa. Different ``FD_RD_DUMP`` options are available:
|
||||
|
||||
- ``enable`` simply enables dumping the command stream on each submit for a
|
||||
given logical device. When a more advanced option is specified, ``enable`` is
|
||||
implied as specified.
|
||||
- ``combine`` will combine all dumps into a single file instead of writing the
|
||||
dump for each submit into a standalone file.
|
||||
- ``full`` will dump every buffer object, which is necessary for replays of
|
||||
command streams (see below).
|
||||
- ``trigger`` will establish a trigger file through which dumps can be better
|
||||
controlled. Writing a positive integer value into the file will enable dumping
|
||||
of that many subsequent submits. Writing -1 will enable dumping of submits
|
||||
until disabled. Writing 0 (or any other value) will disable dumps.
|
||||
|
||||
Output dump files and trigger file (when enabled) are placed under ``/tmp`` or
|
||||
``/data/local/tmp`` under Android by default, but that location can be adjusted
|
||||
through ``FD_RD_DUMP_PATH``. ``FD_RD_DUMP_TESTNAME`` can be used to specify a
|
||||
more descriptive prefix for the output or trigger files.
|
||||
|
||||
Dumping can be limited to specific ranges of frames or submits. For example,
|
||||
``FD_RD_DUMP_SUBMITS=120-140,160,165`` will dump command streams only for the
|
||||
specified submits. Similarly, ``FD_RD_DUMP_FRAMES`` can be set to specify for
|
||||
which frames any submitted command stream should be dumped.
|
||||
|
||||
Functionality is generic to any Freedreno-based backend, but is currently only
|
||||
integrated in the MSM backend of Turnip. Using the existing ``TU_DEBUG=rd``
|
||||
option will translate to ``FD_RD_DUMP=enable``.
|
||||
|
||||
Capturing Hang RD
|
||||
+++++++++++++++++
|
||||
|
||||
Devcore file doesn't contain all submitted command streams, only the hanging one.
|
||||
Additionally it is geared towards analyzing the GPU state at the moment of the crash.
|
||||
|
||||
Alternatively, it's possible to obtain the whole submission with all command
|
||||
streams via ``/sys/kernel/debug/dri/0/hangrd``:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
sudo cat /sys/kernel/debug/dri/0/hangrd > logfile.rd // Do the cat _before_ the expected hang
|
||||
|
||||
The format of hangrd is the same as in ordinary command stream capture.
|
||||
``rd_full`` also has the same effect on it.
|
||||
|
||||
Replaying Command Stream
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``replay`` tool allows capturing and replaying ``rd`` to reproduce GPU faults.
|
||||
Especially useful for transient GPU issues since it has much higher chances to
|
||||
reproduce them.
|
||||
|
||||
Dumping rendering results or even just memory is currently unsupported.
|
||||
|
||||
- Replaying command streams requires kernel with ``MSM_INFO_SET_IOVA`` support.
|
||||
- Requires ``rd`` capture to have full snapshots of the memory (``rd_full`` is enabled).
|
||||
|
||||
Replaying is done via ``replay`` tool:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./replay test_replay.rd
|
||||
|
||||
More examples:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./replay --first=start_submit_n --last=last_submit_n test_replay.rd
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./replay --override=0 test_replay.rd
|
||||
|
||||
.. _freedreno-editing-command-stream:
|
||||
|
||||
Editing Command Stream (a6xx+)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
While replaying a fault is useful in itself, modifying the capture to
|
||||
understand what causes the fault could be even more useful.
|
||||
|
||||
``rddecompiler`` decompiles a single cmdstream from ``rd`` into compilable C source.
|
||||
Given the address space bounds the generated program creates a new ``rd`` which
|
||||
could be used to override cmdstream with 'replay'. Generated ``rd`` is not replayable
|
||||
on its own and depends on buffers provided by the source ``rd``.
|
||||
|
||||
C source could be compiled by putting it into src/freedreno/decode/generate-rd.cc.
|
||||
|
||||
The workflow would look like this:
|
||||
|
||||
1. Find the cmdstream № you want to edit;
|
||||
2. Decompile it:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./rddecompiler -s %cmd_stream_n% example.rd > src/freedreno/decode/generate-rd.cc
|
||||
|
||||
3. Edit the command stream;;
|
||||
4. Compile and deploy freedreno tools;
|
||||
5. Plug the generator into cmdstream replay:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
./replay --override=%cmd_stream_№%
|
||||
|
||||
6. Repeat 3-5.
|
||||
|
||||
GPU Hang Debugging
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Not a guide for how to do it but mostly an enumeration of methods.
|
||||
|
||||
Useful ``TU_DEBUG`` (for Turnip) options to narrow down the hang cause:
|
||||
|
||||
``sysmem``, ``gmem``, ``nobin``, ``forcebin``, ``noubwc``, ``nolrz``, ``flushall``, ``syncdraw``, ``rast_order``
|
||||
|
||||
Useful ``FD_MESA_DEBUG`` (for Freedreno) options:
|
||||
|
||||
``sysmem``, ``gmem``, ``nobin``, ``noubwc``, ``nolrz``, ``notile``, ``dclear``, ``ddraw``, ``flush``, ``inorder``, ``noblit``
|
||||
|
||||
Useful ``IR3_SHADER_DEBUG`` options:
|
||||
|
||||
``nouboopt``, ``spillall``, ``nopreamble``, ``nofp16``
|
||||
|
||||
Use Graphics Flight Recorder to narrow down the place which hangs,
|
||||
use our own breadcrumbs implementation in case of unrecoverable hangs.
|
||||
|
||||
In case of faults use RenderDoc to find the problematic command. If it's
|
||||
a draw call, edit shader in RenderDoc to find whether it culprit is a shader.
|
||||
If yes, bisect it.
|
||||
|
||||
If editing the shader messes the assembly too much and the issue becomes unreproducible
|
||||
try editing the assembly itself via ``IR3_SHADER_OVERRIDE_PATH``.
|
||||
|
||||
If fault or hang is transient try capturing an ``rd`` and replay it. If issue
|
||||
is reproduced - bisect the GPU packets until the culprit is found.
|
||||
|
||||
Do the above if culprit is not a shader.
|
||||
|
||||
The hang recovery mechanism in Kernel is not perfect, in case of unrecoverable
|
||||
hangs check whether the kernel is up to date and look for unmerged patches
|
||||
which could improve the recovery.
|
||||
|
||||
GPU Breadcrumbs
|
||||
+++++++++++++++
|
||||
|
||||
Breadcrumbs described below are available only in Turnip.
|
||||
|
||||
Freedreno has simpler breadcrumbs, in debug build writes breadcrumbs
|
||||
into ``CP_SCRATCH_REG[6]`` and per-tile breadcrumbs into ``CP_SCRATCH_REG[7]``,
|
||||
in this way they are available in the devcoredump. TODO: generalize Tunip's
|
||||
breadcrumbs implementation.
|
||||
|
||||
This is a simple implementations of breadcrumbs tracking of GPU progress
|
||||
intended to be a last resort when debugging unrecoverable hangs.
|
||||
For best results use Vulkan traces to have a predictable place of hang.
|
||||
|
||||
For ordinary hangs as a more user-friendly solution use GFR
|
||||
"Graphics Flight Recorder".
|
||||
|
||||
Or breadcrumbs implementation aims to handle cases where nothing can be done
|
||||
after the hang. In-driver breadcrumbs also allow more precise tracking since
|
||||
we could target a single GPU packet.
|
||||
|
||||
While breadcrumbs support gmem, try to reproduce the hang in a sysmem mode
|
||||
because it would require much less breadcrumb writes and syncs.
|
||||
|
||||
Breadcrumbs settings:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
TU_BREADCRUMBS=%IP%:%PORT%,break=%BREAKPOINT%:%BREAKPOINT_HITS%
|
||||
|
||||
``BREAKPOINT``
|
||||
The breadcrumb starting from which we require explicit ack.
|
||||
``BREAKPOINT_HITS``
|
||||
How many times breakpoint should be reached for break to occur.
|
||||
Necessary for a gmem mode and re-usable cmdbuffers in both of which
|
||||
the same cmdstream could be executed several times.
|
||||
|
||||
A typical work flow would be:
|
||||
|
||||
- Start listening for breadcrumbs on a remote host:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
nc -lkvup $PORT | stdbuf -o0 xxd -pc -c 4 | awk -Wposix '{printf("%u:%u\n", "0x" $0, a[$0]++)}'
|
||||
|
||||
- Start capturing command stream;
|
||||
- Replay the hanging trace with:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
TU_BREADCRUMBS=$IP:$PORT,break=-1:0
|
||||
|
||||
- Increase hangcheck period:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
echo -n 60000 > /sys/kernel/debug/dri/0/hangcheck_period_ms
|
||||
|
||||
- After GPU hang note the last breadcrumb and relaunch trace with:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
TU_BREADCRUMBS=%IP%:%PORT%,break=%LAST_BREADCRUMB%:%HITS%
|
||||
|
||||
- After the breakpoint is reached each breadcrumb would require
|
||||
explicit ack from the user. This way it's possible to find
|
||||
the last packet which didn't hang.
|
||||
|
||||
- Find the packet in the decoded cmdstream.
|
||||
|
||||
Debugging random failures
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In most cases random GPU faults and rendering artifacts are caused by some kind
|
||||
of undefined behavior that falls under the following categories:
|
||||
|
||||
- Usage of a stale reg value;
|
||||
- Usage of stale memory (e.g. expecting it to be zeroed when it is not);
|
||||
- Lack of the proper synchronization.
|
||||
|
||||
Finding instances of stale reg reads
|
||||
++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Turnip has a debug option to stomp the registers with invalid values to catch
|
||||
the cases where stale data is read.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
MESA_VK_ABORT_ON_DEVICE_LOSS=1 \
|
||||
TU_DEBUG_STALE_REGS_RANGE=0x00000c00,0x0000be01 \
|
||||
TU_DEBUG_STALE_REGS_FLAGS=cmdbuf,renderpass \
|
||||
./app
|
||||
|
||||
.. envvar:: TU_DEBUG_STALE_REGS_RANGE
|
||||
|
||||
the reg range in which registers would be stomped. Add ``inverse`` to the
|
||||
flags in order for this range to specify which registers NOT to stomp.
|
||||
|
||||
.. envvar:: TU_DEBUG_STALE_REGS_FLAGS
|
||||
|
||||
``cmdbuf``
|
||||
stomp registers at the start of each command buffer.
|
||||
``renderpass``
|
||||
stomp registers before each render pass.
|
||||
``inverse``
|
||||
changes ``TU_DEBUG_STALE_REGS_RANGE`` meaning to
|
||||
"regs that should NOT be stomped".
|
||||
|
||||
The best way to pinpoint the reg which causes a failure is to bisect the regs
|
||||
range. In case when a fail is caused by combination of several registers
|
||||
the ``inverse`` flag may be set to find the reg which prevents the failure.
|
||||
|
||||
Runtime toggling of ``TU_DEBUG`` options
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In some cases, it is useful to toggle ``TU_DEBUG`` options at runtime, such as
|
||||
when assessing the performance impact of a particular option. This can be done
|
||||
by setting the ``TU_DEBUG_FILE`` environment variable to a file path, and writing
|
||||
the desired ``TU_DEBUG`` options to that file. The driver will check the file at
|
||||
startup and apply all debug options (runtime and non-runtime) but then will
|
||||
continue to monitor the file for changes and only apply runtime options.
|
||||
|
||||
The folder containing the file should exist prior to running the application, and
|
||||
deleting the folder during runtime will result in the driver no longer picking up
|
||||
any changes even if the folder is recreated.
|
||||
|
||||
Additionally, not all ``TU_DEBUG`` options can be toggled at runtime, the following
|
||||
are supported at the moment: ``nir``, ``nobin``, ``sysmem``, ``gmem``, ``forcebin``,
|
||||
``layout``, ``nolrz``, ``nolrzfc``, ``perf``, ``flushall``, ``syncdraw``,
|
||||
``rast_order``, ``unaligned_store``, ``log_skip_gmem_ops``, ``3d_load``, ``fdm``,
|
||||
``noconcurrentresolves``, ``noconcurrentunresolves``, ``nobinmerging``.
|
||||
|
||||
Some of these options will behave differently when toggled at runtime, for example:
|
||||
``nolrz`` will still result in LRZ allocation which would not happen if the option
|
||||
was set in the environment variable.
|
||||
|
||||
Autotune
|
||||
^^^^^^^^
|
||||
|
||||
Turnip supports dynamically selecting between SYSMEM and GMEM rendering with the
|
||||
autotune system, the behavior of which can be controlled with the following
|
||||
environment variables:
|
||||
|
||||
.. envvar:: TU_AUTOTUNE_ALGO
|
||||
|
||||
Selects the algorithm used for autotuning. Supported values are:
|
||||
|
||||
``bandwidth``
|
||||
Estimates the bandwidth usage of rendering in SYSMEM and GMEM modes, and chooses
|
||||
the one with lower estimated bandwidth. This is the default algorithm.
|
||||
|
||||
``profiled``
|
||||
Dynamically profiles the RP timings in SYSMEM and GMEM modes, and uses that to
|
||||
move a probability distribution towards the optimal choice over time. This
|
||||
algorithm tends to be far more accurate than the bandwidth algorithm at choosing
|
||||
the optimal rendering mode but may result in larger FPS variance due to being
|
||||
based on a probability distribution with random sampling.
|
||||
|
||||
``profiled_imm``
|
||||
Similar to ``profiled``, but only profiles the first few instances of a RP
|
||||
and then sticks to the chosen mode for subsequent instances. This is meant
|
||||
for single-frame traces run multiple times in a CI where this algorithm can
|
||||
immediately chose the optimal rendering mode for each RP.
|
||||
|
||||
``prefer_sysmem``/``prefer_gmem``
|
||||
Always chooses SYSMEM/GMEM rendering. This is useful for games that work
|
||||
better in one mode over the other due to their rendering patterns, setting
|
||||
this is better than using ``TU_DEBUG=sysmem``/``TU_DEBUG=gmem`` when done
|
||||
for performance reasons, since these still allow the other mode to be used
|
||||
in some high-confidence cases as well as letting ``TU_AUTOTUNE_FLAGS`` still
|
||||
be applied.
|
||||
|
||||
The algorithm can be set via the driconf option ``tu_autotune_algorithm`` as well.
|
||||
|
||||
.. envvar:: TU_AUTOTUNE_FLAGS
|
||||
|
||||
Modifies the behavior of the selected algorithm. Supported flags are:
|
||||
|
||||
``big_gmem``
|
||||
Always chooses GMEM rendering if the amount of draw calls in the render pass
|
||||
is greater than a certain threshold. Larger RPs generally benefit more from
|
||||
GMEM rendering due to less overhead from tiling. This tends to lead to worse
|
||||
performance in most cases, so it's only useful for testing.
|
||||
|
||||
``tune_small``
|
||||
Enables tuning for small render passes (those with a small number of draw
|
||||
calls). By default, small RPs always use SYSMEM mode as they generally don't
|
||||
benefit from GMEM rendering due to the overhead of tiling.
|
||||
|
||||
``preempt_optimize``
|
||||
Tries to keep non-preemptible time in the render pass is below a certain
|
||||
threshold. This is useful for systems with GPU-based compositors where long
|
||||
non-preemptible times can lead to missed frame deadlines, causing noticeable
|
||||
stuttering. This flag will reduce the performance of the render pass in order
|
||||
to improve overall system responsiveness, it should not be used unless the
|
||||
rest of the system is affected by preemption delays.
|
||||
|
||||
This is done by measuring the time between a preemption request and preemption
|
||||
actually occurring, if it is above a threshold we force those RPs to use GMEM
|
||||
rendering, where non-preemptible times are driven by tile size which we
|
||||
progressively reduce until the non-preemptible time is below the threshold.
|
||||
|
||||
It should be noted that this will occupy the last two CP performance counters
|
||||
which may interfere with other profiling tools (such as `fdperf`).
|
||||
|
||||
Multiple flags can be combined by separating them with commas, e.g.
|
||||
``TU_AUTOTUNE_FLAGS=big_gmem,tune_small``.
|
||||
|
||||
If no flags are specified, the default behavior is used.
|
||||
@@ -0,0 +1,286 @@
|
||||
Fragment Density Map
|
||||
====================
|
||||
|
||||
``VK_EXT_fragment_density_map`` is an extension which is intended to allow
|
||||
users to render parts of the screen at a lower resolution. It is designed to be
|
||||
implemented on tiled rendering GPU architectures such as Adreno, and the
|
||||
intention is that it is implemented by rendering some of the tiles at a lower
|
||||
resolution and scaling them up when resolving to system memory or when sampling
|
||||
the resulting image. This inherently means that it is "all or nothing," that
|
||||
is, it must be enabled or disabled for the entire render pass. While the idea is
|
||||
simple, the implementation in turnip is very subtle with lots of
|
||||
interactions with various different features. This page attempts to document
|
||||
the main principles behind the implementation.
|
||||
|
||||
Coordinate Space Soup
|
||||
---------------------
|
||||
|
||||
In order to render a tile at lower resolution, we have to override the user's
|
||||
viewport and scissor for each tile depending on the scaling factor provided by
|
||||
the user. This becomes complicated fast, so let's start by defining a few
|
||||
coordinate spaces that we'll have to work with.
|
||||
|
||||
Framebuffer space
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
This is the space of the final rendered image. From the user's perspective
|
||||
everything is specified in this space, and fragments created by the rasterizer
|
||||
appear to be larger than 1 pixel. But this is not what actually happens in the
|
||||
hardware, it is a fiction created by the driver. The other spaces below are
|
||||
what the hardware actually "sees".
|
||||
|
||||
GMEM Space
|
||||
^^^^^^^^^^
|
||||
|
||||
This space exists whenever tiled rendering/GMEM is used, even without FDM. It
|
||||
is the space used to access GMEM, with the origin at the upper left of the
|
||||
tile. The hardware automatically transforms rendering space into GMEM space
|
||||
whenever GMEM is accessed using the various ``*_WINDOW_OFFSET`` registers. The
|
||||
origin of this space in rendering space, or the value of ``*_WINDOW_OFFSET``,
|
||||
will be called :math:`b_{cs}`, the common bin start, for reasons that are
|
||||
explained below. When using FDM, coordinates in this space must be multiplied
|
||||
by the scaling factor :math:`s` derived from the fragment density map, or
|
||||
equivalently divided by the fragment area (as defined by the Vulkan
|
||||
specification), with the origin still at the upper left of the tile. For
|
||||
example, if :math:`s_x = 1/2`, then the bin is half as wide as it would've been
|
||||
without FDM and all coordinates in this space must be divided by 2.
|
||||
|
||||
Rendering space
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
This is the space in which the hardware rasterizer operates and produces
|
||||
fragments. Normally this is the same as framebuffer space, but with FDM it is
|
||||
not. We transform the viewport and scissor from framebuffer space to
|
||||
rendering space by patching them per-tile in the driver and then when we
|
||||
resolve the tile we scale the resulting tile back to the correct resolution by
|
||||
blitting from the rendering space source to the framebuffer space destination.
|
||||
|
||||
In order to come up with the correct transform from framebuffer space to
|
||||
rendering space, it has to shrink the coordinates by :math:`s` while
|
||||
mapping the original bin start in framebuffer space :math:`b_s` to
|
||||
:math:`b_{cs}`. Since :math:`b_{cs}` is entirely defined by the driver when
|
||||
programming ``*_WINDOW_OFFSET``, one tempting way to do this is to just
|
||||
multiply by :math:`s` and define :math:`b_{cs} = b_s * s`. It turns out,
|
||||
however, that this doesn't work. A key requirement is to handle cases where the
|
||||
same scene is rendered in multiple different views at the same time using
|
||||
``VK_KHR_multiview``, as in VR use-cases, and in this case we want :math:`s` to
|
||||
vary per view, but :math:`b_{cs}` is always the same for every view because
|
||||
there is only one ``*_WINDOW_OFFSET`` register for all layers (hence the name).
|
||||
|
||||
We follow the blob by leaving :math:`b_{cs}` the same regardless of whether FDM
|
||||
is enabled or not. This means that normally :math:`b_s = b_{cs}`, although this
|
||||
is not the case if ``VK_EXT_fragment_density_map_offset`` is in use and the
|
||||
bins are shifted per-view. Since the coordinates need to be scaled by :math:`s`,
|
||||
we know that the transform needs to look like :math:`x' = s * x + o`, where
|
||||
only the offset :math:`o` is free. Plugging in the constraint that :math:`b_s`
|
||||
maps to :math:`b_{cs}`, we get that :math:`b_{cs} = s * b_s + o` or
|
||||
:math:`o = b_{cs} - s * b_s`. This is the function computed by
|
||||
``tu_fdm_per_bin_offset`` and used to calculate the transform for the viewport,
|
||||
scissor, and ``gl_FragCoord``. One critical thing is that the offset must be an
|
||||
integer, or in other words the framebuffer space bin start :math:`b_s` must be
|
||||
a multiple of :math:`1 / s`. This is a natural constraint anyway, because if
|
||||
it wasn't the case then the bin would start in the middle of a fragment which
|
||||
isn't possible to handle correctly.
|
||||
|
||||
Subsampled Space
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
When using subsampled images, this is the space where the bin is stored in the
|
||||
underlying subsampled image. When sampling from a subsampled image, the driver
|
||||
inserts shader code to transform from framebuffer space to subsampled space
|
||||
using metadata written when rendering to the image.
|
||||
|
||||
Accesses towards the edge of a bin may partially bleed into its neighboring bin
|
||||
with linear or bicubic sampling. If its neighbor has a different scale or isn't
|
||||
adjacent in subsampled space, we will sample the incorrect data or empty space
|
||||
and return a corrupted result. In order to handle this, we need to insert an
|
||||
"apron" around problematic edges and corners. This is done by blitting from the
|
||||
nearest neighbor of each bin after the renderpass.
|
||||
|
||||
Subsampled space is normally scaled down similar to rendering space, which is
|
||||
the point of subsampled images in the first place, but the origin of the bin
|
||||
is up to the driver. The driver chooses the origin of each bin when rendering a
|
||||
given render pass and then encodes it in the metadata used when sampling the
|
||||
image. Bins that require an apron must be far enough away from each other that
|
||||
their aprons don't intersect, and all of the bins must be contained within the
|
||||
underlying image.
|
||||
|
||||
Even when subsampled images are in use, not all bins may be subsampled. For
|
||||
example, there may not be enough space to insert aprons around every bin. When
|
||||
this is the case, subsampled space is not scaled like rendering space, that is
|
||||
we expand the bin when resolving similar to non-subsampled images, however the
|
||||
origin of the bin may still differ from framebuffer space origin.
|
||||
|
||||
The algorithm used by turnip used to calculate the bin layout in subsampled
|
||||
space is to start with a "default" layout of the bins and then recursively
|
||||
solve conflicts caused by bins whose aprons are too close together. The first
|
||||
strategy used is to shift one of the bins over by a certain amount. The second
|
||||
fallback strategy is to un-subsample both neighboring bins, making them
|
||||
expanded so that they touch each other and there is no apron.
|
||||
|
||||
One natural choice for the "default" layout is to just use rendering space.
|
||||
That is, start each bin at :math:`b_cs` by default. That mostly works, except
|
||||
for two problems. The first is easier to solve, and has to do with the border
|
||||
when sampling: it is allowed to use border colors with subsampled images, and
|
||||
when that happens and the framebuffer covers the entire image, it is expected
|
||||
that sampling around the edge correctly blends the border color and the edge
|
||||
pixel. In order for that to happen, bins that touch or intersect the edge of
|
||||
the framebuffer in framebuffer space have to be shifted over so that their edge
|
||||
touches the framebuffer edge in subsampled space too.
|
||||
|
||||
Doing this also allows an optimization: because we are storing the tile's
|
||||
contents one to one from GMEM to system memory instead of scaling it up, we can
|
||||
use the dedicated resolve engine instead of GRAS to resolve the tile to system
|
||||
memory. Normally GRAS has to be used with non-subsampled images to scale up the
|
||||
bin when resolving. However this doesn't work for tiles around the right and
|
||||
bottom edge where we have to shift over the tile to align to the edge. This
|
||||
also gets a bit tricky when the tile is shifted to avoid apron conflicts, because
|
||||
normally the resolve engine would write the tile directly without shifting.
|
||||
However there is a trick we can use to avoid falling back to GRAS: by
|
||||
overriding ``RB_RESOLVE_WINDOW_OFFSET``, we can effectively apply an offset by
|
||||
telling the resolve engine that the tile was rendered somewhere else. This
|
||||
means that the shift amount has to be aligned to the alignment of
|
||||
``RB_RESOLVE_WINDOW_OFFSET``, which is ``tile_align_*`` in the device info.
|
||||
|
||||
The other problem with making subsampled space equal rendering space is that
|
||||
with an FDM offset, rendering space can be arbitrarily larger than framebuffer
|
||||
space, and we may overflow the attachments by up to the size of a tile. The API
|
||||
is designed to allow the driver to allocate extra slop space in the image in
|
||||
this case, because there are image create flags for subsampled and FDM offset,
|
||||
however the maximum tile size is far too large and images would take up
|
||||
far too much memory if we allocated enough slop space for the largest
|
||||
possible tile. An alternative is to use a hybrid of framebuffer space and
|
||||
rendering space: shift over the tiles by :math:`b_o` so that their origin
|
||||
is :math:`b_s` instead of :math:`b_cs`, but leave them scaled down. This
|
||||
requires no slop space whatsoever, because the bins are shifted inside the
|
||||
original image, but we can no longer use the resolve engine as the tile offsets
|
||||
are no longer aligned to ``tile_align_*``. So in the driver we combine both
|
||||
approaches: we calculate an aligned offset :math:`b_o'` which is :math:`b_o`
|
||||
aligned down to ``tile_align_*`` and shift over the tiles by subtracting
|
||||
:math:`b_o'` instead of :math:`b_o`. This requires slop space, but only
|
||||
:math:`b_o - b_o'` slop space is required, which must be less than
|
||||
``tile_align_*``. As usual the first row/column are not shifted over in x/y
|
||||
respectively.
|
||||
|
||||
Here is an example of what a subsampled image looks like in memory, in this
|
||||
case without any FDM offset:
|
||||
|
||||
.. figure:: subsampled_annotated.jpg
|
||||
:alt: Example of a subsampled image
|
||||
|
||||
Note how some of the bins are shifted over to make space for the apron. After
|
||||
applying the coordinate transform when sampling, this is the final image:
|
||||
|
||||
.. figure:: subsampled_final.jpg
|
||||
:alt: Example of a subsampled image after coordinate transform
|
||||
|
||||
When ``VK_EXT_custom_resolve`` and subsampled images are used together, the
|
||||
custom resolve subpass writes directly to the subsampled image. This means that
|
||||
it needs to use subsampled space instead of rendering space, which in practice
|
||||
means replacing :math:`b_{cs}` with the origin of the bin in the subsampled
|
||||
image.
|
||||
|
||||
Viewport and Scissor Patching
|
||||
-----------------------------
|
||||
|
||||
In order to have :math:`s` differ per view, we have to be able to override the
|
||||
viewport per view. That is, we need to transform the viewport for each view
|
||||
differently. If there is only one viewport, then we duplicate the user's
|
||||
viewport for each view and transform it using the :math:`b_s` and :math:`s` for
|
||||
that view, and we set a "per-view viewport" bit to select the viewport per view
|
||||
instead of using the default viewport 0. When
|
||||
``VK_VALVE_fragment_density_map_layered`` is in use, we instead have to insert
|
||||
shader code to achieve the same thing.
|
||||
|
||||
If the user specifies multiple viewports but they are per-view because
|
||||
``VK_QCOM_multiview_per_view_viewport`` is enabled, then we can just set the
|
||||
per-view viewport bit and transform each user viewport individually by the
|
||||
corresponding scale. But if the user explicitly writes ``gl_ViewportIndex``,
|
||||
then there is nothing we can do and we have to make :math:`s` the same for all
|
||||
views by conservatively taking the minimum. Then we apply :math:`s` to all of
|
||||
the user-specified viewports.
|
||||
|
||||
Because the bin size is now per-view, the usual mechanism of
|
||||
``*_WINDOW_SCISSOR`` for clipping fragments outside the bin doesn't work.
|
||||
Instead the driver needs to intersect the transformed user-specified scissor
|
||||
with the transformed rendering-space bin coordinates, effectively replacing
|
||||
``*_WINDOW_SCISSOR``.
|
||||
|
||||
Fragment density map offset
|
||||
---------------------------
|
||||
|
||||
In order to "properly" implement ``VK_EXT_fragment_density_map_offset``, we
|
||||
need to add an extra row/column of bins at the end and then shift the binning
|
||||
grid up and to the left by an offset :math:`b_o`. This offset is based on the
|
||||
user's offset but has the opposite sign, i.e. when shifting the FDM to the left
|
||||
we have to shift the binning grid to the right, and once the user's offset
|
||||
becomes large enough then we "wrap around" and shift over the scaling factor
|
||||
:math:`s` to the next bin. This has to happen per-view. In turnip the function
|
||||
that computes :math:`b_o` is called ``tu_bin_offset``. Each tile then gets an
|
||||
offseted start :math:`b_s = b_{cs} - b_o` except for the first row/column which
|
||||
only shrink in height/width respectively.
|
||||
|
||||
If we cannot make :math:`s` per-view, then we also cannot make :math:`b_s`
|
||||
per-view and so we cannot shift the bins over. Therefore we fall back to only
|
||||
shifting where :math:`s` is sampled from, which produces jittery and jarring
|
||||
transitions when a bin suddenly changes resolution.
|
||||
|
||||
Bin merging
|
||||
-----------
|
||||
|
||||
FDM shrinks the size of the bin in GMEM, which results in a lot of wasteful
|
||||
unused extra space in GMEM. a7xx mitigates this by introducing "bin merging".
|
||||
If two tiles next to each other have the same scaling for each view, then we
|
||||
combine them into one tile, as long as the combined size in rendering space
|
||||
isn't larger than the original size of an unscaled bin in framebuffer space. We
|
||||
can even merge larger groups of tiles. The only hardware feature needed for
|
||||
this to work is the ability to merge the visibility streams for the tiles,
|
||||
which was added on a7xx by a new bitmask in ``CP_SET_BIN_DATA5`` and variants.
|
||||
Only bins within the same visibility stream/VSC pipe can be merged.
|
||||
|
||||
Hardware scaling registers and LRZ
|
||||
----------------------------------
|
||||
|
||||
One disadvantage of FDM on a6xx is that low-resolution tiles cannot use
|
||||
LRZ, because the LRZ hardware is not aware of the transform between framebuffer
|
||||
space and rendering space and applies the framebuffer-space LRZ values to the
|
||||
rendering-space fragments. In order to fix this, a740 adds new offset and scale
|
||||
registers. The offset :math:`o'` is applied to fragment coordinates during
|
||||
rasterization *after* LRZ, so that viewport, scissor, and LRZ are in a
|
||||
new "LRZ space" while the other operations (resolves and unresolves, and
|
||||
attachment writes) still happen in the rendering space which is now offset.
|
||||
:math:`o'` is specified for each layer. The scale :math:`s` is the same as
|
||||
before, and it is used to multiply the fragment area covered by each LRZ pixel.
|
||||
|
||||
Without ``VK_EXT_fragment_density_map_offset``, we can simply make LRZ space
|
||||
equal to framebuffer space scaled down by :math:`s`. That is, we can set
|
||||
:math:`o'` to what :math:`o` was before and then set :math:`o` to 0, only
|
||||
scaling down the viewport but not shifting it and letting the hardware handle
|
||||
the shift. Then LRZ pixels will be scaled up appropriately and everything will
|
||||
work. However, this doesn't work if there is a bin offset :math:`b_o`. In order
|
||||
to make binning work, we shift the viewport and scissor by :math:`b_o` when
|
||||
binning. Unfortunately the offset registers do not have any effect when
|
||||
binning, so rendering space and LRZ space have to be the same when binning, and
|
||||
the visibility stream is generated from rendering space. This means that LRZ
|
||||
space also has to be shifted over compared to framebuffer space, and the LRZ
|
||||
buffer must be overallocated when FDM offset might be used with it (which is
|
||||
signalled by ``VK_IMAGE_CREATE_FRAGMENT_DENSITY_MAP_OFFSET_BIT_EXT``) because
|
||||
the LRZ image will be shifted by :math:`b_o`.
|
||||
|
||||
In order for LRZ to work, LRZ space when rendering must be equal to LRZ space
|
||||
when binning scaled down by :math:`s`. The origin of LRZ space when binning is
|
||||
:math:`-b_o`, and this must be mapped to 0. The transform from
|
||||
framebuffer space to LRZ space is :math:`x' = x * s + o`, and the transform
|
||||
from framebuffer space to rendering space is :math:`x'' = x * s + o + o'`.
|
||||
We get that :math:`o + o' = b_{cs} - b_s * s`, similar to before, and
|
||||
:math:`0 = -b_o * s + o` so that :math:`o = b_o * s` and finally
|
||||
:math:`o' = b_{cs} - b_s * s - b_o * s`, or after rearranging
|
||||
:math:`o' = b_{cs} - (b_s + b_o) * s`. For all tiles except those in the first
|
||||
row or column, this simplifies to :math:`o' = b_{cs} - b_{cs} * s` because
|
||||
:math:`b_{cs} = b_s + b_o`. For tiles in the first row or column, :math:`b_s`
|
||||
and :math:`b_{cs}` are both 0 in one of the coordinates, so it becomes
|
||||
:math:`o' = -b_o * s` in that coordinate. This isn't representable in hardware,
|
||||
both because it is negative (which can be worked around by artifically
|
||||
shifting :math:`b_{cs}`) but more importantly because it may not meet the
|
||||
alignment requirements for the hardware register (which is currently 8 pixels).
|
||||
We have to just disable LRZ in this case.
|
||||
@@ -0,0 +1,153 @@
|
||||
Low Resolution Z Buffer
|
||||
=======================
|
||||
|
||||
This doc is based on a6xx HW reverse engineering, a5xx should be similar to
|
||||
a6xx before gen3.
|
||||
|
||||
Low Resolution Z buffer is very similar to a depth prepass that helps
|
||||
the HW to avoid executing the fragment shader on those fragments that will
|
||||
be subsequently discarded by the depth test afterwards.
|
||||
|
||||
The interesting part of this feature is that it allows applications
|
||||
to submit the vertices in any order.
|
||||
|
||||
Citing official Adreno documentation:
|
||||
|
||||
::
|
||||
|
||||
[A Low Resolution Z (LRZ)] pass is also referred to as draw order independent
|
||||
depth rejection. During the binning pass, a low resolution Z-buffer is constructed,
|
||||
and can reject LRZ-tile wide contributions to boost binning performance. This LRZ
|
||||
is then used during the rendering pass to reject pixels efficiently before testing
|
||||
against the full resolution Z-buffer.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
There are two main limitations of LRZ:
|
||||
|
||||
- Since LRZ is an early depth test, such test cannot be used when late-z is required;
|
||||
- LRZ buffer could be formed only in one direction, changing depth comparison directions
|
||||
without disabling LRZ would lead to a malformed LRZ buffer.
|
||||
|
||||
Pre-a650 (before gen3)
|
||||
----------------------
|
||||
|
||||
The direction is fully tracked on CPU. In render pass LRZ starts with
|
||||
unknown direction, the direction is set first time when depth write occurs
|
||||
and if it does change afterwards then the direction becomes invalid and LRZ is
|
||||
disabled for the rest of the render pass.
|
||||
|
||||
Since the direction is not tracked by the GPU, it's impossible to know whether
|
||||
LRZ is enabled during construction of secondary command buffers.
|
||||
|
||||
For the same reason, it's impossible to reuse LRZ between render passes.
|
||||
|
||||
A650+ (gen3+)
|
||||
-------------
|
||||
|
||||
Now LRZ direction can be tracked on GPU. There are two parts:
|
||||
|
||||
- Direction byte which stores current LRZ direction - ``GRAS_LRZ_CNTL.DIR``.
|
||||
- Parameters of the last used depth view - ``GRAS_LRZ_DEPTH_VIEW``.
|
||||
|
||||
The idea is the same as when LRZ tracked on CPU: when ``GRAS_LRZ_CNTL``
|
||||
is used, its direction is compared to the previously known direction
|
||||
and direction byte is set to disabled when directions are incompatible.
|
||||
|
||||
Additionally, to reuse LRZ between render passes, ``GRAS_LRZ_CNTL`` checks
|
||||
if the current value of ``GRAS_LRZ_DEPTH_VIEW`` is equal to the value
|
||||
stored in the buffer. If not, LRZ is disabled. This is necessary
|
||||
because depth buffer may have several layers and mip levels, while the
|
||||
LRZ buffer represents only a single layer + mip level.
|
||||
|
||||
A7XX
|
||||
-------------
|
||||
|
||||
A7XX introduces the concept of bidirectional LRZ where there are two LRZ
|
||||
buffers, one for each direction. This way LRZ doesn't need to be disabled
|
||||
when the direction changes, by default, this behavior is disabled but the
|
||||
LRZ buffers have to be allocated with this space in mind as fast clears
|
||||
will always write metadata for both.
|
||||
|
||||
Additionally, there are now two seperate LRZ buffers (on top of one for
|
||||
each direction, a total of four) - due to concurrent binning, one can be
|
||||
used for binning and the other for rendering concurrently. These can be
|
||||
flipped between via the `LRZ_FLIP_BUFFER` event which can be put inside
|
||||
a conditional block for either the BV or BR.
|
||||
|
||||
LRZ Fast-Clear
|
||||
--------------
|
||||
|
||||
The LRZ fast-clear buffer is initialized to zeroes and read/written
|
||||
when ``GRAS_LRZ_CNTL.FC_ENABLE`` is set. It appears to store 1b/block.
|
||||
``0`` means block has original depth clear value, and ``1`` means that the
|
||||
corresponding block in LRZ has been modified.
|
||||
|
||||
LRZ fast-clear conservatively clears LRZ buffer. At the point where LRZ is
|
||||
written the LRZ block which corresponds to a single fast-clear bit is cleared:
|
||||
|
||||
- To ``0.0`` if depth comparison is ``GREATER``
|
||||
- To ``1.0`` if depth comparison is ``LESS``
|
||||
|
||||
This way it's always valid to fast-clear.
|
||||
|
||||
On A7XX, the original depth clear value can be specified exactly allowing for
|
||||
fast-clear to any value rather than just ``1.0`` or ``0.0``.
|
||||
|
||||
LRZ Feedback
|
||||
-------------
|
||||
|
||||
Some draws do write depth but cannot contribute to LRZ during the BINNING pass
|
||||
e.g. when fragment shader has "discard" in it, however they can contribute to LRZ
|
||||
during the RENDERING pass via LRZ feedback mechanism. This may allow the draws
|
||||
that follow to depth test against the updated LRZ, this is especially important
|
||||
if such "bad" draws were at the start of the renderpass.
|
||||
|
||||
LRZ feedback happens during the RENDERING pass when ``LRZ_FEEDBACK_ZMODE_MASK``
|
||||
is set, if draw has a6xx_ztest_mode that has corresponding flag set in
|
||||
``LRZ_FEEDBACK_ZMODE_MASK`` - its depth values would be used for feedback.
|
||||
|
||||
LRZ feedback alongside with LRZ testing also works during sysmem rendering.
|
||||
|
||||
LRZ Precision
|
||||
-------------
|
||||
|
||||
LRZ always uses ``Z16_UNORM``. The epsilon for it is ``1.f / (1 << 16)`` which is
|
||||
not enough to represent all values of ``Z32_UNORM`` or ``Z32_FLOAT``.
|
||||
This especially raises questions in context of fast-clear, if fast-clear
|
||||
uses a value which cannot be precisely represented by LRZ - we wouldn't
|
||||
be able to round it in the correct direction since direction is tracked
|
||||
on GPU.
|
||||
|
||||
However, it seems that depth comparisons with LRZ values have some "slack"
|
||||
and nothing special should be done for such depth clear values.
|
||||
|
||||
How it was tested:
|
||||
|
||||
- Clear ``Z32_FLOAT`` attachment to ``1.f / (1 << 17)``
|
||||
|
||||
- LRZ buffer contains all zeroes.
|
||||
|
||||
- Do draws and check whether all samples are passing:
|
||||
|
||||
- ``OP_GREATER`` with ``(1.f / (1 << 17) + float32_epsilon)`` - passing;
|
||||
- ``OP_GREATER`` with ``(1.f / (1 << 17) - float32_epsilon)`` - not passing;
|
||||
- ``OP_LESS`` with ``(1.f / (1 << 17) - float32_epsilon)`` - samples;
|
||||
- ``OP_LESS`` with ``(1.f / (1 << 17) + float32_epsilon)``- not passing;
|
||||
- ``OP_LESS_OR_EQ`` with ``(1.f / (1 << 17) + float32_epsilon)`` - not passing.
|
||||
|
||||
In all cases resulting LRZ buffer is all zeroes and LRZ direction is updated.
|
||||
|
||||
LRZ Caches
|
||||
----------
|
||||
|
||||
``LRZ_FLUSH`` flushes and invalidates LRZ caches, there are two caches:
|
||||
|
||||
- Cache for fast-clear buffer;
|
||||
- Cache for direction byte + depth view params.
|
||||
|
||||
They could be cleared by ``LRZ_CLEAR``. To become visible in GPU memory
|
||||
the caches should be flushed with ``LRZ_FLUSH`` afterwards.
|
||||
|
||||
``GRAS_LRZ_CNTL`` reads from these caches.
|
||||
@@ -0,0 +1,403 @@
|
||||
IR3 NOTES
|
||||
=========
|
||||
|
||||
Some notes about ir3, the compiler and machine-specific IR for the shader ISA introduced with Adreno 3xx. The same shader ISA is present, with some small differences, in Adreno 4xx.
|
||||
|
||||
Compared to the previous generation a2xx ISA (ir2), the a3xx ISA is a "simple" scalar instruction set. However, the compiler is responsible, in most cases, to schedule the instructions. The hardware does not try to hide the shader core pipeline stages. For a common example, a common (cat2) ALU instruction takes four cycles, so a subsequent cat2 instruction which uses the result must have three intervening instructions (or NOPs). When operating on vec4's, typically the corresponding scalar instructions for operating on the remaining three components could typically fit. Although that results in a lot of edge cases where things fall over, like:
|
||||
|
||||
::
|
||||
|
||||
ADD TEMP[0], TEMP[1], TEMP[2]
|
||||
MUL TEMP[0], TEMP[1], TEMP[0].wzyx
|
||||
|
||||
Here, the second instruction needs the output of the first group of scalar instructions in the wrong order, resulting in not enough instruction spots between the ``add r0.w, r1.w, r2.w`` and ``mul r0.x, r1.x, r0.w``. Which is why the original (old) compiler which merely translated nearly literally from TGSI to ir3, had a strong tendency to fall over.
|
||||
|
||||
So the current compiler instead, in the frontend, generates a directed-acyclic-graph of instructions and basic blocks, which go through various additional passes to eventually schedule and do register assignment.
|
||||
|
||||
For additional documentation about the hardware, see wiki: `a3xx ISA
|
||||
<https://github.com/freedreno/freedreno/wiki/A3xx-shader-instruction-set-architecture>`__.
|
||||
|
||||
External Structure
|
||||
------------------
|
||||
|
||||
``ir3_shader``
|
||||
A single vertex/fragment/etc shader from gallium perspective (i.e.
|
||||
maps to a single TGSI shader), and manages a set of shader variants
|
||||
which are generated on demand based on the shader key.
|
||||
|
||||
``ir3_shader_key``
|
||||
The configuration key that identifies a shader variant. I.e. based
|
||||
on other GL state (two-sided-color, render-to-alpha, etc) or render
|
||||
stages (binning-pass vertex shader) different shader variants are
|
||||
generated.
|
||||
|
||||
``ir3_shader_variant``
|
||||
The actual HW shader generated based on input TGSI and shader key.
|
||||
|
||||
``ir3_compiler``
|
||||
Compiler frontend which generates ir3 and runs the various backend
|
||||
stages to schedule and do register assignment.
|
||||
|
||||
The IR
|
||||
------
|
||||
|
||||
The ir3 IR maps quite directly to the hardware, in that instruction opcodes map directly to hardware opcodes, and that dst/src register(s) map directly to the hardware dst/src register(s). But there are a few extensions, in the form of meta_ instructions. And additionally, for normal (non-const, etc) src registers, the ``IR3_REG_SSA`` flag is set and ``reg->instr`` points to the source instruction which produced that value. So, for example, the following TGSI shader:
|
||||
|
||||
::
|
||||
|
||||
VERT
|
||||
DCL IN[0]
|
||||
DCL IN[1]
|
||||
DCL OUT[0], POSITION
|
||||
DCL TEMP[0], LOCAL
|
||||
1: DP3 TEMP[0].x, IN[0].xyzz, IN[1].xyzz
|
||||
2: MOV OUT[0], TEMP[0].xxxx
|
||||
3: END
|
||||
|
||||
eventually generates:
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph G {
|
||||
rankdir=RL;
|
||||
nodesep=0.25;
|
||||
ranksep=1.5;
|
||||
subgraph clusterdce198 {
|
||||
label="vert";
|
||||
inputdce198 [shape=record,label="inputs|<in0> i0.x|<in1> i0.y|<in2> i0.z|<in4> i1.x|<in5> i1.y|<in6> i1.z"];
|
||||
instrdcf348 [shape=record,style=filled,fillcolor=lightgrey,label="{mov.f32f32|<dst0>|<src0> }"];
|
||||
instrdcedd0 [shape=record,style=filled,fillcolor=lightgrey,label="{mad.f32|<dst0>|<src0> |<src1> |<src2> }"];
|
||||
inputdce198:<in2>:w -> instrdcedd0:<src0>
|
||||
inputdce198:<in6>:w -> instrdcedd0:<src1>
|
||||
instrdcec30 [shape=record,style=filled,fillcolor=lightgrey,label="{mad.f32|<dst0>|<src0> |<src1> |<src2> }"];
|
||||
inputdce198:<in1>:w -> instrdcec30:<src0>
|
||||
inputdce198:<in5>:w -> instrdcec30:<src1>
|
||||
instrdceb60 [shape=record,style=filled,fillcolor=lightgrey,label="{mul.f|<dst0>|<src0> |<src1> }"];
|
||||
inputdce198:<in0>:w -> instrdceb60:<src0>
|
||||
inputdce198:<in4>:w -> instrdceb60:<src1>
|
||||
instrdceb60:<dst0> -> instrdcec30:<src2>
|
||||
instrdcec30:<dst0> -> instrdcedd0:<src2>
|
||||
instrdcedd0:<dst0> -> instrdcf348:<src0>
|
||||
instrdcf400 [shape=record,style=filled,fillcolor=lightgrey,label="{mov.f32f32|<dst0>|<src0> }"];
|
||||
instrdcedd0:<dst0> -> instrdcf400:<src0>
|
||||
instrdcf4b8 [shape=record,style=filled,fillcolor=lightgrey,label="{mov.f32f32|<dst0>|<src0> }"];
|
||||
instrdcedd0:<dst0> -> instrdcf4b8:<src0>
|
||||
outputdce198 [shape=record,label="outputs|<out0> o0.x|<out1> o0.y|<out2> o0.z|<out3> o0.w"];
|
||||
instrdcf348:<dst0> -> outputdce198:<out0>:e
|
||||
instrdcf400:<dst0> -> outputdce198:<out1>:e
|
||||
instrdcf4b8:<dst0> -> outputdce198:<out2>:e
|
||||
instrdcedd0:<dst0> -> outputdce198:<out3>:e
|
||||
}
|
||||
}
|
||||
|
||||
(after scheduling, etc, but before register assignment).
|
||||
|
||||
Internal Structure
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``ir3_block``
|
||||
Represents a basic block.
|
||||
|
||||
TODO: currently blocks are nested, but I think I need to change that
|
||||
to a more conventional arrangement before implementing proper flow
|
||||
control. Currently the only flow control handles is if/else which
|
||||
gets flattened out and results chosen with ``sel`` instructions.
|
||||
|
||||
``ir3_instruction``
|
||||
Represents a machine instruction or meta_ instruction. Has pointers
|
||||
to dst register (``regs[0]``) and src register(s) (``regs[1..n]``),
|
||||
as needed.
|
||||
|
||||
``ir3_register``
|
||||
Represents a src or dst register, flags indicate const/relative/etc.
|
||||
If ``IR3_REG_SSA`` is set on a src register, the actual register
|
||||
number (name) has not been assigned yet, and instead the ``instr``
|
||||
field points to src instruction.
|
||||
|
||||
In addition there are various util macros/functions to simplify manipulation/traversal of the graph:
|
||||
|
||||
``foreach_src(srcreg, instr)``
|
||||
Iterate each instruction's source ``ir3_register``\s
|
||||
|
||||
``foreach_src_n(srcreg, n, instr)``
|
||||
Like ``foreach_src``, also setting ``n`` to the source number (starting
|
||||
with ``0``).
|
||||
|
||||
``foreach_ssa_src(srcinstr, instr)``
|
||||
Iterate each instruction's SSA source ``ir3_instruction``\s. This skips
|
||||
non-SSA sources (consts, etc), but includes virtual sources (such as the
|
||||
address register if `relative addressing`_ is used).
|
||||
|
||||
``foreach_ssa_src_n(srcinstr, n, instr)``
|
||||
Like ``foreach_ssa_src``, also setting ``n`` to the source number.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
foreach_ssa_src_n(src, i, instr) {
|
||||
unsigned d = delay_calc_srcn(ctx, src, instr, i);
|
||||
delay = MAX2(delay, d);
|
||||
}
|
||||
|
||||
|
||||
TODO probably other helper/util stuff worth mentioning here
|
||||
|
||||
.. _meta:
|
||||
|
||||
Meta Instructions
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
**input**
|
||||
Used for shader inputs (registers configured in the command-stream
|
||||
to hold particular input values, written by the shader core before
|
||||
start of execution. Also used for connecting up values within a
|
||||
basic block to an output of a previous block.
|
||||
|
||||
**output**
|
||||
Used to hold outputs of a basic block.
|
||||
|
||||
**flow**
|
||||
TODO
|
||||
|
||||
**phi**
|
||||
TODO
|
||||
|
||||
**collect**
|
||||
Groups registers which need to be assigned to consecutive scalar
|
||||
registers, for example ``sam`` (texture fetch) src instructions (see
|
||||
`register groups`_) or array element dereference
|
||||
(see `relative addressing`_).
|
||||
|
||||
**split**
|
||||
The counterpart to **collect**, when an instruction such as ``sam``
|
||||
writes multiple components, splits the result into individual
|
||||
scalar components to be consumed by other instructions.
|
||||
|
||||
|
||||
.. _`flow control`:
|
||||
|
||||
Flow Control
|
||||
~~~~~~~~~~~~
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
.. _`register groups`:
|
||||
|
||||
Register Groups
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Certain instructions, such as texture sample instructions, consume multiple consecutive scalar registers via a single src register encoded in the instruction, and/or write multiple consecutive scalar registers. In the simplest example:
|
||||
|
||||
::
|
||||
|
||||
sam (f32)(xyz)r2.x, r0.z, s#0, t#0
|
||||
|
||||
for a 2d texture, would read ``r0.zw`` to get the coordinate, and write ``r2.xyz``.
|
||||
|
||||
Before register assignment, to group the two components of the texture src together:
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph G {
|
||||
{ rank=same;
|
||||
collect;
|
||||
};
|
||||
{ rank=same;
|
||||
coord_x;
|
||||
coord_y;
|
||||
};
|
||||
sam -> collect [label="regs[1]"];
|
||||
collect -> coord_x [label="regs[1]"];
|
||||
collect -> coord_y [label="regs[2]"];
|
||||
coord_x -> coord_y [label="right",style=dotted];
|
||||
coord_y -> coord_x [label="left",style=dotted];
|
||||
coord_x [label="coord.x"];
|
||||
coord_y [label="coord.y"];
|
||||
}
|
||||
|
||||
The frontend sets up the SSA ptrs from ``sam`` source register to the ``collect`` meta instruction, which in turn points to the instructions producing the ``coord.x`` and ``coord.y`` values. And the grouping_ pass sets up the ``left`` and ``right`` neighbor pointers to the ``collect``\'s sources, used later by the `register assignment`_ pass to assign blocks of scalar registers.
|
||||
|
||||
And likewise, for the consecutive scalar registers for the destination:
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
{ rank=same;
|
||||
A;
|
||||
B;
|
||||
C;
|
||||
};
|
||||
{ rank=same;
|
||||
split_0;
|
||||
split_1;
|
||||
split_2;
|
||||
};
|
||||
A -> split_0;
|
||||
B -> split_1;
|
||||
C -> split_2;
|
||||
split_0 [label="split\noff=0"];
|
||||
split_0 -> sam;
|
||||
split_1 [label="split\noff=1"];
|
||||
split_1 -> sam;
|
||||
split_2 [label="split\noff=2"];
|
||||
split_2 -> sam;
|
||||
split_0 -> split_1 [label="right",style=dotted];
|
||||
split_1 -> split_0 [label="left",style=dotted];
|
||||
split_1 -> split_2 [label="right",style=dotted];
|
||||
split_2 -> split_1 [label="left",style=dotted];
|
||||
sam;
|
||||
}
|
||||
|
||||
.. _`relative addressing`:
|
||||
|
||||
Relative Addressing
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Most instructions support addressing indirectly (relative to address register) into const or gpr register file in some or all of their src/dst registers. In this case the register accessed is taken from ``r<a0.x + n>`` or ``c<a0.x + n>``, i.e. address register (``a0.x``) value plus ``n``, where ``n`` is encoded in the instruction (rather than the absolute register number).
|
||||
|
||||
Note that cat5 (texture sample) instructions are the notable exception, not
|
||||
supporting relative addressing of src or dst.
|
||||
|
||||
Relative addressing of the const file (for example, a uniform array) is relatively simple. We don't do register assignment of the const file, so all that is required is to schedule things properly. I.e. the instruction that writes the address register must be scheduled first, and we cannot have two different address register values live at one time.
|
||||
|
||||
But relative addressing of gpr file (which can be as src or dst) has additional restrictions on register assignment (i.e. the array elements must be assigned to consecutive scalar registers). And in the case of relative dst, subsequent instructions now depend on both the relative write, as well as the previous instruction which wrote that register, since we do not know at compile time which actual register was written.
|
||||
|
||||
Each instruction has an optional ``address`` pointer, to capture the dependency on the address register value when relative addressing is used for any of the src/dst register(s). This behaves as an additional virtual src register, i.e. ``foreach_ssa_src()`` will also iterate the address register (last).
|
||||
|
||||
Note that ``nop``\'s for timing constraints, type specifiers (i.e.
|
||||
``add.f`` vs ``add.u``), etc, omitted for brevity in examples
|
||||
|
||||
::
|
||||
|
||||
mova a0.x, hr1.y
|
||||
sub r1.y, r2.x, r3.x
|
||||
add r0.x, r1.y, c<a0.x + 2>
|
||||
|
||||
results in:
|
||||
|
||||
.. graphviz::
|
||||
|
||||
digraph {
|
||||
rankdir=LR;
|
||||
sub;
|
||||
const [label="const file"];
|
||||
add;
|
||||
mova;
|
||||
add -> mova;
|
||||
add -> sub;
|
||||
add -> const [label="off=2"];
|
||||
}
|
||||
|
||||
The scheduling pass has some smarts to schedule things such that only a single ``a0.x`` value is used at any one time.
|
||||
|
||||
To implement variable arrays, the NIR registers are stored as an ``ir3_array``,
|
||||
which will be register allocated to consecutive hardware registers. The array
|
||||
access uses the id field in the ``ir3_register`` to map to the array being
|
||||
accessed, and the offset field for the fixed offset within the array. A NIR
|
||||
indirect register read such as:
|
||||
|
||||
::
|
||||
|
||||
decl_reg vec2 32 r0[2]
|
||||
...
|
||||
vec2 32 ssa_19 = mov r0[0 + ssa_9]
|
||||
|
||||
|
||||
results in:
|
||||
|
||||
::
|
||||
|
||||
0000:0000:001: shl.b hssa_19, hssa_17, himm[0.000000,1,0x1]
|
||||
0000:0000:002: mov.s16s16 hr61.x, hssa_19
|
||||
0000:0000:002: mov.u32u32 ssa_21, arr[id=1, offset=0, size=4, ssa_12], address=_[0000:0000:002: mov.s16s16]
|
||||
0000:0000:002: mov.u32u32 ssa_22, arr[id=1, offset=1, size=4, ssa_12], address=_[0000:0000:002: mov.s16s16]
|
||||
|
||||
|
||||
Array writes write to the array in ``instr->regs[0]->array.id``. A NIR indirect
|
||||
register write such as:
|
||||
|
||||
::
|
||||
|
||||
decl_reg vec2 32 r0[2]
|
||||
...
|
||||
r0[0 + ssa_12] = mov ssa_13
|
||||
|
||||
results in:
|
||||
|
||||
::
|
||||
|
||||
0000:0000:001: shl.b hssa_29, hssa_27, himm[0.000000,1,0x1]
|
||||
0000:0000:002: mov.s16s16 hr61.x, hssa_29
|
||||
0000:0000:001: mov.u32u32 arr[id=1, offset=0, size=4, ssa_17], c2.y, address=_[0000:0000:002: mov.s16s16]
|
||||
0000:0000:004: mov.u32u32 arr[id=1, offset=1, size=4, ssa_31], c2.z, address=_[0000:0000:002: mov.s16s16]
|
||||
|
||||
Note that only cat1 (mov) can do indirect write, and thus NIR register stores
|
||||
may need to introduce an extra mov.
|
||||
|
||||
ir3 array accesses in the DAG get serialized by the ``instr->barrier_class`` and
|
||||
containing ``IR3_BARRIER_ARRAY_W`` or ``IR3_BARRIER_ARRAY_R``.
|
||||
|
||||
Shader Passes
|
||||
-------------
|
||||
|
||||
After the frontend has generated the use-def graph of instructions, they are run through various passes which include scheduling_ and `register assignment`_. Because inserting ``mov`` instructions after scheduling would also require inserting additional ``nop`` instructions (since it is too late to reschedule to try and fill the bubbles), the earlier stages try to ensure that (at least given an infinite supply of registers) that `register assignment`_ after scheduling_ cannot fail.
|
||||
|
||||
Note that we essentially have ~256 scalar registers in the
|
||||
architecture (although larger register usage will at some thresholds
|
||||
limit the number of threads which can run in parallel). And at some
|
||||
point we will have to deal with spilling.
|
||||
|
||||
.. _flatten:
|
||||
|
||||
Flatten
|
||||
~~~~~~~
|
||||
|
||||
In this stage, simple if/else blocks are flattened into a single block with ``phi`` nodes converted into ``sel`` instructions. The a3xx ISA has very few predicated instructions, and we would prefer not to use branches for simple if/else.
|
||||
|
||||
|
||||
.. _`copy propagation`:
|
||||
|
||||
Copy Propagation
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Currently the frontend inserts ``mov``\s in various cases, because certain categories of instructions have limitations about const regs as sources. And the CP pass simply removes all simple ``mov``\s (i.e. src-type is same as dst-type, no abs/neg flags, etc).
|
||||
|
||||
The eventual plan is to invert that, with the front-end inserting no ``mov``\s and CP legalize things.
|
||||
|
||||
|
||||
.. _grouping:
|
||||
|
||||
Grouping
|
||||
~~~~~~~~
|
||||
|
||||
In the grouping pass, instructions which need to be grouped (for ``collect``\s, etc) have their ``left`` / ``right`` neighbor pointers setup. In cases where there is a conflict (i.e. one instruction cannot have two unique left or right neighbors), an additional ``mov`` instruction is inserted. This ensures that there is some possible valid `register assignment`_ at the later stages.
|
||||
|
||||
|
||||
.. _depth:
|
||||
|
||||
Depth
|
||||
~~~~~
|
||||
|
||||
In the depth pass, a depth is calculated for each instruction node within its basic block. The depth is the sum of the required cycles (delay slots needed between two instructions plus one) of each instruction plus the max depth of any of its source instructions. (meta_ instructions don't add to the depth). As an instruction's depth is calculated, it is inserted into a per block list sorted by deepest instruction. Unreachable instructions and inputs are marked.
|
||||
|
||||
TODO: we should probably calculate both hard and soft depths (?) to
|
||||
try to coax additional instructions to fit in places where we need
|
||||
to use sync bits, such as after a texture fetch or SFU.
|
||||
|
||||
.. _scheduling:
|
||||
|
||||
Scheduling
|
||||
~~~~~~~~~~
|
||||
|
||||
After the grouping_ pass, there are no more instructions to insert or remove. Start scheduling each basic block from the deepest node in the depth sorted list created by the depth_ pass, recursively trying to schedule each instruction after its source instructions plus delay slots. Insert ``nop``\s as required.
|
||||
|
||||
.. _`register assignment`:
|
||||
|
||||
Register Assignment
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
@@ -0,0 +1,99 @@
|
||||
KosmicKrisp
|
||||
###########
|
||||
|
||||
KosmicKrisp is a Vulkan conformant implementation for macOS on Apple Silicon
|
||||
hardware. It is implemented on top of Metal 4, which requires macOS 26 and up.
|
||||
|
||||
No iOS support is present as of now. However, iOS was taken into consideration
|
||||
during development to support A14 Bionic GPUs and upwards.
|
||||
|
||||
Building
|
||||
********
|
||||
|
||||
The following build instructions assume Homebrew as the package manager to
|
||||
install dependencies. Homebrew homepage https://brew.sh/
|
||||
Homebrew install command line:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
Terminal restart is recommended after Homebrew installation.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
- Xcode and Xcode command line tools
|
||||
- Homebrew packages
|
||||
- meson (1.9.1+, can also be installed as a Python package)
|
||||
- cmake
|
||||
- pkg-config
|
||||
- libclc
|
||||
- llvm (20.1.8+, lower versions may work but have not been tested)
|
||||
- spirv-llvm-translator
|
||||
- spirv-tools (static library preferred which is not included in the package, Vulkan macOS SDK does)
|
||||
|
||||
Users might already have llvm installed, run ``llvm-config --version`` and
|
||||
check that the command works and prints at least ``20.1.8``. Otherwise install
|
||||
llvm from homebrew and add it to the ``PATH`` (follow homebrew instructions).
|
||||
|
||||
- Python
|
||||
- Python packages
|
||||
- mako
|
||||
- packaging
|
||||
- pyyaml
|
||||
- meson (1.9.1+, if not installed through Homebrew)
|
||||
|
||||
Since Homebrew manages the Python environment, it is encouraged to create a
|
||||
Python virtual environment and install all packages in that environment. To
|
||||
create a Python virtual environment (e.g. ``$HOME/venv_mesa``):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
python3 -m venv $HOME/venv_mesa
|
||||
|
||||
To enable a Python virtual environment:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
source $HOME/venv_mesa/bin/activate
|
||||
|
||||
Build instructions
|
||||
==================
|
||||
|
||||
Out of tree build directory is recommended.
|
||||
|
||||
Once all requirements have been installed, the following command line can be
|
||||
used to create a debug build:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
meson setup <path/to/mesa> --buildtype=debug -Dplatforms=macos -Dvulkan-drivers=kosmickrisp -Dgallium-drivers= -Dopengl=false -Dzstd=disabled --prefer-static
|
||||
|
||||
Environment variables
|
||||
*********************
|
||||
|
||||
KosmicKrisp specific environment variables:
|
||||
|
||||
- ``MESA_KK_DEBUG``: ``msl`` to log all generated Metal Shading Language (MSL) shaders. ``force_robustness`` to force robustness on all shaders.
|
||||
- ``MESA_KK_GPU_CAPTURE``: Starts Metal capture at device create and ends it at device destroy. Set to ``1`` to activate.
|
||||
- ``MESA_KK_GPU_CAPTURE_DIRECTORY``: Metal capture will be saved to the specified directory. Defaults to Xcode if no path is provided.
|
||||
- ``MESA_KK_DISABLE_WORKAROUNDS``: Provide ``all`` to disable all workarounds. Otherwise, provide a comma separated list to disable wanted workarounds e.g. ``1,3,4`` to disable workaround 1, 3 and 4.
|
||||
|
||||
Metal workarounds
|
||||
*****************
|
||||
|
||||
Different workarounds are applied throughout the project to avoid issues such
|
||||
as:
|
||||
|
||||
- Metal API and Vulkan API discrepancies
|
||||
- Metal bugs
|
||||
- MSL compiler bugs
|
||||
- MSL compiler crashes
|
||||
|
||||
These workarounds can be found in:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
kosmickrisp/workarounds
|
||||
@@ -0,0 +1,333 @@
|
||||
KosmicKrisp workarounds
|
||||
#######################
|
||||
|
||||
This file documents the relevant issues found in either Metal, the MSL
|
||||
compiler or any other component we have no control over that needed to be
|
||||
worked around to accomplish Vulkan conformance.
|
||||
|
||||
All workarounds must be documented here and no code comment info should be
|
||||
provided other than the name ``KK_WORKAROUND_#``.
|
||||
|
||||
Once a workaround was removed from the code, the code comment will be
|
||||
removed but the documentation here will be kept.
|
||||
|
||||
Template
|
||||
========
|
||||
|
||||
Use the following template to create documentation for a new workaround:
|
||||
|
||||
.. code-block::
|
||||
|
||||
KK_WORKAROUND_#
|
||||
---------------
|
||||
| macOS version:
|
||||
| Metal ticket:
|
||||
| Metal ticket status:
|
||||
| CTS test failure/crash:
|
||||
| Comments:
|
||||
| Log:
|
||||
|
||||
``macOS version`` needs to have the OS version with which it was found.
|
||||
|
||||
``Metal ticket`` needs to be either the Metal ticket number with the GitLab
|
||||
handle of the user that reported the ticket or ``Unreported``.
|
||||
|
||||
``Metal ticket status`` needs to be either ``Fixed in macOS # (Build hash)``,
|
||||
|
||||
``Waiting resolution`` or empty if unreported. If Apple reported that the issue
|
||||
was fixed, but no user has verified the fix, append ``[Untested]``.
|
||||
``CTS test failure/crash`` (remove ``failure`` or ``crash`` based on test
|
||||
behavior) needs to be the name of the test or test family the issue can be
|
||||
reproduced with.
|
||||
|
||||
``Comments`` needs to include as much information on the issue and how the
|
||||
workaround fixes it.
|
||||
|
||||
``Log`` needs to have the dates (yyyy-mm-dd, the only correct date format) with
|
||||
info on what was updated.
|
||||
|
||||
Workarounds
|
||||
===========
|
||||
|
||||
KK_WORKAROUND_10
|
||||
----------------
|
||||
| macOS version: 26.4.1
|
||||
| Metal ticket: Not reported
|
||||
| Metal ticket status:
|
||||
| CTS test failure: ``dEQP-VK.subgroups.arithmetic.compute.subgroupinclusive*_vec4``
|
||||
| Comments:
|
||||
|
||||
See comment
|
||||
https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41186#note_3470793
|
||||
|
||||
In short, certain ``ior`` operations can be and will be turned into ``bcsel``
|
||||
before reaching NIR to MSL. However, the MSL compiler seems to incorrectly
|
||||
handle ``bcsel`` and the compiled shader misbehaves while the ``ior`` version
|
||||
does not. This is worked around by adding a known true value to the conditional
|
||||
of the ``bcsel``.
|
||||
|
||||
| Log:
|
||||
| 2026-05-14: Workaround implemented
|
||||
|
||||
KK_WORKAROUND_9
|
||||
---------------
|
||||
| macOS version: 26.4.1
|
||||
| Metal ticket: Not reported
|
||||
| Metal ticket status:
|
||||
| CTS test failure: ``dEQP-VK.reconvergence.maximal.compute.nesting*``
|
||||
| Comments:
|
||||
|
||||
Metal seems to re-order the sole break case of a loop such that execution
|
||||
reconverges earlier than expected.
|
||||
|
||||
From the above mentioned CTS test, consider the following, which is the
|
||||
only code path that breaks within a loop:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
if (subgroupElect()) {
|
||||
outputC.loc[gl_LocalInvocationIndex]++;
|
||||
outputB.b[(outLoc++)*invocationStride + gl_LocalInvocationIndex] =
|
||||
subgroupBallot(true);
|
||||
break;
|
||||
}
|
||||
|
||||
The test expects the ``subgroupBallot`` to yield just one bit set for the
|
||||
thread picked by ``subgroupElect``; however, Metal returns the full 0xFFFFFFFF,
|
||||
presumably because it re-ordered the operations to after the loop.
|
||||
|
||||
To work around this, we add a trivial, always-true runtime condition to the
|
||||
break to ensure that the prior logic is not re-ordered.
|
||||
|
||||
KK_WORKAROUND_8
|
||||
---------------
|
||||
| macOS version: 26.4.1
|
||||
| Metal ticket: FB22579201 (@squidbus)
|
||||
| Metal ticket status: Waiting resolution
|
||||
| CTS test failure: N/A
|
||||
| Comments:
|
||||
|
||||
Metal GPU capture uses ``currentAllocatedSize`` to create an internal buffer
|
||||
over ``MTLHeap`` for the purpose of capturing its contents.
|
||||
|
||||
Suppose we have a heap whose size is under the memory page size. Under native
|
||||
ARM execution, both the heap ``size`` and ``currentAllocatedSize`` will be
|
||||
aligned up to 16K. However, it has been observed that under Rosetta 2, ``size``
|
||||
will be aligned up to 4K but ``currentAllocatedSize`` will still be aligned up
|
||||
to 16K.
|
||||
|
||||
These two in combination mean that, when GPU capture attempts to create buffers
|
||||
for these small heaps, it will fail, as ``currentAllocatedSize`` is larger than
|
||||
the heap ``size``. This will cause Metal validation layer errors if they are
|
||||
enabled, and attempting to take a GPU capture will crash the application.
|
||||
|
||||
This workaround ensures that under Rosetta 2, heap sizes will be aligned to a
|
||||
minimum of 16K, prevening this scenario from occurring.
|
||||
|
||||
| Log:
|
||||
| 2026-04-27: Workaround implemented
|
||||
|
||||
KK_WORKAROUND_7
|
||||
---------------
|
||||
| macOS version: 26.0.1
|
||||
| Metal ticket: Not reported
|
||||
| Metal ticket status:
|
||||
| CTS test failure: ``dEQP-VK.renderpasses.renderpass2.depth_stencil_resolve.image_2d_*testing_stencil_samplemask``
|
||||
| Comments:
|
||||
|
||||
Metal seems to ignore sample_mask out for cases for the stencil attachment
|
||||
where we have no color attachments, a multisample depth_stencil attachment
|
||||
with at least 2 samples and a fragment shader that only writes the depth and
|
||||
a static sample_mask out.
|
||||
|
||||
My conclusion is that they may try to prematurely optimize by doing early
|
||||
fragment testing disregarding completely the sample_mask out and applying
|
||||
the value to all stencil samples.
|
||||
|
||||
The failing tests do something along the lines of, start render pass with
|
||||
depth_stencil cleared to 0.0f and 0 respectively, if depth test passes set
|
||||
stencil to 1. Sample mask out is 1 (first sample). Draw framebuffer size square
|
||||
with 0.5f depth. End render pass storing values. Start render pass loading the
|
||||
previous output but if depth passes stencil will be set to 255. Sample mask out
|
||||
is 2 (second sample). Draw framebuffer size square with 0.5f depth.
|
||||
|
||||
In a similar fashion to 2 workarounds below this one, we do a conditional
|
||||
discard at the end discarding fragments not covered by the coverage_mask.
|
||||
|
||||
| Log:
|
||||
| 2026-02-05: Workaround implemented
|
||||
|
||||
KK_WORKAROUND_6
|
||||
---------------
|
||||
| macOS version: 26.0.1
|
||||
| Metal ticket: Not reported
|
||||
| Metal ticket status:
|
||||
| CTS test failure: ``dEQP-VK.spirv_assembly.instruction.*.float16.opcompositeinsert.*``
|
||||
| Comments:
|
||||
|
||||
Metal does not respect its own Memory Coherency Model (MSL spec 4.8). From
|
||||
the spec:
|
||||
``By default, memory in the device address space has threadgroup coherence.``
|
||||
|
||||
If we have a single thread compute dispatch so that we do (simplified version):
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
for (...) {
|
||||
value = ssbo_data[0]; // ssbo_data is a device buffer
|
||||
...
|
||||
ssbo_data[0] = new_value;
|
||||
}
|
||||
|
||||
``ssbo_data[0]`` will not correctly store/load the values so the value
|
||||
written in iteration 0, will not be available in iteration 1. The workaround
|
||||
to this issue is marking the device memory pointer through which the memory
|
||||
is accessed as coherent so that the value is stored and loaded correctly.
|
||||
Hopefully this does not affect performance much.
|
||||
|
||||
| Log:
|
||||
| 2025-12-08: Workaround implemented and reported to Apple
|
||||
|
||||
KK_WORKAROUND_5
|
||||
---------------
|
||||
| macOS version: 26.0.1
|
||||
| Metal ticket: Not reported
|
||||
| Metal ticket status:
|
||||
| CTS test failure: ``dEQP-VK.fragment_operations.early_fragment.discard_no_early_fragment_tests_depth``
|
||||
| Comments:
|
||||
|
||||
Fragment shaders that have side effects (like writing to a buffer) will be
|
||||
prematurely discarded if there is a ``discard_fragment`` that will always
|
||||
execute. To work around this, we just make the discard "optional" by moving
|
||||
it inside a run time conditional that will always be true (such as is the
|
||||
fragment a helper?). This tricks the MSL compiler into not optimizing it into
|
||||
a premature discard.
|
||||
|
||||
| Log:
|
||||
| 2025-12-01: Workaround implemented
|
||||
|
||||
KK_WORKAROUND_4
|
||||
---------------
|
||||
| macOS version: 26.0.1
|
||||
| Metal ticket: FB21124215 (@aitor)
|
||||
| Metal ticket status: Waiting resolution
|
||||
| CTS test failure: ``dEQP-VK.draw.renderpass.shader_invocation.helper_invocation*`` and few others
|
||||
| Comments:
|
||||
|
||||
``simd_is_helper_thread()`` will always return true if the shader was started
|
||||
as a non-helper thread, even after ``discard_fragment()`` is called. The
|
||||
workaround is to have a variable tracking this state and update it when the
|
||||
fragment is discarded. This issue is present in M1 and M2 chips.
|
||||
|
||||
| Log:
|
||||
| 2025-11-22: Workaround implemented and reported to Apple
|
||||
|
||||
KK_WORKAROUND_3
|
||||
---------------
|
||||
| macOS version: 15.4.x
|
||||
| Metal ticket: FB20113490 (@aitor)
|
||||
| Metal ticket status: Waiting resolution
|
||||
| CTS test failure: ``dEQP-VK.subgroups.ballot_other.*.subgroupballotfindlsb``, ``dEQP-VK.subgroups.arithmetic.graphics.*``, ``dEQP-VK.subgroups.shader_quad_control.divergent_condition``
|
||||
| Comments:
|
||||
|
||||
``simd_ballot`` within a conditional block does not seem to behave as
|
||||
documented in the MSL specification. For example, the following code blocks
|
||||
misbehave:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
bool execute = (gl_SubGroupInvocation & 1u) != 0u;
|
||||
if (execute)
|
||||
temp = simd_ballot(true); /* <- This may return all active threads... */
|
||||
else
|
||||
temp = 2u;
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
if (simd_is_first())
|
||||
temp = 3u;
|
||||
else
|
||||
temp = simd_ballot(true); /* <- This will return all active threads... */
|
||||
|
||||
This appears to also apply to ``quad_any`` and ``quad_all``, and likely the
|
||||
``simd`` equivalents as well.
|
||||
|
||||
The way to fix this is to use ``simd_or`` instead:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
bool execute = (gl_SubGroupInvocation & 1u) != 0u;
|
||||
if (execute)
|
||||
temp = simd_or(1 << gl_SubGroupInvocation);
|
||||
else
|
||||
temp = 2u;
|
||||
|
||||
Alternatively, the conditional can be changed to include ``simd_ballot(true)``:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
bool execute = (gl_SubGroupInvocation & 1u) != 0u;
|
||||
if (execute && (ulong)simd_ballot(true))
|
||||
temp = simd_ballot(true);
|
||||
else
|
||||
temp = 2u;
|
||||
|
||||
|
||||
| Log:
|
||||
| 2025-09-09: Workaround implemented and reported to Apple
|
||||
| 2026-04-28: Workaround updated to expand to all ballot/vote ops.
|
||||
|
||||
KK_WORKAROUND_2
|
||||
---------------
|
||||
| macOS version: 15.4.x
|
||||
| Metal ticket: FB21065475 (@aitor)
|
||||
| Metal ticket status: Waiting resolution
|
||||
| CTS test crash: ``dEQP-VK.graphicsfuzz.cov-nested-loops-never-change-array-element-one`` and ``dEQP-VK.graphicsfuzz.disc-and-add-in-func-in-loop``
|
||||
| Comments:
|
||||
|
||||
We need to loop to infinite since MSL compiler crashes if we have something
|
||||
like (simplified version):
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
while (true) {
|
||||
if (some_conditional) {
|
||||
break_loop = true;
|
||||
} else {
|
||||
break_loop = false;
|
||||
}
|
||||
if (break_loop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
The issue I believe is that ``some_conditional`` wouldn't change the value no
|
||||
matter in which iteration we are (something like fetching the same value from
|
||||
a buffer) and the MSL compiler doesn't seem to like that much to the point it
|
||||
crashes.
|
||||
|
||||
The implemented solution is to change the ``while(true)`` loop with
|
||||
``for (uint64_t no_crash = 0u; no_crash < UINT64_MAX; ++no_crash)``, which
|
||||
tricks the MSL compiler into believing we are not doing an infinite loop
|
||||
(wink wink).
|
||||
|
||||
| Log:
|
||||
| 2025-09-08: Workaround implemented
|
||||
|
||||
KK_WORKAROUND_1
|
||||
---------------
|
||||
| macOS version: 15.4.x
|
||||
| Metal ticket: FB17604106 (@aitor)
|
||||
| Metal ticket status: [Untested] Fixed in macOS 26 Beta (25A5279m)
|
||||
| CTS test crash: ``dEQP-VK.glsl.indexing.tmp_array.vec3_dynamic_loop_write_dynamic_loop_read_fragment``
|
||||
| Comments:
|
||||
|
||||
Uninitialized local scratch variable causes the MSL compiler to crash.
|
||||
Initialize scratch to avoid issue.
|
||||
|
||||
| Log:
|
||||
| 2025-05-14: Workaround implemented and reported to Apple
|
||||
| 2025-06-14: Apple reported back saying it is now fixed in macOS 26 Beta (Build 25A5279m)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
Lima
|
||||
====
|
||||
|
||||
Lima is an open source graphics driver which supports Mali Utgard
|
||||
(Mali-4xx) embedded GPUs from ARM. It’s a reverse-engineered,
|
||||
community-developed driver, and is not endorsed by ARM. Lima was
|
||||
upstreamed in Mesa 19.1 and Linux kernel 5.2.
|
||||
|
||||
======== ============ ===========
|
||||
Product Architecture Status
|
||||
======== ============ ===========
|
||||
Mali-400 Utgard Supported
|
||||
Mali-450 Utgard Supported
|
||||
Mali-470 Utgard Unsupported
|
||||
======== ============ ===========
|
||||
|
||||
Newer Mali chips based on the Midgard/Bifrost architectures (Mali T or G
|
||||
series) are handled by the :doc:`Panfrost <panfrost>` driver, not Lima.
|
||||
|
||||
Note that the Mali GPU is only for rendering: the GPU does not control a
|
||||
display and has little to do with display-related issues.
|
||||
Each SoC has its own separate display engine to control the display
|
||||
output. To display the contents rendered by the Mali GPU to a screen, a
|
||||
separate `display driver <#display-drivers>`__ is also required, which
|
||||
is able to share buffers with the GPU. In Mesa, this is handled by
|
||||
``kmsro``.
|
||||
|
||||
Supported APIs
|
||||
--------------
|
||||
|
||||
Lima mainly targets **OpenGL ES 2.0**, as well as **OpenGL 2.1**
|
||||
(desktop) to some extent.
|
||||
|
||||
The OpenGL (desktop) implementation is enabled by Mesa and Gallium,
|
||||
where it is possible to reuse the same implementation backend. That way,
|
||||
it is possible to support running a majority of Linux desktop
|
||||
applications designed for OpenGL. It is not possible to fully support
|
||||
OpenGL (desktop), though, due to hardware limitations. Some (but not
|
||||
all) features of OpenGL 2.1 that are not supported directly in hardware
|
||||
are enabled by internal shader transformations.
|
||||
Check the `known hardware limitations <#known-hardware-limitations>`__
|
||||
list for additional information.
|
||||
|
||||
**OpenGL ES 1.1** and **OpenGL 1.x** are also provided by Mesa and
|
||||
similarly supported to some extent in Lima.
|
||||
|
||||
Display drivers
|
||||
---------------
|
||||
|
||||
These are some display drivers that have been tested with Lima:
|
||||
|
||||
- Allwinner: ``sun4i-drm``
|
||||
- Amlogic: ``meson``
|
||||
- Ericsson MCDE: ``mcde``
|
||||
- Exynos: ``exynos``
|
||||
- Rockchip: ``rockchip``
|
||||
- Tiny DRM: ``tinydrm``
|
||||
- Xilinx ZynqMP: ``zynqmp-dpsub``
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
|
||||
These are some Lima-specific environment variables that may aid in
|
||||
debugging. None of this is required for normal use.
|
||||
|
||||
.. envvar:: LIMA_DEBUG
|
||||
|
||||
accepts the following comma-separated list of flags:
|
||||
|
||||
``bocache``
|
||||
print debug info for BO cache
|
||||
``diskcache``
|
||||
print debug info for shader disk cache
|
||||
``dump``
|
||||
dump GPU command stream to ``$PWD/lima.dump``
|
||||
``gp``
|
||||
print GP shader compiler result of each stage
|
||||
``noblit``
|
||||
use generic u_blitter instead of Lima-specific
|
||||
``nobocache``
|
||||
disable BO cache
|
||||
``nogrowheap``
|
||||
disable growable heap buffer
|
||||
``notiling``
|
||||
don’t use tiled buffers
|
||||
``pp``
|
||||
print PP shader compiler result of each stage
|
||||
``precompile``
|
||||
precompile shaders for shader-db
|
||||
``shaderdb``
|
||||
print shader information for shaderdb
|
||||
``singlejob``
|
||||
disable multi job optimization
|
||||
|
||||
|
||||
.. envvar:: LIMA_CTX_NUM_PLB
|
||||
|
||||
set number of PLB per context (used for development purposes)
|
||||
|
||||
.. envvar:: LIMA_PLB_MAX_BLK
|
||||
|
||||
set PLB max block (used for development purposes)
|
||||
|
||||
.. envvar:: LIMA_PPIR_FORCE_SPILLING
|
||||
|
||||
force spilling of variables in PPIR (used for development purposes)
|
||||
|
||||
.. envvar:: LIMA_PLB_PP_STREAM_CACHE_SIZE
|
||||
|
||||
set PP stream cache size (used for development purposes)
|
||||
|
||||
Known hardware limitations
|
||||
--------------------------
|
||||
|
||||
Here are some known caveats in OpenGL support:
|
||||
|
||||
- ``glPolygonMode()`` with ``GL_LINE`` is not supported. This is not part of
|
||||
OpenGL ES 2.0 and so it is not possible to reverse engineer.
|
||||
|
||||
- Precision limitations in fragment shaders:
|
||||
|
||||
- In general, only
|
||||
`FP16 <https://en.wikipedia.org/wiki/Half-precision_floating-point_format>`__
|
||||
precision is supported in fragment shaders. Specifying ``highp``
|
||||
will have no effect.
|
||||
- Integers are not supported in hardware, they are lowered down to
|
||||
FP16.
|
||||
- There is a higher precision (FP24) path for texture lookups, if
|
||||
there is *no* math performed on texture coordinates obtained from
|
||||
varyings. If there is *any* calculation done in the texture
|
||||
coordinates, the texture coordinates will fall back to FP16 and
|
||||
that may affect the quality of the texture lookup.
|
||||
|
||||
- Lima supports FP16 textures in OpenGL ES (through
|
||||
:ext:`GL_OES_texture_half_float<GL_OES_texture_float>`), but not in OpenGL.
|
||||
This is because it would require :ext:`GL_ARB_texture_float` which would
|
||||
also require 32-bit float textures, that the Mali-4xx does not support.
|
||||
- Rendering to FP16 is possible, but the result is clamped to the
|
||||
[0.0,1.0] range.
|
||||
|
||||
Bug Reporting
|
||||
-------------
|
||||
|
||||
Please try the latest Mesa development branch or at least Mesa latest
|
||||
release before reporting issues. Please review the
|
||||
:doc:`Mesa bug report guidelines <../bugs>`.
|
||||
|
||||
Issues should be filed as a `Mesa issue`_.
|
||||
Lima tags will be added accordingly by the developers.
|
||||
|
||||
`apitrace <https://github.com/apitrace/apitrace>`__ traces are very
|
||||
welcome in issue reports and significantly ease the debug and fix
|
||||
process.
|
||||
|
||||
FAQ
|
||||
---
|
||||
|
||||
Will Lima support OpenGL 3.x+ / OpenGL ES 3.x+ / OpenCL / Vulkan ?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**No.** The Mali-4xx was designed to implement OpenGL ES 2.0 and OpenGL
|
||||
ES 1.1. The hardware lacks features to properly implement some features
|
||||
required by newer APIs.
|
||||
|
||||
How complete is Lima? Is reverse engineering complete?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
At the time of writing, with local runs of the
|
||||
`OpenGL ES Conformance Tests <https://github.com/KhronosGroup/VK-GL-CTS/>`__
|
||||
(dEQP) for OpenGL ES 2.0, Lima reports **97%** pass rate.
|
||||
This coverage is on par with coverage provided by the ARM Mali driver.
|
||||
Some tests that pass with Lima fail on Mali and vice versa. Some of
|
||||
these issues are related to precision limitations which likely don’t
|
||||
affect end user applications.
|
||||
|
||||
The work being done in Lima at this stage is largely decoupled from
|
||||
reverse engineering. Reverse engineering is still useful sometimes to
|
||||
obtain details on how to implement low level features (e.g. how to
|
||||
enable some missing legacy OpenGL ES 1.1 feature to support an
|
||||
additional application), but with the current information Lima is
|
||||
already able to cover most of OpenGL ES 2.0.
|
||||
|
||||
Much of the work to be done is related to plumbing features within the
|
||||
frameworks provided by Mesa, fixing bugs (e.g. artifacts or crashes in
|
||||
specific applications), shader compiler improvements, which are not
|
||||
necessarily related to new hardware bits and not related at all to the
|
||||
Mali driver.
|
||||
|
||||
When will Feature XYZ be supported? Is there a roadmap for features implementation?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There is no established roadmap for features implementation.
|
||||
Development is driven by improving coverage in existing OpenGL test
|
||||
frameworks, adding support to features that enable more existing Linux
|
||||
applications, and fixing issues reported by users in their applications.
|
||||
Development is fully based on community contributions.
|
||||
|
||||
If some desired feature is missing or there is an OpenGL-related bug
|
||||
while running some application, please do file a `Mesa issue`_.
|
||||
Issues that are not reproduced by an existing test suite or common
|
||||
application and are also not reported by users are just likely not going
|
||||
to be noticed and fixed.
|
||||
|
||||
How does Lima compare to Mali (blob)? How is performance?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By the fact that Lima is a fully open source driver and leverages a lot
|
||||
of Mesa and Linux functionality, feature-wise Lima is able to support
|
||||
many things that Mali does not. As already mentioned, supporting OpenGL
|
||||
2.1 is one of them. This allows Lima to support many more Linux desktop
|
||||
applications out of the box. Through the abstractions implemented in
|
||||
Mesa, Lima supports a number of OpenGL and OpenGL ES extensions that
|
||||
originally the Mali did not support. Lima is also aligned with the
|
||||
current status of the Linux graphics stack and is therefore able to
|
||||
leverage modern features (such as zero copy pipelines) much more
|
||||
seamlessly. Finally, Lima continues to gain improvements as the Linux
|
||||
graphics ecosystem evolves.
|
||||
|
||||
The entire software stack of the Mali driver and the software stack with
|
||||
Lima are significantly different which makes it hard to offer a single
|
||||
number comparison for performance of the GPU driver. The difference
|
||||
really depends on the type of application. Keep in mind that hardware
|
||||
containing a Mali-4xx is usually quite limited for modern standards and
|
||||
it might not perform as well as hoped. For example: while it is now
|
||||
technically possible to run full GL modern desktop environments at 1080p
|
||||
(which might not have been even possible before due to limited GL
|
||||
support), that might not be very performant due to memory bandwidth, CPU
|
||||
and GPU limitations of the SoC with a Mali-4xx.
|
||||
|
||||
Overall performance with Lima is good for many applications where the
|
||||
Mali-4xx would be a suitable target GPU.
|
||||
But bottom line for a performance evaluation, you need to try with your
|
||||
target application. If performance with Lima does not seem right in some
|
||||
application where it should reasonably perform better, please file a
|
||||
`Mesa issue`_ (in which case some indication on why Lima in particular
|
||||
seems to be the bottleneck would also be helpful).
|
||||
|
||||
Communication channels
|
||||
----------------------
|
||||
|
||||
- `#lima channel <irc://irc.oftc.net/lima>`__ on `irc.oftc.net <https://webchat.oftc.net/>`__
|
||||
- `lima mailing list <https://lists.freedesktop.org/mailman/listinfo/lima>`__
|
||||
- `dri-devel mailing list <https://lists.freedesktop.org/mailman/listinfo/dri-devel>`__
|
||||
|
||||
Dump tool
|
||||
---------
|
||||
|
||||
A tool to dump the runtime of the closed source Mali driver for
|
||||
reverse engineering is available at:
|
||||
https://gitlab.freedesktop.org/lima/mali-syscall-tracker
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
Luc Verhaegen’s original Lima site:
|
||||
https://web.archive.org/web/20180101212947/http://limadriver.org/
|
||||
|
||||
.. _Mesa issue: https://gitlab.freedesktop.org/mesa/mesa/-/issues?label_name%5B%5D=lima
|
||||
@@ -0,0 +1,402 @@
|
||||
LLVMpipe
|
||||
========
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The Gallium LLVMpipe driver is a software rasterizer that uses LLVM to
|
||||
do runtime code generation. Shaders, point/line/triangle rasterization
|
||||
and vertex processing are implemented with LLVM IR which is translated
|
||||
to x86, x86-64, or ppc64le machine code. Also, the driver is
|
||||
multithreaded to take advantage of multiple CPU cores (up to 32 at this
|
||||
time). It's the fastest software rasterizer for Mesa.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
- For x86 or amd64 processors, 64-bit mode is recommended. Support for
|
||||
SSE2 is strongly encouraged. Support for SSE3 and SSE4.1 will yield
|
||||
the most efficient code. The fewer features the CPU has the more
|
||||
likely it is that you will run into underperforming, buggy, or
|
||||
incomplete code.
|
||||
|
||||
For ppc64le processors, use of the Altivec feature (the Vector
|
||||
Facility) is recommended if supported; use of the VSX feature (the
|
||||
Vector-Scalar Facility) is recommended if supported AND Mesa is built
|
||||
with LLVM version 4.0 or later.
|
||||
|
||||
See ``/proc/cpuinfo`` to know what your CPU supports.
|
||||
|
||||
- Unless otherwise stated, LLVM version 3.9 or later is required.
|
||||
|
||||
For Linux, on a recent Debian based distribution do:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
aptitude install llvm-dev
|
||||
|
||||
If you want development snapshot builds of LLVM for Debian and
|
||||
derived distributions like Ubuntu, you can use the APT repository at
|
||||
`apt.llvm.org <https://apt.llvm.org/>`__, which are maintained by
|
||||
Debian's LLVM maintainer.
|
||||
|
||||
For a RPM-based distribution do:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
yum install llvm-devel
|
||||
|
||||
If you want development snapshot builds of LLVM for Fedora, you can
|
||||
use the Copr repository at `fedora-llvm-team/llvm-snapshots <https://copr.fedorainfracloud.org/coprs/g/fedora-llvm-team/llvm-snapshots/>`__,
|
||||
which is maintained by Red Hat's LLVM team.
|
||||
|
||||
For Windows you will need to build LLVM from source with MSVC or
|
||||
MINGW (either natively or through cross compilers) and CMake, and set
|
||||
the ``LLVM`` environment variable to the directory you installed it
|
||||
to. LLVM will be statically linked, so when building on MSVC it needs
|
||||
to be built with a matching CRT as Mesa, and you'll need to pass
|
||||
``-DLLVM_USE_CRT_xxx=yyy`` as described below.
|
||||
|
||||
|
||||
+-----------------+----------------------------------------------------------------+
|
||||
| LLVM build-type | Mesa build-type |
|
||||
| +--------------------------------+-------------------------------+
|
||||
| | debug,checked | release,profile |
|
||||
+=================+================================+===============================+
|
||||
| Debug | ``-DLLVM_USE_CRT_DEBUG=MTd`` | ``-DLLVM_USE_CRT_DEBUG=MT`` |
|
||||
+-----------------+--------------------------------+-------------------------------+
|
||||
| Release | ``-DLLVM_USE_CRT_RELEASE=MTd`` | ``-DLLVM_USE_CRT_RELEASE=MT`` |
|
||||
+-----------------+--------------------------------+-------------------------------+
|
||||
|
||||
You can build only the x86 target by passing
|
||||
``-DLLVM_TARGETS_TO_BUILD=X86`` to CMake.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build everything on Linux invoke meson as:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
meson -D glx=xlib -D gallium-drivers=llvmpipe
|
||||
ninja
|
||||
|
||||
Building for Android
|
||||
--------------------
|
||||
|
||||
To build for Android requires the additional step of building LLVM
|
||||
for Android using the NDK. Before following the steps in
|
||||
:doc:`Android's documentation <../android>` you must build a version
|
||||
of LLVM that targets the NDK with all the required libraries for
|
||||
llvmpipe, and then create a wrap file so that meson knows where to
|
||||
find the LLVM libraries. It can be a bit tricky to get LLVM to build
|
||||
properly using the Android NDK, so the script below can be
|
||||
used as a reference to configure LLVM to build with the NDK for x86.
|
||||
You need to set the ``ANDROID_NDK_ROOT``, ``ANDROID_SDK_VERSION`` and
|
||||
``LLVML_INSTALL_PREFIX`` environment variables appropriately.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
# Early check for required env variables, relies on `set -u`
|
||||
: "$ANDROID_NDK_ROOT"
|
||||
: "$ANDROID_SDK_VERSION"
|
||||
: "$LLVM_INSTALL_PREFIX"
|
||||
|
||||
cmake -GNinja -S llvm -B build/ \
|
||||
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake \
|
||||
-DANDROID_ABI=x86_64 \
|
||||
-DANDROID_PLATFORM=android-${ANDROID_SDK_VERSION} \
|
||||
-DANDROID_NDK=${ANDROID_NDK_ROOT} \
|
||||
-DCMAKE_ANDROID_ARCH_ABI=x86_64 \
|
||||
-DCMAKE_ANDROID_NDK=${ANDROID_NDK_ROOT} \
|
||||
-DCMAKE_BUILD_TYPE=MinSizeRel \
|
||||
-DCMAKE_SYSTEM_NAME=Android \
|
||||
-DCMAKE_SYSTEM_VERSION=${ANDROID_SDK_VERSION} \
|
||||
-DCMAKE_INSTALL_PREFIX=${LLVM_INSTALL_PREFIX} \
|
||||
-DCMAKE_CXX_FLAGS="-march=x86-64 --target=x86_64-linux-android${ANDROID_SDK_VERSION} -fno-rtti" \
|
||||
-DLLVM_HOST_TRIPLE=x86_64-linux-android${ANDROID_SDK_VERSION} \
|
||||
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||
-DLLVM_BUILD_LLVM_DYLIB=OFF \
|
||||
-DLLVM_BUILD_TESTS=OFF \
|
||||
-DLLVM_BUILD_EXAMPLES=OFF \
|
||||
-DLLVM_BUILD_DOCS=OFF \
|
||||
-DLLVM_BUILD_TOOLS=OFF \
|
||||
-DLLVM_ENABLE_RTTI=OFF \
|
||||
-DLLVM_BUILD_INSTRUMENTED_COVERAGE=OFF \
|
||||
-DLLVM_NATIVE_TOOL_DIR=${ANDROID_NDK_ROOT}toolchains/llvm/prebuilt/linux-x86_64/bin \
|
||||
-DLLVM_ENABLE_PIC=False
|
||||
|
||||
ninja -C build/ install
|
||||
|
||||
You will also need to create a wrap file, so that meson is able
|
||||
to find the LLVM libraries built with the NDK. The process for this
|
||||
is described in :doc:`meson documentation <../meson>`.
|
||||
|
||||
For example the following script will create the
|
||||
``subprojects/llvm/meson.build`` wrap file, after setting ``LLVM_INSTALL_PREFIX``
|
||||
to the path where LLVM was installed to.
|
||||
|
||||
The list of libraries passed in `dep_llvm` below should match what it was
|
||||
produced by the LLVM build from above.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -exu
|
||||
|
||||
# Early check for required env variables, relies on `set -u`
|
||||
: "$LLVM_INSTALL_PREFIX"
|
||||
|
||||
if [ ! -d "$LLVM_INSTALL_PREFIX" ]; then
|
||||
echo "Cannot find an LLVM build in $LLVM_INSTALL_PREFIX" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p subprojects/llvm
|
||||
|
||||
cat << EOF > subprojects/llvm/meson.build
|
||||
project('llvm', ['cpp'])
|
||||
|
||||
cpp = meson.get_compiler('cpp')
|
||||
|
||||
_deps = []
|
||||
_search = join_paths('$LLVM_INSTALL_PREFIX', 'lib')
|
||||
|
||||
foreach d: ['libLLVMAggressiveInstCombine', 'libLLVMAnalysis', 'libLLVMAsmParser', 'libLLVMAsmPrinter', 'libLLVMBinaryFormat', 'libLLVMBitReader', 'libLLVMBitstreamReader', 'libLLVMBitWriter', 'libLLVMCFGuard', 'libLLVMCFIVerify', 'libLLVMCodeGen', 'libLLVMCodeGenTypes', 'libLLVMCore', 'libLLVMCoroutines', 'libLLVMCoverage', 'libLLVMDebugInfoBTF', 'libLLVMDebugInfoCodeView', 'libLLVMDebuginfod', 'libLLVMDebugInfoDWARF', 'libLLVMDebugInfoGSYM', 'libLLVMDebugInfoLogicalView', 'libLLVMDebugInfoMSF', 'libLLVMDebugInfoPDB', 'libLLVMDemangle', 'libLLVMDiff', 'libLLVMDlltoolDriver', 'libLLVMDWARFLinker', 'libLLVMDWARFLinkerClassic', 'libLLVMDWARFLinkerParallel', 'libLLVMDWP', 'libLLVMExecutionEngine', 'libLLVMExegesis', 'libLLVMExegesisX86', 'libLLVMExtensions', 'libLLVMFileCheck', 'libLLVMFrontendDriver', 'libLLVMFrontendHLSL', 'libLLVMFrontendOffloading', 'libLLVMFrontendOpenACC', 'libLLVMFrontendOpenMP', 'libLLVMFuzzerCLI', 'libLLVMFuzzMutate', 'libLLVMGlobalISel', 'libLLVMHipStdPar', 'libLLVMInstCombine', 'libLLVMInstrumentation', 'libLLVMInterfaceStub', 'libLLVMInterpreter', 'libLLVMipo', 'libLLVMIRPrinter', 'libLLVMIRReader', 'libLLVMJITLink', 'libLLVMLibDriver', 'libLLVMLineEditor', 'libLLVMLinker', 'libLLVMLTO', 'libLLVMMC', 'libLLVMMCA', 'libLLVMMCDisassembler', 'libLLVMMCJIT', 'libLLVMMCParser', 'libLLVMMIRParser', 'libLLVMObjCARCOpts', 'libLLVMObjCopy', 'libLLVMObject', 'libLLVMObjectYAML', 'libLLVMOption', 'libLLVMOrcDebugging', 'libLLVMOrcJIT', 'libLLVMOrcShared', 'libLLVMOrcTargetProcess', 'libLLVMPasses', 'libLLVMProfileData', 'libLLVMRemarks', 'libLLVMRuntimeDyld', 'libLLVMScalarOpts', 'libLLVMSelectionDAG', 'libLLVMSupport', 'libLLVMSymbolize', 'libLLVMTableGen', 'libLLVMTableGenCommon', 'libLLVMTarget', 'libLLVMTargetParser', 'libLLVMTextAPI', 'libLLVMTextAPIBinaryReader', 'libLLVMTransformUtils', 'libLLVMVectorize', 'libLLVMWindowsDriver', 'libLLVMWindowsManifest', 'libLLVMX86AsmParser', 'libLLVMX86CodeGen', 'libLLVMX86Desc', 'libLLVMX86Disassembler', 'libLLVMX86Info', 'libLLVMX86TargetMCA', 'libLLVMXRay']
|
||||
_deps += cpp.find_library(d, dirs : _search)
|
||||
endforeach
|
||||
|
||||
dep_llvm = declare_dependency(
|
||||
include_directories : include_directories('$LLVM_INSTALL_PREFIX/include'),
|
||||
dependencies : _deps,
|
||||
version : '$(sed -n -e 's/^#define LLVM_VERSION_STRING "\([^"]*\)".*/\1/p' "${LLVM_INSTALL_PREFIX}/include/llvm/Config/llvm-config.h" )',
|
||||
)
|
||||
|
||||
has_rtti = false
|
||||
irbuilder_h = files('$LLVM_INSTALL_PREFIX/include/llvm/IR/IRBuilder.h')
|
||||
EOF
|
||||
|
||||
Afterwards you can continue following the instructors to build mesa
|
||||
on :doc:`Android <../android>` and follow the steps to add the driver
|
||||
directly to an Android OS image.
|
||||
|
||||
Using
|
||||
-----
|
||||
|
||||
Environment variables
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. envvar:: LP_NATIVE_VECTOR_WIDTH
|
||||
|
||||
We can use it to override vector bits. Because sometimes it turns
|
||||
out LLVMpipe can be fastest by using 128 bit vectors,
|
||||
yet use AVX instructions.
|
||||
|
||||
.. envvar:: GALLIUM_NOSSE
|
||||
|
||||
Deprecated in favor of ``GALLIUM_OVERRIDE_CPU_CAPS``,
|
||||
use ``GALLIUM_OVERRIDE_CPU_CAPS=nosse`` instead.
|
||||
|
||||
.. envvar:: LP_FORCE_SSE2
|
||||
|
||||
Deprecated in favor of ``GALLIUM_OVERRIDE_CPU_CAPS``
|
||||
use ``GALLIUM_OVERRIDE_CPU_CAPS=sse2`` instead.
|
||||
|
||||
Linux
|
||||
~~~~~
|
||||
|
||||
On Linux, building will create a drop-in alternative for ``libGL.so``
|
||||
into
|
||||
|
||||
::
|
||||
|
||||
build/foo/gallium/targets/libgl-xlib/libGL.so
|
||||
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
lib/gallium/libGL.so
|
||||
|
||||
To use it set the ``LD_LIBRARY_PATH`` environment variable accordingly.
|
||||
|
||||
Windows
|
||||
~~~~~~~
|
||||
|
||||
On Windows, building will create
|
||||
``build/windows-x86-debug/gallium/targets/libgl-gdi/opengl32.dll`` which
|
||||
is a drop-in alternative for system's ``opengl32.dll``, which will use
|
||||
the Mesa ICD, ``build/windows-x86-debug/gallium/targets/wgl/libgallium_wgl.dll``.
|
||||
To use it put both DLLs in the same directory as your application. It can also
|
||||
be used by replacing the native ICD driver, but it's quite an advanced usage, so if
|
||||
you need to ask, don't even try it.
|
||||
|
||||
There is however an easy way to replace the OpenGL software renderer
|
||||
that comes with Microsoft Windows 7 (or later) with LLVMpipe (that is,
|
||||
on systems without any OpenGL drivers):
|
||||
|
||||
- copy
|
||||
``build/windows-x86-debug/gallium/targets/wgl/libgallium_wgl.dll`` to
|
||||
``C:\Windows\SysWOW64\mesadrv.dll``
|
||||
|
||||
- load this registry settings:
|
||||
|
||||
::
|
||||
|
||||
REGEDIT4
|
||||
|
||||
; https://technet.microsoft.com/en-us/library/cc749368.aspx
|
||||
; https://www.msfn.org/board/topic/143241-portable-windows-7-build-from-winpe-30/page-5#entry942596
|
||||
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL]
|
||||
"DLL"="mesadrv.dll"
|
||||
"DriverVersion"=dword:00000001
|
||||
"Flags"=dword:00000001
|
||||
"Version"=dword:00000002
|
||||
|
||||
- Ditto for 64 bits drivers if you need them.
|
||||
|
||||
Profiling
|
||||
---------
|
||||
|
||||
Linux perf integration
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
On Linux, it is possible to have symbol resolution of JIT code with
|
||||
`Linux perf <https://perfwiki.github.io/main/>`__:
|
||||
|
||||
::
|
||||
|
||||
perf record -g /my/application
|
||||
perf report
|
||||
|
||||
When run inside Linux perf, LLVMpipe will create a
|
||||
``/tmp/perf-XXXXX.map`` file with symbol address table. It also dumps
|
||||
assembly code to ``/tmp/perf-XXXXX.map.asm``, which can be used by the
|
||||
``bin/perf-annotate-jit.py`` script to produce disassembly of the
|
||||
generated code annotated with the samples.
|
||||
|
||||
You can obtain a call graph via
|
||||
`Gprof2Dot <https://github.com/jrfonseca/gprof2dot#linux-perf>`__.
|
||||
|
||||
FlameGraph support
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Outside Linux, it is possible to generate a
|
||||
`FlameGraph <https://github.com/brendangregg/FlameGraph>`__
|
||||
with resolved JIT symbols.
|
||||
|
||||
Set the environment variable ``JIT_SYMBOL_MAP_DIR`` to a directory path,
|
||||
and run your LLVMpipe program. Follow the FlameGraph instructions:
|
||||
capture traces using a supported tool (for example DTrace),
|
||||
and fold the stacks using the associated script
|
||||
(``stackcollapse.pl`` for DTrace stacks).
|
||||
|
||||
LLVMpipe will create a ``jit-symbols-XXXXX.map`` file containing the symbol
|
||||
address table inside the chosen directory. It will also dump the JIT
|
||||
disassemblies to ``jit-symbols-XXXXX.map.asm``. Run your folded traces and
|
||||
both output files through the ``bin/flamegraph_map_lp_jit.py`` script to map
|
||||
addresses to JIT symbols, and annotate the disassembly with the sample counts.
|
||||
|
||||
Unit testing
|
||||
------------
|
||||
|
||||
Building will also create several unit tests in
|
||||
``build/linux-???-debug/gallium/drivers/llvmpipe``:
|
||||
|
||||
- ``lp_test_blend``: blending
|
||||
- ``lp_test_conv``: SIMD vector conversion
|
||||
- ``lp_test_format``: pixel unpacking/packing
|
||||
|
||||
Some of these tests can output results and benchmarks to a tab-separated
|
||||
file for later analysis, e.g.:
|
||||
|
||||
::
|
||||
|
||||
build/linux-x86_64-debug/gallium/drivers/llvmpipe/lp_test_blend -o blend.tsv
|
||||
|
||||
Development Notes
|
||||
-----------------
|
||||
|
||||
- When looking at this code for the first time, start in lp_state_fs.c,
|
||||
and then skim through the ``lp_bld_*`` functions called there, and
|
||||
the comments at the top of the ``lp_bld_*.c`` functions.
|
||||
- The driver-independent parts of the LLVM / Gallium code are found in
|
||||
``src/gallium/auxiliary/gallivm/``. The filenames and function
|
||||
prefixes need to be renamed from ``lp_bld_`` to something else
|
||||
though.
|
||||
- We use LLVM-C bindings for now. They are not documented, but follow
|
||||
the C++ interfaces very closely, and appear to be complete enough for
|
||||
code generation. See `this stand-alone
|
||||
example <https://npcontemplation.blogspot.com/2008/06/secret-of-llvm-c-bindings.html>`__.
|
||||
See the ``llvm-c/Core.h`` file for reference.
|
||||
|
||||
.. _recommended_reading:
|
||||
|
||||
Recommended Reading
|
||||
-------------------
|
||||
|
||||
- Rasterization
|
||||
|
||||
- `Triangle Scan Conversion using 2D Homogeneous
|
||||
Coordinates <https://userpages.cs.umbc.edu/olano/papers/2dh-tri/>`__
|
||||
- `Rasterization on
|
||||
Larrabee <https://www.gamedeveloper.com/programming/sponsored-feature-rasterization-on-larrabee----adaptive-rasterization-helps-boost-efficiency>`__
|
||||
- `Rasterization using half-space
|
||||
functions <http://web.archive.org/web/20110820052005/http://www.devmaster.net/codespotlight/show.php?id=17>`__
|
||||
- `Advanced
|
||||
Rasterization <http://web.archive.org/web/20140514220546/http://devmaster.net/posts/6145/advanced-rasterization>`__
|
||||
- `Optimizing Software Occlusion
|
||||
Culling <https://fgiesen.wordpress.com/2013/02/17/optimizing-sw-occlusion-culling-index/>`__
|
||||
|
||||
- Texture sampling
|
||||
|
||||
- `Perspective Texture
|
||||
Mapping <https://chrishecker.com/Miscellaneous_Technical_Articles#Perspective_Texture_Mapping>`__
|
||||
- `Texturing As In
|
||||
Unreal <https://www.flipcode.com/archives/Texturing_As_In_Unreal.shtml>`__
|
||||
- `Run-Time MIP-Map
|
||||
Filtering <http://web.archive.org/web/20220709145555/http://www.gamasutra.com/view/feature/3301/runtime_mipmap_filtering.php>`__
|
||||
- `Will "brilinear" filtering
|
||||
persist? <https://alt.3dcenter.org/artikel/2003/10-26_a_english.php>`__
|
||||
- `Trilinear
|
||||
filtering <http://ixbtlabs.com/articles2/gffx/nv40-rx800-3.html>`__
|
||||
- `Texture tiling and
|
||||
swizzling <https://fgiesen.wordpress.com/2011/01/17/texture-tiling-and-swizzling/>`__
|
||||
|
||||
- SIMD
|
||||
|
||||
- `Whole-Function
|
||||
Vectorization <https://compilers.cs.uni-saarland.de/projects/wfv/#pubs>`__
|
||||
|
||||
- Optimization
|
||||
|
||||
- `Optimizing Pixomatic For Modern x86
|
||||
Processors <https://web.archive.org/web/20250114121202/https://www.drdobbs.com/optimizing-pixomatic-for-modern-x86-proc/184405807>`__
|
||||
- `Intel 64 and IA-32 Architectures Optimization Reference
|
||||
Manual <https://www.intel.com/content/www/us/en/content-details/779559/intel-64-and-ia-32-architectures-optimization-reference-manual.html>`__
|
||||
- `Software optimization
|
||||
resources <https://www.agner.org/optimize/>`__
|
||||
- `Intel Intrinsics
|
||||
Guide <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html>`__
|
||||
|
||||
- LLVM
|
||||
|
||||
- `LLVM Language Reference
|
||||
Manual <https://llvm.org/docs/LangRef.html>`__
|
||||
- `The secret of LLVM C
|
||||
bindings <https://npcontemplation.blogspot.com/2008/06/secret-of-llvm-c-bindings.html>`__
|
||||
|
||||
- General
|
||||
|
||||
- `A trip through the Graphics
|
||||
Pipeline <https://fgiesen.wordpress.com/2011/07/09/a-trip-through-the-graphics-pipeline-2011-index/>`__
|
||||
- `WARP Architecture and
|
||||
Performance <https://learn.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp#warp-architecture-and-performance>`__
|
||||
@@ -0,0 +1,93 @@
|
||||
NVK
|
||||
===
|
||||
|
||||
NVK is a Vulkan driver for NVIDIA GPUs.
|
||||
|
||||
Hardware support
|
||||
----------------
|
||||
|
||||
NVK currently supports Kepler (GeForce 600 and 700 series) and later GPUs up to
|
||||
and including Ada (RTX 4000 series), as well as consumer Blackwell GPUs
|
||||
(RTX 5000 series).
|
||||
|
||||
Conformance status:
|
||||
-------------------
|
||||
|
||||
NVK is a conformant Vulkan 1.4 implementation for all officially supported
|
||||
GPUs.
|
||||
|
||||
OpenGL support through Zink:
|
||||
----------------------------
|
||||
|
||||
Starting with Mesa 25.1, all Turing (RTX 2000 series and GTX 16xx) and
|
||||
later GPUs will get NVK+Zink as their OpenGL implementation by default
|
||||
instead of the old Nouveau GL driver. NVK+Zink is a conformant OpenGL 4.6
|
||||
implementation. The Nouveau GL driver is no longer supported on these cards.
|
||||
|
||||
Kernel requirements
|
||||
-------------------
|
||||
|
||||
NVK requires at least a Linux 6.6 kernel
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
Here are a few environment variable debug environment variables
|
||||
specific to NVK:
|
||||
|
||||
.. envvar:: NAK_DEBUG
|
||||
|
||||
a comma-separated list of named flags affecting the NVK back-end shader
|
||||
compiler:
|
||||
|
||||
``print``
|
||||
Prints the shader at various stages of the compile pipeline
|
||||
``serial``
|
||||
Forces serial instruction execution; this is often useful for
|
||||
debugging or working around dependency bugs
|
||||
``spill``
|
||||
Forces the GPR file to a minimal size to test the spilling code
|
||||
``annotate``
|
||||
Adds extra annotation instructions to the IR to track information
|
||||
from various compile passes
|
||||
|
||||
.. envvar:: NVK_DEBUG
|
||||
|
||||
a comma-separated list of named flags, which do various things:
|
||||
|
||||
``push``
|
||||
Dumps all pushbufs to stderr on submit. This requires that
|
||||
``push_sync`` also be set.
|
||||
``push_sync``
|
||||
Waits for submit to complete before continuing
|
||||
``zero_memory``
|
||||
Zeros all VkDeviceMemory objects upon creation
|
||||
``trash_memory``
|
||||
Write repeating nonzero patterns to client memory allocations
|
||||
``vm``
|
||||
Logs VM binds and unbinds
|
||||
``no_cbuf``
|
||||
Disables automatic promotion of UBOs to constant buffers
|
||||
``edb_bview``
|
||||
Forces the driver to use the VK_EXT_descriptor_buffer path for buffer
|
||||
views.
|
||||
``gart``
|
||||
Forces all memory to be allocated from system RAM (GART)
|
||||
``coherent``
|
||||
Forces all memory maps to be coherent with the CPU caches. This only
|
||||
applies to Tegra devices.
|
||||
|
||||
.. envvar:: NVK_I_WANT_A_BROKEN_VULKAN_DRIVER
|
||||
|
||||
If defined to ``1`` or ``true``, this will enable enumeration of all
|
||||
GPUs Kepler and later, including GPUs for which hardware support is
|
||||
poorly tested or completely broken. This is intended for developer use
|
||||
only.
|
||||
|
||||
Developer info
|
||||
--------------
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
nvk/*
|
||||
@@ -0,0 +1,134 @@
|
||||
|
||||
External Hardware Documentation and Resources
|
||||
=============================================
|
||||
|
||||
Information about hardware behavior comes from a mix of official and
|
||||
reverse-engineered sources.
|
||||
|
||||
Command buffers
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
* `NVIDIA open-gpu-doc repository`_ is official documentation from NVIDIA that
|
||||
has been released to the public. The majority of this documentation comes in
|
||||
the form of class headers which describe the class state registers.
|
||||
|
||||
* `NVIDIA open-gpu-kernel-modules repository`_ is the open-source kernel mode
|
||||
driver that NVIDIA ships on Turing+ GPUs with GSP. The code here can provide
|
||||
examples of how to use some hardware features. If open-gpu-doc is missing a
|
||||
class header, sometimes there will be one here.
|
||||
|
||||
* Reverse-engineered command names from `envytools`_ are available in mesa
|
||||
under eg. ``src/gallium/drivers/nouveau/nvc0/nvc0_3d.xml.h``. These are no
|
||||
longer updated. nvk instead uses the open-gpu-doc headers
|
||||
|
||||
* `envyhooks`_ is the modern way to dump command sequences from the proprietary
|
||||
driver
|
||||
|
||||
* ``nv_push_dump`` is part of mesa and can disassemble command sequences (build
|
||||
with ``-D tools=nouveau``, run ``src/nouveau/headers/nv_push_dump`` from the
|
||||
build dir)
|
||||
|
||||
.. _NVIDIA open-gpu-doc repository: https://github.com/NVIDIA/open-gpu-doc
|
||||
.. _NVIDIA open-gpu-kernel-modules repository: https://github.com/NVIDIA/open-gpu-kernel-modules
|
||||
.. _envyhooks: https://gitlab.freedesktop.org/nouveau/envyhooks
|
||||
|
||||
Shader ISA
|
||||
^^^^^^^^^^
|
||||
|
||||
* `NVIDIA PTX documentation`_ is NVIDIA documentation for CUDA's
|
||||
intermediate representation. We don't use PTX directly, but this often has
|
||||
hints about how underlying hardware instructions work. For example, the PTX
|
||||
`redux` instruction is pretty much identical to the hardware instruction of
|
||||
the same name.
|
||||
|
||||
* `CUDA Binary Utilities`_ is documentation for CUDA's disassembler,
|
||||
`nvdisasm`. It includes a brief description of most hardware instructions.
|
||||
There's also an `older version`_ that has older architectures (Kepler through
|
||||
Volta).
|
||||
|
||||
* Kuter Dinel has reverse-engineered instruction encodings for the `Hopper
|
||||
ISA`_ and `Ada ISA`_ which are autogenerated from his `nv_isa_solver`_
|
||||
project.
|
||||
|
||||
* `nv-shader-tools`_ has some additional tools for disassembling and fuzzing
|
||||
the hardware ISA
|
||||
|
||||
* Mel has dumped a `list of avaiable instructions`_ and their opcodes on recent
|
||||
architectures by scraping nvdisasm error messages.
|
||||
|
||||
* The `Volta whitepaper`_ section "Independent Thread Scheduling" has an
|
||||
overview of the control flow model used on Volta+ GPUs.
|
||||
|
||||
* `Dissecting the NVidia Turing T4 GPU via Microbenchmarking`_ has
|
||||
reverse-engineered info about the Turing instruction encoding. See especially
|
||||
section "2.1 Control information" for an overview of compiler-inserted delays
|
||||
and waits on Maxwell and later.
|
||||
|
||||
* `Analyzing Modern NVIDIA GPU cores`_ has additional reverse-engineered info
|
||||
about the semantics of compiler-inserted delays and waits.
|
||||
|
||||
* `Control Flow Management in Modern GPUs`_ has more detail about control flow
|
||||
reconvergence on Volta+
|
||||
|
||||
* `maxas`_ has some reverse-engineered info on the Maxwell ISA
|
||||
|
||||
* `asfermi`_ has some reverse-engineered info on the older Fermi ISA
|
||||
|
||||
* Red Hat has some NDA'd documentation on instruction latencies from NVIDIA.
|
||||
Bother karolherbst or airlied on irc if you're missing a latency class for an
|
||||
instruction on recent architectures.
|
||||
|
||||
* Behavior of instructions are tested using the hardware tests in
|
||||
``src/nouveau/compiler/nak/hw_tests.rs`` and the corresponding ``Foldable``
|
||||
implementations in ``src/nouveau/compiler/nak/ir.rs`` (build with ``-D
|
||||
build-tests=true`` and run ``src/nouveau/compiler/nak hw_tests`` from the
|
||||
build dir)
|
||||
|
||||
* NAK's instruction encodings are tested against nvdisasm using
|
||||
``src/nouveau/compiler/nak/nvdisasm_tests.rs`` (build with ``-D
|
||||
build-tests=true`` and run ``src/nouveau/compiler/nak nvdisasm_tests`` from
|
||||
the build dir)
|
||||
|
||||
* The old GL driver's compiler, under ``src/gallium/drivers/nouveau/codegen``,
|
||||
has some information. This is especially useful for graphics-only
|
||||
instructions, which are often not covered by other sources.
|
||||
|
||||
* `Compiler explorer`_ is a convenient tool to see what assembly NVIDIA
|
||||
generates for a given CUDA program.
|
||||
|
||||
.. _NVIDIA PTX documentation: https://docs.nvidia.com/cuda/parallel-thread-execution/index.html
|
||||
.. _CUDA Binary Utilities: https://docs.nvidia.com/cuda/cuda-binary-utilities/index.html#instruction-set-reference
|
||||
.. _older version: https://docs.nvidia.com/cuda/archive/11.8.0/cuda-binary-utilities/index.html#instruction-set-ref
|
||||
.. _Hopper ISA: https://kuterdinel.com/nv_isa/
|
||||
.. _Ada ISA: https://kuterdinel.com/nv_isa_sm89/
|
||||
.. _nv_isa_solver: https://github.com/kuterd/nv_isa_solver
|
||||
.. _nv-shader-tools: https://gitlab.freedesktop.org/nouveau/nv-shader-tools
|
||||
.. _list of avaiable instructions: https://gitlab.freedesktop.org/mhenning/re/-/tree/main/opclass?ref_type=heads
|
||||
.. _Volta whitepaper: https://images.nvidia.com/content/volta-architecture/pdf/volta-architecture-whitepaper.pdf
|
||||
.. _Dissecting the NVidia Turing T4 GPU via Microbenchmarking: https://arxiv.org/pdf/1903.07486
|
||||
.. _Analyzing Modern NVIDIA GPU cores: https://arxiv.org/pdf/2503.20481
|
||||
.. _Control Flow Management in Modern GPUs: https://arxiv.org/pdf/2407.02944
|
||||
.. _maxas: https://github.com/NervanaSystems/maxas/wiki
|
||||
.. _asfermi: https://github.com/hyqneuron/asfermi/wiki
|
||||
.. _Compiler explorer: https://godbolt.org/z/1jrfhq5G7
|
||||
|
||||
Misc
|
||||
^^^^
|
||||
|
||||
* `envytools`_ has reverse-engineered documentation for maxwell and earlier
|
||||
hardware.
|
||||
* The nvidia architecture whitepapers give a basic overview of what has changed
|
||||
between hardware revisions. See eg. the `Blackwell whitepaper`_
|
||||
* The nvidia architecture tuning guides often mention how details of a hardware
|
||||
generation has changed, often with information about the memory subsystem or
|
||||
occupancy. See eg. the `Blackwell tuning guide`_
|
||||
* `The Nouveau wiki's CodeNames page`_ is useful for mapping NVIDIA marketing
|
||||
names to engineering names
|
||||
* `Matching CUDA arch and CUDA gencode for various NVIDIA architectures`_ has a
|
||||
useful table comparing SM versions to engineering names
|
||||
|
||||
.. _envytools: https://envytools.readthedocs.io/en/latest/hw/index.html
|
||||
.. _Blackwell whitepaper: https://images.nvidia.com/aem-dam/Solutions/geforce/blackwell/nvidia-rtx-blackwell-gpu-architecture.pdf
|
||||
.. _Blackwell tuning guide: https://docs.nvidia.com/cuda/blackwell-tuning-guide/index.html
|
||||
.. _The Nouveau wiki's CodeNames page: https://nouveau.freedesktop.org/CodeNames.html
|
||||
.. _Matching CUDA arch and CUDA gencode for various NVIDIA architectures: https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/
|
||||
@@ -0,0 +1,126 @@
|
||||
|
||||
Hardware docs
|
||||
=============
|
||||
|
||||
Command buffers
|
||||
---------------
|
||||
|
||||
Command format
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Each command sent to the GPU contains a method and some data. Method names are
|
||||
documented in corresponding header files copied from NVIDIA, eg. the fermi
|
||||
graphics methods are in src/nouveau/headers/nvidia/classes/cl9097.h
|
||||
|
||||
P_IMMD
|
||||
""""""
|
||||
|
||||
A lot of the time, you will want to issue a single method with its data, which
|
||||
can be done with P_IMMD::
|
||||
|
||||
P_IMMD(p, NV9097, WAIT_FOR_IDLE, 0);
|
||||
|
||||
P_IMMD will emit either a single `immediate-data method`_, which takes a single
|
||||
word, or a pair of words that's equivalent to P_MTHD + the provided data. Code
|
||||
must count P_IMMD as possibly 2 words as a result.
|
||||
|
||||
.. _immediate-data method: https://github.com/NVIDIA/open-gpu-doc/blob/87ba53e0c385285a3aa304b864dccb975a9a0dd4/manuals/turing/tu104/dev_ram.ref.txt#L1214
|
||||
|
||||
P_MTHD
|
||||
""""""
|
||||
|
||||
`P_MTHD`_ is a convenient way to execute multiple consecutive methods without
|
||||
repeating the method header. For example, the code::
|
||||
|
||||
P_MTHD(p, NV9097, SET_REPORT_SEMAPHORE_A);
|
||||
P_NV9097_SET_REPORT_SEMAPHORE_A(p, addr >> 32);
|
||||
P_NV9097_SET_REPORT_SEMAPHORE_B(p, addr);
|
||||
P_NV9097_SET_REPORT_SEMAPHORE_C(p, value);
|
||||
|
||||
generates four words - one word for the method header (defaulting to 1INC) and
|
||||
then the next three words for data. 1INC will automatically increment the method
|
||||
id by one word for each data value, which is why the example can advance from
|
||||
SET_REPORT_SEMAPHORE_A to B to C.
|
||||
|
||||
.. _P_MTHD: https://github.com/NVIDIA/open-gpu-doc/blob/87ba53e0c385285a3aa304b864dccb975a9a0dd4/manuals/turing/tu104/dev_ram.ref.txt#L1042
|
||||
|
||||
P_0INC
|
||||
""""""
|
||||
|
||||
`0INC`_ will issue the same method repeatedly for each following data word.
|
||||
|
||||
.. _0INC: https://github.com/NVIDIA/open-gpu-doc/blob/87ba53e0c385285a3aa304b864dccb975a9a0dd4/manuals/turing/tu104/dev_ram.ref.txt#L1096
|
||||
|
||||
P_1INC
|
||||
""""""
|
||||
|
||||
`1INC`_ will increment after one word and then issue the following method
|
||||
repeatedly. For example, the code::
|
||||
|
||||
P_1INC(p, NV9097, CALL_MME_MACRO(NVK_MME_SET_PRIV_REG));
|
||||
P_INLINE_DATA(p, 0);
|
||||
P_INLINE_DATA(p, BITFIELD_BIT(3));
|
||||
|
||||
issues one NV9097_CALL_MME_MACRO command, then increments the method and issues
|
||||
two NV9097_CALL_MME_DATA commands.
|
||||
|
||||
.. _1INC: https://github.com/NVIDIA/open-gpu-doc/blob/87ba53e0c385285a3aa304b864dccb975a9a0dd4/manuals/turing/tu104/dev_ram.ref.txt#L1149
|
||||
|
||||
Execution barriers
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Commands within a command buffer can be synchronized in a few different ways.
|
||||
|
||||
* Explicit WFI - Idles all engines before executing the next command eg. via
|
||||
`NVA16F_WFI` or `NV9097_WAIT_FOR_IDLE`
|
||||
* Semaphores - Delay execution based on values in a memory location. See
|
||||
`open-gpu-doc on semaphores`_
|
||||
* A subchannel switch - Causes the hardware to execute an implied WFI
|
||||
|
||||
.. _open-gpu-doc on semaphores: https://github.com/NVIDIA/open-gpu-doc/blob/master/manuals/turing/tu104/dev_pbdma.ref.txt#L3231
|
||||
|
||||
Subchannel switches
|
||||
"""""""""""""""""""
|
||||
|
||||
A subchannel switch occurs when the hardware receives a command for a different
|
||||
subchannel than the one that it's currently executing. For example, if the
|
||||
hardware is currently executing commands on the 3D engine (`SUBC_NV9097 == 0`), a
|
||||
command executed on the compute engine (`SUBC_NV90C0 == 1`) will cause a
|
||||
subchannel switch. Host methods (class \*6F) are an exception to this - they can
|
||||
be issued to any subchannel and will not trigger a subchannel switch
|
||||
[#fhostsubcswitch]_ [#foverlapnvk]_.
|
||||
|
||||
Subchannel switches act the same way that an explicit WFI does - they fully idle
|
||||
the channel before issuing commands to the next engine [#fnsight]_
|
||||
[#foverlapnvk]_.
|
||||
|
||||
This works the same on Blackwell. Some NVIDIA documentation contradicts this:
|
||||
"On NVIDIA Blackwell Architecture GPUs and newer, subchannel switches do not
|
||||
occur between 3D and compute workloads"[#fnsight]_. This documentation appears
|
||||
to be wrong or inapplicable for some reason - tests do not reproduce this
|
||||
behavior [#foverlapnvk]_ [#foverlapprop]_, and the blob does not change its
|
||||
event implementation for blackwell [#feventprop]_.
|
||||
|
||||
|
||||
.. [#fnsight] https://docs.nvidia.com/nsight-graphics/UserGuide/gpu-trace-ui.html#subchannel-switch-overlay
|
||||
.. [#foverlapnvk] Based on reverse engineering by running
|
||||
https://gitlab.freedesktop.org/mhenning/re/-/tree/main/vk_test_overlap_exec
|
||||
under nvk, possibly with alterations to the way nvk generates commands for
|
||||
pipeline barriers.
|
||||
.. [#foverlapprop] By running
|
||||
https://gitlab.freedesktop.org/mhenning/re/-/tree/main/vk_test_overlap_exec
|
||||
under the proprietary driver and examining the main output (eg. execution
|
||||
overlaps without pipeline barriers)
|
||||
.. [#feventprop] For example, the event commands generated here only wait on a
|
||||
single engine
|
||||
https://gitlab.freedesktop.org/mhenning/re/-/blob/6ce8c860da65fbf2ab26d124d25f907dea2cf33a/vk_event/blackwell.out#L20612-20793
|
||||
.. [#fhostsubcswitch] https://github.com/NVIDIA/open-gpu-doc/blob/87ba53e0c385285a3aa304b864dccb975a9a0dd4/manuals/turing/tu104/dev_ram.ref.txt#L1009
|
||||
|
||||
Copy engine
|
||||
^^^^^^^^^^^
|
||||
|
||||
The copy engine's `PIPELINED` mode allows a new transfer to start before the
|
||||
previous transfer finishes, while `NON_PIPELINED` acts as an execution barrier
|
||||
between the current copy and the previous one [#fpipelined]_.
|
||||
|
||||
.. [#fpipelined] https://github.com/NVIDIA/open-gpu-kernel-modules/blob/2b436058a616676ec888ef3814d1db6b2220f2eb/src/common/sdk/nvidia/inc/ctrl/ctrl0050.h#L75-L83
|
||||
@@ -0,0 +1,66 @@
|
||||
ISBE Layout
|
||||
=============================================
|
||||
|
||||
The ISBE is a piece of staging memory shared between the :abbr:`PE (Primitive Engine)`
|
||||
and :abbr:`SM (Streaming Multiprocessor)` containing the attributes for
|
||||
:abbr:`VTG (Vertex, Tesselation, Geometry)` stages.
|
||||
|
||||
In normal cases, no shader needs to access it and instead use ``ALD`` or ``AST``
|
||||
but in some some cases (like ``ALD`` vtx handling or mesh shaders) direct access to
|
||||
it is necessary.
|
||||
|
||||
This document the observations made about the layout of it.
|
||||
|
||||
The ISBE "space" is separated in multiple regions (map, patch, primitive and attribute)
|
||||
and can be accessed with ``ISBERD`` and ``ISBEWR`` instructions.
|
||||
|
||||
There is always two ISBE "space" present: one for inputs and one for outputs.
|
||||
Input and output ISBE memory can be shared by setting bit 25 of the
|
||||
:abbr:`SPH (Shader Program Header)`.
|
||||
|
||||
**NOTE**: Before Turing, only ``ISBERD`` was supported with the ``.MAP`` flag.
|
||||
|
||||
Map region
|
||||
""""""""""
|
||||
|
||||
The Map region contains all the vertex indices for all primitives.
|
||||
|
||||
When used as output, it starts with the primitives count.
|
||||
|
||||
==================================================== ==================== ===================================================
|
||||
Byte Range Name Note
|
||||
==================================================== ==================== ===================================================
|
||||
0x0 primitive_count 32-bit, Only present as output ISBE
|
||||
0x0?..(0x0? + primitive_count * $vtx_per_prim_count) primitive_indices[i] 8-bit, Offset is 0x04 as output ISBE otherwise 0x00
|
||||
==================================================== ==================== ===================================================
|
||||
|
||||
Attribute region
|
||||
""""""""""""""""
|
||||
|
||||
The Attribute region contains all attributes and is allocated based on the
|
||||
:abbr:`SPH (Shader Program Header)` input/output mask and :doc:`mesh shader methods <mesh_shading_notes>`.
|
||||
|
||||
With the ``SKEW`` flag applied on ``ISBERD`` and ``ISBEWR``, each type of attribute is packed with
|
||||
32 values of the same type forming "memory lines" of 128 bytes each.
|
||||
|
||||
Additionally, the order of the attribute packs is determined by the unique attribute id.
|
||||
(see `nak_attr` or :abbr:`SPH (Shader Program Header)` headers definitions for the values)
|
||||
|
||||
Finally, if more than 32 values are needed, the layout repeat itself.
|
||||
|
||||
Here is an example for 256 vertices being defined with ``ATTR_POINT_SIZE`` (0x6C) and
|
||||
``ATTR_POSITION_X`` (0x70) active:
|
||||
|
||||
==================================================== ========================
|
||||
Byte Range Name
|
||||
==================================================== ========================
|
||||
0x000..0x080 ATTR_POINT_SIZE[0..31]
|
||||
0x000..0x100 ATTR_POSITION_X[0..31]
|
||||
0x180..0x200 ATTR_POINT_SIZE[32..63]
|
||||
0x200..0x280 ATTR_POSITION_X[32..63]
|
||||
0x280..0x300 ATTR_POINT_SIZE[63..95]
|
||||
0x300..0x380 ATTR_POSITION_X[63..95]
|
||||
... ...
|
||||
0x700..0x780 ATTR_POINT_SIZE[224..255]
|
||||
0x780..0x800 ATTR_POSITION_X[224..255]
|
||||
==================================================== ========================
|
||||
@@ -0,0 +1,159 @@
|
||||
Mesh Shading Notes
|
||||
=============================================
|
||||
|
||||
Mesh shaders support is present on Turing and later.
|
||||
It reuses the 3D engine and regular graphics stages.
|
||||
|
||||
Draw
|
||||
""""
|
||||
|
||||
When performing a draw with a mesh shader bound, ``SET_DRAW_CONTROL_A`` needs to
|
||||
be used while ignoring the base indices.
|
||||
|
||||
This can be done with the following commands:
|
||||
.. code-block::
|
||||
|
||||
NVC597_SET_VERTEX_ID_BASE
|
||||
.V = (0x0)
|
||||
NVC597_SET_DRAW_CONTROL_A
|
||||
.TOPOLOGY = POINTS
|
||||
.PRIMITIVE_ID = FIRST
|
||||
.INSTANCE_ID = FIRST
|
||||
.SPLIT_MODE = NORMAL_BEGIN_NORMAL_END
|
||||
.INSTANCE_ITERATE_ENABLE = FALSE
|
||||
.IGNORE_GLOBAL_BASE_VERTEX_INDEX = TRUE
|
||||
.IGNORE_GLOBAL_BASE_INSTANCE_INDEX = TRUE
|
||||
NVC597_DRAW_VERTEX_ARRAY_BEGIN_END_A
|
||||
.START = (0x0)
|
||||
NVC597_DRAW_VERTEX_ARRAY_BEGIN_END_B
|
||||
.COUNT = ($groupCount)
|
||||
|
||||
|
||||
Where:
|
||||
|
||||
- ``$groupCount`` is the number of local workgroups to dispatch.
|
||||
|
||||
**NOTE**: The topology of ``SET_DRAW_CONTROL_A`` will be ignored and
|
||||
``SET_MESH_SHADER_A`` topology will be used.
|
||||
|
||||
Stages
|
||||
""""""
|
||||
|
||||
Before binding a task or mesh shader, ``SET_MESH_CONTROL`` needs to be set.
|
||||
|
||||
Shared Memory
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Shared memory is allocated in the output :doc:`ISBE Attribute region <isbe_layout>` after all attributes.
|
||||
|
||||
Part of the shared memory can be made accessible to the next stage
|
||||
as part of the input :doc:`ISBE Attribute region <isbe_layout>` using ``.OUTPUT_TO_M_S_LINES``.
|
||||
|
||||
As such, the task payload is the first part of the shared memory on the task stage.
|
||||
|
||||
|
||||
Task
|
||||
^^^^
|
||||
|
||||
The task shader is always bound as a vertex shader.
|
||||
|
||||
Additionally, ``SET_MESH_INIT_SHADER`` is used to set the number of local invocations
|
||||
to use and the size used for shared memory.
|
||||
|
||||
This can be done with the following command:
|
||||
::
|
||||
|
||||
NVC597_SET_MESH_INIT_SHADER
|
||||
.THREAD_COUNT = ($thread_count)
|
||||
.LOCAL_BUFFER_LINES = ($local_buffer_lines)
|
||||
.OUTPUT_TO_M_S_LINES = ($output_to_m_s_lines)
|
||||
|
||||
Where:
|
||||
|
||||
- ``$thread_count`` is the number of local invocations (up to 32).
|
||||
- ``$local_buffer_lines`` is the total amount of shared memory lines (including task payload)
|
||||
to allocate in the output :doc:`ISBE Attribute region <isbe_layout>` after all attributes.
|
||||
- ``$output_to_m_s_lines`` is the total amount of shared memory lines that will be available
|
||||
to the next stage.
|
||||
|
||||
**NOTE**: The size of a memory line is 128 bytes.
|
||||
|
||||
The workgroup_index is implemented using the ``VERTEX_ID`` read from the input :doc:`ISBE Attribute region <isbe_layout>` (with SKEW applied).
|
||||
|
||||
``EmitMeshTasksEXT`` is lowered to the equivalent pseudo code:
|
||||
::
|
||||
|
||||
void EmitMeshTasksEXT(uint x, uint y, uint z) {
|
||||
uint taskCount = x * y * z;
|
||||
|
||||
ISBEWR.O.ATTR.32 [0x04], taskCount;
|
||||
ISBEWR.O.ATTR.32 [0x08], x;
|
||||
ISBEWR.O.ATTR.32 [0x0C], y;
|
||||
ISBEWR.O.ATTR.32 [0x10], z;
|
||||
}
|
||||
|
||||
Mesh
|
||||
^^^^
|
||||
|
||||
The mesh shader is bound to the vertex stage if no task shader is present
|
||||
or the tess control stage otherwise.
|
||||
|
||||
Attributes are stored in the output :doc:`ISBE Attribute region <isbe_layout>` **with SKEW applied**.
|
||||
|
||||
If any per primitive attributes are in use, they are stored after all per vertex
|
||||
attributes and the geometry stage will be enabled in passthrough mode with only
|
||||
its program header present.
|
||||
|
||||
For more details about the ISBE Attribute or Map layout, see the dedicated :doc:`ISBE <isbe_layout>` page.
|
||||
|
||||
Additionally, ``SET_MESH_SHADER_A`` and ``SET_MESH_SHADER_B`` are used
|
||||
to set the number of local invocations to use, the size used for shared memory
|
||||
and topology details.
|
||||
|
||||
This can be done with the following command:
|
||||
::
|
||||
|
||||
NVC597_SET_MESH_SHADER_A
|
||||
.OUTPUT_TOPOLOGY = $topology
|
||||
.MAX_VERTEX = ($max_vertex)
|
||||
.MAX_PRIMITIVE = ($max_primitive)
|
||||
NVC597_SET_MESH_SHADER_B
|
||||
.SHARED_MEM_LINES = ($shared_mem_lines)
|
||||
.THREAD_COUNT = ($thread_count)
|
||||
|
||||
Where:
|
||||
|
||||
- ``$topology`` is the topology to use.
|
||||
- ``$max_vertex`` is the max count of vertices used by the mesh shader.
|
||||
- ``$max_primitive`` is the max count primitives used by the mesh shader.
|
||||
- ``$shared_mem_lines`` is the total amount of shared memory line to allocate
|
||||
in the output :doc:`ISBE Attribute region <isbe_layout>` after all attributes.
|
||||
- ``$thread_count`` is the number of local invocations (up to 32).
|
||||
|
||||
**NOTE**: The size of a memory line is 128 bytes.
|
||||
|
||||
The workgroup_index is implemented in the following way:
|
||||
|
||||
- If a task shader is present, the value is read from the input :doc:`ISBE Attribute region <isbe_layout>` **without SKEW applied**.
|
||||
- Otherwise, it is implemented using the ``VERTEX_ID`` read from the input :doc:`ISBE Attribute region <isbe_layout>` **with SKEW applied**.
|
||||
|
||||
``SetMeshOutputsEXT`` is lowered to the equivalent pseudo code:
|
||||
::
|
||||
|
||||
void SetMeshOutputsEXT(uint vertexCount, uint primitiveCount) {
|
||||
// vertexCount is unused
|
||||
ISBEWR.O.MAP.32 [0x3], primitiveCount;
|
||||
}
|
||||
|
||||
**NOTE**: This is effectively a write to offset 0x0 but the output :doc:`ISBE Map region <isbe_layout>` process writes in reverse.
|
||||
|
||||
All primitive indices are stored as 8-bit indices starting at offset 0x4 in the ouptut :doc:`ISBE Map region <isbe_layout>`.
|
||||
|
||||
|
||||
Hardware limitations
|
||||
""""""""""""""""""""
|
||||
|
||||
* Only up to 32 local invocations are supported.
|
||||
* The shared memory being part of the :doc:`ISBE Attribute region <isbe_layout>` makes it that
|
||||
we do not have any atomics for it.
|
||||
* Task / mesh invocations need to be counted inside the shader.
|
||||
@@ -0,0 +1,88 @@
|
||||
Panfrost
|
||||
========
|
||||
|
||||
The Panfrost driver stack includes an OpenGL ES implementation for Arm Mali
|
||||
GPUs based on the Midgard and Bifrost microarchitectures. It is **conformant**
|
||||
on `Mali-G52 <https://www.khronos.org/conformance/adopters/conformant-products/opengles#submission_949>`__,
|
||||
`Mali-G57 <https://www.khronos.org/conformance/adopters/conformant-products/opengles#submission_980>`__
|
||||
and `Mali-G610 <https://www.khronos.org/conformance/adopters/conformant-products/opengles#submission_1053>`__,
|
||||
but **non-conformant** on other GPUs.
|
||||
|
||||
PanVK, the Vulkan implementation in the Panfrost driver stack, is currently
|
||||
**conformant** on `Mali-G610 <https://www.khronos.org/conformance/adopters/conformant-products#submission_939>`__,
|
||||
but *non-conformant* on other GPUs.
|
||||
|
||||
The following hardware is currently supported:
|
||||
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| Models | Architecture | OpenGL ES | OpenGL | Vulkan |
|
||||
+====================+===============+===========+========+========+
|
||||
| T600, T620, T720 | Midgard (v4) | 2.0 | 2.1 | |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| T760, T820, T830 | Midgard (v5) | 3.1 | 3.1 | |
|
||||
| T860, T880 | | | | |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G72 | Bifrost (v6) | 3.1 | 3.1 | 1.0 |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G31, G51, G52, G76 | Bifrost (v7) | 3.1 | 3.1 | 1.0 |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G57 | Valhall (v9) | 3.1 | 3.1 | |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G310, G610 | Valhall (v10) | 3.1 | 3.1 | 1.4 |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G720 | 5th Gen (v12) | 3.1 | 3.1 | 1.4 |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G725 | 5th Gen (v13) | 3.1 | 3.1 | 1.4 |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
| G1-Pro, G1-Premium | 5th Gen (v14) | 3.1 | 3.1 | 1.4 |
|
||||
| G1-Ultra | | | | |
|
||||
+--------------------+---------------+-----------+--------+--------+
|
||||
|
||||
Other Midgard and Bifrost chips (e.g. G71) are not yet supported.
|
||||
|
||||
Older Mali chips based on the Utgard architecture (Mali-400, Mali-450) are
|
||||
supported in the :doc:`Lima <lima>` driver, not Panfrost. Lima is also
|
||||
available in Mesa.
|
||||
|
||||
Other graphics APIs (OpenCL) are not supported at this time.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Panfrost's OpenGL support is a Gallium driver. Since Mali GPUs are 3D-only and
|
||||
do not include a display controller, Mesa uses kmsro to support display
|
||||
controllers paired with Mali GPUs. If your board with a Panfrost supported GPU
|
||||
has a display controller with mainline Linux support not supported by kmsro,
|
||||
it's easy to add support, see the commit ``cff7de4bb597e9`` as an example.
|
||||
|
||||
Build like ``meson . build/ -Dvulkan-drivers=panfrost
|
||||
-Dgallium-drivers=panfrost`` for a build directory
|
||||
``build``.
|
||||
|
||||
LLVM is required by Panfrost's compilers at build time.
|
||||
|
||||
In case of cross compilation without LLVM,
|
||||
you can build and install the required tools on the host (with LLVM installed) with
|
||||
``meson . build-host/ -Dtools=panfrost -Dmesa-clc=enabled -Dinstall-mesa-clc=true
|
||||
-Dprecomp-compiler=enabled -Dinstall-precomp-compiler=true``
|
||||
and then use ``-Dmesa-clc=system -Dprecomp-compiler=system`` on the cross compile side.
|
||||
|
||||
For general information on building Mesa, read :doc:`the install documentation
|
||||
<../install>`.
|
||||
|
||||
Chat
|
||||
----
|
||||
|
||||
Panfrost developers and users hang out on IRC at ``#panfrost`` on OFTC. Note
|
||||
that registering and authenticating with ``NickServ`` is required to prevent
|
||||
spam. `Join the chat. <https://webchat.oftc.net/?channels=panfrost>`__
|
||||
|
||||
Technical details
|
||||
-----------------
|
||||
|
||||
You can read more technical details about Panfrost here:
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
panfrost/*
|
||||
@@ -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
|
||||
@@ -0,0 +1,139 @@
|
||||
PowerVR
|
||||
=======
|
||||
|
||||
PowerVR is an open source Vulkan driver for Imagination Technologies PowerVR
|
||||
GPUs, starting with those based on the Rogue architecture.
|
||||
|
||||
The driver is conformant to Vulkan 1.2 on `BXS-4-64 <https://www.khronos.org/conformance/adopters/conformant-products#submission_981>`__,
|
||||
but **not yet on other GPUs and Vulkan versions**, so it requires exporting
|
||||
``PVR_I_WANT_A_BROKEN_VULKAN_DRIVER=1`` to the environment for GPUs that aren't
|
||||
conformant to any Vulkan version before running any Vulkan content.
|
||||
|
||||
The following hardware is currently in active development:
|
||||
|
||||
========= =========== ============== =======
|
||||
Product Series B.V.N.C Vulkan
|
||||
========= =========== ============== =======
|
||||
AXE-1-16M A-Series 33.15.11.3 1.2
|
||||
BXM-4-64 B-Series 36.52.104.182 1.2
|
||||
BXM-4-64 B-Series 36.56.104.183 1.2
|
||||
BXS-4-64 B-Series 36.53.104.796 1.2
|
||||
========= =========== ============== =======
|
||||
|
||||
The following hardware is partially supported and not currently
|
||||
under active development:
|
||||
|
||||
========= =========== ============== =======
|
||||
Product Series B.V.N.C Vulkan
|
||||
========= =========== ============== =======
|
||||
GX6250 Series 6XT 4.40.2.51 1.2
|
||||
========= =========== ============== =======
|
||||
|
||||
Various core-specific features and workarounds are likely to be unimplemented
|
||||
for this hardware. Some very simple Vulkan applications may run unhindered, but
|
||||
instability and corruption are to be expected until additional feature support
|
||||
and workarounds are in place.
|
||||
|
||||
The following hardware is unsupported and not under active development:
|
||||
|
||||
========= =========== ==============
|
||||
Product Series B.V.N.C
|
||||
========= =========== ==============
|
||||
G6110 Series 6XE 5.9.1.46
|
||||
GX6250 Series 6XT 4.45.2.58
|
||||
GX6650 Series 6XT 4.46.6.62
|
||||
GE7800 Series 7XE 15.5.1.64
|
||||
GE8300 Series 8XE 22.67.54.30
|
||||
GE8300 Series 8XE 22.68.54.30
|
||||
GE8300 Series 8XE 22.102.54.38
|
||||
BXE-2-32 B-Series 36.29.52.182
|
||||
BXE-4-32 B-Series 36.50.54.182
|
||||
========= =========== ==============
|
||||
|
||||
Device info and firmware_ have been made available for these devices, typically
|
||||
due to community requests or interest, but no support is guaranteed beyond this.
|
||||
|
||||
.. _firmware: https://gitlab.freedesktop.org/imagination/linux-firmware
|
||||
|
||||
In some cases, a product name is shared across multiple BVNCs so to check for
|
||||
support make sure the BVNC matches the one listed. As the feature set and
|
||||
hardware issues can vary between BVNCs, additional driver changes might be
|
||||
necessary even for devices sharing the same product name.
|
||||
|
||||
Hardware documentation can be found at: https://docs.imgtec.com/
|
||||
|
||||
Note: GPUs prior to Series6 do not have the hardware capabilities required to
|
||||
support Vulkan and therefore cannot be supported by this driver.
|
||||
|
||||
Multi-Architecture support
|
||||
--------------------------
|
||||
|
||||
To support multiple distinct hardware generations without too much
|
||||
spaghetti-code, the PowerVR build compiles a few files multiple times (once
|
||||
per hardware architecture) and uses a system of macros and aliases to be
|
||||
able to refer to the different versions.
|
||||
|
||||
The files that get compiled multiple times are those named
|
||||
:file:`pvr_arch_*.c`. These files contain definitions of functions prefixed
|
||||
with ``pvr_arch_`` (instead of the normal ``pvr_``-prefix). The ``arch``-bit
|
||||
of that function is a placeholder, which gets replaced at compile-time, thanks
|
||||
to a system of defines in the corresponding header file, supported by a set
|
||||
of macros defined in :file:`pvr_macros.h`.
|
||||
|
||||
The intention is that these functions are mostly called from architecture
|
||||
specific entrypoints, that are handled by the common vulkan dispatch-table
|
||||
code. This means that an architecture specific function can easily call either
|
||||
architecture specific or architecture agnostic code.
|
||||
|
||||
The tricky bit comes when architecture agnostic code calls architecture
|
||||
specific code. In that case, we have the ``PVR_ARCH_DISPATCH`` and
|
||||
``PVR_ARCH_DISPATCH_RET`` macros. These are a bit error-prone to use because
|
||||
they need to see definitions for all architecture versions of each entrypoint,
|
||||
which isn't something we have available. To work around this, we define a
|
||||
``PER_ARCH_FUNCS(arch)`` macro in each source-file that needs to use these
|
||||
dispatch macros and make sure to instantiate it once per architecture.
|
||||
|
||||
To avoid confusion, please do not add functions that are prefixed with
|
||||
``pvr_arch_`` if they are not part of the system described here.
|
||||
|
||||
drm-shim
|
||||
--------
|
||||
|
||||
PowerVR implements ``drm-shim``, stubbing out the powervr DRM kernel interface.
|
||||
This allows for use-cases such as offline compiler testing and control-stream
|
||||
dumping.
|
||||
|
||||
To build Mesa with the PowerVR drm-shim, configure Meson with
|
||||
``-Dvulkan-drivers=imagination`` and ``-Dtools=drm-shim``.
|
||||
The drm-shim binary will be built to
|
||||
``build/src/imagination/drm-shim/libpowervr_noop_drm_shim.so``.
|
||||
|
||||
To use, set the ``LD_PRELOAD`` environment variable to the drm-shim binary.
|
||||
|
||||
By default, drm-shim mocks a BXS-4-64 with a BVNC of 36.53.104.796,
|
||||
however this can be overridden by setting the ``PVR_SHIM_DEVICE_BVNC``
|
||||
environment variable. The value to set should match those found in the
|
||||
``struct pvr_device_ident`` instances defined in
|
||||
``src/imagination/common/device_info/*.h``.
|
||||
|
||||
The reported quirks and enhancements can be overridden by setting the
|
||||
``PVR_SHIM_MUSTHAVE_QUIRKS``, ``PVR_SHIM_QUIRKS``, and ``PVR_SHIM_ENHANCEMENTS``
|
||||
environment variables. These should be set to a comma/semicolon-separated list
|
||||
of quirk/enhancement IDs.
|
||||
|
||||
Chat
|
||||
----
|
||||
|
||||
PowerVR developers and users hang out on IRC at ``#powervr`` on OFTC. Note
|
||||
that registering and authenticating with ``NickServ`` is required to prevent
|
||||
spam. `Join the chat. <https://webchat.oftc.net/?channels=powervr>`_
|
||||
|
||||
Hardware glossary
|
||||
-----------------
|
||||
|
||||
.. glossary:: :sorted:
|
||||
|
||||
BVNC
|
||||
Set of four numbers used to uniquely identify each GPU (Series6 onwards).
|
||||
This is used to determine the GPU feature set, along with any known
|
||||
hardware issues.
|
||||
@@ -0,0 +1,234 @@
|
||||
RADV
|
||||
====
|
||||
|
||||
RADV is a Vulkan driver for AMD GCN/RDNA GPUs.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
RADV is a userspace driver that implements the Vulkan API on most modern AMD GPUs.
|
||||
|
||||
Many Linux distributions include RADV in their default installation as part of their Mesa packages.
|
||||
It is also the Vulkan driver on the Steam Deck, the handheld console developed by Valve.
|
||||
|
||||
Features
|
||||
~~~~~~~~
|
||||
|
||||
The easiest way to track the feature set of RADV (and other Vulkan drivers in Mesa) is to
|
||||
take a look at the
|
||||
`Mesa matrix <https://mesamatrix.net/#Vulkan>`__.
|
||||
|
||||
Contributing to RADV
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
RADV is part of Mesa, so we recommend reading Mesa's
|
||||
guidelines for :doc:`submitting patches <../submittingpatches>`.
|
||||
|
||||
Additionally, the RADV team agreed on the following workflow for contributions:
|
||||
|
||||
* Do NOT merge any MRs without at least one approval from someone familiar with
|
||||
the code.
|
||||
* There are different types of approvals:
|
||||
|
||||
- Reviewed-by tag
|
||||
|
||||
- MR approval button
|
||||
|
||||
- Acked-by tag
|
||||
|
||||
* After receiving the first approval, please wait at least 24 hours (excluding
|
||||
weekends) before merging the MR. This is to make sure that anyone who may be
|
||||
interested had opportunity and time to look at the MR (eg. timezone
|
||||
differences). The wait is not required in the following cases:
|
||||
|
||||
- The MR is trivial (eg. very simple cleanups, typos, CI flakes updates)
|
||||
|
||||
- The MR has been approved by two or more developers
|
||||
|
||||
* Even if your MR has been approved and is ready to be merged, please make sure
|
||||
to resolve any open conversations about potentially controversial commits.
|
||||
|
||||
In case your MR needs to be merged immediately (should be rarely needed), feel
|
||||
free to ping the developers to get more approvals.
|
||||
|
||||
Supported hardware
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All GCN and RDNA GPUs that are supported by the Linux kernel (and capable of graphics)
|
||||
are also supported by RADV, starting from GCN 1.
|
||||
We are always working on supporting the very latest GPUs too.
|
||||
|
||||
Vulkan API support:
|
||||
|
||||
* GFX6-7 (GCN 1-2): Vulkan 1.3
|
||||
* GFX8 and newer (GCN 3-5 and RDNA): Vulkan 1.4
|
||||
|
||||
`The exact list of Vulkan conformant products can be seen here. <https://www.khronos.org/conformance/adopters/conformant-products>`__
|
||||
|
||||
Each GPU chip can contain various hardware blocks (also known as IP blocks), and each of those are separately versioned.
|
||||
We usually refer to hardware generations by the main version number of the GFX (graphics) hardware block.
|
||||
|
||||
Each hardware generation has different chips which usually only have minor differences between each other,
|
||||
such as number of compute units and different minor versions of some IP blocks.
|
||||
We refer to each chip by the code name of its first release, and we don't differentiate between
|
||||
refreshes of the same chip, because they are functionally exactly the same.
|
||||
|
||||
For more information about which GPU chip name corresponds to which GPU product,
|
||||
`see the src/amd/common/amd_family.h file <https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/amd_family.h>`__.
|
||||
|
||||
Note that for GFX6-7 (GCN 1-2) GPUs, the ``amdgpu`` kernel driver is currently not the default in Linux
|
||||
(by default the old ``radeon`` KMD is used for these old GPUs, which is not supported by RADV),
|
||||
so users need to manually enable ``amdgpu`` by adding the following to the kernel command line:
|
||||
``radeon.si_support=0 radeon.cik_support=0 amdgpu.si_support=1 amdgpu.cik_support=1``
|
||||
|
||||
Basics
|
||||
~~~~~~
|
||||
|
||||
The RADV source code is located in ``src/amd/vulkan``.
|
||||
|
||||
RADV is a userspace driver, compiled to a shared library file.
|
||||
On Linux, typically ``libvulkan_radeon.so`` (equivalent to a ``.dll`` on Windows).
|
||||
|
||||
When you start a Vulkan application, the Vulkan loader (in userspace)
|
||||
will find a set of Vulkan drivers (also known as Vulkan implementations),
|
||||
all of which are technically shared libraries.
|
||||
If you are running on a system with a supported AMD GPU and have RADV installed,
|
||||
the loader will find ``libvulkan_radeon.so`` and load that.
|
||||
The Vulkan application can then choose which available Vulkan implementation to use.
|
||||
|
||||
With RADV, when the application makes Vulkan API calls (aka. entry points),
|
||||
the ``vk*`` functions will end up calling ``radv_*`` functions.
|
||||
For example, ``vkCmdDrawMeshTasksEXT`` will actually call ``radv_CmdDrawMeshTasksEXT``.
|
||||
|
||||
Responsibilities of RADV vs. the kernel driver
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Due to the complexity of how modern GPUs work, the graphics stack is split
|
||||
between kernel-mode drivers (KMD) and user-mode drivers (UMD).
|
||||
All Graphics APIs such as Vulkan, OpenGL, etc. are implemented in userspace.
|
||||
|
||||
RADV is a UMD that currently works with the ``amdgpu`` KMD in the Linux kernel.
|
||||
Interacting with the KMD is done by RADV's winsys code.
|
||||
|
||||
The KMD is responsible for:
|
||||
|
||||
* Talking to the GPU through a PCIe port and power management (such as choosing voltage, frequency, sleep modes etc.)
|
||||
* Display functionality
|
||||
* Video memory management (and GTT)
|
||||
* Writing submitted commands to the GPU's ring buffers
|
||||
|
||||
The UMD (in our case, RADV) is responsible for:
|
||||
|
||||
* Recording commands in a binary format that the GPU can understand
|
||||
* Programming GPU registers for correct functionality
|
||||
* Compiling shaders to the GPU's native ISA (instruction set architecture)
|
||||
and uploading them to memory accessible by the GPU
|
||||
* Submitting recorded commands to the kernel through a system call
|
||||
|
||||
To communicate with ``amdgpu``, RADV relies on the DRM userspace API (uAPI) of the Linux kernel,
|
||||
which is a set of system calls.
|
||||
RADV depends on ``libdrm`` for some functionality and for others it uses the system calls directly.
|
||||
|
||||
Command submission and PM4 packets
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Vulkan applications record a series of commands in command buffers and
|
||||
later submit these command buffers to one of the queues in the GPU.
|
||||
|
||||
Command buffer recording in RADV is implemented by emitting packets in a buffer,
|
||||
which is called a command stream (CS).
|
||||
|
||||
Command packets are more or less analogous to Vulkan API calls,
|
||||
which means that each Vulkan command (such as draw or dispatch)
|
||||
corresponds to one or more packets.
|
||||
However, depending on how closely the hardware follows Vulkan spec
|
||||
some commands will require a more complex implementation.
|
||||
For example, a simple ``vkCmdDraw`` call will result in just one packet,
|
||||
however emitting draw state (before the draw) may take dozens of packets.
|
||||
|
||||
For the graphics queue (GFX) and async compute queue (ACE), the PM4 packet
|
||||
format is used; other queues such as SDMA and the various video queues
|
||||
have their own format. Commands typically vary between different GPU generations.
|
||||
|
||||
When submitting to a queue, several CSs are submitted to the kernel at once.
|
||||
The kernel terminology calls these indirect buffers (IB) because the kernel
|
||||
typically uses the ``INDIRECT_BUFFER`` PM4 command to execute them.
|
||||
Modern AMD GPUs have several queues, which more or less map to the Vulkan
|
||||
queue types, though sometimes we need to submit to more than one HW queue
|
||||
at the same time.
|
||||
|
||||
After command submission, the packets in the IBs will be executed
|
||||
by the command processor (CP) of the queue that the buffer was submitted to.
|
||||
The exact capabilities and supported commands of the CP depend on HW generation and queue type.
|
||||
|
||||
Each CP has a collection of registers that control how the CP behaves.
|
||||
Registers in the CP are not to be confused with registers in shaders.
|
||||
For example, the addresses of shader programs, some details of shader execution,
|
||||
draw call information etc. have a corresponding register.
|
||||
These registers can be typically set by PM4 packets.
|
||||
|
||||
Shader compilation
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
One of the main responsibilities of RADV is compiling shaders for Vulkan applications.
|
||||
|
||||
RADV relies on the Mesa shader compiler stack.
|
||||
Here is a rough overview of how it works:
|
||||
|
||||
* Vulkan applications pass shaders to RADV in the SPIR-V format
|
||||
* RADV calls ``spirv_to_nir`` which translates the shader into the NIR intermediate representation
|
||||
* We perform various optimizations and lowerings on the NIR shader
|
||||
* The shader is linked with the other shaders it is compiled with (if any)
|
||||
* Still using NIR, the shader is further lowered to be closer to the hardware
|
||||
* Finally, we pass the lowered NIR shader to ACO, our compiler backend, which compiles it into GPU specific ISA
|
||||
|
||||
ACO is the default shader-compiler used in RADV.
|
||||
`Read its documentation here. <https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/compiler/README.md>`__
|
||||
|
||||
We still maintain an LLVM based compiler backend too,
|
||||
which is these days solely used for testing and hardware bringup.
|
||||
Users are recommended **NOT** to use the LLVM backend.
|
||||
|
||||
Shader execution
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Some commands (such as draws and dispatches) ask the CP to launch shaders.
|
||||
Shader launch is handled by the firmware, based on registers that control shader programs.
|
||||
Additionally, draw commands will also automatically use the appropriate fixed function
|
||||
units in the hardware.
|
||||
|
||||
Shaders are executed by a so-called compute unit on the GPU, which is a SIMD machine.
|
||||
A shader invocation is a single SIMD lane (AMD also calls it thread, not to be confused by a CPU HW thread),
|
||||
and a subgroup is all 64 or 32 SIMD lanes together (also known as a wave).
|
||||
Each wave is a separate running instance of a shader program,
|
||||
but multiple waves can be grouped together into workgroups.
|
||||
|
||||
Registers in shaders (not to be confused with registers in the CP):
|
||||
|
||||
* VGPR - vector general purpose register: each SIMD lane has a different value for this register
|
||||
* SGPR - scalar general purpose register: same value within a wave
|
||||
|
||||
For further reading, AMD has publised whitepapers and documentation for the GCN and RDNA GPU architectures.
|
||||
`These can be found on their GPUOpen site. <https://gpuopen.com/amd-gpu-architecture-programming-documentation/>`__
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
For a list of environment variables to debug RADV, please see
|
||||
:ref:`radv env-vars` for a list.
|
||||
|
||||
Instructions for debugging GPU hangs can be found :ref:`here <radv-debug-hang>`.
|
||||
|
||||
Hardware Documentation
|
||||
----------------------
|
||||
|
||||
You can find a list of documentation for the various generations of
|
||||
AMD hardware on the `X.Org wiki
|
||||
<https://www.x.org/wiki/RadeonFeature/#documentation>`__.
|
||||
|
||||
Additional community-written documentation is also available in Mesa:
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
|
||||
amd/hw/*
|
||||
@@ -0,0 +1,229 @@
|
||||
VMware SVGA3D
|
||||
=============
|
||||
|
||||
This page describes how to build, install and use the
|
||||
`VMware <https://www.vmware.com/>`__ guest GL driver (aka the SVGA or
|
||||
SVGA3D driver) for Linux using the latest source code. This driver gives
|
||||
a Linux virtual machine access to the host's GPU for
|
||||
hardware-accelerated 3D. VMware Workstation running on Linux or Windows
|
||||
and VMware Fusion running on MacOS are all supported.
|
||||
|
||||
With VMware Workstation 17 / Fusion 13 releases, OpenGL 4.3 is
|
||||
supported in the guest. This requires
|
||||
|
||||
- The vmwgfx kernel module version 2.20 or later
|
||||
- The VM needs to be configured to hardware version 20 or later.
|
||||
- MESA 22.0 or later should be installed.
|
||||
|
||||
You can disable GL4.3 support using environment variable SVGA_GL43=0 or
|
||||
lowering hardware version.
|
||||
|
||||
Most modern Linux distributions include the SVGA3D driver so end users
|
||||
shouldn't be concerned with this information. But if your distributions
|
||||
lacks the driver or you want to update to the latest code these
|
||||
instructions explain what to do.
|
||||
|
||||
Getting VMware Workstation Pro for Linux
|
||||
----------------------------------------
|
||||
|
||||
Before you can run Mesa on an SVGA3D device in a VM, you need to actually have
|
||||
a hypervisor that supports SVGA3D in its entirety. Such hypervisors are VMware's
|
||||
Workstaton Pro (Linux, Windows) and Fusion (macOS). Here we list the steps to
|
||||
get VMware Workstation Pro running on Linux.
|
||||
|
||||
- Step 1: Find VMware Workstation Pro to download
|
||||
|
||||
- Step 1.1: Create a broadcom.com account
|
||||
|
||||
* Go to https://support.broadcom.com and click "log in" at the top-right
|
||||
* Create an account
|
||||
* Go to your email to verify your account
|
||||
* Log in to your account
|
||||
|
||||
- Step 1.2: Once logged in, navigate to the Free Downloads section
|
||||
|
||||
* Follow link to Free Downloads section https://support.broadcom.com/group/ecx/free-downloads
|
||||
|
||||
- Step 1.3: Select your VMware Workstation Pro release
|
||||
|
||||
* In the search box type "VMware Workstation Pro", wait for results to show up
|
||||
* Select "VMware Workstation Pro"
|
||||
* Select version 17 (as of this writing) platform -- Linux or Windows
|
||||
* Select the desired full version -- 17.6.3 as of this writing
|
||||
|
||||
- Step 1.4: Accept the Terms & Conditions and download
|
||||
|
||||
* There's a checkbox for this above the file list - tick it
|
||||
* Click the cloud-download button-icon for the Linux version at the bottom of the list
|
||||
* You will now be prompted to enter your full name and address for export controls
|
||||
* It will probably return you to the base support page now, so repeat steps 1.2, 1.3, 1.4
|
||||
* You are now downloading the vmware workstation bundle
|
||||
|
||||
- Step 2: Install VMware Workstation Pro
|
||||
|
||||
* It is advisable to uninstall any previous version first; check for potentially installed *vmware-workstation*
|
||||
|
||||
::
|
||||
|
||||
sudo ./VMware-Workstation-Full-17.6.3-24583834.x86_64.bundle -l
|
||||
|
||||
* If present, proceed to uninstall it; make sure to keep your old configuration if asked!
|
||||
|
||||
::
|
||||
|
||||
sudo ./VMware-Workstation-Full-17.6.3-24583834.x86_64.bundle --uninstall-component=vmware-workstation
|
||||
|
||||
* Provide kernel headers for Workstation kernel modules built by the bundle setup script
|
||||
* On Ubuntu
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install linux-headers-$(uname -r)
|
||||
|
||||
* On Fedora
|
||||
|
||||
::
|
||||
|
||||
sudo dnf install linux-devel-$(uname -r)
|
||||
|
||||
* Run the following to install the package
|
||||
|
||||
::
|
||||
|
||||
sudo ./VMware-Workstation-Full-17.6.3-24583834.x86_64.bundle
|
||||
|
||||
- Step 3: Run VMware Workstation Pro
|
||||
|
||||
::
|
||||
|
||||
/usr/bin/vmware
|
||||
|
||||
|
||||
Components
|
||||
----------
|
||||
|
||||
The components involved in this include:
|
||||
|
||||
- Linux kernel module: vmwgfx
|
||||
- User-space libdrm library
|
||||
- Mesa/Gallium OpenGL driver: "svga"
|
||||
|
||||
All of these components reside in the guest Linux virtual machine. On
|
||||
the host, all you're doing is running VMware
|
||||
`Fusion or Workstation <https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion>`__.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- vmwgfx Kernel module version at least 2.20
|
||||
- Ubuntu: For Ubuntu you need to install a number of build
|
||||
dependencies.
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install autoconf automake libtool flex bison zstd
|
||||
sudo apt-get install build-essential g++ git
|
||||
sudo apt-get install libexpat1-dev libpciaccess-dev \
|
||||
libpthread-stubs0-dev \
|
||||
libudev-dev libx11-xcb-dev \
|
||||
libxcb-dri2-0-dev libxcb-dri3-dev
|
||||
sudo apt-get install libxcb-glx0-dev libxcb-present-dev \
|
||||
libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
sudo apt-get install libxdamage-dev libxext-dev \
|
||||
libxfixes-dev libxkbcommon-dev
|
||||
sudo apt-get install libxml2-dev libxrandr-dev \
|
||||
libxshmfence-dev libxxf86vm-dev
|
||||
sudo apt-get install mesa-utils meson ninja-build \
|
||||
pkg-config python3-mako python3-setuptools
|
||||
sudo apt-get install x11proto-dri2-dev x11proto-gl-dev \
|
||||
xutils-dev libglvnd-dev
|
||||
|
||||
Depending on your Linux distribution, other packages may be needed. Meson
|
||||
should tell you what's missing.
|
||||
|
||||
Getting the Latest Source Code
|
||||
------------------------------
|
||||
|
||||
Begin by saving your current directory location:
|
||||
|
||||
::
|
||||
|
||||
export TOP=$PWD
|
||||
|
||||
|
||||
- Mesa/Gallium main branch. This code is used to build libGL, and the
|
||||
direct rendering svga driver for libGL, vmwgfx_dri.so, and the X
|
||||
acceleration library libxatracker.so.x.x.x.
|
||||
|
||||
::
|
||||
|
||||
git clone https://gitlab.freedesktop.org/mesa/mesa.git
|
||||
|
||||
|
||||
- libdrm, a user-space library that interfaces with DRM. Most
|
||||
distributions ship with this but it's safest to install a newer
|
||||
version. To get the latest code from Git:
|
||||
|
||||
::
|
||||
|
||||
git clone https://gitlab.freedesktop.org/mesa/drm.git
|
||||
|
||||
|
||||
Building the Code
|
||||
-----------------
|
||||
|
||||
- Determine where the GL-related libraries reside on your system and
|
||||
set the LIBDIR environment variable accordingly.
|
||||
|
||||
For Ubuntu systems:
|
||||
|
||||
::
|
||||
|
||||
export LIBDIR=/usr/lib/x86_64-linux-gnu
|
||||
|
||||
|
||||
- Build libdrm:
|
||||
|
||||
::
|
||||
|
||||
cd $TOP/drm
|
||||
meson builddir --prefix=/usr --libdir=${LIBDIR}
|
||||
meson compile -C builddir
|
||||
sudo meson install -C builddir
|
||||
|
||||
|
||||
- Build Mesa:
|
||||
|
||||
::
|
||||
|
||||
cd $TOP/mesa
|
||||
meson builddir -Dvulkan-drivers= -Dgallium-drivers=svga -Ddri-drivers= -Dglvnd=enabled -Dglvnd-vendor-name=mesa
|
||||
|
||||
meson compile -C builddir
|
||||
sudo meson install -C builddir
|
||||
|
||||
|
||||
Note that you may have to install other packages that Mesa depends
|
||||
upon if they're not installed in your system. You should be told
|
||||
what's missing.
|
||||
|
||||
The generated vmwgfx_dri.so is used by the OpenGL libraries during direct rendering, and by the X.Org
|
||||
server during accelerated indirect GL rendering.
|
||||
|
||||
Running OpenGL Programs
|
||||
-----------------------
|
||||
|
||||
In a shell, run 'glxinfo' and look for the following to verify that the
|
||||
driver is working:
|
||||
|
||||
::
|
||||
|
||||
OpenGL vendor string: VMware, Inc.
|
||||
OpenGL renderer string: SVGA3D; build: RELEASE;
|
||||
OpenGL version string: 4.3 (Compatibility Profile) Mesa 23.0
|
||||
|
||||
If OpenGL 4.3 is not working (you only get OpenGL 4.1):
|
||||
|
||||
- Make sure the VM uses hardware version 20 or later.
|
||||
- Make sure the vmwgfx kernel module is version 2.20.0 or later.
|
||||
- Check the vmware.log file for errors.
|
||||
@@ -0,0 +1,50 @@
|
||||
V3D
|
||||
===
|
||||
|
||||
Mesa's V3D graphics driver stack includes a `conformant GLES3.1
|
||||
driver
|
||||
<https://www.khronos.org/conformance/adopters/conformant-products/opengles#submission_882>`__
|
||||
called V3D and a Vulkan graphics driver called V3DV, notably
|
||||
used on the Raspberry Pi 4 and Raspberry Pi 5.
|
||||
|
||||
The V3D Mesa drivers communicate directly with the `V3D
|
||||
<https://www.kernel.org/doc/html/latest/gpu/v3d.html>`__ kernel DRM
|
||||
driver for scheduling GPU commands. Additionally, on the Raspberry Pi
|
||||
4 and 5, the kernel uses the VC4 DRM driver for display support, so Mesa
|
||||
exposes a ``vc4_dri.so`` using the kmsro helpers to do
|
||||
behind-the-scenes buffer management between the two kernel drivers,
|
||||
while executing rendering on the V3D kernel module.
|
||||
|
||||
Initial development work was done on the Broadcom 7268 (V3D 3.3) and
|
||||
7278 (V3D 4.1). Development since then has been on V3D 4.2 (Raspberry
|
||||
Pi 4), and V3D 7.1 (Raspberry Pi 5). When the support for V3D 7.1
|
||||
landed, the support for 3.3 and 4.1 was dropped as it was not tested
|
||||
anymore (see `MR#25851
|
||||
<https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/25851>`__)
|
||||
Broadcom's reference software platforms do not make use of the open
|
||||
source V3D stack, but porting a particular hardware implementation to
|
||||
use it would still be possible.
|
||||
|
||||
Hardware Documentation
|
||||
----------------------
|
||||
|
||||
Broadcom never released a public specification for the V3D 3.x or 4.x
|
||||
series.
|
||||
|
||||
For driver developers, Broadcom publicly released a `specification
|
||||
<https://docs.broadcom.com/doc/12358545>`__ PDF for the 21553, which
|
||||
is closely related to the VC4 GPU present in the Raspberry Pi. They
|
||||
also released a `snapshot <https://docs.broadcom.com/docs/12358546>`__
|
||||
of a corresponding Android graphics driver. That graphics driver was
|
||||
ported to Raspbian for a demo, but was not expected to have ongoing
|
||||
development.
|
||||
|
||||
Developers with NDA access with Broadcom or Raspberry Pi can get
|
||||
access to the V3D architecture specification for documentation of the
|
||||
GPU's programming model. There is also a C++ software simulator
|
||||
called simpenrose, and the Mesa driver includes a backend
|
||||
(``src/broadcom/drm-shim/``) to use simpenrose from an x86 system with
|
||||
the i915 graphics driver with all of the VC4 rendering commands
|
||||
emulated on simpenrose and memcpyed to the real GPU. Note that
|
||||
simpenrose's API drifts over time, so you need to be synced up with
|
||||
whatever version Mesa was last being developed against.
|
||||
@@ -0,0 +1,298 @@
|
||||
VC4
|
||||
===
|
||||
|
||||
Mesa's VC4 graphics driver supports multiple implementations of
|
||||
Broadcom's VideoCore IV GPU. It is notably used in the Raspberry Pi 0
|
||||
through Raspberry Pi 3 hardware, and the driver is included as an
|
||||
option as of the 2016-02-09 Raspbian release using ``raspi-config``.
|
||||
On most other distributions such as Debian or Fedora, you need no
|
||||
configuration to enable the driver.
|
||||
|
||||
This Mesa driver talks directly to the `VC4
|
||||
<https://www.kernel.org/doc/html/latest/gpu/vc4.html>`__ kernel DRM
|
||||
driver for scheduling graphics commands, and that module also provides
|
||||
KMS display support. The driver makes no use of the closed source VPU
|
||||
firmware on the VideoCore IV block, instead talking directly to the
|
||||
GPU block from Linux.
|
||||
|
||||
GLES2 support
|
||||
-------------
|
||||
|
||||
The VC4 driver is a nearly conformant GLES2 driver, and the hardware
|
||||
has achieved GLES2 conformance with other driver stacks.
|
||||
|
||||
OpenGL support
|
||||
--------------
|
||||
|
||||
Along with GLES 2.0, the Mesa driver also exposes OpenGL 2.1, which is
|
||||
mostly correct but with a few caveats.
|
||||
|
||||
* 4-byte index buffers.
|
||||
|
||||
GLES2.0, and VC4, don't have ``GL_UNSIGNED_INT`` index buffers. To support
|
||||
them in VC4, we create a shadow copy of your index buffer with the
|
||||
indices truncated to 2 bytes. This is incorrect (and will assertion
|
||||
fail in debug builds of Mesa) if any of the indices were >65535. To
|
||||
fix that, we would need to detect this case and rewrite the index
|
||||
buffer and vertex buffers to do a series of draws each with small
|
||||
indices and new vertex attrib bindings.
|
||||
|
||||
To avoid this problem, ensure that all index buffers are written using
|
||||
``GL_UNSIGNED_SHORT``, even at the cost of doing multiple draw calls
|
||||
with updated vertex attrib bindings.
|
||||
|
||||
* Occlusion queries
|
||||
|
||||
The VC4 hardware has no support for occlusion queries. GL 2.0
|
||||
requires that you support the occlusion queries extension, but you can
|
||||
report 0 from ``glGetQueryiv(GL_SAMPLES_PASSED,
|
||||
GL_QUERY_COUNTER_BITS)``. This is absurd, but it's how OpenGL handles
|
||||
"we want the functions to be present everywhere, but we want it to be
|
||||
optional for hardware to support it. Sadly, gallium doesn't yet allow
|
||||
the driver to report 0 query bits.
|
||||
|
||||
* Primitive mode
|
||||
|
||||
VC4 doesn't support reducing triangles/quads/polygons to lines and
|
||||
points like desktop GL. If front/back mode matched, we could rewrite
|
||||
the index buffer to the new primitive type, but we don't. If
|
||||
front/back mode don't match, we would need to run the vertex shader in
|
||||
software, classify the prims, write new index buffers, and emit
|
||||
(possibly many) new draw calls to rasterize the new prims in the same
|
||||
order.
|
||||
|
||||
Bug Reporting
|
||||
-------------
|
||||
|
||||
VC4 rendering bugs should go to Mesa's GitLab `issues
|
||||
<https://gitlab.freedesktop.org/mesa/mesa/-/issues>`__ page.
|
||||
|
||||
By far the easiest way to communicate bug reports for rendering
|
||||
problems is to take an apitrace. This passes exactly the drawing you
|
||||
saw to the developer, without the developer needing to download and
|
||||
build the application and replicate whatever steps you took to produce
|
||||
the problem. Traces attached to bug reports should ideally be small.
|
||||
|
||||
For GPU hangs, if you can get a short apitrace that produces the
|
||||
problem, that's still the best. If the problem takes a long time to
|
||||
reproduce or you can't capture it in a trace, describing how to
|
||||
reproduce and including a GPU hang dump would be the most
|
||||
useful. Install `vc4-gpu-tools
|
||||
<https://github.com/anholt/vc4-gpu-tools/>`__ and use
|
||||
``vc4_dump_hang_state my-app.hang``. Sometimes the hang file will
|
||||
provide useful information.
|
||||
|
||||
Tiled Rendering
|
||||
---------------
|
||||
|
||||
VC4 is a tiled renderer, chopping the screen into 64x64 (non-MSAA) or
|
||||
32x32 (MSAA) tiles and rendering the scene per tile. Rasterization
|
||||
looks like::
|
||||
|
||||
(CPU) Allocate space to store a list of draw commands per tile
|
||||
(CPU) Set up a command list per tile that does:
|
||||
Either load the current tile's color buffer from memory, or clear it.
|
||||
Either load the current tile's depth buffer from memory, or clear it.
|
||||
Branch into the draw list for the tile
|
||||
Store the depth buffer if anybody might read it.
|
||||
Store the color buffer if anybody might read it.
|
||||
(GPU) Initialize the per-tile draw call lists to empty.
|
||||
(GPU) Run all draw calls collecting vertex data
|
||||
(GPU) For each tile covered by a draw call's primitive.
|
||||
Emit state packets to the list to update it to the current draw call's state.
|
||||
Emit a primitive description into the tile's draw call list.
|
||||
|
||||
Tiled rendering avoids the need for large render target caches, at the
|
||||
expense of increasing the cost of vertex processing. Unlike some tiled
|
||||
renderers, VC4 has no non-tiled rendering mode.
|
||||
|
||||
Performance Tricks
|
||||
------------------
|
||||
|
||||
* Reducing memory bandwidth by clearing.
|
||||
|
||||
Even if your drawing is going to cover the entire render target, it's
|
||||
more efficient for VC4 if you emit a ``glClear()`` of the color and
|
||||
depth buffers. This means we can skip the load of the previous state
|
||||
from memory, in favor of a cheap GPU-side ``memset()`` of the tile
|
||||
buffer before we start running the draw calls.
|
||||
|
||||
* Reducing memory bandwidth with scissoring.
|
||||
|
||||
If all draw calls for the frame are with a ``glScissor()`` to only
|
||||
part of the screen, then we can skip setting up the tiles for that
|
||||
area, which means a little less memory used setting up the empty bins,
|
||||
and a lot less memory used loading/storing the unchanged tiles.
|
||||
|
||||
* Reducing memory bandwidth with ``glInvalidateFramebuffer()``.
|
||||
|
||||
If we don't know who might use the contents of the framebuffer's depth
|
||||
or color in the future, then we have to store it for later. If you use
|
||||
glInvalidateFramebuffer() before accessing the results of your
|
||||
rendering, then we can skip the store of the depth or color
|
||||
buffer. Note that this is unimplemented.
|
||||
|
||||
* Avoid non-constant GLSL array indexing
|
||||
|
||||
In VC4 the only non-constant-index array access supported in hardware
|
||||
is uniforms. For everything else (inputs, outputs, temporaries), we
|
||||
have to lower them to an IF ladder like::
|
||||
|
||||
if (index == 0)
|
||||
return array[0]
|
||||
else if (index == 1)
|
||||
return array[1]
|
||||
...
|
||||
|
||||
This is very expensive as we probably have to execute every branch of
|
||||
every IF statement due to it being a SIMD machine. So, it is
|
||||
recommended (if you can) to avoid non-uniform non-constant array
|
||||
indexing.
|
||||
|
||||
Note that if you do variable indexing within a bounded loop that Mesa
|
||||
can unroll, that can actually count as constant indexing.
|
||||
|
||||
* Increasing GPU memory Increase CMA pool size
|
||||
|
||||
The memory for the VC4 driver is allocated from the standard Linux CMA
|
||||
pool. The size of this pool defaults to 64 MB. To increase this, pass
|
||||
an additional parameter on the kernel command line. Edit the boot
|
||||
partition's ``cmdline.txt`` to add::
|
||||
|
||||
cma=256M@256M
|
||||
|
||||
``cmdline.txt`` is a single line with whitespace separated parameters.
|
||||
|
||||
The first value is the size of the pool and the second parameter is
|
||||
the start address of the pool. The pool size can be increased further,
|
||||
but it must fit into the memory, so size + start address must be below
|
||||
1024M (Pi 2, 3, 3+) or 512M (Pi B, B+, Zero, Zero W). Also this
|
||||
reduces the memory available to Linux.
|
||||
|
||||
* Decrease firmware memory
|
||||
|
||||
The firmware allocates a fixed chunk of memory before booting
|
||||
Linux. If firmware functions are not required, this amount can be
|
||||
reduced.
|
||||
|
||||
In ``config.txt`` edit ``gpu_mem`` to 16, if you do not need video decoding,
|
||||
edit gpu_mem to 64 if you need video decoding.
|
||||
|
||||
Performance debugging
|
||||
---------------------
|
||||
|
||||
* Step 1: Known issues
|
||||
|
||||
The first tool to look at is running your application with the
|
||||
environment variable ``VC4_DEBUG=perf`` set. This will report debug
|
||||
information for many known causes of performance problems on the
|
||||
console. Not all of them will cause visible performance improvements
|
||||
when fixed, but it's a good first step to see what might going wrong.
|
||||
|
||||
* Step 2: CPU vs GPU
|
||||
|
||||
The primary question is figuring out whether the CPU is busy in your
|
||||
application, the CPU is busy in the GL driver, the GPU is waiting for
|
||||
the CPU, or the CPU is waiting for the GPU. Ideally, you get to the
|
||||
point where the CPU is waiting for the GPU infrequently but for a
|
||||
significant amount of time (however long it takes the GPU to draw a
|
||||
frame).
|
||||
|
||||
Start with top while your application is running. Is the CPU usage
|
||||
around 90%+? If so, then our performance analysis will be with
|
||||
sysprof. If it's not very high, is the GPU staying busy? We don't have
|
||||
a clean tool for this yet, but ``cat /debug/dri/0/v3d_regs`` could be
|
||||
useful. If ``CT0CA`` != ``CT0EA`` or ``CT1CA`` != ``CT1EA``, that
|
||||
means that the GPU is currently busy processing some rendering job.
|
||||
|
||||
* sysprof for CPU usage
|
||||
|
||||
If the CPU is totally busy and the GPU isn't terribly busy, there is
|
||||
an excellent tool for debugging: sysprof. Install, run as root (so you
|
||||
can get system-wide profiling), hit play and later stop. The top-left
|
||||
area shows the flat profile sorted by total time of that symbol plus
|
||||
its descendants. The top few are generally uninteresting (main() and
|
||||
its descendants consuming a lot), but eventually you can get down to
|
||||
something interesting. Click it, and to the right you get the
|
||||
callchains to descendants -- where all that time actually went. On the
|
||||
other hand, the lower left shows callers -- double-clicking those
|
||||
selects that as the symbol to view, instead.
|
||||
|
||||
Note that you need debug symbols for the callgraphs in sysprof to
|
||||
work, which is where most of its value is. Most distributions offer
|
||||
debug symbol packages from their builds which can be installed
|
||||
separately, and sysprof will find them. I've found that on arm, the
|
||||
debug packages are not enough, and if someone could determine what is
|
||||
necessary for callgraphs in debugging, that would be really helpful.
|
||||
|
||||
* perf for CPU waits on GPU
|
||||
|
||||
If the CPU is not very busy and the GPU is not very busy, then we're
|
||||
probably ping-ponging between the two. Most cases of this would be
|
||||
noticed by ``VC4_DEBUG=perf``, but not all. To see all cases where
|
||||
this happens, use the perf tool from the Linux kernel (note: unrelated
|
||||
to ``VC4_DEBUG=perf``)::
|
||||
|
||||
sudo perf record -f -g -e vc4:vc4_wait_for_seqno_begin -c 1 openarena
|
||||
|
||||
If you want to see the whole system's stalls for a period of time
|
||||
(very useful!), use the -a flag instead of a particular command
|
||||
name. Just ``^C`` when you're done capturing data.
|
||||
|
||||
At exit, you'll have ``perf.data`` in the current directory. You can print
|
||||
out the results with::
|
||||
|
||||
perf report | less
|
||||
|
||||
* Debugging for GPU fully busy
|
||||
|
||||
As of Linux kernel 4.17 and Mesa 18.1, we now expose the hardware's
|
||||
performance counters in OpenGL. Install apitrace, and trace your
|
||||
application with::
|
||||
|
||||
apitrace trace <application> # for GLX applications
|
||||
apitrace trace -a egl <application> # for EGL applications
|
||||
|
||||
Once you've captured a trace, you can see what counters are available
|
||||
and replay it while looking while looking at some of those counters::
|
||||
|
||||
apitrace replay <application>.trace --list-metrics
|
||||
|
||||
apitrace replay <application>.trace --pdraw=GL_AMD_performance_monitor:QPU-total-clk-cycles-vertex-coord-shading
|
||||
|
||||
Multiple counters can be captured at once with commas separating them.
|
||||
|
||||
Once you've found what draw calls are surprisingly expensive in one of
|
||||
the counters, you can work out which ones they were at the GL level by
|
||||
opening the trace up in qapitrace and using ``^-G`` to jump to that call
|
||||
number and ``^-L`` to look up the GL state at that call.
|
||||
|
||||
Trace Testing
|
||||
-------------
|
||||
|
||||
shader-db is often used as a proxy for real-world app performance when
|
||||
working on the compiler in Mesa. On VC4, there is a lot of
|
||||
state-dependent code in the shaders (like blending or vertex attribute
|
||||
format handling), so the typical `shader-db
|
||||
<https://gitlab.freedesktop.org/mesa/shader-db>`__ will miss important
|
||||
areas for optimization. Piglit can instead test apitraces, such as
|
||||
those captured in
|
||||
`traces-db <https://gitlab.freedesktop.org/gfx-ci/tracie/traces-db/>`__.
|
||||
|
||||
Hardware Documentation
|
||||
----------------------
|
||||
|
||||
For driver developers, Broadcom publicly released a `specification
|
||||
<https://docs.broadcom.com/doc/12358545>`__ PDF for the 21553, which
|
||||
is closely related to the VC4 GPU present in the Raspberry Pi. They
|
||||
also released a `snapshot <https://docs.broadcom.com/docs/12358546>`__
|
||||
of a corresponding Android graphics driver. That graphics driver was
|
||||
ported to Raspbian for a demo, but was not expected to have ongoing
|
||||
development.
|
||||
|
||||
Developers with NDA access with Broadcom or Raspberry Pi can
|
||||
potentially get access to "simpenrose", the C software simulator of
|
||||
the GPU. The Mesa driver includes a backend (``vc4_simulator.c``) to
|
||||
use simpenrose from an x86 system with the i915 graphics driver with
|
||||
all of the VC4 rendering commands emulated on simpenrose and memcpyed
|
||||
to the real GPU.
|
||||
@@ -0,0 +1,208 @@
|
||||
Virtio-GPU Venus
|
||||
================
|
||||
|
||||
Venus is a Virtio-GPU protocol for Vulkan command serialization. The protocol
|
||||
definition and codegen are hosted at `venus-protocol
|
||||
<https://gitlab.freedesktop.org/virgl/venus-protocol>`__. The renderer is
|
||||
hosted at `virglrenderer
|
||||
<https://gitlab.freedesktop.org/virgl/virglrenderer>`__.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
The Venus renderer requires
|
||||
|
||||
- Linux platform
|
||||
- Vulkan 1.1
|
||||
- :ext:`VK_KHR_external_memory_fd`
|
||||
- Android platform
|
||||
- Vulkan 1.1
|
||||
- :ext:`VK_EXT_external_memory_dma_buf`
|
||||
- :ext:`VK_EXT_image_drm_format_modifier`
|
||||
- :ext:`VK_EXT_queue_family_foreign`
|
||||
|
||||
from the host driver. However, it violates the spec and relies on
|
||||
implementation-defined behaviors to support ``vkMapMemory`` (see `below
|
||||
<#vk-memory-property-host-visible-bit>`__). It is not expected to work on all
|
||||
drivers meeting the requirements. It has only been tested with:
|
||||
|
||||
- ANV 21.1 or later
|
||||
- Note: with Intel Meteor Lake or xe driver, you need 6.16+ kernel and 11.0+
|
||||
QEMU with ``-accel kvm,honor-guest-pat=on`` (request to default that on is
|
||||
`here <https://gitlab.com/qemu-project/qemu/-/work_items/3357>`__).
|
||||
- RADV 21.1 or later
|
||||
- Note: you need 6.13+ kernel that already has
|
||||
`KVM: Stop grabbing references to PFNMAP'd pages
|
||||
<https://lore.kernel.org/all/[email protected]/>`__.
|
||||
- Note: for dGPU paired with Intel CPU, you need 6.16+ kernel and 11.0+ QEMU
|
||||
with ``-accel kvm,honor-guest-pat=on`` (request to default that on is
|
||||
`here <https://gitlab.com/qemu-project/qemu/-/work_items/3357>`__).
|
||||
- NVIDIA (Proprietary) 570.86 or later
|
||||
- Note: if paired with Intel CPU, you need 6.16+ kernel and 11.0+ QEMU with
|
||||
``-accel kvm,honor-guest-pat=on`` (request to default that on is
|
||||
`here <https://gitlab.com/qemu-project/qemu/-/work_items/3357>`__).
|
||||
- ARM Mali (Proprietary) r32p0 or later
|
||||
- Turnip 22.0 or later
|
||||
- PanVK 25.1 or later
|
||||
- Lavapipe 22.1 or later
|
||||
|
||||
The Venus driver requires supports for
|
||||
|
||||
- ``VIRTGPU_PARAM_3D_FEATURES``
|
||||
- ``VIRTGPU_PARAM_CAPSET_QUERY_FIX``
|
||||
- ``VIRTGPU_PARAM_RESOURCE_BLOB``
|
||||
- ``VIRTGPU_PARAM_HOST_VISIBLE``
|
||||
- ``VIRTGPU_PARAM_CONTEXT_INIT``
|
||||
|
||||
from the virtio-gpu kernel driver, unless vtest is used. That usually means
|
||||
the guest kernel should be at least 5.16 or have the parameters back ported,
|
||||
paired with hypervisors such as `crosvm <https://crosvm.dev>`__, or `QEMU
|
||||
<https://www.qemu.org>`__.
|
||||
|
||||
vtest
|
||||
-----
|
||||
|
||||
The simplest way to test Venus is to use virglrenderer's vtest server. To
|
||||
build virglrenderer with Venus support and to start the vtest server,
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ git clone https://gitlab.freedesktop.org/virgl/virglrenderer.git
|
||||
$ cd virglrenderer
|
||||
$ meson out -Dvenus=true
|
||||
$ meson compile -C out
|
||||
$ meson devenv -C out
|
||||
$ ./vtest/virgl_test_server --venus
|
||||
$ exit
|
||||
|
||||
In another shell,
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ export VK_DRIVER_FILES=<path-to-virtio_icd.x86_64.json>
|
||||
$ export VN_DEBUG=vtest
|
||||
$ vulkaninfo
|
||||
$ vkcube
|
||||
|
||||
If the host driver of the system is not new enough, it is a good idea to build
|
||||
the host driver as well when building the Venus driver. Just remember to set
|
||||
:envvar:`VK_DRIVER_FILES` when starting the vtest server so that the vtest
|
||||
server finds the locally built host driver.
|
||||
|
||||
QEMU
|
||||
----
|
||||
|
||||
This is how one might want to start QEMU
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ ./qemu-system-x86_64 \
|
||||
-enable-kvm \
|
||||
-M q35 \
|
||||
-smp 8 \
|
||||
-m 4G \
|
||||
-cpu host \
|
||||
-net nic,model=virtio \
|
||||
-net user,hostfwd=tcp::2222-:22 \
|
||||
-device virtio-gpu-gl,hostmem=4G,blob=true,venus=true \
|
||||
-vga none \
|
||||
-display sdl,gl=on,show-cursor=on \
|
||||
-usb -device usb-tablet \
|
||||
-object memory-backend-memfd,id=mem1,size=4G \
|
||||
-machine memory-backend=mem1 \
|
||||
-hda $IMG
|
||||
|
||||
To build QEMU, this is how one might want to configure it
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ cd <QEMU source dir>
|
||||
$ mkdir build && cd build
|
||||
$ ../configure \
|
||||
--prefix=$HOME/.local \
|
||||
--target-list=x86_64-softmmu \
|
||||
--enable-kvm \
|
||||
--disable-werror \
|
||||
--enable-opengl \
|
||||
--enable-virglrenderer \
|
||||
--enable-gtk \
|
||||
--enable-sdl
|
||||
$ make -j$(nproc)
|
||||
|
||||
crosvm
|
||||
------
|
||||
|
||||
crosvm is written in Rust. To build crosvm, make sure Rust has been installed
|
||||
and
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ git clone --recurse-submodules \
|
||||
https://chromium.googlesource.com/chromiumos/platform/crosvm
|
||||
$ cd crosvm
|
||||
$ RUSTFLAGS="-L<path-to-virglrenderer>/out/src" cargo build \
|
||||
--features "x wl-dmabuf virgl_renderer virgl_renderer_next default-no-sandbox"
|
||||
|
||||
Note that crosvm must be built with ``default-no-sandbox`` or started with
|
||||
``--disable-sandbox`` in this setup.
|
||||
|
||||
This is how one might want to start crosvm
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ sudo LD_LIBRARY_PATH=<...> VK_DRIVER_FILES=<...> ./target/debug/crosvm run \
|
||||
--gpu vulkan=true \
|
||||
--gpu-render-server path=<path-to-virglrenderer>/out/server/virgl_render_server \
|
||||
--display-window-keyboard \
|
||||
--display-window-mouse \
|
||||
--net "host-ip 192.168.0.1,netmask=255.255.255.0,mac=12:34:56:78:9a:bc" \
|
||||
--rwdisk disk.img \
|
||||
-p root=/dev/vda1 \
|
||||
<path-to-bzImage>
|
||||
|
||||
assuming a working system is installed to partition 1 of ``disk.img``.
|
||||
``sudo`` or ``CAP_NET_ADMIN`` is needed to set up the TAP network device.
|
||||
|
||||
Android Cuttlefish
|
||||
------------------
|
||||
|
||||
Venus isn't supported in the upstream Cuttlefish yet, for ``venus_guest_angle``
|
||||
mode used in Mesa CI against Android 16 AOSP, the instruction is
|
||||
`here <https://gitlab.freedesktop.org/gfx-ci/android/aosp-manifest/-/blob/android16-release+venus/README.md>`__.
|
||||
|
||||
Optional Requirements
|
||||
---------------------
|
||||
|
||||
In the future, if virglrenderer's ``virgl_renderer_export_fence`` is
|
||||
supported, the Venus renderer will require :ext:`VK_KHR_external_fence_fd`
|
||||
with ``VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT`` from the host driver.
|
||||
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
-----------------------------------
|
||||
|
||||
The Venus renderer makes assumptions about ``VkDeviceMemory`` that has
|
||||
``VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT``. The assumptions are illegal and rely
|
||||
on the current behaviors of the host drivers. It should be possible to remove
|
||||
some of the assumptions and incrementally improve compatibilities with more
|
||||
host drivers by imposing platform-specific requirements. But the long-term
|
||||
plan is to create a new Vulkan extension for the host drivers to address this
|
||||
specific use case.
|
||||
|
||||
The Venus renderer assumes a device memory that has
|
||||
``VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`` can be exported as a mmapable dma-buf
|
||||
(in the future, the plan is to export the device memory as an opaque fd). It
|
||||
chains ``VkExportMemoryAllocateInfo`` to ``VkMemoryAllocateInfo`` without
|
||||
checking if the host driver can export the device memory.
|
||||
|
||||
The dma-buf is mapped (in the future, the plan is to import the opaque fd and
|
||||
call ``vkMapMemory``) but the mapping is not accessed. Instead, the mapping
|
||||
is passed to ``KVM_SET_USER_MEMORY_REGION``. The hypervisor, host KVM, and
|
||||
the guest kernel work together to set up a write-back or write-combined guest
|
||||
mapping (see ``virtio_gpu_vram_mmap`` of the virtio-gpu kernel driver). CPU
|
||||
accesses to the device memory are via the guest mapping, and are assumed to be
|
||||
coherent when the device memory also has
|
||||
``VK_MEMORY_PROPERTY_HOST_COHERENT_BIT``.
|
||||
|
||||
While the Venus renderer can force a ``VkDeviceMemory`` external, it does not
|
||||
force a ``VkImage`` or a ``VkBuffer`` external. As a result, it can bind an
|
||||
external device memory to a non-external resource.
|
||||
@@ -0,0 +1,86 @@
|
||||
VirGL
|
||||
=====
|
||||
|
||||
What is VirGL?
|
||||
--------------
|
||||
|
||||
VirGL is a virtual 3D GPU for use inside QEMU virtual machines, that
|
||||
allows the guest operating system to use the capabilities of the host GPU
|
||||
to accelerate 3D rendering. The plan is to have a guest GPU that is fully
|
||||
independent of the host GPU.
|
||||
|
||||
What exactly does it entail?
|
||||
----------------------------
|
||||
|
||||
The project entails creating a virtual 3D capable graphics card for
|
||||
virtual machines running inside QEMU. The design of this card is based
|
||||
around the concepts of Gallium3D to make writing Mesa and (eventually)
|
||||
Direct3D drivers for it easy. The card natively uses the Gallium TGSI
|
||||
intermediate representation for its shaders. The implementation of
|
||||
rendering for the card is done in the host system as part of QEMU and is
|
||||
implemented purely on OpenGL so you can get accelerated rendering on any
|
||||
sufficiently capable card/driver combination.
|
||||
|
||||
The project also consists of a complete Linux guest stack, composed of a
|
||||
Linux kernel KMS driver, X.org 2D DDX driver and Mesa 3D driver.
|
||||
|
||||
Current status
|
||||
--------------
|
||||
|
||||
* Many pieces are now upstreamed in various projects.
|
||||
* Kernel Linux 4.2 contains the modesetting only drivers. Linux 4.4
|
||||
contains the 3D supported pieces.
|
||||
* Mesa main contains the VirGL 3D driver.
|
||||
* QEMU 2.4 contained the initial virtio-gpu with no acceleration
|
||||
support. QEMU 2.5 contains 3D support only with the GTK3 frontend with
|
||||
GL enabled.
|
||||
* The virglrenderer library seems mostly API stable.
|
||||
* Limited environment renderer (GLES2)
|
||||
|
||||
So what can it do now?
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Run a desktop and most 3D games I've thrown at it.
|
||||
|
||||
Scope
|
||||
-----
|
||||
|
||||
The project is currently investigating the desktop virtualization use case
|
||||
only. This use case is where the viewer, host and guest are all running on
|
||||
the same machine (i.e. workstation or laptop). Some areas are in scope for
|
||||
future investigation but not being looked at, at this time.
|
||||
|
||||
Future scope
|
||||
^^^^^^^^^^^^
|
||||
|
||||
* Remoting rendering using a codec solution.
|
||||
* Windows guest, Direct3D drivers.
|
||||
* Other architectures
|
||||
|
||||
Out of scope
|
||||
^^^^^^^^^^^^
|
||||
|
||||
* Passing through GPUs or subsets of GPU capabilities.
|
||||
|
||||
Repos
|
||||
-----
|
||||
|
||||
All upstream parts are being developed upstream.
|
||||
|
||||
virglrenderer: the GL renderer https://gitlab.freedesktop.org/virgl/virglrenderer
|
||||
|
||||
Authors and Contributors
|
||||
------------------------
|
||||
|
||||
VirGL is a project undertaken by Dave Airlie at Red Hat. It builds on lots
|
||||
of open source work in a number of projects, primarily the Gallium 3D code
|
||||
from the Mesa project.
|
||||
|
||||
Support or Contact
|
||||
------------------
|
||||
|
||||
mailing list: [email protected]
|
||||
|
||||
https://lists.freedesktop.org/mailman/listinfo/virglrenderer-devel
|
||||
|
||||
IRC: `#virgil3d on OFTC <irc://irc.oftc.net/virgil3d>`__.
|
||||
@@ -0,0 +1,367 @@
|
||||
Zink
|
||||
====
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The Zink driver is a Gallium driver that emits Vulkan API calls instead
|
||||
of targeting a specific GPU architecture. This can be used to get full
|
||||
desktop OpenGL support on devices that only support Vulkan.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
The feature-level of Zink depends on two things; what's implemented in Zink,
|
||||
as well as the capabilities of the Vulkan driver.
|
||||
|
||||
The feature-levels implemented by Zink are exposed by `Vulkan Profiles
|
||||
<https://dev.vulkan.org/tools#vulkan-profiles>`__ in the
|
||||
:file:`VP_ZINK_requirements.json` profiles file.
|
||||
|
||||
Used with the `Vulkan Profiles tools <https://github.com/KhronosGroup/Vulkan-Profiles>`__,
|
||||
we can compare the ZINK profiles with Vulkan devices profiles generated with
|
||||
`Vulkaninfo <https://vulkan.lunarg.com/doc/view/latest/windows/vulkaninfo.html>`__
|
||||
or `downloaded from GPUinfo.org`_
|
||||
to establish the feature-levels supported by these drivers.
|
||||
You can check a running driver against the Zink profiles by building mesa with
|
||||
``--tools=zink`` and running
|
||||
``./src/gallium/drivers/zink/check_requirements/zink_check_requirements``
|
||||
from the build directory.
|
||||
|
||||
OpenGL 2.1
|
||||
^^^^^^^^^^
|
||||
|
||||
OpenGL 2.1 is the minimum version Zink can support, and will always be
|
||||
exposed, given Vulkan support. There's a few features that are required
|
||||
for correct behavior, but not all of these are validated; instead you'll
|
||||
see rendering-issues and likely validation error, or even crashes.
|
||||
|
||||
Here's a list of those requirements:
|
||||
|
||||
* Vulkan 1.0
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`logicOp`
|
||||
* :vk-feat:`fillModeNonSolid`
|
||||
* :vk-feat:`alphaToOne`
|
||||
* :vk-feat:`shaderClipDistance`
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_KHR_maintenance1`
|
||||
* :ext:`VK_KHR_maintenance5`
|
||||
* :ext:`VK_KHR_create_renderpass2`
|
||||
* :ext:`VK_KHR_imageless_framebuffer`
|
||||
* :ext:`VK_KHR_timeline_semaphore`
|
||||
* :ext:`VK_EXT_custom_border_color` with ``customBorderColorWithoutFormat``
|
||||
* :ext:`VK_EXT_provoking_vertex`
|
||||
* :ext:`VK_EXT_line_rasterization`, with the following ``VkPhysicalDeviceLineRasterizationFeaturesEXT``:
|
||||
|
||||
* :vk-feat:`rectangularLines`
|
||||
* :vk-feat:`bresenhamLines`
|
||||
* :vk-feat:`smoothLines`
|
||||
* :vk-feat:`stippledRectangularLines`
|
||||
* :vk-feat:`stippledBresenhamLines`
|
||||
* :vk-feat:`stippledSmoothLines`
|
||||
|
||||
* :ext:`VK_KHR_swapchain_mutable_format`
|
||||
* :ext:`VK_EXT_border_color_swizzle`
|
||||
* :ext:`VK_KHR_descriptor_update_template`
|
||||
|
||||
In addition to this, :ext:`VK_KHR_external_memory` is required to support the
|
||||
DRI code-path.
|
||||
|
||||
We also require either the :ext:`VK_EXT_scalar_block_layout` extension or
|
||||
Vulkan 1.2, with the :vk-feat:`scalarBlockLayout` feature.
|
||||
|
||||
OpenGL 3.0
|
||||
^^^^^^^^^^
|
||||
|
||||
|
||||
For OpenGL 3.0 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`independentBlend`
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_EXT_transform_feedback`
|
||||
* :ext:`VK_EXT_conditional_rendering`
|
||||
|
||||
OpenGL 3.1
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 3.1 support, the following additional ``VkPhysicalDeviceLimits``
|
||||
are required:
|
||||
|
||||
* ``maxPerStageDescriptorSamplers`` ≥ 16
|
||||
|
||||
OpenGL 3.2
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 3.2 support, the following additional requirements must be
|
||||
met, although some of these might not actually get verified:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`depthClamp`
|
||||
* :vk-feat:`geometryShader`
|
||||
* :vk-feat:`shaderTessellationAndGeometryPointSize`
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_EXT_depth_clip_enable`
|
||||
|
||||
OpenGL 3.3
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 3.3 support, the following additional requirements must be
|
||||
met, although some of these might not actually get verified:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`dualSrcBlend`
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_EXT_vertex_attribute_divisor`
|
||||
|
||||
OpenGL 4.0
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.0 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`sampleRateShading`
|
||||
* :vk-feat:`tessellationShader`
|
||||
* :vk-feat:`imageCubeArray`
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_KHR_maintenance2`
|
||||
|
||||
* Formats requiring ``VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT``:
|
||||
|
||||
* ``VK_FORMAT_R32G32B32_SFLOAT``
|
||||
* ``VK_FORMAT_R32G32B32_SINT``
|
||||
* ``VK_FORMAT_R32G32B32_UINT``
|
||||
|
||||
OpenGL 4.1
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.1 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`multiViewport`
|
||||
|
||||
* ``VkPhysicalDeviceLimits``
|
||||
|
||||
* ``maxImageDimension1D`` ≥ 16384
|
||||
* ``maxImageDimension2D`` ≥ 16384
|
||||
* ``maxImageDimension3D`` ≥ 2048
|
||||
* ``maxImageDimensionCube`` ≥ 16384
|
||||
* ``maxImageArrayLayers`` ≥ 2048
|
||||
* ``maxViewports`` ≥ 16
|
||||
|
||||
OpenGL 4.2
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.2 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* Device extensions:
|
||||
* :ext:`VK_EXT_image_2d_view_of_3d`
|
||||
|
||||
* ``VkPhysicalDeviceLimits``:
|
||||
|
||||
* ``shaderStorageImageExtendedFormats``
|
||||
* ``shaderStorageImageWriteWithoutFormat``
|
||||
* ``vertexPipelineStoresAndAtomics``
|
||||
* ``fragmentStoresAndAtomics``
|
||||
|
||||
* For Vulkan 1.2 and above:
|
||||
|
||||
* ``VkPhysicalDeviceVulkan11Features``:
|
||||
|
||||
* :vk-feat:`shaderDrawParameters`
|
||||
|
||||
* For Vulkan 1.1 and below:
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_KHR_shader_draw_parameters`
|
||||
|
||||
OpenGL 4.3
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.3 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`robustBufferAccess`
|
||||
|
||||
* Formats requiring ``VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT``:
|
||||
|
||||
* ``VK_FORMAT_R8G8B8A8_UNORM``
|
||||
* ``VK_FORMAT_R8G8B8A8_SRGB``
|
||||
* ``VK_FORMAT_R16_UNORM``
|
||||
* ``VK_FORMAT_R16G16_UNORM``
|
||||
* ``VK_FORMAT_R16_SNORM``
|
||||
* ``VK_FORMAT_R16G16_SNORM``
|
||||
* ``VK_FORMAT_D32_SFLOAT_S8_UINT``
|
||||
|
||||
OpenGL 4.4
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.4 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* Formats requiring ``VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT``:
|
||||
|
||||
* ``VK_FORMAT_B10G11R11_UFLOAT_PACK32``
|
||||
|
||||
* For Vulkan 1.2 and above:
|
||||
|
||||
* ``VkPhysicalDeviceVulkan12Features``:
|
||||
|
||||
* ``samplerMirrorClampToEdge``
|
||||
|
||||
* For Vulkan 1.1 and below:
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_KHR_sampler_mirror_clamp_to_edge`
|
||||
|
||||
OpenGL 4.5
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.5 support, the following additional ``VkPhysicalDeviceFeatures``
|
||||
are required to be supported
|
||||
|
||||
* :vk-feat:`shaderCullDistance`
|
||||
|
||||
OpenGL 4.6
|
||||
^^^^^^^^^^
|
||||
|
||||
For OpenGL 4.6 support, the following additional requirements must be
|
||||
met:
|
||||
|
||||
* ``VkPhysicalDeviceFeatures``:
|
||||
|
||||
* :vk-feat:`samplerAnisotropy`
|
||||
* :vk-feat:`depthBiasClamp`
|
||||
|
||||
* Device extensions:
|
||||
|
||||
* :ext:`VK_KHR_draw_indirect_count`
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
If you notice poor performance and high CPU usage while running an application,
|
||||
changing the descriptor manager may improve performance:
|
||||
|
||||
.. envvar:: ZINK_DESCRIPTORS <mode> ("auto")
|
||||
|
||||
``auto``
|
||||
Automatically detect best mode. This is the default.
|
||||
``lazy``
|
||||
Attempt to use the least amount of CPU by binding descriptors opportunistically.
|
||||
``db``
|
||||
Use EXT_descriptor_buffer when possible.
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
There's a few tools that are useful for debugging Zink, like this environment
|
||||
variable:
|
||||
|
||||
.. envvar:: ZINK_DEBUG
|
||||
|
||||
Accepts the following comma-separated list of flags:
|
||||
|
||||
``nir``
|
||||
Print the NIR form of all shaders to stderr.
|
||||
``spirv``
|
||||
Write the binary SPIR-V form of all compiled shaders to a file in the
|
||||
current directory, and print a message with the filename to stderr.
|
||||
``tgsi``
|
||||
Print the TGSI form of TGSI shaders to stderr.
|
||||
``validation``
|
||||
Dump Validation layer output.
|
||||
``sync``
|
||||
Emit full synchronization barriers before every draw and dispatch.
|
||||
``compact``
|
||||
Use a maximum of 4 descriptor sets
|
||||
``noreorder``
|
||||
Do not reorder or optimize GL command streams
|
||||
``gpl``
|
||||
Force using Graphics Pipeline Library for all shaders
|
||||
``rp``
|
||||
Enable render pass optimizations (for tiling GPUs)
|
||||
``norp``
|
||||
Disable render pass optimizations (for tiling GPUs)
|
||||
``map``
|
||||
Print info about mapped VRAM
|
||||
``flushsync``
|
||||
Force synchronous flushes/presents
|
||||
``noshobj``
|
||||
Disable EXT_shader_object
|
||||
``optimal_keys``
|
||||
Debug/use optimal_keys
|
||||
``noopt``
|
||||
Disable async optimized pipeline compiles
|
||||
``nobgc``
|
||||
Disable all async pipeline compiles
|
||||
``mem``
|
||||
Enable memory allocation debugging
|
||||
``quiet``
|
||||
Suppress probably-harmless warnings
|
||||
``nopc``
|
||||
No precompilation
|
||||
``msaaopt``
|
||||
Optimize out loads/stores of MSAA attachments (nonconformant)
|
||||
``rploads``
|
||||
Zap renderpass loads for DONT_CARE
|
||||
``nogeneral``
|
||||
Disable GENERAL layout usage for supported hardware
|
||||
|
||||
Vulkan Validation Layers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Another useful tool for debugging is the `Vulkan Validation Layers
|
||||
<https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/README.md>`__.
|
||||
|
||||
The validation layers effectively insert extra checking between Zink and the
|
||||
Vulkan driver, pointing out incorrect usage of the Vulkan API. The layers can
|
||||
be enabled by setting the environment variable :envvar:`VK_LOADER_LAYERS_ENABLE` to
|
||||
``VK_LAYER_KHRONOS_validation``. You can read more about the Validation Layers
|
||||
in the link above.
|
||||
|
||||
Apple macOS and MoltenVK
|
||||
------------------------
|
||||
|
||||
Zink on macOS is experimental with very limited capabilities.
|
||||
The Vulkan SDK (1.3.250 or newer) is required to build Zink.
|
||||
Set the build option ``-Dmoltenvk-dir=<directory>`` to point at your Vulkan SDK install or MoltenVK build.
|
||||
Add Zink to the Gallium drivers build option ``-Dgallium-drivers=zink``.
|
||||
If installed using ``brew``, you can set ``-D moltenvk-dir=$(brew --prefix molten-vk)``.
|
||||
|
||||
|
||||
IRC
|
||||
---
|
||||
|
||||
In order to make things a bit easier to follow, we have decided to create our
|
||||
own IRC channel. If you're interested in contributing, or have any technical
|
||||
questions, don't hesitate to visit `#zink on OFTC
|
||||
<irc://irc.oftc.net/zink>`__ and say hi!
|
||||
|
||||
|
||||
.. _downloaded from GPUinfo.org: https://www.saschawillems.de/blog/2022/03/12/vulkan-profiles-support-for-the-vulkan-hardware-capability-viewer-and-database/
|
||||
Reference in new issue
Block a user