This commit is contained in:
elwrcl committed 2026-06-06 13:25:15 +03:00
1 parent 04d4dc8b09
commit 818cab85dc
12511 files changed
+6156641

No files matched your search

+173
View File
@@ -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.
+85
View File
@@ -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.
+477
View File
@@ -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.
+164
View File
@@ -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)``)
+437
View File
@@ -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&#92;n area | { <bt4> BT4 | <bt3> BT3 | ... | <bt0> BT0 } }|{ surface state&#92;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
+427
View File
@@ -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).
+65
View File
@@ -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.
+746
View File
@@ -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.
+286
View File
@@ -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.
+153
View File
@@ -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.
+403
View File
@@ -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

+99
View File
@@ -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
+333
View File
@@ -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)
+258
View File
@@ -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
+402
View File
@@ -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>`__
+93
View File
@@ -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/*
+134
View File
@@ -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/
+126
View File
@@ -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
+66
View File
@@ -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]
==================================================== ========================
+159
View File
@@ -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.
+88
View File
@@ -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/*
+71
View File
@@ -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.
+78
View File
@@ -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
+117
View File
@@ -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`.
+17
View File
@@ -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.
+37
View File
@@ -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.
+108
View File
@@ -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
+139
View File
@@ -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.
+234
View File
@@ -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/*
+229
View File
@@ -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.
+50
View File
@@ -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.
+298
View File
@@ -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.
+208
View File
@@ -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.
+86
View File
@@ -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>`__.
+367
View File
@@ -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/