vkd3d-shader/ir: Transform clip/cull input/outputs into arrays, version 4.
Problem recap
There is a difference regarding how clip/cull distance sysvals are stored in DirectX and Vulkan/OpenGL/Metal.
While the former uses a convention of two 4-component registers (shared between clip and cull distance sysvals), the latter use a convention of two built-in arrays of up to 8 scalars. Either way, the sum of clip/cull distance components cannot exceed 8.
These semantics can be passed around as both inputs and outputs across shaders. Sometimes several instances of the same component can appear as input, for shaders that work with multiple control points.
The following table shows the forms on which registers with clip/cull sysvals can appear on the different shader types (after IO normalization):
Where [i] is the array index, [p] the control point index, and [e] the signature element index.
Note that the array index [i] will be added by us when targeting the SPIR-V/GLSL/MSL backends. Only in rare occasions the array index is already present. For that to happen, the shader must declare an array such as:
float4 arr[2] : SV_CLIPDISTANCE;
in these cases, relative addressing is allowed, but only on SM6.
Also, it is worth noting that we don't have HLSL support for clip/cull distances yet, !1285 should add it at some point.
Current implementation
We currently have a partial solution for some of these cases on the spirv.c backend:
This implementation declares a private 1-register variable for every clip/cull distance signature element, and regular instructions now refer to this private variable. Then, on the epilogue function, called at the end of the program, every component is copied into one element of the real built-in array.
Currently this implementation does not transform clip/cull distances in 2-register format when they are used as srcs, which can happen when they come from previous stages. This is more complex, since srcs can not only come from the input_signature but also from the output_signature (on hull shader's patch constant's fork/join phases) and we have to account for several control points on the shaders apply.
When clip/cull distances appear as src, the test fails, or even worse, the driver segfaults (as it happens for clip-cull-distance.shader_test on my AMD card on current master).
Another problem is that it does not account for receiving clip/cull signature elements that are arrays of length 2, which also results in segfaults for me.
Proposed solution
This is basically an improvement of !1700 (closed), which I decided to send as a new MR, because a good amount of discussion has happened there (many improvements thanks to Gio's insights). Also, this is written on top of !1801 (merged) which took care of some surprises that appeared along the way.
As a recap, this strategy removes the spirv.c backend handling in favor of a general vsir pass in ir.c.
This strategy runs 4 times, for clip-inputs, cull-inputs, clip-outputs, and cull-outputs:
- The signature elements that have the right sysval are identified, registers are remembered, and a new array signature element is created.
- An indexable temp with the total number of registers (1 or 2), multiplied by the number of control points, if any, is created.
- When the signature elements are used on srcs, MOV instructions are added at the beginning of the program (or pertinent phase), from the signature array to the indexable temp.
- When the signature elements are used as dsts, MOV instructions are added at the return points (or emits), that copy the values from the indexable temps to the signature array.
- All other references to the original signature elements (either as dst or src) in the program are mapped to references to the indexable temp.
- Finally, the original signature elements are removed.
Changes from version 3
These are some of the changes compared to version 3 from !1700 (closed).
- The sysval_array_normalizer now works identifying the registers used by the sysvals instead of relying on the semantic indexes, which are not relevant on SM6. This since SM6 allows for semantic indexes above the 0-1 range, as long as they can be allocated in 2 registers.
- Fixed bug where sometimes the first non-declaration instruction skipped register mapping.
- Now assigning proper unused register indexes to the newly generated signature elements.
- Also assigning proper unused register indexes to the variables created by user defined clip-planes (3/7), which is a requirement for the new way registers are identified by the normalization pass.
- Added additional tests for clip/cull distances passed as input (1/7), semantics above the 0-1 range (2/7), and relative addressing (6/7).
- I realized that more spirv.c code backend can be removed (last patch).
Alternative implementations
One alternative solution is to extend the support for clip/cull inputs on the spirv.c backend, and also fix the scenario of the semantics already present as arrays in the input. This was the direction !564 (closed) was going for. However, this would mean having to implement the whole thing on both the glsl.c and the msl.c backends, and adding support for "prelude" functions.
Another alternative (already proposed in !1540 (closed), but with room for improvement) is a vsir pass that prepends/appends MOV instructions from the new array into a temp after instructions that have a clip/cull distance sysval as src/dst, and then replacing the original instruction's registers with this temp. But at that time, this was not preferred to the current proposal.
