Repository navigation
feat(shader): emulate fragment barycentrics with a generated geometry stage - #1547
Conversation
|
On the overlap with #1252: both PRs add one call after |
9032b65 to
54e5719
Compare
|
Tested on top of main Head 0.237278 is what
I did not check lavapipe. If it weights unsnapped positions, the snapped reference could fail there, so the tolerance change may be the safer one. AI-assisted: yes (Claude Code). |
54e5719 to
1e08abb
Compare
|
Thanks @yahya308, confirmed: with window vertices snapped to 1/256 my reference gives the same 0.237278 at (53, 36), and up to 1.43e-4 off the unsnapped weights. I took the tolerance option (both checks now 2.5e-4, 1e08abb, rebased on main) because snapping the reference fails on lavapipe, which reads 0.237381 there: llvmpipe snaps to 1/256 only for coverage ( |
1e08abb to
dcbc6db
Compare
dcbc6db to
2c80448
Compare
… stage Without VK_KHR_fragment_shader_barycentric the recompiler dropped v_interp_p1_f32 and made v_interp_p2_f32 read the attribute Vulkan interpolated, but never wrote the I/J VGPRs: a pixel shader that read them for anything else got 0, and v_interp_mov_f32 of P10/P20 (or P0 of an interpolated input) needed PerVertexKHR and failed pipeline validation. The fallback translation now loads the I/J VGPRs and marks v_interp_p1 and v_interp_p2 with witnesses. After the front-end simplification (SSA, folding, DCE, read-lane and masked-select elimination), HostInterpolationChecker requires every p2 to read its J, and its p1's I, from a barycentric of the parameter's kind, every p1 result to reach only the matching p2 (through a write masked by the p2's EXEC, whose inactive lanes keep the old register), no parameter read as raw vertices and no slot shared by different interpolations. It then lowers the witnesses to the host-interpolated attribute as before, reruns DCE and masked-select elimination, and accepts the program only when no barycentric read remains, so reusing the I/J VGPRs after interpolating keeps host interpolation and the previous SPIR-V. Any other program is translated with the barycentric path and marked as emulated: its barycentrics become vec3 inputs (NoPerspective for the linear weights) and its per-vertex parameters flat vec4[3] inputs, placed in the free locations by LayoutBarycentricEmulation. The driver then inserts a generated geometry stage between the vertex and fragment shaders that copies the position and parameters, writes the one-hot weights of each vertex and the three vertices' parameter values, so the rasterizer interpolates the same weights the extension provides. The stage needs geometryShader and a vertex shader before it; tessellation, mesh and rect-list pipelines, other primitive types and interfaces beyond 32 locations or the device's geometry limits throw. CompiledShaderArtifact carries the emulation (shader disk cache format 21). Hardware snaps window corners to 1/256 pixel before interpolating (measured by yahya308 on an RTX 4070 Laptop and a Radeon 780M in boykopovar#1547); lavapipe snaps only for coverage (lp_rast.h FIXED_ORDER 8), so the test accepts either reference per pixel at the original 1e-4 margin. Sources: - v_interp_p1_f32/v_interp_p2_f32/v_interp_mov_f32 semantics (P0, P10, P20, I/J): AMD RDNA2 ISA, "Parameter Interpolation". - BaryCoordKHR/BaryCoordNoPerspKHR weights and PerVertexKHR vertex order: SPV_KHR_fragment_shader_barycentric, VK_KHR_fragment_shader_barycentric. - Geometry shader triangle input order and provoking vertex: Vulkan specification, "Primitive Topologies" and "Geometry Shading". - Interpolation decorations decided by the fragment input: Vulkan specification, "Interpolation Decorations". - lavapipe interpolation: Mesa src/gallium/drivers/llvmpipe/lp_rast.h (FIXED_ORDER 8), lp_setup_tri.c (subpixel_snap for coverage only), lp_state_setup.c (init_args, plane setup from float positions).
|
Thanks @yahya308, the hardware traces settled it. In 2c80448 (rebased on main |
|
Checked on lavapipe ( The branch conflicted with Not checked here: behaviour on NVIDIA/Windows hardware. @yahya308, if you can, please rerun |
2c80448 to
886a704
Compare
… stage Without VK_KHR_fragment_shader_barycentric the recompiler dropped v_interp_p1_f32 and made v_interp_p2_f32 read the attribute Vulkan interpolated, but never wrote the I/J VGPRs: a pixel shader that read them for anything else got 0, and v_interp_mov_f32 of P10/P20 (or P0 of an interpolated input) needed PerVertexKHR and failed pipeline validation. The fallback translation now loads the I/J VGPRs and marks v_interp_p1 and v_interp_p2 with witnesses. After the front-end simplification (SSA, folding, DCE, read-lane and masked-select elimination), HostInterpolationChecker requires every p2 to read its J, and its p1's I, from a barycentric of the parameter's kind, every p1 result to reach only the matching p2 (through a write masked by the p2's EXEC, whose inactive lanes keep the old register), no parameter read as raw vertices and no slot shared by different interpolations. It then lowers the witnesses to the host-interpolated attribute as before, reruns DCE and masked-select elimination, and accepts the program only when no barycentric read remains, so reusing the I/J VGPRs after interpolating keeps host interpolation and the previous SPIR-V. Any other program is translated with the barycentric path and marked as emulated: its barycentrics become vec3 inputs (NoPerspective for the linear weights) and its per-vertex parameters flat vec4[3] inputs, placed in the free locations by LayoutBarycentricEmulation. The driver then inserts a generated geometry stage between the vertex and fragment shaders that copies the position and parameters, writes the one-hot weights of each vertex and the three vertices' parameter values, so the rasterizer interpolates the same weights the extension provides. The stage needs geometryShader and a vertex shader before it; tessellation, mesh and rect-list pipelines, other primitive types and interfaces beyond 32 locations or the device's geometry limits throw. CompiledShaderArtifact carries the emulation (shader disk cache format 21). Hardware snaps window corners to 1/256 pixel before interpolating (measured by yahya308 on an RTX 4070 Laptop and a Radeon 780M in boykopovar#1547); lavapipe snaps only for coverage (lp_rast.h FIXED_ORDER 8), so the test accepts either reference per pixel at the original 1e-4 margin. Sources: - v_interp_p1_f32/v_interp_p2_f32/v_interp_mov_f32 semantics (P0, P10, P20, I/J): AMD RDNA2 ISA, "Parameter Interpolation". - BaryCoordKHR/BaryCoordNoPerspKHR weights and PerVertexKHR vertex order: SPV_KHR_fragment_shader_barycentric, VK_KHR_fragment_shader_barycentric. - Geometry shader triangle input order and provoking vertex: Vulkan specification, "Primitive Topologies" and "Geometry Shading". - Interpolation decorations decided by the fragment input: Vulkan specification, "Interpolation Decorations". - lavapipe interpolation: Mesa src/gallium/drivers/llvmpipe/lp_rast.h (FIXED_ORDER 8), lp_setup_tri.c (subpixel_snap for coverage only), lp_state_setup.c (init_args, plane setup from float positions).
… stage Without VK_KHR_fragment_shader_barycentric the recompiler dropped v_interp_p1_f32 and made v_interp_p2_f32 read the attribute Vulkan interpolated, but never wrote the I/J VGPRs: a pixel shader that read them for anything else got 0, and v_interp_mov_f32 of P10/P20 (or P0 of an interpolated input) needed PerVertexKHR and failed pipeline validation. The fallback translation now loads the I/J VGPRs and marks v_interp_p1 and v_interp_p2 with witnesses. After the front-end simplification (SSA, folding, DCE, read-lane and masked-select elimination), HostInterpolationChecker requires every p2 to read its J, and its p1's I, from a barycentric of the parameter's kind, every p1 result to reach only the matching p2 (through a write masked by the p2's EXEC, whose inactive lanes keep the old register), no parameter read as raw vertices and no slot shared by different interpolations. It then lowers the witnesses to the host-interpolated attribute as before, reruns DCE and masked-select elimination, and accepts the program only when no barycentric read remains, so reusing the I/J VGPRs after interpolating keeps host interpolation and the previous SPIR-V. Any other program is translated with the barycentric path and marked as emulated: its barycentrics become vec3 inputs (NoPerspective for the linear weights) and its per-vertex parameters flat vec4[3] inputs, placed in the free locations by LayoutBarycentricEmulation. The driver then inserts a generated geometry stage between the vertex and fragment shaders that copies the position and parameters, writes the one-hot weights of each vertex and the three vertices' parameter values, so the rasterizer interpolates the same weights the extension provides. The stage needs geometryShader and a vertex shader before it; tessellation, mesh and rect-list pipelines, other primitive types and interfaces beyond 32 locations or the device's geometry limits throw. CompiledShaderArtifact carries the emulation (shader disk cache format 21). Hardware snaps window corners to 1/256 pixel before interpolating (measured by yahya308 on an RTX 4070 Laptop and a Radeon 780M in boykopovar#1547); lavapipe snaps only for coverage (lp_rast.h FIXED_ORDER 8), so the test accepts either reference per pixel at the original 1e-4 margin. Sources: - v_interp_p1_f32/v_interp_p2_f32/v_interp_mov_f32 semantics (P0, P10, P20, I/J): AMD RDNA2 ISA, "Parameter Interpolation". - BaryCoordKHR/BaryCoordNoPerspKHR weights and PerVertexKHR vertex order: SPV_KHR_fragment_shader_barycentric, VK_KHR_fragment_shader_barycentric. - Geometry shader triangle input order and provoking vertex: Vulkan specification, "Primitive Topologies" and "Geometry Shading". - Interpolation decorations decided by the fragment input: Vulkan specification, "Interpolation Decorations". - lavapipe interpolation: Mesa src/gallium/drivers/llvmpipe/lp_rast.h (FIXED_ORDER 8), lp_setup_tri.c (subpixel_snap for coverage only), lp_state_setup.c (init_args, plane setup from float positions).
|
Checked on lavapipe ( The branch conflicted with Not checked here: behaviour on NVIDIA/Windows hardware. @yahya308, if you can, please rerun |
886a704 to
4a2e605
Compare
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
|
… stage Without VK_KHR_fragment_shader_barycentric the recompiler dropped v_interp_p1_f32 and made v_interp_p2_f32 read the attribute Vulkan interpolated, but never wrote the I/J VGPRs: a pixel shader that read them for anything else got 0, and v_interp_mov_f32 of P10/P20 (or P0 of an interpolated input) needed PerVertexKHR and failed pipeline validation. The fallback translation now loads the I/J VGPRs and marks v_interp_p1 and v_interp_p2 with witnesses. After the front-end simplification (SSA, folding, DCE, read-lane and masked-select elimination), HostInterpolationChecker requires every p2 to read its J, and its p1's I, from a barycentric of the parameter's kind, every p1 result to reach only the matching p2 (through a write masked by the p2's EXEC, whose inactive lanes keep the old register), no parameter read as raw vertices and no slot shared by different interpolations. It then lowers the witnesses to the host-interpolated attribute as before, reruns DCE and masked-select elimination, and accepts the program only when no barycentric read remains, so reusing the I/J VGPRs after interpolating keeps host interpolation and the previous SPIR-V. Any other program is translated with the barycentric path and marked as emulated: its barycentrics become vec3 inputs (NoPerspective for the linear weights) and its per-vertex parameters flat vec4[3] inputs, placed in the free locations by LayoutBarycentricEmulation. The driver then inserts a generated geometry stage between the vertex and fragment shaders that copies the position and parameters, writes the one-hot weights of each vertex and the three vertices' parameter values, so the rasterizer interpolates the same weights the extension provides. The stage needs geometryShader and a vertex shader before it; tessellation, mesh and rect-list pipelines, other primitive types and interfaces beyond 32 locations or the device's geometry limits throw. CompiledShaderArtifact carries the emulation (shader disk cache format 21). Hardware snaps window corners to 1/256 pixel before interpolating (measured by yahya308 on an RTX 4070 Laptop and a Radeon 780M in boykopovar#1547); lavapipe snaps only for coverage (lp_rast.h FIXED_ORDER 8), so the test accepts either reference per pixel at the original 1e-4 margin. Sources: - v_interp_p1_f32/v_interp_p2_f32/v_interp_mov_f32 semantics (P0, P10, P20, I/J): AMD RDNA2 ISA, "Parameter Interpolation". - BaryCoordKHR/BaryCoordNoPerspKHR weights and PerVertexKHR vertex order: SPV_KHR_fragment_shader_barycentric, VK_KHR_fragment_shader_barycentric. - Geometry shader triangle input order and provoking vertex: Vulkan specification, "Primitive Topologies" and "Geometry Shading". - Interpolation decorations decided by the fragment input: Vulkan specification, "Interpolation Decorations". - lavapipe interpolation: Mesa src/gallium/drivers/llvmpipe/lp_rast.h (FIXED_ORDER 8), lp_setup_tri.c (subpixel_snap for coverage only), lp_state_setup.c (init_args, plane setup from float positions).
…ic-emulation feat(shader): emulate fragment barycentrics with a generated geometry stage
What
Without
VK_KHR_fragment_shader_barycentric(lavapipe in CI, NVIDIA before Turing, older Intel), the recompiler droppedv_interp_p1_f32and hadv_interp_p2_f32read the attribute Vulkan interpolated, but it never wrote the I/J VGPRs. So onmain:v_interp_p1/p2of the same pair (EvaluateAttributeAt*-style offsets, custom weights, exporting them) silently gets 0;v_interp_mov_f32of P10/P20, or of P0 of an interpolated input, needsPerVertexKHRand fails pipeline validation ("device does not support enabled VK_KHR_fragment_shader_barycentric");This removes the silent stub listed in TechnicalDebt and replaces it with exact emulation:
v_interp_p1_f32writes anInterpolateHostP1witness, andv_interp_p2_f32adds a side-effectingInterpolateHostP2(attr, chan, J, partial)witness before its usual host-interpolated write.HostInterpolationChecker, which runs after the SSA/fold/DCE/read-lane/masked-select passes:BaryCoordKHR/BaryCoordNoPerspKHRbecomevec3inputs; the linear one isNoPerspective.PerVertexKHRparameters becomeFlat vec4[3]inputs.LayoutBarycentricEmulation.RecompileResult::barycentricEmulationrecords this; the shader disk cache format goes to 8.BuildBarycentricGeometryShadergenerates a pass-through geometry stage: triangles in, a three-vertex strip out. It copies the position and the plain parameters, writes the one-hot weights (1,0,0)/(0,1,0)/(0,0,1), and copies the three vertices' values into each per-vertex array. The rasterizer then interpolates exactly the weightsBaryCoordKHRgives, with the same vertex order (gl_in[0]stays the provoking vertex). The driver inserts the stage between the vertex and fragment shaders, like the rect-list stages.ValidateShadersaccepts[Vertex, Geometry, Fragment]only for an emulated fragment shader and triangle topologies. The pipeline and validation keys treat the stage as generated, andPipelinerequiresgeometryShader.Limits. Tessellation, mesh and rect-list pipelines with such a shader throw, as do other primitive types, more than 32 fragment input locations, and interfaces past the device's geometry limits. These limits are recorded in TechnicalDebt.
Behaviour that does not change:
v_interp_p1/p2keep host interpolation and their previous SPIR-V.Sources:
v_interp_p1_f32/v_interp_p2_f32/v_interp_mov_f32(P0, P10, P20, I/J): AMD "RDNA 2" Instruction Set Architecture, section "Parameter Interpolation".VK_KHR_fragment_shader_barycentric.Tested
Ubuntu (Linux 7.0, GCC 15.2.0), Release,
-DBUILD_TESTING=ON, lavapipe (Mesa, LLVM 21), which has noVK_KHR_fragment_shader_barycentricbut hasgeometryShader. No title or hardware GPU.agc_driver_pixel_barycentric_emulation(GPU, lavapipe). It draws a triangle whose vertices have w = 1, 2.5 and 0.5 into a 64x48 R32G32B32A32 float target:v0, v1with PERSP_CENTER, then with LINEAR_CENTER, matches the perspective-correct and the screen-space barycentrics computed on the CPU within 2.5e-4 at all 932 interior pixels (hardware snaps window vertices to 1/256 pixel before computing the weights, lavapipe does not).v_interp_mov_f32P10, P20 and P0 of an interpolated input give exactlyx1-x0,x2-x0andx0.v_interp_p1/p2keeps host interpolation and matches the perspective-correct value.mainthe same exports read 0 at every pixel (e.g. pixel (32,24): 0 0 0 0, expected I=0.137, J=0.717), and thev_interp_movdraw throws.agc_driver_graphics(barycentricEmulationTests), CPU only:FragmentBarycentricKHR, noBaryCoordKHR, flat arrays);ValidateShadersaccepts the three-stage pipeline for triangle lists and strips and refuses points or a non-emulated fragment shader;agc_shader_disk_cache: the new result fields round-trip.cteston this branch (rebased onmain6b56175): 480/480 passed, 24 GPU tests skipped by lavapipe (subgroup size 8), the same set as onmain.python3 tools/check_conventions.py --base upstream/main: passed.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository (attach them to this PR)