shader: keep pixel helper lanes out of the EXEC and VCC words

This commit is contained in:
nmzik
2026-10-26 06:53:55 +01:00
parent 025d1f7edf
commit 5429888d4c
20 changed files with 428 additions and 390 deletions
+137
View File
@@ -1436,6 +1436,7 @@ struct GraphicsCase {
u32 pixel_custom_interpolation_mask = 0;
u32 width = 1;
std::vector<u32> object_ids;
std::vector<u32> gds_initial;
};
struct CompiledShader {
@@ -19919,8 +19920,40 @@ public:
push_constant_range.size = ShaderRecompiler::IR::NativePushConstantSize;
}
using Kind = ShaderRecompiler::IR::DescriptorBindingKind;
const bool uses_gds = !test.gds_initial.empty();
const auto gds_bit = uint64_t{1} << static_cast<u32>(Kind::Gds);
Require(test.name, "graphics", fragment_bind.descriptor_mask == (uses_gds ? gds_bit : 0u),
"a graphics case binds GDS only, and only with initial GDS data");
Buffer gds_buffer;
vk::DescriptorBufferInfo gds_info{};
vk::WriteDescriptorSet gds_write{};
vk::DescriptorSetLayout descriptor_layout = nullptr;
if (uses_gds) {
gds_buffer = CreateStorageBuffer(test.name, test.gds_initial, test.gds_initial.size());
gds_info = {gds_buffer.buffer, 0, gds_buffer.size};
const vk::DescriptorSetLayoutBinding binding{
ShaderRecompiler::IR::NativeBinding(ShaderType::Pixel, Kind::Gds),
vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eFragment};
vk::DescriptorSetLayoutCreateInfo layout_info{};
layout_info.sType = vk::StructureType::eDescriptorSetLayoutCreateInfo;
layout_info.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR;
layout_info.bindingCount = 1;
layout_info.pBindings = &binding;
RequireVk(test.name, "graphics",
m_device.createDescriptorSetLayout(&layout_info, nullptr, &descriptor_layout),
"vkCreateDescriptorSetLayout");
gds_write.sType = vk::StructureType::eWriteDescriptorSet;
gds_write.dstBinding = binding.binding;
gds_write.descriptorCount = 1;
gds_write.descriptorType = vk::DescriptorType::eStorageBuffer;
gds_write.pBufferInfo = &gds_info;
}
vk::PipelineLayoutCreateInfo pipeline_layout_info{};
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
pipeline_layout_info.setLayoutCount = uses_gds ? 1u : 0u;
pipeline_layout_info.pSetLayouts = &descriptor_layout;
pipeline_layout_info.pushConstantRangeCount =
push_constant_range.size != 0 ? 1u : 0u;
pipeline_layout_info.pPushConstantRanges =
@@ -20077,6 +20110,10 @@ public:
}
cmd.beginRendering(rendering);
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
if (uses_gds) {
cmd.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, pipeline_layout, 0, 1,
&gds_write);
}
if (push_constant_range.size != 0) {
ShaderRecompiler::IR::PushData push_data;
if (object_id) {
@@ -20104,6 +20141,8 @@ public:
m_device.destroyPipeline(pipeline, nullptr);
m_device.destroyPipelineLayout(pipeline_layout, nullptr);
m_device.destroyDescriptorSetLayout(descriptor_layout, nullptr);
DestroyBuffer(&gds_buffer);
m_device.destroyShaderModule(fragment_module, nullptr);
m_device.destroyShaderModule(vertex_module, nullptr);
DestroyBuffer(&vertex_buffer);
@@ -21639,6 +21678,7 @@ private:
available_features.drawIndirectFirstInstance &&
available_features.multiDrawIndirect &&
available_features.depthBounds &&
available_features.fragmentStoresAndAtomics &&
available_depth_clip.depthClipEnable &&
available_clip_control.depthClipControl &&
available_color_write.colorWriteEnable &&
@@ -21738,6 +21778,7 @@ private:
device_features.drawIndirectFirstInstance = m_rasterization_supported;
device_features.multiDrawIndirect = m_rasterization_supported;
device_features.depthBounds = m_rasterization_supported;
device_features.fragmentStoresAndAtomics = m_rasterization_supported;
device_info.pEnabledFeatures = &device_features;
std::vector<const char *> device_extensions{
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
@@ -44098,6 +44139,92 @@ GraphicsCase GraphicsDsAddtidScratchExport() {
O::DS_READ_ADDTID_B32, O::EXP, O::S_ENDPGM}};
}
// Covers only pixel 1 of a 2x1 target, so the rest of its quad runs as helper invocations, and
// starts a GDS counter at 10 in dword 5.
void CoverOnePixelOfQuad(GraphicsCase *test) {
test->width = 2;
test->vertices = {0x00000000u, 0xbf800000u, 0, 0, 0, 0,
0x40400000u, 0xbf800000u, 0, 0, 0, 0,
0x00000000u, 0x40400000u, 0, 0, 0, 0};
test->gds_initial = {0, 0, 0, 0, 0, 10};
}
GraphicsCase GraphicsDsAppendSkipsHelperLanes(bool consume) {
using O = ShaderOpcode;
// The EXEC and VCC words exclude the helper lanes. Exports the first counter value, the second
// minus the first minus s_bcnt1(exec) (reversed for consume), s_bcnt1(exec), and the EXEC plus
// VCC counts after an if/endif on a compare of the interpolant (zero in every lane).
const u32 op = consume ? 0x3d : 0x3e;
std::vector<u32> code;
code.push_back(EncodeVintrp(0x00, 7, 0, 0, 0));
code.push_back(EncodeVintrp(0x01, 7, 0, 0, 1));
AppendSMovLiteral(&code, 124, 0x100);
code.push_back(EncodeDs0(op, 20, true));
code.push_back(EncodeDs1(1, 0, 0));
code.push_back(EncodeDs0(op, 20, true));
code.push_back(EncodeDs1(2, 0, 0));
code.push_back(EncodeSop1(0x10, 0, 126));
code.push_back(EncodeVop2(0x26, 3, Vgpr(consume ? 1 : 2), consume ? 2 : 1));
code.push_back(EncodeVop2(0x27, 3, 0, 3));
code.push_back(EncodeVop1(0x06, 4, Vgpr(1)));
code.push_back(EncodeVop1(0x05, 5, Vgpr(3)));
code.push_back(EncodeVop1(0x06, 6, 0));
code.push_back(EncodeVopc(0xc2, InlineU32(0), 7));
code.push_back(EncodeSop1(0x24, 2, 106));
code.push_back(EncodeSop2(0x11, 126, 126, 2));
code.push_back(EncodeSop1(0x10, 1, 126));
code.push_back(EncodeSop1(0x10, 4, 106));
code.push_back(EncodeSop2(0x00, 1, 1, 4));
code.push_back(EncodeVop1(0x06, 7, 1));
code.push_back(EncodeExp0(0x00, 0xf));
code.push_back(EncodeExp1(4, 5, 6, 7));
AppendEnd(&code);
GraphicsCase test;
test.name = consume ? "GraphicsDsConsumeSkipsHelperLanes" : "GraphicsDsAppendSkipsHelperLanes";
test.fragment_code = std::move(code);
CoverOnePixelOfQuad(&test);
test.expected_pixel = {0, 0, 0, 0, 0x41200000u, 0, 0x3f800000u, 0x40000000u};
test.opcodes = {O::V_INTERP_P1_F32, O::V_INTERP_P2_F32, O::S_MOV_B32,
consume ? O::DS_CONSUME : O::DS_APPEND, O::S_BCNT1_I32_B64,
O::V_SUB_NC_U32, O::V_SUBREV_NC_U32, O::V_CVT_F32_U32, O::V_CVT_F32_I32,
O::V_CMP_EQ_U32, O::S_AND_SAVEEXEC_B64, O::S_OR_B64, O::S_ADD_U32, O::EXP,
O::S_ENDPGM};
return test;
}
GraphicsCase GraphicsDsOrderedCountSkipsHelperLanes() {
using O = ShaderOpcode;
// A live lane adds the first lane's ADDR value (1) for the wave. Exports the first counter
// value and the difference to the second.
std::vector<u32> code;
code.push_back(EncodeVintrp(0x00, 7, 0, 0, 0));
code.push_back(EncodeVintrp(0x01, 7, 0, 0, 1));
code.push_back(EncodeVop1(0x01, 2, InlineU32(1)));
AppendSMovLiteral(&code, 124, 0x00100000u); // M0 base 0x10; offset0 adds 4: dword 5.
code.push_back(EncodeDs0(0x3f, 7, true));
code.push_back(EncodeDs1(1, 2, 2));
code.push_back(EncodeDs0(0x3f, 7, true));
code.push_back(EncodeDs1(3, 2, 2));
code.push_back(EncodeVop2(0x26, 4, Vgpr(3), 1));
code.push_back(EncodeVop1(0x06, 5, Vgpr(1)));
code.push_back(EncodeVop1(0x06, 6, Vgpr(4)));
code.push_back(EncodeExp0(0x00, 0xf));
code.push_back(EncodeExp1(5, 6, 6, 6));
AppendEnd(&code);
GraphicsCase test;
test.name = "GraphicsDsOrderedCountSkipsHelperLanes";
test.fragment_code = std::move(code);
CoverOnePixelOfQuad(&test);
test.expected_pixel = {0, 0, 0, 0, 0x41200000u, 0x3f800000u, 0x3f800000u, 0x3f800000u};
test.opcodes = {O::V_INTERP_P1_F32, O::V_INTERP_P2_F32, O::V_MOV_B32, O::S_MOV_B32,
O::DS_ORDERED_COUNT, O::V_SUB_NC_U32, O::V_CVT_F32_U32, O::EXP, O::S_ENDPGM};
return test;
}
GraphicsCase GraphicsDirectSgprPushConstantExport() {
using O = ShaderOpcode;
@@ -44970,6 +45097,9 @@ std::vector<GraphicsCase> MakeGraphicsCases() {
GraphicsFrontFaceBits(true, true),
GraphicsFlatInterpolatorExport(),
GraphicsDsAddtidScratchExport(),
GraphicsDsAppendSkipsHelperLanes(false),
GraphicsDsAppendSkipsHelperLanes(true),
GraphicsDsOrderedCountSkipsHelperLanes(),
GraphicsDirectSgprPushConstantExport(),
GraphicsInlineSrtScalarPromotionExport(),
GraphicsNullVmExportDiscardsInactiveExec(),
@@ -50967,6 +51097,13 @@ int main(int argc, char **argv) {
RunCase(&vulkan, Vop3pIntegerNegationCapturedAndSelectedHalves());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--helper-lanes-only") == 0) {
VulkanHarness vulkan;
RunGraphicsCase(&vulkan, GraphicsDsAppendSkipsHelperLanes(false));
RunGraphicsCase(&vulkan, GraphicsDsAppendSkipsHelperLanes(true));
RunGraphicsCase(&vulkan, GraphicsDsOrderedCountSkipsHelperLanes());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--dpp-only") == 0) {
VulkanHarness vulkan;
RunGraphicsCase(&vulkan, GraphicsSparseWaveMinimum());
+28
View File
@@ -13659,6 +13659,34 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
}
}
// The wave32 live mask projects to the entry predicate, and VCC keeps its compare predicate
// after WQM.
const uint32_t live_mask_shader[] = {
EncodeSop1(0x03, 0, 126), // s_mov_b32 s0, exec_lo
EncodeSop2(0x0e, 126, 126, 0), // s_and_b32 exec_lo, exec_lo, s0
EncodeVop1(0x01, 0, 242), // v_mov_b32 v0, 1.0
EncodeExp0(0x00, 0x1), EncodeExp1(0, 0, 0, 0),
EncodeSopp(0x01),
};
const uint32_t wqm_vcc_shader[] = {
EncodeSop1(0x09, 126, 126), // s_wqm_b32 exec_lo, exec_lo
EncodeVop2(0x23, 0, 193, 1), // v_mbcnt_lo_u32_b32 v0, -1, v1
EncodeVopc(0xc1, 129, 0), // v_cmp_lt_u32 vcc_lo, 1, v0
EncodeVop2(0x01, 2, 128, 0), // v_cndmask_b32 v2, 0, v0, vcc_lo
EncodeExp0(0x00, 0x1), EncodeExp1(2, 2, 2, 2),
EncodeSopp(0x01),
};
auto wave32_pixel_options = MakeCompileOptions(ShaderType::Pixel);
wave32_pixel_options.wave_size = 32u;
for (const std::span<const uint32_t> shader : {std::span<const uint32_t>(live_mask_shader),
std::span<const uint32_t>(wqm_vcc_shader)}) {
const auto pixel_result = RecompileForTest(shader, wave32_pixel_options);
CheckSpirvBinaryValidates(pixel_result.spirv);
Check(DisassembleSpirvBinary(pixel_result.spirv).find("OpGroupNonUniformBallot") ==
std::string::npos,
"wave32 pixel mask rebuilt a known predicate from a ballot");
}
const uint32_t local_shader[] = {
EncodeVopc(0xc1, 5 + 256, 8), // v_cmp_lt_u32 vcc, v5, v8
EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc