graphics: use per-invocation shader masks

This commit is contained in:
nmzik
2026-08-21 08:02:21 +02:00
parent 79443aa104
commit 49ea1680f8
30 changed files with 897 additions and 1140 deletions
+351 -214
View File
@@ -574,15 +574,14 @@ constexpr u32 EncodeMimg0(u32 opcode, u32 dmask, u32 nsa_dwords = 0,
return (0x3cu << 26u) | ((opcode >> 7u) & 0x1u) |
((nsa_dwords & 0x3u) << 1u) | ((dim & 0x7u) << 3u) |
((dmask & 0xfu) << 8u) | (glc ? (1u << 13u) : 0u) |
(r128 ? (1u << 15u) : 0u) |
((opcode & 0x7fu) << 18u);
(r128 ? (1u << 15u) : 0u) | ((opcode & 0x7fu) << 18u);
}
constexpr u32 EncodeMimg1(u32 vdata, u32 vaddr, u32 srsrc = 0, u32 ssamp = 0,
bool a16 = false, bool d16 = false) {
return ((ssamp & 0x1fu) << 21u) | ((srsrc & 0x1fu) << 16u) |
((vdata & 0xffu) << 8u) | (vaddr & 0xffu) |
(a16 ? (1u << 30u) : 0u) | (d16 ? (1u << 31u) : 0u);
((vdata & 0xffu) << 8u) | (vaddr & 0xffu) | (a16 ? (1u << 30u) : 0u) |
(d16 ? (1u << 31u) : 0u);
}
constexpr u32 EncodeVintrp(u32 opcode, u32 dst, u32 attr, u32 chan, u32 src) {
@@ -1178,7 +1177,8 @@ CompiledShader CompileCase(const TestCase &test) {
result.resources.user_data[reg - result.program.user_data_base]);
}
packed_user_data.resize(result.program.bindings.ShaderDataDwords());
const auto buffer_count = static_cast<u32>(result.program.info.buffers.size());
const auto buffer_count =
static_cast<u32>(result.program.info.buffers.size());
for (u32 i = 0; i < buffer_count; i++) {
u32 offset = 0;
if (i < test.storage_buffer_offsets.size()) {
@@ -1197,8 +1197,8 @@ CompiledShader CompileCase(const TestCase &test) {
Require(test.name, "shader data", offset < 256,
"address-memory offset is not representable");
const auto index = buffer_count + i;
packed_user_data[result.program.bindings.memory_offset_dword + index / 4u] |=
offset << ((index % 4u) * 8u);
packed_user_data[result.program.bindings.memory_offset_dword +
index / 4u] |= offset << ((index % 4u) * 8u);
}
return {std::move(result.spirv), std::move(result.program),
std::move(result.resources.flattened_srt),
@@ -3562,8 +3562,7 @@ public:
narrow_target.info.tile_mode = Prospero::TileMode::kRenderTarget;
narrow_target.info.mip_layout[0] = {0, 0x10000, 128, 1};
narrow_target.view_info.format = narrow_target.info.pixel_format;
narrow_target.view_info.usage =
vk::ImageUsageFlagBits::eColorAttachment;
narrow_target.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
const auto narrow_target_id = texture_cache.FindImage(narrow_target);
const auto narrow_target_view =
texture_cache.FindRenderTarget(narrow_target_id, narrow_target);
@@ -3868,14 +3867,13 @@ public:
std::memcpy(memory + raw_d16_offset, raw_d16_values.data(),
sizeof(raw_d16_values));
auto raw_d16 = MakeLinearDesc(
base + raw_d16_offset, sizeof(raw_d16_values),
vk::Format::eD16Unorm, Prospero::BufferFormat::k16UNorm,
Prospero::ImageType::kColor2D, {2, 1, 1}, 1, sizeof(uint16_t), 1);
base + raw_d16_offset, sizeof(raw_d16_values), vk::Format::eD16Unorm,
Prospero::BufferFormat::k16UNorm, Prospero::ImageType::kColor2D,
{2, 1, 1}, 1, sizeof(uint16_t), 1);
raw_d16.type = BindingType::DepthTarget;
raw_d16.view_info.format = vk::Format::eD16Unorm;
raw_d16.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
raw_d16.view_info.usage =
vk::ImageUsageFlagBits::eDepthStencilAttachment;
raw_d16.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
const auto raw_d16_image = texture_cache.FindImage(raw_d16);
(void)texture_cache.FindDepthTarget(raw_d16_image, raw_d16);
@@ -3887,13 +3885,13 @@ public:
raw_d16_uint.view_info.aspect = vk::ImageAspectFlagBits::eColor;
raw_d16_uint.view_info.usage = vk::ImageUsageFlagBits::eSampled;
const auto raw_d16_uint_image = texture_cache.FindImage(raw_d16_uint);
Require(name, "raw D16 uint texture backing",
raw_d16_uint_image && raw_d16_uint_image != raw_d16_image &&
!TextureCacheTestAccess::Contains(texture_cache,
raw_d16_image) &&
texture_cache.GetImage(raw_d16_uint_image)
.backing.format == vk::Format::eR16Uint,
"D16 uint sampling did not recreate a true integer color backing");
Require(
name, "raw D16 uint texture backing",
raw_d16_uint_image && raw_d16_uint_image != raw_d16_image &&
!TextureCacheTestAccess::Contains(texture_cache, raw_d16_image) &&
texture_cache.GetImage(raw_d16_uint_image).backing.format ==
vk::Format::eR16Uint,
"D16 uint sampling did not recreate a true integer color backing");
Require(name, "raw D16 uint texture view",
texture_cache.FindTexture(raw_d16_uint_image, raw_d16_uint) !=
nullptr,
@@ -3916,9 +3914,9 @@ public:
layered_d16_color);
auto &layered_d16_color_native =
texture_cache.GetImage(layered_d16_color_image);
layered_d16_color_native.Transit(
vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, {}, scheduler.Current().Handle());
layered_d16_color_native.Transit(vk::ImageLayout::eTransferDstOptimal,
vk::AccessFlagBits2::eTransferWrite, {},
scheduler.Current().Handle());
vk::ClearColorValue layered_raw_d16_clear_value{};
layered_raw_d16_clear_value.float32[0] = 1.0f;
const vk::ImageSubresourceRange layered_raw_d16_clear_range{
@@ -3931,8 +3929,7 @@ public:
auto layered_raw_d16_depth = layered_d16_color;
layered_raw_d16_depth.type = BindingType::DepthTarget;
layered_raw_d16_depth.info.data.size =
sizeof(uint16_t) * 2 * 5;
layered_raw_d16_depth.info.data.size = sizeof(uint16_t) * 2 * 5;
layered_raw_d16_depth.info.pixel_format = vk::Format::eD16Unorm;
layered_raw_d16_depth.info.guest_format =
Prospero::BufferFormat::k16UNorm;
@@ -3940,8 +3937,7 @@ public:
layered_raw_d16_depth.info.mip_layout[0].size =
layered_raw_d16_depth.info.data.size;
layered_raw_d16_depth.view_info.format = vk::Format::eD16Unorm;
layered_raw_d16_depth.view_info.aspect =
vk::ImageAspectFlagBits::eDepth;
layered_raw_d16_depth.view_info.aspect = vk::ImageAspectFlagBits::eDepth;
layered_raw_d16_depth.view_info.layer_count = 5;
layered_raw_d16_depth.view_info.usage =
vk::ImageUsageFlagBits::eDepthStencilAttachment;
@@ -3949,28 +3945,25 @@ public:
texture_cache.FindImage(layered_raw_d16_depth);
const auto &layered_depth_owner =
texture_cache.GetImage(layered_raw_d16_depth_image);
Require(
name, "layered raw D16 owner layout",
layered_raw_d16_depth_image != layered_d16_color_image &&
!TextureCacheTestAccess::Contains(
texture_cache, layered_d16_color_image) &&
layered_depth_owner.info.resources.layers == 6 &&
layered_depth_owner.info.data.size ==
sizeof(layered_raw_d16_values) &&
layered_depth_owner.info.mip_layout[0].size ==
sizeof(layered_raw_d16_values),
"a partial depth view detached the retained six-layer owner from "
"its full guest range");
Require(name, "layered raw D16 owner layout",
layered_raw_d16_depth_image != layered_d16_color_image &&
!TextureCacheTestAccess::Contains(texture_cache,
layered_d16_color_image) &&
layered_depth_owner.info.resources.layers == 6 &&
layered_depth_owner.info.data.size ==
sizeof(layered_raw_d16_values) &&
layered_depth_owner.info.mip_layout[0].size ==
sizeof(layered_raw_d16_values),
"a partial depth view detached the retained six-layer owner from "
"its full guest range");
(void)texture_cache.FindDepthTarget(layered_raw_d16_depth_image,
layered_raw_d16_depth);
auto reacquired_layered_raw_d16_uint = layered_d16_color;
reacquired_layered_raw_d16_uint.info.pixel_format =
vk::Format::eR16Uint;
reacquired_layered_raw_d16_uint.info.pixel_format = vk::Format::eR16Uint;
reacquired_layered_raw_d16_uint.info.guest_format =
Prospero::BufferFormat::k16UInt;
reacquired_layered_raw_d16_uint.view_info.format =
vk::Format::eR16Uint;
reacquired_layered_raw_d16_uint.view_info.format = vk::Format::eR16Uint;
const auto reacquired_layered_raw_d16_uint_image =
texture_cache.FindImage(reacquired_layered_raw_d16_uint);
const auto reacquired_layered_raw_d16_uint_view =
@@ -3986,13 +3979,13 @@ public:
vk::AccessFlagBits2::eTransferRead, {}, scheduler.Current().Handle());
vk::BufferImageCopy layered_raw_d16_copy{};
layered_raw_d16_copy.bufferRowLength = 2;
layered_raw_d16_copy.imageSubresource = {
vk::ImageAspectFlagBits::eColor, 0, 0, 6};
layered_raw_d16_copy.imageSubresource = {vk::ImageAspectFlagBits::eColor,
0, 0, 6};
layered_raw_d16_copy.imageExtent = {2, 1, 1};
scheduler.Current().Handle().copyImageToBuffer(
reacquired_layered_raw_d16_native.backing.image,
vk::ImageLayout::eTransferSrcOptimal,
layered_raw_d16_readback.buffer, 1, &layered_raw_d16_copy);
vk::ImageLayout::eTransferSrcOptimal, layered_raw_d16_readback.buffer,
1, &layered_raw_d16_copy);
HostReadBarrier(layered_raw_d16_readback.buffer,
layered_raw_d16_readback.size,
vk::PipelineStageFlagBits::eTransfer,
@@ -4010,8 +4003,8 @@ public:
reacquired_layered_raw_d16_uint_image &&
reacquired_layered_raw_d16_uint_image !=
layered_raw_d16_depth_image &&
!TextureCacheTestAccess::Contains(
texture_cache, layered_raw_d16_depth_image) &&
!TextureCacheTestAccess::Contains(texture_cache,
layered_raw_d16_depth_image) &&
reacquired_layered_raw_d16_native.backing.format ==
vk::Format::eR16Uint &&
reacquired_layered_raw_d16_uint_view != nullptr &&
@@ -7717,14 +7710,13 @@ public:
RenderDepthInfo read_only_clear_depth{};
RenderExecutorTestAccess::ResolveRenderDepthTarget(
executor, 1, scheduler.Current(), read_only_clear_depth);
Require(
name, "read-only stencil clear suppression",
read_only_clear_depth.image_id == phased_depth.image_id &&
!read_only_clear_depth.stencil_clear_enable &&
!read_only_clear_depth.AttachmentWriteAspects() &&
depth_attachment_layout(read_only_clear_depth) ==
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
"a PS5 target-level stencil write disable retained a host clear");
Require(name, "read-only stencil clear suppression",
read_only_clear_depth.image_id == phased_depth.image_id &&
!read_only_clear_depth.stencil_clear_enable &&
!read_only_clear_depth.AttachmentWriteAspects() &&
depth_attachment_layout(read_only_clear_depth) ==
vk::ImageLayout::eDepthStencilReadOnlyOptimal,
"a PS5 target-level stencil write disable retained a host clear");
registers.SetDepthRenderTarget(phased_depth_target);
registers.SetDepthControl(phased_depth_control);
@@ -9972,21 +9964,21 @@ public:
const auto surface_format = TextureGetSurfaceFormatInfo(format);
constexpr u32 tail_xy[7][2] = {{64, 0}, {0, 64}, {32, 0}, {0, 32},
{16, 0}, {8, 16}, {0, 24}};
bool valid =
built && Prospero::NumBytesPerElement(format) == 4 &&
Prospero::BlockCompressedBytesPerBlock(format) == 0 &&
Prospero::IsSampledTextureFormat(format) &&
Prospero::IsUintTextureFormat(format) &&
Prospero::RemapTextureFormat(format) ==
Prospero::BufferFormat::k32UInt &&
surface_format.vk_format == vk::Format::eR32Uint &&
surface_format.conversion_format == format &&
VulkanFormat(format) == vk::Format::eUndefined &&
surface.texture.block.block_width == 128 &&
surface.texture.block.block_height == 128 &&
surface.texture.block.block_depth == 1 &&
surface.first_tail_level == 1 && surface.mips[0].offset == 0x10000 &&
total.size == 0x20000 && total.align == 0x10000;
bool valid = built && Prospero::NumBytesPerElement(format) == 4 &&
Prospero::BlockCompressedBytesPerBlock(format) == 0 &&
Prospero::IsSampledTextureFormat(format) &&
Prospero::IsUintTextureFormat(format) &&
Prospero::RemapTextureFormat(format) ==
Prospero::BufferFormat::k32UInt &&
surface_format.vk_format == vk::Format::eR32Uint &&
surface_format.conversion_format == format &&
VulkanFormat(format) == vk::Format::eUndefined &&
surface.texture.block.block_width == 128 &&
surface.texture.block.block_height == 128 &&
surface.texture.block.block_depth == 1 &&
surface.first_tail_level == 1 &&
surface.mips[0].offset == 0x10000 && total.size == 0x20000 &&
total.align == 0x10000;
for (u32 level = 1; level < levels && valid; level++) {
valid &= surface.mips[level].offset == 0 &&
surface.mips[level].tail_x == tail_xy[level - 1][0] &&
@@ -9999,16 +9991,15 @@ public:
const auto storage_only_format = Prospero::BufferFormat::k32SInt;
const auto storage_only_surface =
TextureGetSurfaceFormatInfo(storage_only_format);
Require(
name, "storage-only surface mapping",
!Prospero::IsSampledTextureFormat(storage_only_format) &&
!Prospero::IsUintTextureFormat(storage_only_format) &&
Prospero::RemapTextureFormat(storage_only_format) ==
storage_only_format &&
storage_only_surface.vk_format == vk::Format::eR32Sint &&
storage_only_surface.conversion_format ==
Prospero::BufferFormat::kInvalid,
"storage-only native backing was conflated with sampled support");
Require(name, "storage-only surface mapping",
!Prospero::IsSampledTextureFormat(storage_only_format) &&
!Prospero::IsUintTextureFormat(storage_only_format) &&
Prospero::RemapTextureFormat(storage_only_format) ==
storage_only_format &&
storage_only_surface.vk_format == vk::Format::eR32Sint &&
storage_only_surface.conversion_format ==
Prospero::BufferFormat::kInvalid,
"storage-only native backing was conflated with sampled support");
}
{
@@ -11997,18 +11988,26 @@ TestCase ScalarNotB64UpdatesScc() {
code.push_back(EncodeSop1(0x08, 8, 6));
capture_scc(10);
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
code.push_back(EncodeSop1(0x04, 12, 106));
code.push_back(EncodeSop1(0x08, 14, 12));
capture_scc(16);
AppendStoreSgpr(&code, 4, 0);
AppendStoreSgpr(&code, 10, 1);
AppendStoreSgprPair(&code, 2, 2);
AppendStoreSgprPair(&code, 8, 4);
AppendStoreSgpr(&code, 16, 6);
AppendStoreSgprPair(&code, 14, 7);
AppendEnd(&code);
return {"ScalarNotB64UpdatesScc",
code,
{},
{0, 1, 0, 0, 1, 0},
{0, 1, 0, 0, 1, 0, 0, 0xfffffffeu, 0xffffffffu},
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_NOT_B64, O::S_CSELECT_B32,
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
}
TestCase ScalarQuadmaskB64() {
@@ -12109,9 +12108,8 @@ TestCase ScalarSaveExecOps() {
return {"ScalarSaveExecOps",
code,
{},
{0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0xffffffffu,
0xffffffffu, 0xffffffffu, 0x00000003u, 0xffffffffu, 0x00000003u,
0x00000002u, 0xffffffffu, 1},
// EXEC and saved EXEC values are invocation-local Booleans.
{1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0},
{O::S_MOV_B32, O::S_AND_SAVEEXEC_B64, O::S_ORN2_SAVEEXEC_B64,
O::S_ANDN1_SAVEEXEC_B64, O::S_AND_SAVEEXEC_B32,
O::S_ANDN1_SAVEEXEC_B32, O::S_MOV_B64, O::V_MOV_B32,
@@ -12138,7 +12136,7 @@ TestCase ScalarOrn2SaveexecUsesSourceOrNotExec() {
return {"ScalarOrn2SaveexecUsesSourceOrNotExec",
code,
{},
{0x0000000cu, 0x80000000u, 0xfffffff3u, 0x7fffffffu, 1},
{1, 0, 1, 0, 1},
{O::S_MOV_B32, O::S_ORN2_SAVEEXEC_B64, O::S_MOV_B64, O::V_MOV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
@@ -12380,15 +12378,13 @@ TestCase Scalar64BitOps() {
O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase ScalarAndn2B64SccBranch() {
TestCase ScalarAndn2B64SccUsesMaskShadow() {
using O = ShaderOpcode;
std::vector<u32> code = {
EncodeSMovB32(0, InlineU32(3)),
EncodeSMovB32(1, InlineU32(0)),
EncodeSMovB32(2, InlineU32(1)),
EncodeSMovB32(3, InlineU32(0)),
EncodeSop2(0x15, 4, 0, 2),
EncodeVopc(0xc2, InlineU32(0), 0),
EncodeSop1(0x04, 0, 106),
EncodeSop2(0x15, 4, 0, InlineU32(0)),
EncodeVop1(0x01, 0, InlineU32(1)),
EncodeSopp(0x04, 1),
EncodeVop1(0x01, 0, InlineU32(7)),
@@ -12396,14 +12392,141 @@ TestCase ScalarAndn2B64SccBranch() {
AppendStoreVgpr(&code, 0, 0);
AppendEnd(&code);
return {"ScalarAndn2B64SccBranch",
return {"ScalarAndn2B64SccUsesMaskShadow",
code,
{},
{7},
{O::S_MOV_B32, O::S_ANDN2_B64, O::V_MOV_B32, O::S_CBRANCH_SCC0,
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_ANDN2_B64, O::V_MOV_B32,
O::S_CBRANCH_SCC0, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase ScalarMaskHighWriteInvalidatesProvenance() {
using O = ShaderOpcode;
std::vector<u32> code = {
EncodeVopc(0xc2, Vgpr(0), 0), EncodeSop1(0x04, 0, 106),
EncodeSMovB32(1, InlineU32(1)), EncodeSop1(0x08, 2, 0),
EncodeVop1(0x01, 0, InlineU32(1)), EncodeSopp(0x04, 1),
EncodeVop1(0x01, 0, InlineU32(7)),
};
AppendStoreVgpr(&code, 0, 0);
AppendEnd(&code);
return {"ScalarMaskHighWriteInvalidatesProvenance",
code,
{},
{7},
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_MOV_B32, O::S_NOT_B64,
O::V_MOV_B32, O::S_CBRANCH_SCC0, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
}
TestCase ScalarSelectB64PreservesMaskProvenance() {
using O = ShaderOpcode;
std::vector<u32> code;
const auto set_scc = [&] {
code.push_back(EncodeSopc(0x06, InlineU32(1), InlineU32(1)));
};
const auto capture_scc = [&](u32 dst) {
code.push_back(EncodeSop2(0x0a, dst, InlineU32(1), InlineU32(0)));
};
AppendSMovLiteral(&code, 0, 0xfffffffeu);
AppendSMovLiteral(&code, 1, 0xffffffffu);
code.push_back(EncodeSMovB32(2, InlineU32(0)));
code.push_back(EncodeSMovB32(3, InlineU32(0)));
set_scc();
code.push_back(EncodeSop2(0x0b, 4, 0, 2));
code.push_back(EncodeSop1(0x08, 6, 4));
capture_scc(8);
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
code.push_back(EncodeSop1(0x04, 10, 106));
set_scc();
code.push_back(EncodeSop2(0x0b, 12, 10, 2));
code.push_back(EncodeSop1(0x08, 14, 12));
capture_scc(16);
AppendStoreSgpr(&code, 8, 0);
AppendStoreSgpr(&code, 16, 1);
AppendEnd(&code);
return {"ScalarSelectB64PreservesMaskProvenance",
code,
{},
{1, 0},
{O::S_MOV_B32, O::S_CMP_EQ_U32, O::S_CSELECT_B64, O::S_NOT_B64,
O::S_CSELECT_B32, O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_MOV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase ScalarWqmB64SelectsSccDomain() {
using O = ShaderOpcode;
std::vector<u32> code;
const auto capture_scc = [&](u32 dst) {
code.push_back(EncodeSop2(0x0a, dst, InlineU32(1), InlineU32(0)));
};
code.push_back(EncodeSMovB32(0, InlineU32(0)));
code.push_back(EncodeSMovB32(1, InlineU32(1)));
code.push_back(EncodeSop1(0x0a, 2, 0));
capture_scc(4);
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
code.push_back(EncodeSop1(0x04, 6, 106));
code.push_back(EncodeSop1(0x08, 8, 6));
code.push_back(EncodeSop1(0x0a, 10, 8));
capture_scc(12);
AppendStoreSgpr(&code, 4, 0);
AppendStoreSgpr(&code, 12, 1);
AppendEnd(&code);
return {"ScalarWqmB64SelectsSccDomain",
code,
{},
{1, 0},
{O::S_MOV_B32, O::S_WQM_B64, O::S_CSELECT_B32, O::V_CMP_EQ_U32,
O::S_MOV_B64, O::S_NOT_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
}
TestCase ScalarMaskProvenanceOverlapAndMixedBinary() {
using O = ShaderOpcode;
std::vector<u32> code;
const auto capture_scc = [&](u32 dst) {
code.push_back(EncodeSop2(0x0a, dst, InlineU32(1), InlineU32(0)));
};
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
code.push_back(EncodeSop1(0x04, 0, 106));
code.push_back(EncodeSop1(0x04, 1, 0));
code.push_back(EncodeSop1(0x08, 3, 1));
capture_scc(5);
code.push_back(EncodeVopc(0xc2, Vgpr(0), 0));
code.push_back(EncodeSop1(0x04, 8, 106));
code.push_back(EncodeSMovB32(10, InlineU32(1)));
code.push_back(EncodeSMovB32(11, InlineU32(0)));
code.push_back(EncodeSop2(0x19, 12, 8, 10));
capture_scc(14);
AppendStoreSgpr(&code, 5, 0);
AppendStoreSgpr(&code, 14, 1);
AppendEnd(&code);
return {"ScalarMaskProvenanceOverlapAndMixedBinary",
code,
{},
{0, 1},
{O::V_CMP_EQ_U32, O::S_MOV_B64, O::S_NOT_B64, O::S_CSELECT_B32,
O::S_MOV_B32, O::S_NAND_B64, O::V_MOV_B32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM}};
}
TestCase ScalarLiteral() {
using O = ShaderOpcode;
@@ -13003,7 +13126,9 @@ TestCase VectorAddcWritesPerLaneCarryOut() {
TestCase test;
test.name = "VectorAddcWritesPerLaneCarryOut";
test.code = code;
test.expected = std::vector<u32>(8, 0x0fu);
// VCC is represented as the current invocation's carry bit, not a shared
// four-lane ballot word.
test.expected = std::vector<u32>(8, 1u);
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_F_U32, O::V_ADDC_U32, O::BUFFER_STORE_DWORD,
O::S_ENDPGM};
@@ -13121,7 +13246,8 @@ TestCase VectorVop3BCarryOutWritesSgprMask() {
TestCase test;
test.name = "VectorVop3BCarryOutWritesSgprMask";
test.code = code;
test.expected = std::vector<u32>(12, 0x0fu);
// Scalar mask destinations keep the current invocation's bit.
test.expected = std::vector<u32>(12, 1u);
test.opcodes = {O::V_MOV_B32, O::V_ADD_I32, O::V_SUB_I32,
O::V_SUBREV_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
@@ -13153,7 +13279,7 @@ TestCase VectorVop3BCarryOutUsesEncodedSdst() {
TestCase test;
test.name = "VectorVop3BCarryOutUsesEncodedSdst";
test.code = code;
test.expected = std::vector<u32>(12, 0x0fu);
test.expected = std::vector<u32>(12, 1u);
test.opcodes = {O::V_MOV_B32, O::V_ADD_I32, O::V_SUB_I32,
O::V_SUBREV_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
@@ -13192,7 +13318,7 @@ TestCase VectorVop3BSubCoU32UsesRdna2Opcode310() {
TestCase test;
test.name = "VectorVop3BSubCoU32UsesRdna2Opcode310";
test.code = code;
test.expected = {0xffffffffu, 0, 0xfffffffeu, 0x80000001u, 9, 9, 9, 9};
test.expected = {0xffffffffu, 0, 0xfffffffeu, 0x80000001u, 1, 0, 0, 1};
test.opcodes = {O::V_MOV_B32, O::V_CMP_EQ_U32, O::V_CNDMASK_B32,
O::V_SUB_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
@@ -13631,9 +13757,12 @@ TestCase VectorPermlane16FetchInactiveZero() {
using O = ShaderOpcode;
std::vector<u32> code = {
EncodeVop1(0x01, 1, 0), EncodeVop2(0x25, 1, InlineU32(10), 1),
EncodeSMovB32(126, InlineU32(1)), EncodeSMovB32(127, InlineU32(0)),
EncodeSMovB32(0, InlineU32(1)), EncodeSMovB32(1, InlineU32(0)),
EncodeVop1(0x01, 1, 0),
EncodeVop2(0x25, 1, InlineU32(10), 1),
EncodeVopc(0xc2, InlineU32(0), 0),
EncodeSop1(0x04, 126, 106),
EncodeSMovB32(0, InlineU32(1)),
EncodeSMovB32(1, InlineU32(0)),
};
AppendVop3(&code, 0x377, 2, Vgpr(1), 0, 1);
AppendStoreVgpr(&code, 2, 0);
@@ -13643,8 +13772,9 @@ TestCase VectorPermlane16FetchInactiveZero() {
test.name = "VectorPermlane16FetchInactiveZero";
test.code = code;
test.expected = {0};
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32,
O::V_PERMLANE16_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_CMP_EQ_U32,
O::S_MOV_B64, O::S_MOV_B32, O::V_PERMLANE16_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
@@ -13657,9 +13787,9 @@ TestCase VectorPermlane16FetchInactiveFi() {
using O = ShaderOpcode;
std::vector<u32> code = {
EncodeVop1(0x01, 1, Vgpr(0)), EncodeVop2(0x25, 1, InlineU32(10), 1),
EncodeSMovB32(126, InlineU32(1)), EncodeSMovB32(127, InlineU32(0)),
EncodeSMovB32(0, InlineU32(1)), EncodeSMovB32(1, InlineU32(0)),
EncodeVop1(0x01, 1, Vgpr(0)), EncodeVop2(0x25, 1, InlineU32(10), 1),
EncodeVopc(0xc2, InlineU32(0), 0), EncodeSop1(0x04, 126, 106),
EncodeSMovB32(0, InlineU32(1)), EncodeSMovB32(1, InlineU32(0)),
};
AppendVop3(&code, 0x377, 2, Vgpr(1), 0, 1, 0, 1);
AppendStoreVgpr(&code, 2, 0);
@@ -13669,8 +13799,9 @@ TestCase VectorPermlane16FetchInactiveFi() {
test.name = "VectorPermlane16FetchInactiveFi";
test.code = code;
test.expected = {11};
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32,
O::V_PERMLANE16_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::V_CMP_EQ_U32,
O::S_MOV_B64, O::S_MOV_B32, O::V_PERMLANE16_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
@@ -13803,11 +13934,10 @@ TestCase Vop1MoveRelDestination() {
AppendVMovLiteral(&code, 6, 0xbbbbbbbbu);
AppendVMovLiteral(&code, 7, 0xccccccccu);
code.push_back(EncodeSMovB32(124, InlineU32(2)));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
code.push_back(EncodeSMovB32(127, InlineU32(0)));
code.push_back(EncodeVopc(0xc2, InlineU32(0), 0));
code.push_back(EncodeSop1(0x04, 126, 106));
code.push_back(EncodeVop1(0x42, 5, Vgpr(1)));
code.push_back(EncodeSMovB32(126, InlineU32(0xf)));
code.push_back(EncodeSMovB32(127, InlineU32(0)));
code.push_back(EncodeSop1(0x04, 126, 193u));
code.push_back(EncodeVop2(0x1a, 4, InlineU32(2), 0));
AppendBufferStoreDword(&code, 7, 4);
AppendEnd(&code);
@@ -13816,8 +13946,9 @@ TestCase Vop1MoveRelDestination() {
test.name = "Vop1MoveRelDestination";
test.code = code;
test.expected = {0x12345678u, 0xccccccccu, 0xccccccccu, 0xccccccccu};
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_MOVRELD_B32,
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_CMP_EQ_U32,
O::S_MOV_B64, O::V_MOVRELD_B32, O::V_LSHLREV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
@@ -14542,7 +14673,6 @@ TestCase VectorVop3CompareGtU64OnGpu() {
{1, 1, 0},
{O::V_MOV_B32, O::S_MOV_B32, O::V_CMP_GT_U64, O::V_CNDMASK_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
test.required_spirv = {"OpUGreaterThan"};
test.compute_info.threads_num[0] = 64;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
@@ -14731,13 +14861,10 @@ TestCase Vop3CndmaskUsesSgprMaskLaneBits() {
using O = ShaderOpcode;
std::vector<u32> code = {
EncodeVop1(0x01, 1, 0),
EncodeVop2(0x1a, 1, InlineU32(2), 1),
EncodeVop2(0x25, 1, Vgpr(0), 1),
EncodeVopc(0xc0, Vgpr(0), 0),
EncodeVop1(0x01, 1, 0), EncodeVop2(0x1a, 1, InlineU32(2), 1),
EncodeVop2(0x25, 1, Vgpr(0), 1), EncodeVopc(0xc2, InlineU32(0), 0),
EncodeSop1(0x04, 4, 106),
};
AppendSMovLiteral(&code, 4, 1);
AppendSMovLiteral(&code, 5, 0);
AppendVop3(&code, 0x101, 2, InlineU32(10), InlineU32(20), 4);
code.push_back(EncodeVop2(0x1a, 3, InlineU32(2), 1));
AppendBufferStoreDword(&code, 2, 3);
@@ -14748,7 +14875,7 @@ TestCase Vop3CndmaskUsesSgprMaskLaneBits() {
test.code = code;
test.expected = {20, 10, 10, 10, 20, 10, 10, 10};
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_F_U32, O::S_MOV_B32, O::V_CNDMASK_B32,
O::V_CMP_EQ_U32, O::S_MOV_B64, O::V_CNDMASK_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.threads_num[0] = 4;
test.compute_info.threads_num[1] = 1;
@@ -14962,7 +15089,7 @@ TestCase SimpleLoop() {
O::S_BRANCH, O::V_MOV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
}
TestCase BranchVccnzUsesWholeMask() {
TestCase BranchVccnzUsesInvocationMask() {
using O = ShaderOpcode;
std::vector<u32> code = {
@@ -14980,9 +15107,9 @@ TestCase BranchVccnzUsesWholeMask() {
AppendEnd(&code);
TestCase test;
test.name = "BranchVccnzUsesWholeMask";
test.name = "BranchVccnzUsesInvocationMask";
test.code = code;
test.expected = std::vector<u32>(8, 42);
test.expected = {42, 11, 11, 11, 42, 11, 11, 11};
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_EQ_U32, O::S_CBRANCH_VCCNZ, O::S_BRANCH,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
@@ -16647,7 +16774,7 @@ TestCase FlatSubdwordLoadsApplyByteOffset() {
return test;
}
TestCase BranchVccnzUsesCarryProducedWholeMask() {
TestCase BranchVccnzUsesCarryProducedInvocationMask() {
using O = ShaderOpcode;
std::vector<u32> code = {
@@ -16665,9 +16792,9 @@ TestCase BranchVccnzUsesCarryProducedWholeMask() {
AppendEnd(&code);
TestCase test;
test.name = "BranchVccnzUsesCarryProducedWholeMask";
test.name = "BranchVccnzUsesCarryProducedInvocationMask";
test.code = code;
test.expected = std::vector<u32>(8, 42);
test.expected = {11, 42, 42, 42, 42, 42, 42, 42};
test.opcodes = {O::V_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::V_CMP_F_U32, O::V_ADDC_U32, O::S_CBRANCH_VCCNZ,
O::S_BRANCH, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
@@ -18165,7 +18292,8 @@ TestCase ImageD16GatherPacksHalfPairs() {
O::S_ENDPGM};
test.sampled_image_rgba = image;
test.decoded_counts = {{"d16=1", 1}};
test.native_ir_counts = {{"dmask=0x1 data_dwords=2 data_bits=16 component=0/4", 1}};
test.native_ir_counts = {
{"dmask=0x1 data_dwords=2 data_bits=16 component=0/4", 1}};
test.required_spirv = {"OpImageGather", "PackHalf2x16"};
return test;
}
@@ -18376,8 +18504,8 @@ TestCase ImageD16StoreUnpacksHalfPairs() {
AppendEnd(&code);
auto expected_image = MakeRgbaImage(4, 4);
SetRgbaPixel(&expected_image, 4, 1, 2, 0x3e800000u, 0x3f000000u,
0x3f400000u, 0x3f800000u);
SetRgbaPixel(&expected_image, 4, 1, 2, 0x3e800000u, 0x3f000000u, 0x3f400000u,
0x3f800000u);
TestCase test;
test.name = "ImageD16StoreUnpacksHalfPairs";
@@ -18386,7 +18514,8 @@ TestCase ImageD16StoreUnpacksHalfPairs() {
test.storage_image_rgba = MakeRgbaImage(4, 4);
test.expected_storage_image_rgba = expected_image;
test.decoded_counts = {{"d16=1", 1}};
test.native_ir_counts = {{"dmask=0xf data_dwords=2 data_bits=16 component=0/4", 1}};
test.native_ir_counts = {
{"dmask=0xf data_dwords=2 data_bits=16 component=0/4", 1}};
test.required_spirv = {"UnpackHalf2x16", "OpImageWrite"};
return test;
}
@@ -19286,7 +19415,11 @@ std::vector<TestCase> MakeCases() {
AddCase(ScalarBfeI32CapturedRawSignExtends);
AddCase(BitfieldExtractWidthPastEndEdges);
AddCase(Scalar64BitOps);
AddCase(ScalarAndn2B64SccBranch);
AddCase(ScalarAndn2B64SccUsesMaskShadow);
AddCase(ScalarMaskHighWriteInvalidatesProvenance);
AddCase(ScalarSelectB64PreservesMaskProvenance);
AddCase(ScalarWqmB64SelectsSccDomain);
AddCase(ScalarMaskProvenanceOverlapAndMixedBinary);
AddCase(ScalarLiteral);
AddCase(VectorMoves);
AddCase(VectorVop3MoveAppliesFloatSourceModifiers);
@@ -19365,8 +19498,8 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorCompareInvertedMaskSelect);
AddCase(BranchSelect);
AddCase(SimpleLoop);
AddCase(BranchVccnzUsesWholeMask);
AddCase(BranchVccnzUsesCarryProducedWholeMask);
AddCase(BranchVccnzUsesInvocationMask);
AddCase(BranchVccnzUsesCarryProducedInvocationMask);
AddCase(ScalarMemoryLoadVariants);
AddCase(ScalarLoadSignedImmediateOffsetAddsSoffset);
AddCase(ScalarLoadAlignsComponentsAndMasksAddress);
@@ -21053,8 +21186,8 @@ void CheckBasicStorageTextureDescriptor() {
"PPSA02604 BGRA 2D storage descriptor fixture is malformed");
ValidateStorageTexture(BasicBgraStorageTextureResource(), bgra, 0x870000);
const ShaderTextureResource r128{{0x0202e500u, 0xc8200000u, 0x010dc1dfu,
0x91b00facu, 0, 0, 0, 0}};
const ShaderTextureResource r128{
{0x0202e500u, 0xc8200000u, 0x010dc1dfu, 0x91b00facu, 0, 0, 0, 0}};
auto r128_resource = BasicBgraStorageTextureResource();
r128_resource.r128 = true;
Require("BasicStorageTexture", "PPSA01736 R128 descriptor",
@@ -22542,7 +22675,8 @@ void CheckPm4DirectShaderRegisterFallback(RenderContext &renderer) {
processor.Process(execution, packet.data(), packet.size()) ==
Pm4ProcessResult::Complete &&
processor.GetShCtx().GetVs().es_regs.data_addr == shader_address,
"direct SET_SH_REG did not route its ES base pair through the register handlers");
"direct SET_SH_REG did not route its ES base pair through the "
"register handlers");
std::printf("[host] %-32s ok\n", "Pm4DirectShaderRegisterFallback");
}
@@ -22577,7 +22711,8 @@ void CheckPm4GuardBandRegisterRanges(RenderContext &renderer) {
std::bit_cast<uint32_t>(10.0f),
};
Pm4Execution range_execution;
const auto range_result = processor.Process(range_execution, range.data(), range.size());
const auto range_result =
processor.Process(range_execution, range.data(), range.size());
const auto &range_viewport = processor.GetCtx().GetScreenViewport();
Require("Pm4GuardBandRegisterRanges", "full range",
range_result == Pm4ProcessResult::Complete &&
@@ -22644,12 +22779,13 @@ void CheckPm4BlendColorRegisterRanges(RenderContext &renderer) {
std::bit_cast<uint32_t>(14.0f),
};
Pm4Execution full_execution;
const auto full_result = processor.Process(full_execution, full.data(), full.size());
const auto full_result =
processor.Process(full_execution, full.data(), full.size());
const auto &full_color = processor.GetCtx().GetBlendColor();
Require("Pm4BlendColorRegisterRanges", "full range",
full_result == Pm4ProcessResult::Complete && full_color.red == 11.0f &&
full_color.green == 12.0f && full_color.blue == 13.0f &&
full_color.alpha == 14.0f,
full_result == Pm4ProcessResult::Complete &&
full_color.red == 11.0f && full_color.green == 12.0f &&
full_color.blue == 13.0f && full_color.alpha == 14.0f,
"full blend-color range did not preserve native PS5 register order");
std::printf("[host] %-32s ok\n", "Pm4BlendColorRegisterRanges");
}
@@ -22727,28 +22863,27 @@ void CheckAgcWaitPackets(RenderContext &renderer) {
nullptr,
0};
const auto address32 = reinterpret_cast<uint64_t>(&label32);
auto *emitted32 = Gen5::AgcDcbWaitRegMem(
reinterpret_cast<Gen5::CommandBuffer *>(&dcb32), 0, 3, 0, 0,
&label32, label32, UINT32_MAX, 400);
Require(
"AgcWaitPackets", "native 32-bit packet",
Gen5::AgcDcbWaitOnAddressGetSize(0) == 56 &&
emitted32 == packet32.data() &&
dcb32.cursor_up == packet32.data() + 14 &&
packet32[0] == 0xc0027904u && packet32[1] == 0x342u &&
packet32[2] ==
(0xc8010000u |
(static_cast<uint32_t>(address32 >> 32u) & 0xffffu)) &&
packet32[3] == static_cast<uint32_t>(address32) &&
packet32[4] == 0xc0053c00u && packet32[5] == 0x13u &&
packet32[6] == (static_cast<uint32_t>(address32) & ~0x3u) &&
packet32[7] ==
(static_cast<uint32_t>(address32 >> 32u) & 0x3ffffu) &&
packet32[8] == label32 && packet32[9] == UINT32_MAX &&
packet32[10] == 25u && packet32[11] == 0xc0017904u &&
packet32[12] == 0x342u && packet32[13] == 0xc8000000u &&
execute(packet32.data(), 14),
"32-bit waitOnAddress stream differs from native AGC");
auto *emitted32 =
Gen5::AgcDcbWaitRegMem(reinterpret_cast<Gen5::CommandBuffer *>(&dcb32), 0,
3, 0, 0, &label32, label32, UINT32_MAX, 400);
Require("AgcWaitPackets", "native 32-bit packet",
Gen5::AgcDcbWaitOnAddressGetSize(0) == 56 &&
emitted32 == packet32.data() &&
dcb32.cursor_up == packet32.data() + 14 &&
packet32[0] == 0xc0027904u && packet32[1] == 0x342u &&
packet32[2] ==
(0xc8010000u |
(static_cast<uint32_t>(address32 >> 32u) & 0xffffu)) &&
packet32[3] == static_cast<uint32_t>(address32) &&
packet32[4] == 0xc0053c00u && packet32[5] == 0x13u &&
packet32[6] == (static_cast<uint32_t>(address32) & ~0x3u) &&
packet32[7] ==
(static_cast<uint32_t>(address32 >> 32u) & 0x3ffffu) &&
packet32[8] == label32 && packet32[9] == UINT32_MAX &&
packet32[10] == 25u && packet32[11] == 0xc0017904u &&
packet32[12] == 0x342u && packet32[13] == 0xc8000000u &&
execute(packet32.data(), 14),
"32-bit waitOnAddress stream differs from native AGC");
alignas(8) uint32_t patched_label32 = 0xaabbccddu;
const auto patched_address32 = reinterpret_cast<uint64_t>(&patched_label32);
@@ -22757,7 +22892,8 @@ void CheckAgcWaitPackets(RenderContext &renderer) {
Require(
"AgcWaitPackets", "native 32-bit patch",
Gen5::AgcWaitRegMemPatchAddress(packet32.data(), &patched_label32) == 0 &&
Gen5::AgcWaitRegMemPatchReference(packet32.data(), patched_label32) == 0 &&
Gen5::AgcWaitRegMemPatchReference(packet32.data(), patched_label32) ==
0 &&
packet32[2] ==
(0xc8010000u |
(static_cast<uint32_t>(patched_address32 >> 32u) & 0xffffu)) &&
@@ -22780,31 +22916,30 @@ void CheckAgcWaitPackets(RenderContext &renderer) {
nullptr,
0};
const auto address64 = reinterpret_cast<uint64_t>(&label64);
auto *emitted64 = Gen5::AgcDcbWaitRegMem(
reinterpret_cast<Gen5::CommandBuffer *>(&dcb64), 1, 3, 0, 0,
&label64, label64, UINT64_MAX, 400);
Require(
"AgcWaitPackets", "native 64-bit packet",
Gen5::AgcDcbWaitOnAddressGetSize(1) == 64 &&
Gen5::AgcDcbWaitOnAddressGetSize(2) == 0 &&
emitted64 == packet64.data() &&
dcb64.cursor_up == packet64.data() + packet64.size() &&
packet64[0] == 0xc0027904u && packet64[1] == 0x342u &&
packet64[2] ==
(0xc8020000u |
(static_cast<uint32_t>(address64 >> 32u) & 0xffffu)) &&
packet64[3] == static_cast<uint32_t>(address64) &&
packet64[4] == 0xc0079300u && packet64[5] == 0x13u &&
packet64[6] == (static_cast<uint32_t>(address64) & ~0x7u) &&
packet64[7] ==
(static_cast<uint32_t>(address64 >> 32u) & 0x3ffffu) &&
packet64[8] == static_cast<uint32_t>(label64) &&
packet64[9] == static_cast<uint32_t>(label64 >> 32u) &&
packet64[10] == UINT32_MAX && packet64[11] == UINT32_MAX &&
packet64[12] == 25u && packet64[13] == 0xc0017904u &&
packet64[14] == 0x342u && packet64[15] == 0xc8000000u &&
execute(packet64.data(), packet64.size()),
"64-bit waitOnAddress stream differs from native AGC");
auto *emitted64 =
Gen5::AgcDcbWaitRegMem(reinterpret_cast<Gen5::CommandBuffer *>(&dcb64), 1,
3, 0, 0, &label64, label64, UINT64_MAX, 400);
Require("AgcWaitPackets", "native 64-bit packet",
Gen5::AgcDcbWaitOnAddressGetSize(1) == 64 &&
Gen5::AgcDcbWaitOnAddressGetSize(2) == 0 &&
emitted64 == packet64.data() &&
dcb64.cursor_up == packet64.data() + packet64.size() &&
packet64[0] == 0xc0027904u && packet64[1] == 0x342u &&
packet64[2] ==
(0xc8020000u |
(static_cast<uint32_t>(address64 >> 32u) & 0xffffu)) &&
packet64[3] == static_cast<uint32_t>(address64) &&
packet64[4] == 0xc0079300u && packet64[5] == 0x13u &&
packet64[6] == (static_cast<uint32_t>(address64) & ~0x7u) &&
packet64[7] ==
(static_cast<uint32_t>(address64 >> 32u) & 0x3ffffu) &&
packet64[8] == static_cast<uint32_t>(label64) &&
packet64[9] == static_cast<uint32_t>(label64 >> 32u) &&
packet64[10] == UINT32_MAX && packet64[11] == UINT32_MAX &&
packet64[12] == 25u && packet64[13] == 0xc0017904u &&
packet64[14] == 0x342u && packet64[15] == 0xc8000000u &&
execute(packet64.data(), packet64.size()),
"64-bit waitOnAddress stream differs from native AGC");
alignas(8) uint64_t patched_label64 = 0x11223344aabbccddull;
const auto patched_address64 = reinterpret_cast<uint64_t>(&patched_label64);
@@ -22826,18 +22961,20 @@ void CheckAgcWaitPackets(RenderContext &renderer) {
0x5a580000u) &&
packet64[8] == 0xaabbccddu && packet64[9] == 0x11223344u &&
execute(packet64.data(), packet64.size()),
"64-bit native wait patch did not preserve the native high reference DWORD");
"64-bit native wait patch did not preserve the native high reference "
"DWORD");
std::array<uint32_t, 16> invalid{};
const uint32_t short_payload = 0x342u;
Require("AgcWaitPackets", "invalid packet",
Gen5::AgcWaitRegMemPatchAddress(invalid.data(), &label32) ==
packet_mismatch &&
Gen5::AgcWaitRegMemPatchReference(invalid.data(), label32) ==
packet_mismatch &&
!Gen5::AgcIsInternalDataPacket(
KYTY_PM4(2, Pm4::IT_SET_UCONFIG_REG, 1), &short_payload),
"native wait patch accepted a packet without the AGC metadata prefix");
Require(
"AgcWaitPackets", "invalid packet",
Gen5::AgcWaitRegMemPatchAddress(invalid.data(), &label32) ==
packet_mismatch &&
Gen5::AgcWaitRegMemPatchReference(invalid.data(), label32) ==
packet_mismatch &&
!Gen5::AgcIsInternalDataPacket(
KYTY_PM4(2, Pm4::IT_SET_UCONFIG_REG, 1), &short_payload),
"native wait patch accepted a packet without the AGC metadata prefix");
std::printf("[host] %-32s ok\n", "AgcWaitPackets");
}
@@ -22873,17 +23010,17 @@ void CheckAgcDrawIndirectMultiPacket(RenderContext &renderer) {
0x55667788u, count_address, 0xaabbccddu, modifier);
const uint32_t invalid_payload[]{0x342u, 0xc6000010u};
Require(
"AgcDrawIndirectMulti", "native packet",
emitted == packet.data() && dcb.cursor_up == packet.data() + packet.size() &&
packet == expected && execute(packet.data(), 3u) &&
execute(packet.data() + 13u, 3u) &&
Gen5::AgcWaitRegMemPatchAddress(packet.data(), count_address) ==
packet_mismatch &&
Gen5::AgcWaitRegMemPatchReference(packet.data(), 0x12345678u) ==
packet_mismatch &&
!Gen5::AgcIsInternalDataPacket(0xc0017904u, invalid_payload),
"drawIndirectMulti stream differs from native AGC");
Require("AgcDrawIndirectMulti", "native packet",
emitted == packet.data() &&
dcb.cursor_up == packet.data() + packet.size() &&
packet == expected && execute(packet.data(), 3u) &&
execute(packet.data() + 13u, 3u) &&
Gen5::AgcWaitRegMemPatchAddress(packet.data(), count_address) ==
packet_mismatch &&
Gen5::AgcWaitRegMemPatchReference(packet.data(), 0x12345678u) ==
packet_mismatch &&
!Gen5::AgcIsInternalDataPacket(0xc0017904u, invalid_payload),
"drawIndirectMulti stream differs from native AGC");
std::printf("[host] %-32s ok\n", "AgcDrawIndirectMulti");
}
+87 -356
View File
@@ -8,7 +8,6 @@
#include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
#include "graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/pipeline/shaderSubgroup.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/backend/spirv/SpirvEmitter.h"
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
@@ -988,8 +987,7 @@ void TestSpirvRequirementsAnalysis() {
&analysis_error),
"initial SPIR-V requirements analysis failed");
const auto requirements = *program.spirv_requirements;
Check(requirements.requires_exact_subgroup && requirements.subgroup_ballot &&
requirements.subgroup_shuffle &&
Check(requirements.subgroup_ballot && requirements.subgroup_shuffle &&
requirements.subgroup_local_invocation_id &&
requirements.compute_derivatives &&
requirements.image_gather_extended && requirements.function_lds &&
@@ -1044,8 +1042,7 @@ void TestSpirvRequirementsAnalysis() {
&analysis_error),
"ADD_TID SPIR-V requirements analysis failed");
const auto add_tid_requirements = *add_tid.spirv_requirements;
Check(add_tid_requirements.requires_exact_subgroup &&
add_tid_requirements.subgroup_local_invocation_id &&
Check(add_tid_requirements.subgroup_local_invocation_id &&
!add_tid_requirements.subgroup_ballot &&
!add_tid_requirements.subgroup_shuffle,
"buffer ADD_TID requested the wrong subgroup contract");
@@ -1071,8 +1068,7 @@ void TestSpirvRequirementsAnalysis() {
&analysis_error),
"empty SPIR-V requirements analysis failed");
const auto empty_requirements = *empty.spirv_requirements;
Check(!empty_requirements.requires_exact_subgroup &&
!empty_requirements.subgroup_ballot &&
Check(!empty_requirements.subgroup_ballot &&
!empty_requirements.subgroup_shuffle &&
!empty_requirements.subgroup_local_invocation_id &&
!empty_requirements.compute_derivatives &&
@@ -1082,129 +1078,6 @@ void TestSpirvRequirementsAnalysis() {
"empty IR unexpectedly requested SPIR-V features");
}
void TestNativeSubgroupPolicy() {
const auto SetTypedValues = [](ShaderRecompiler::IR::Program &program,
ShaderRecompiler::IR::ValueOpcode opcode =
ShaderRecompiler::IR::ValueOpcode::Void) {
using namespace ShaderRecompiler::IR;
program.values = std::make_shared<ValueProgram>();
program.values->block_storage.push_back(std::make_unique<Block>());
auto *block = program.values->block_storage.back().get();
program.values->blocks.push_back(block);
if (opcode == ValueOpcode::ReadLane) {
block->AppendNewInst(opcode, {Value(0u), Value(0u)});
} else if (opcode == ValueOpcode::Ballot) {
block->AppendNewInst(opcode, {Value(true)});
} else if (opcode == ValueOpcode::DataAppend) {
program.values->memory_info.push_back({.kind = ResourceKind::Lds});
auto &append = block->AppendNewInst(
opcode, {Value(0u), Value(true), Value(1u), Value(0u)});
append.SetFlags(MemoryFlags{.index = 0});
}
std::string error;
Check(ShaderRecompiler::Spirv::AnalyzeProgramRequirements(program, &error),
"native subgroup requirements analysis failed");
};
GraphicContext context;
context.subgroup_size = 32;
context.min_subgroup_size = 32;
context.max_subgroup_size = 32;
context.subgroup_size_control_enabled = true;
context.required_subgroup_size_stages = vk::ShaderStageFlagBits::eAll;
ShaderRecompiler::IR::Program safe;
safe.wave_size = 32;
safe.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
SetTypedValues(safe);
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eVertex, safe)
.mode == ShaderSubgroupMode::Natural,
"native wave32 policy changed");
safe.wave_size = 64;
Check(SelectGraphicsLaneMaskMode(safe.wave_size) ==
ShaderLaneMaskMode::PerInvocation &&
ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eVertex, safe)
.mode == ShaderSubgroupMode::Unsupported,
"wave64 graphics mismatch accepted native-wave mask lowering");
safe.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eVertex, safe)
.mode == ShaderSubgroupMode::PerInvocationGraphics,
"wave64 graphics mismatch did not select per-invocation masks");
ShaderRecompiler::IR::Program cross_lane = safe;
SetTypedValues(cross_lane, ShaderRecompiler::IR::ValueOpcode::ReadLane);
Check(cross_lane.spirv_requirements->requires_exact_subgroup &&
ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eVertex,
cross_lane)
.mode == ShaderSubgroupMode::PerInvocationGraphics,
"graphics mismatch did not select per-invocation masks");
auto cross_lane_compute = cross_lane;
cross_lane_compute.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eCompute,
cross_lane_compute)
.mode == ShaderSubgroupMode::FlattenedMasks,
"split wave64 compute did not use native subgroup operations");
ShaderRecompiler::IR::Program zero_exec = safe;
zero_exec.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
SetTypedValues(zero_exec);
Check(!zero_exec.spirv_requirements->requires_exact_subgroup &&
ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eCompute,
zero_exec)
.mode == ShaderSubgroupMode::FlattenedMasks,
"compile-time uniform-zero EXEC write did not stay on the mask-free "
"path");
ShaderRecompiler::IR::Program selective_exec = safe;
SetTypedValues(selective_exec, ShaderRecompiler::IR::ValueOpcode::Ballot);
Check(selective_exec.spirv_requirements->requires_exact_subgroup,
"selective EXEC write was classified mask-free");
ShaderRecompiler::IR::Program carry = safe;
SetTypedValues(carry, ShaderRecompiler::IR::ValueOpcode::Ballot);
Check(carry.spirv_requirements->requires_exact_subgroup,
"lane-varying VCC carry producer was classified mask-free");
ShaderRecompiler::IR::Program vcc_branch = safe;
SetTypedValues(vcc_branch, ShaderRecompiler::IR::ValueOpcode::Ballot);
Check(vcc_branch.spirv_requirements->requires_exact_subgroup,
"whole-wave VCC branch was classified mask-free");
ShaderRecompiler::IR::Program ds_partial = safe;
ds_partial.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
SetTypedValues(ds_partial, ShaderRecompiler::IR::ValueOpcode::DataAppend);
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eCompute, ds_partial)
.mode == ShaderSubgroupMode::FlattenedMasks,
"split wave64 DS append did not use native subgroup operations");
context.max_subgroup_size = 64;
const auto controlled = ConfigureShaderSubgroup(
ShaderSubgroupCapabilities{context}, vk::ShaderStageFlagBits::eCompute,
cross_lane_compute);
Check(controlled.mode == ShaderSubgroupMode::Controlled &&
controlled.required_size == 64,
"supported controlled wave64 was not preferred over splitting");
context.subgroup_size = 64;
context.subgroup_size_control_enabled = false;
cross_lane.wave_size = 32;
cross_lane.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eFragment, cross_lane)
.mode == ShaderSubgroupMode::Unsupported,
"inverse graphics mismatch was accepted as one guest wave");
cross_lane_compute.wave_size = 32;
Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities{context},
vk::ShaderStageFlagBits::eCompute,
cross_lane_compute)
.mode == ShaderSubgroupMode::FlattenedMasks,
"inverse compute mismatch did not use the native subgroup");
}
std::array<uint32_t, 64>
ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
std::array<uint32_t, 64> data{};
@@ -5567,8 +5440,7 @@ void TestGraphicsCreateInterpolantMapping() {
"flat/custom interpolant mapping bits were unexpected");
Check(regs[3].value == 0x01580304u,
"f16 interpolant mapping bits were unexpected");
Check(regs[6].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 6u &&
regs[6].value == 6u,
Check(regs[6].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 6u && regs[6].value == 6u,
"interpolant identity tail was not filled");
Check(Gen5::AgcCreateInterpolantMapping2(native_regs, &gs, &ps) == 0,
@@ -7502,7 +7374,8 @@ void TestNewShaderRecompilerCfgMultipleLoopLatches() {
"multiple-latch fixture lacks two native backedges");
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
Check(graph.blocks.size() == original_block_count + 2u,
"multiple native latches did not create one synthetic continue and one empty header");
"multiple native latches did not create one synthetic continue and one "
"empty header");
Check(graph.back_edges.size() == 1u && graph.natural_loops.size() == 1u,
"multiple native latches were not coalesced to one SPIR-V backedge");
const auto &loop = graph.natural_loops.front();
@@ -7700,23 +7573,22 @@ void TestNewShaderRecompilerCfgExecSccSharedArm() {
}
}
for (const auto lane_mode :
{ShaderLaneMaskMode::NativeWave, ShaderLaneMaskMode::PerInvocation}) {
auto options = MakeCompileOptions(ShaderType::Compute);
options.lane_mask_mode = lane_mode;
options.dump_ir = true;
ShaderRecompiler::CompileResult result;
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
Check(!result.program.dispatcher_fallback &&
Common::ContainsStr(result.ir_dump, "mode=structured"),
"EXEC/SCC shared-arm epilogue did not stay structured");
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 3u,
"EXEC/SCC shared-arm SPIR-V has the wrong selection-merge count");
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
"EXEC/SCC shared-arm SPIR-V unexpectedly used dispatcher OpSwitch");
CheckSpirvBinaryValidates(result.spirv);
}
auto options = MakeCompileOptions(ShaderType::Compute);
options.dump_ir = true;
ShaderRecompiler::CompileResult result;
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
Check(!result.program.dispatcher_fallback &&
Common::ContainsStr(result.ir_dump, "mode=structured"),
"EXEC/SCC shared-arm epilogue did not stay structured");
Check(SpirvInstructionOpcodeCount(result.spirv, 247) == 3u,
"EXEC/SCC shared-arm SPIR-V has the wrong selection-merge count");
Check(SpirvInstructionOpcodeCount(result.spirv, 251) == 0u,
"EXEC/SCC shared-arm SPIR-V unexpectedly used dispatcher OpSwitch");
Check(!Common::ContainsStr(DisassembleSpirvBinary(result.spirv),
"OpGroupNonUniformBallot"),
"per-invocation EXEC/SCC branch reconstructed a native subgroup mask");
CheckSpirvBinaryValidates(result.spirv);
}
void TestNewShaderRecompilerCfgNestedTailEarlyExit() {
@@ -7755,13 +7627,16 @@ void TestNewShaderRecompilerCfgNestedTailEarlyExit() {
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
original_coverage,
"nested-tail routing changed semantic instruction coverage");
Check(std::ranges::count_if(graph.blocks, [](const auto &block) {
return block.terminator.condition ==
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
}) == 1u &&
std::ranges::count_if(graph.blocks, [](const auto &block) {
return block.terminator.goto_value >= 0;
}) == 3u,
Check(std::ranges::count_if(
graph.blocks,
[](const auto &block) {
return block.terminator.condition ==
ShaderRecompiler::CFG::BranchCondition::GotoVariable;
}) == 1u &&
std::ranges::count_if(graph.blocks,
[](const auto &block) {
return block.terminator.goto_value >= 0;
}) == 3u,
"nested-tail early exit did not use typed route state");
auto options = MakeCompileOptions(ShaderType::Pixel);
@@ -8000,9 +7875,10 @@ void TestNewShaderRecompilerCfgSharedTerminalEarlyExit() {
const auto original_coverage =
CfgInstructionCoverage(graph, decoded.instructions.size());
Check(ShaderRecompiler::CFG::Structurize(graph, &error), error.c_str());
Check(CfgInstructionCoverage(graph, decoded.instructions.size()) ==
original_coverage,
"shared-terminal structurization changed semantic instruction coverage");
Check(
CfgInstructionCoverage(graph, decoded.instructions.size()) ==
original_coverage,
"shared-terminal structurization changed semantic instruction coverage");
auto options = MakeCompileOptions(ShaderType::Pixel);
options.dump_ir = true;
@@ -8171,7 +8047,6 @@ void TestNewShaderRecompilerU64PairLowering() {
ir.Emit(IR::ValueOpcode::ULessThan64, {shifted, base});
ir.Emit(IR::ValueOpcode::SLessThan64, {shifted, base});
ir.Emit(IR::ValueOpcode::BitCount64, {shifted});
ir.Emit(IR::ValueOpcode::WqmU64, {shifted});
for (const uint32_t count : {1u, 31u, 32u, 33u, 63u}) {
ir.Emit(IR::ValueOpcode::ShiftLeftLogical64, {base, IR::Value(count)});
ir.Emit(IR::ValueOpcode::ShiftRightLogical64, {base, IR::Value(count)});
@@ -8710,10 +8585,9 @@ void TestDemandDrivenSpirvDeclarations() {
void TestTypedEntryStateIsMinimal() {
using namespace ShaderRecompiler;
const auto translate = [](ShaderLaneMaskMode lane_mode, uint32_t wave_size) {
const auto check = [](uint32_t wave_size) {
IR::Program source;
source.stage = ShaderType::Compute;
source.lane_mask_mode = lane_mode;
source.wave_size = wave_size;
source.user_data_count = 0;
source.blocks.emplace_back();
@@ -8726,11 +8600,7 @@ void TestTypedEntryStateIsMinimal() {
Check(Frontend::TranslateProgram(source, values, nullptr, nullptr, &compute,
&error),
error.c_str());
return values;
};
const auto check = [&](ShaderLaneMaskMode lane_mode, uint32_t wave_size) {
auto values = translate(lane_mode, wave_size);
uint32_t set_exec = 0;
uint32_t set_exec_lo = 0;
uint32_t set_exec_hi = 0;
@@ -8738,13 +8608,11 @@ void TestTypedEntryStateIsMinimal() {
IR::Value exec;
IR::Value exec_lo;
IR::Value exec_hi;
const IR::Inst *ballot = nullptr;
for (const auto *block : values.blocks) {
for (const auto &inst : *block) {
switch (inst.GetOpcode()) {
case IR::ValueOpcode::Ballot:
ballots++;
ballot = &inst;
break;
case IR::ValueOpcode::SetExec:
set_exec++;
@@ -8760,6 +8628,7 @@ void TestTypedEntryStateIsMinimal() {
break;
case IR::ValueOpcode::SetScalarRegister:
case IR::ValueOpcode::SetThreadBitScalarRegister:
case IR::ValueOpcode::SetScalarMaskTag:
case IR::ValueOpcode::SetVectorRegister:
case IR::ValueOpcode::SetScc:
case IR::ValueOpcode::SetVcc:
@@ -8774,44 +8643,19 @@ void TestTypedEntryStateIsMinimal() {
}
}
Check(set_exec == 1u && set_exec_lo == 1u && set_exec_hi == 1u &&
exec.IsImmediate() && exec.U1(),
"typed entry does not define EXEC exactly once");
if (lane_mode == ShaderLaneMaskMode::PerInvocation) {
Check(ballots == 0u && exec_lo.IsImmediate() && exec_lo.U32() == 1u &&
exec_hi.IsImmediate() && exec_hi.U32() == 0u,
"per-invocation entry EXEC is not the local {1,0} mask");
} else {
Check(ballots == 1u && !exec_lo.IsImmediate(),
"native compute entry EXEC does not use the active subgroup mask");
const auto *lo = exec_lo.ResolveInstruction();
Check(lo != nullptr &&
lo->GetOpcode() == IR::ValueOpcode::CompositeExtractU32x4 &&
lo->Arg(0).ResolveInstruction() == ballot &&
lo->Arg(1).Resolve().U32() == 0u,
"native compute EXEC_LO does not use ballot word zero");
if (wave_size == 32u) {
Check(exec_hi.IsImmediate() && exec_hi.U32() == 0u,
"native wave32 entry defined EXEC_HI");
} else {
const auto *hi = exec_hi.ResolveInstruction();
Check(hi != nullptr &&
hi->GetOpcode() == IR::ValueOpcode::CompositeExtractU32x4 &&
hi->Arg(0).ResolveInstruction() == ballot &&
hi->Arg(1).Resolve().U32() == 1u,
"native wave64 EXEC_HI does not use ballot word one");
}
}
ballots == 0u && exec.IsImmediate() && exec.U1() &&
exec_lo.IsImmediate() && exec_lo.U32() == 1u &&
exec_hi.IsImmediate() && exec_hi.U32() == 0u,
"typed entry is not the local {1,0} invocation mask");
IR::RewriteToSsa(values.blocks);
IR::RemoveIdentities(values.blocks);
IR::EliminateDeadCode(values.blocks);
std::string error;
Check(IR::ValidateValueProgram(values, true, &error), error.c_str());
};
check(ShaderLaneMaskMode::NativeWave, 32u);
check(ShaderLaneMaskMode::NativeWave, 64u);
check(ShaderLaneMaskMode::PerInvocation, 64u);
check(32u);
check(64u);
}
void TestFinalSsaRejectsRegisterStatePseudos() {
@@ -9145,36 +8989,9 @@ void TestValuePhiValidation() {
}
}
void TestWqmConstantPropagation() {
void TestWqmMaskSignatureAndU64ShiftConstantPropagation() {
using namespace ShaderRecompiler::IR;
ValueProgram program;
program.block_storage.push_back(std::make_unique<Block>());
program.blocks.push_back(program.block_storage.back().get());
program.block_info.emplace_back();
IREmitter ir(program.blocks.front());
constexpr std::array cases = {
std::pair{uint64_t{0}, uint64_t{0}},
std::pair{uint64_t{1}, uint64_t{0xfull}},
std::pair{uint64_t{1} << 31u, uint64_t{0xf} << 28u},
std::pair{uint64_t{1} << 32u, uint64_t{0xf} << 32u},
std::pair{uint64_t{0x8000001180000001ull},
uint64_t{0xf00000fff000000full}},
};
std::array<Value, cases.size()> results;
for (size_t index = 0; index < cases.size(); index++) {
results[index] = ir.Emit(ValueOpcode::WqmU64, {Value(cases[index].first)});
}
ConstantPropagationPass(program.blocks);
for (size_t index = 0; index < cases.size(); index++) {
const auto value = results[index].Resolve();
Check(value.IsImmediate() && value.GetType() == Type::U64 &&
value.U64() == cases[index].second,
"WqmU64 constant propagation crossed a quad or dword boundary");
}
const auto check_invalid_signature = [](ValueOpcode opcode, Value valid,
Value invalid) {
ValueProgram malformed;
@@ -9189,7 +9006,6 @@ void TestWqmConstantPropagation() {
Common::ContainsStr(error, "argument 0 has type"),
"value IR accepted a WQM operand that violates opcode metadata");
};
check_invalid_signature(ValueOpcode::WqmU64, Value(uint64_t{1}), Value(true));
check_invalid_signature(ValueOpcode::WqmMask, Value(true),
Value(uint64_t{1}));
@@ -9624,7 +9440,7 @@ void TestNewShaderRecompilerVertexSystemInputsWithoutMirrors() {
"vertex system values were routed through guest VGPR mirrors");
}
void TestNewShaderRecompilerVertexExportUsesLaneExecMask() {
void TestNewShaderRecompilerVertexExportUsesInvocationExecMask() {
const uint32_t shader[] = {
EncodeSop1(0x04, 126, 129), // s_mov_b64 exec, 1
EncodeExp0(0x0c, 0xf),
@@ -9640,27 +9456,11 @@ void TestNewShaderRecompilerVertexExportUsesLaneExecMask() {
Check(ShaderRecompiler::TryRecompile(shader, options, result, &error),
error.c_str());
CheckSpirvBinaryValidates(result.spirv);
const auto source = DisassembleSpirvBinary(result.spirv);
Check(Common::ContainsStr(source, "gl_SubgroupInvocationID"),
"vertex export EXEC guard did not test the current lane");
Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
"vertex EXEC guard depends on the native subgroup lane");
Check(Common::ContainsStr(source, "OpBranchConditional"),
"vertex export EXEC guard did not branch on the lane-active mask");
options.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
ShaderRecompiler::CompileResult local_result;
Check(ShaderRecompiler::TryRecompile(shader, options, local_result, &error),
error.c_str());
Check(local_result.program.lane_mask_mode ==
ShaderLaneMaskMode::PerInvocation,
"per-invocation mask mode was not preserved in the immutable program");
CheckSpirvBinaryValidates(local_result.spirv);
const auto local_source = DisassembleSpirvBinary(local_result.spirv);
Check(!Common::ContainsStr(local_source,
"OpLoad %uint %gl_SubgroupInvocationID"),
"per-invocation EXEC still depends on the native subgroup lane");
Check(Common::ContainsStr(local_source, "OpBranchConditional"),
"per-invocation vertex export lost its EXEC guard");
"vertex export lost its per-invocation EXEC guard");
}
void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
@@ -9677,7 +9477,6 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
};
auto options = MakeCompileOptions(ShaderType::Vertex);
options.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
options.dump_ir = true;
ShaderRecompiler::CompileResult result;
@@ -9720,45 +9519,6 @@ void TestNewShaderRecompilerPerInvocationMasksWithoutMirrors() {
Check(SpirvInstructionOpcodeCount(result.spirv, 132u) == 1u,
"wave32 WQM retained the unused high ballot-word expansion");
const uint32_t native_wqm_shader[] = {
EncodeSop1(0x0a, 2, 0), // s_wqm_b64 s[2:3], s[0:1]
EncodeVop1(0x01, 0, 2), // v_mov_b32 v0, s2
EncodeExp0(0x0c, 0x1), EncodeExp1(0, 0, 0, 0), EncodeSopp(0x01),
};
auto native_options = options;
native_options.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
Check(ShaderRecompiler::TryRecompile(native_wqm_shader, native_options,
result, &error),
error.c_str());
CheckSpirvBinaryValidates(result.spirv);
Check(Common::ContainsStr(result.ir_dump, "WqmU64") &&
!Common::ContainsStr(DisassembleSpirvBinary(result.spirv),
"OpGroupNonUniformBallot"),
"native scalar WQM retained frontend expansion or subgroup machinery");
Check(SpirvInstructionOpcodeCount(result.spirv, 132u) == 1u &&
SpirvInstructionOpcodeCount(result.spirv, 194u) == 2u,
"native scalar WQM emitter was folded away or did not use the compact "
"path");
const uint32_t native_special_wqm_shader[] = {
EncodeSop1(0x04, 126, 0), // s_mov_b64 exec, s[0:1]
EncodeSop1(0x0a, 126, 126), // s_wqm_b64 exec, exec
EncodeSop1(0x04, 106, 0), // s_mov_b64 vcc, s[0:1]
EncodeSop1(0x0a, 106, 106), // s_wqm_b64 vcc, vcc
EncodeVop1(0x01, 0, 126), // v_mov_b32 v0, exec_lo
EncodeVop1(0x01, 1, 106), // v_mov_b32 v1, vcc_lo
EncodeExp0(0x0c, 0x3), EncodeExp1(0, 1, 0, 0), EncodeSopp(0x01),
};
Check(ShaderRecompiler::TryRecompile(native_special_wqm_shader,
native_options, result, &error),
error.c_str());
CheckSpirvBinaryValidates(result.spirv);
const auto native_special_source = DisassembleSpirvBinary(result.spirv);
Check(CountSourceOccurrences(result.ir_dump, "WqmU64") == 2u &&
!Common::ContainsStr(native_special_source,
"OpGroupNonUniformBallot"),
"native EXEC/VCC WQM retained destination-dependent ballot lowering");
const uint32_t cross_lane_shader[] = {
EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0
EncodeVop1(0x01, 0, 250),
@@ -9789,7 +9549,6 @@ void TestNewShaderRecompilerPerInvocationU64Complement() {
};
auto options = MakeCompileOptions(ShaderType::Vertex);
options.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
options.dump_ir = true;
ShaderRecompiler::CompileResult result;
@@ -9919,7 +9678,8 @@ void TestNewShaderRecompilerExpPixelOutputs() {
Check(ShaderRecompiler::TryRecompile(compressed_ba_shader, options,
unorm16_ba_result, &error),
error.c_str());
const auto unorm16_ba_source = DisassembleSpirvBinary(unorm16_ba_result.spirv);
const auto unorm16_ba_source =
DisassembleSpirvBinary(unorm16_ba_result.spirv);
Check(CountSourceOccurrences(unorm16_ba_source, "UnpackUnorm2x16") == 1u,
"compressed UNORM16 BA-only export did not unpack VSRC1");
CheckSpirvBinaryValidates(unorm16_ba_result.spirv);
@@ -10114,14 +9874,14 @@ void TestRenderTargetReverseExportMapping() {
mappings[0] = gr32.export_mapping;
ShaderPixelInputInfo compiled_info{};
std::span<const uint32_t> compiled_spirv;
Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info,
mappings, compiled_info, compiled_spirv) &&
Check(ShaderCompileInfoPS(regs, sh, vs_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0].IsIdentity(),
"inactive reverse MRT mapping was not normalized out of the shader "
"cache key");
sh.target_output_mode[0] = 4;
Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info,
mappings, compiled_info, compiled_spirv) &&
Check(ShaderCompileInfoPS(regs, sh, vs_info, mappings, compiled_info,
compiled_spirv) &&
compiled_info.target_export_mapping[0] == gr32.export_mapping,
"active reverse MRT mapping was lost before shader specialization");
}
@@ -11098,8 +10858,7 @@ void TestNewShaderRecompilerPixelPipelineEntry() {
HW::ShaderRegisters sh{};
ShaderPixelInputInfo input_info{};
std::vector<uint32_t> spirv;
Check(ShaderCompileSpirvPS(regs, sh, ShaderLaneMaskMode::NativeWave,
input_info, spirv),
Check(ShaderCompileSpirvPS(regs, sh, input_info, spirv),
"new pixel shader recompiler wrapper did not produce SPIR-V");
Check(!spirv.empty(),
"new pixel shader recompiler wrapper returned empty SPIR-V");
@@ -11132,8 +10891,7 @@ void TestNewShaderRecompilerPixelPipelineEntry() {
ShaderPixelInputInfo vcc_input{};
std::vector<uint32_t> vcc_spirv;
Check(ShaderCompileSpirvPS(vcc_regs, sh, ShaderLaneMaskMode::NativeWave,
vcc_input, vcc_spirv),
Check(ShaderCompileSpirvPS(vcc_regs, sh, vcc_input, vcc_spirv),
"VCC raw-load pointer was lost");
CheckSpirvBinaryValidates(vcc_spirv);
}
@@ -11226,7 +10984,6 @@ void TestComputeLdsAllocationIdentity() {
void TestPixelProgramCacheDescriptorSetIdentity() {
const uint32_t shader_01[] = {0xbf810000u};
const uint32_t shader_10[] = {0xbf810000u};
const uint32_t shader_mask[] = {0xbf810000u};
HW::ShaderRegisters sh{};
auto check_transition = [&](const uint32_t *shader, uint64_t checksum,
@@ -11249,8 +11006,8 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
ShaderPixelInputInfo ps_info{};
std::span<const uint32_t> spirv;
Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave,
vs_info, identity_mappings, ps_info, spirv),
Check(ShaderCompileInfoPS(regs, sh, vs_info, identity_mappings, ps_info,
spirv),
"pixel program-cache transition failed to compile");
const auto expected_set = has_vs_descriptors ? 1u : 0u;
Check(ps_info.descriptor_set == expected_set &&
@@ -11266,28 +11023,6 @@ void TestPixelProgramCacheDescriptorSetIdentity() {
check_transition(shader_01, 0x91a27e6300000001ull, false, true);
check_transition(shader_10, 0x91a27e6300000002ull, true, false);
HW::PixelShaderInfo mask_regs{};
mask_regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader_mask);
mask_regs.ps_regs.chksum = 0x91a27e6300000003ull;
ShaderMappedData mapped{};
mapped.code_size_bytes = sizeof(shader_mask);
ShaderMapUserData(mask_regs.ps_regs.data_addr, mapped);
auto compile_mask_mode = [&](ShaderLaneMaskMode mode) {
ShaderVertexInputInfo vs_info{};
vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
const std::array<Prospero::ColorComponentMapping, 8> identity_mappings{};
ShaderPixelInputInfo ps_info{};
std::span<const uint32_t> spirv;
Check(ShaderCompileInfoPS(mask_regs, sh, mode, vs_info, identity_mappings,
ps_info, spirv),
"pixel lane-mask cache transition failed to compile");
Check(ps_info.stage.program != nullptr &&
ps_info.stage.program->lane_mask_mode == mode,
"pixel program cache reused a different lane-mask lowering");
};
compile_mask_mode(ShaderLaneMaskMode::NativeWave);
compile_mask_mode(ShaderLaneMaskMode::PerInvocation);
}
void TestNewShaderRecompilerUnsupportedMemoryDecode() {
@@ -11379,23 +11114,21 @@ void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
}
void TestNewShaderRecompilerSpirvSizeBaselines() {
const auto compile =
[](const char *name, std::span<const uint32_t> shader,
const SpirvMetrics &budget, ShaderType stage = ShaderType::Compute,
ShaderLaneMaskMode lane_mask_mode = ShaderLaneMaskMode::NativeWave) {
auto options = MakeCompileOptions(stage);
options.dump_ir = true;
options.lane_mask_mode = lane_mask_mode;
const auto compile = [](const char *name, std::span<const uint32_t> shader,
const SpirvMetrics &budget,
ShaderType stage = ShaderType::Compute) {
auto options = MakeCompileOptions(stage);
options.dump_ir = true;
ShaderRecompiler::CompileResult result;
std::string error;
const bool compiled =
ShaderRecompiler::TryRecompile(shader, options, result, &error);
Check(compiled, error.c_str());
CheckSpirvBinaryValidates(result.spirv);
CheckSpirvBudget(name, result.spirv, budget);
return result;
};
ShaderRecompiler::CompileResult result;
std::string error;
const bool compiled =
ShaderRecompiler::TryRecompile(shader, options, result, &error);
Check(compiled, error.c_str());
CheckSpirvBinaryValidates(result.spirv);
CheckSpirvBudget(name, result.spirv, budget);
return result;
};
const uint32_t empty[] = {EncodeSopp(0x01)};
const auto empty_result =
@@ -11595,19 +11328,18 @@ void TestNewShaderRecompilerSpirvSizeBaselines() {
EncodeExp1(0, 0, 0, 0), // POS0.x
EncodeSopp(0x01),
};
const auto wqm_result =
compile("wqm", wqm,
{.words = 407,
.instructions = 98,
.variables = 4,
.loads = 3,
.stores = 1,
.labels = 6,
.selection_merges = 1,
.branches = 4,
.conditional_branches = 1,
.ballots = 1},
ShaderType::Vertex, ShaderLaneMaskMode::PerInvocation);
const auto wqm_result = compile("wqm", wqm,
{.words = 407,
.instructions = 98,
.variables = 4,
.loads = 3,
.stores = 1,
.labels = 6,
.selection_merges = 1,
.branches = 4,
.conditional_branches = 1,
.ballots = 1},
ShaderType::Vertex);
Check(Common::ContainsStr(wqm_result.ir_dump, "WqmMask"),
"WQM size fixture no longer reaches per-invocation WqmMask IR");
@@ -11653,7 +11385,6 @@ int main() {
TestSpirvRequirementsAnalysis();
TestNewShaderRecompilerSpirvSizeBaselines();
TestDemandDrivenSpirvDeclarations();
TestNativeSubgroupPolicy();
TestNewShaderRecompilerSMovB32();
TestNewShaderRecompilerNativeWideScalarMemoryIr();
TestNewShaderRecompilerNativeWideBufferIr();
@@ -11720,13 +11451,13 @@ int main() {
TestNewShaderRecompilerPrunesUnreachableSetpcMetadata();
TestNewShaderRecompilerSetpcDwordJumpTable();
TestTypedEntryStateIsMinimal();
TestWqmConstantPropagation();
TestWqmMaskSignatureAndU64ShiftConstantPropagation();
TestFinalSsaRejectsRegisterStatePseudos();
TestValuePhiValidation();
TestNativeWideValueValidation();
TestNewShaderRecompilerZeroInitialRegisterState();
TestNewShaderRecompilerVertexSystemInputsWithoutMirrors();
TestNewShaderRecompilerVertexExportUsesLaneExecMask();
TestNewShaderRecompilerVertexExportUsesInvocationExecMask();
TestNewShaderRecompilerPerInvocationMasksWithoutMirrors();
TestNewShaderRecompilerPerInvocationU64Complement();
TestNewShaderRecompilerExpPixelOutputs();