feat(shader): implement the D16 byte/short loads and high-half stores

buffer/flat/global/scratch_load_{ubyte,sbyte,short}_d16(_hi),
buffer/flat/global/scratch_store_{byte,short}_d16_hi and
ds_read_{u8,i8}_d16(_hi), ds_write_b8_d16_hi threw in the decoder. They use
the same 16-bit destination/source select as the existing ds_read_u16_d16
and ds_write_b16_d16_hi: a load writes one half and keeps the other, a
_d16_hi store takes its data from the high half.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-02 07:01:47 +02:00
parent d3ec1f15fb
commit 504a2fd8af
5 changed files with 227 additions and 12 deletions

View File

@ -0,0 +1,118 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t InputBytes = 16;
constexpr std::uint32_t Results = 16;
alignas(256) std::array<std::uint8_t, Threads * InputBytes> Input{};
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
alignas(256) constexpr std::array<std::uint32_t, 107> D16Code{
0x34020084, 0x34060086, 0xbe880300, 0x8709ff01, 0x0000ffff, 0x7e1402ff, 0xabcdabcd, 0x7e1602ff,
0xabcdabcd, 0x7e1802ff, 0xabcdabcd, 0x7e1a02ff, 0xabcdabcd, 0x7e1c02ff, 0xabcdabcd, 0x7e1e02ff,
0xabcdabcd, 0x7e2002ff, 0xabcdabcd, 0x7e2202ff, 0xabcdabcd, 0x7e2402ff, 0xabcdabcd, 0x7e2602ff,
0xabcdabcd, 0x7e2802ff, 0xabcdabcd, 0x7e2a02ff, 0xabcdabcd, 0xe0801001, 0x80000a01, 0xe0841002,
0x80000a01, 0xe0881003, 0x80000b01, 0xe08c1005, 0x80000b01, 0xe0901006, 0x80000c01, 0xe0941008,
0x80000c01, 0xdc808001, 0x0d080001, 0xdc848002, 0x0d080001, 0xdc888003, 0x0e080001, 0xdc8c8005,
0x0e080001, 0xdc908006, 0x0f080001, 0xdc948008, 0x0f080001, 0x34040082, 0xbf8c3f70, 0xe0301000,
0x80000401, 0xbf8c3f70, 0xd8340000, 0x00000402, 0xbf8cc07f, 0xda880001, 0x10000002, 0xda8c0002,
0x10000002, 0xda900003, 0x11000002, 0xda940001, 0x11000002, 0xda980002, 0x12000002, 0xda9c0000,
0x12000002, 0xbf8cc07f, 0x7e2602ff, 0x80c1a2b3, 0xe0641028, 0x80011303, 0xe06c102c, 0x80011303,
0xda800001, 0x00001302, 0xbf8cc07f, 0xd8d80000, 0x14000002, 0xbf8c0070, 0xe0701000, 0x80010a03,
0xe0701004, 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03,
0xe0701014, 0x80010f03, 0xe0701018, 0x80011003, 0xe070101c, 0x80011103, 0xe0701020, 0x80011203,
0xe0701024, 0x80011403, 0xbf810000,
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x01016facu};
}
void Run(AgcDriver::VulkanDevice& device) {
for (std::uint32_t i = 0; i < Input.size(); ++i) Input[i] = static_cast<std::uint8_t>(i * 37u + 0x81u);
Output.fill(0xdeadbeefu);
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size()));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const std::span<const std::uint32_t> code(D16Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 4u * Threads, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
void Check() {
constexpr std::array<const char*, 12> names{
"buffer_load_ubyte_d16(_hi)", "buffer_load_sbyte_d16(_hi)", "buffer_load_short_d16(_hi)",
"global_load_ubyte_d16(_hi)", "global_load_sbyte_d16(_hi)", "global_load_short_d16(_hi)",
"ds_read_u8_d16(_hi)", "ds_read_i8_d16(_hi)", "ds_read_u16_d16(_hi)", "ds_write_b8_d16_hi",
"buffer_store_byte_d16_hi", "buffer_store_short_d16_hi",
};
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::uint8_t* in = &Input[tid * InputBytes];
const auto u8 = [&](std::uint32_t at) { return static_cast<std::uint32_t>(in[at]); };
const auto i8 = [&](std::uint32_t at) { return static_cast<std::uint32_t>(static_cast<std::int16_t>(static_cast<std::int8_t>(in[at]))) & 0xffffu; };
const auto u16 = [&](std::uint32_t at) { return u8(at) | (u8(at + 1u) << 8u); };
const std::uint32_t loads[3] = {u8(1) | (u8(2) << 16u), i8(3) | (i8(5) << 16u), u16(6) | (u16(8) << 16u)};
std::uint32_t dword = 0;
std::memcpy(&dword, in, 4);
const std::uint32_t written = (dword & 0xffff00ffu) | (0xc1u << 8u);
std::array<std::uint32_t, 12> expected{
loads[0], loads[1], loads[2], loads[0], loads[1], loads[2],
u8(1) | (u8(2) << 16u), i8(3) | (i8(1) << 16u), u16(2) | (u16(0) << 16u), written,
0xdeadbec1u, 0xdead80c1u,
};
const auto* out = &Output[tid * Results];
for (std::uint32_t j = 0; j < expected.size(); ++j) {
Require(out[j] == expected[j], std::string("d16 memory: lane ") + std::to_string(tid) + " " + names[j] + " is " + Hex(out[j]) + ", expected " + Hex(expected[j]));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Run(*device);
Check();
std::puts("d16 memory tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -638,6 +638,14 @@ enum class RdnaOpcode : std::uint16_t {
BufferLoadDwordx3,
BufferStoreByte,
BufferStoreShort,
BufferLoadUbyteD16,
BufferLoadUbyteD16Hi,
BufferLoadSbyteD16,
BufferLoadSbyteD16Hi,
BufferLoadShortD16,
BufferLoadShortD16Hi,
BufferStoreByteD16Hi,
BufferStoreShortD16Hi,
BufferStoreDwordx2,
BufferStoreDwordx3,
TbufferLoadFormatX,
@ -673,6 +681,14 @@ enum class RdnaOpcode : std::uint16_t {
FlatLoadDwordx4,
FlatStoreByte,
FlatStoreShort,
FlatLoadUbyteD16,
FlatLoadUbyteD16Hi,
FlatLoadSbyteD16,
FlatLoadSbyteD16Hi,
FlatLoadShortD16,
FlatLoadShortD16Hi,
FlatStoreByteD16Hi,
FlatStoreShortD16Hi,
FlatStoreDword,
FlatStoreDwordx2,
FlatStoreDwordx3,
@ -736,6 +752,11 @@ enum class RdnaOpcode : std::uint16_t {
DsCmpstRtnF32,
DsWrapRtnB32,
DsNop,
DsReadU8D16,
DsReadU8D16Hi,
DsReadI8D16,
DsReadI8D16Hi,
DsWriteB8D16Hi,
DsWrite2st64B32,
DsWrite2B64,
DsWrite2st64B64,

View File

@ -8,6 +8,37 @@ namespace ShaderRecompiler {
namespace {
RdnaOperand d16Half(RdnaOperand operand, RdnaOpcode opcode) {
switch (opcode) {
case RdnaOpcode::BufferLoadUbyteD16:
case RdnaOpcode::BufferLoadSbyteD16:
case RdnaOpcode::BufferLoadShortD16:
case RdnaOpcode::FlatLoadUbyteD16:
case RdnaOpcode::FlatLoadSbyteD16:
case RdnaOpcode::FlatLoadShortD16:
case RdnaOpcode::DsReadU8D16:
case RdnaOpcode::DsReadI8D16:
case RdnaOpcode::DsReadU16D16: operand.sdwaSel = 4u; break;
case RdnaOpcode::BufferLoadUbyteD16Hi:
case RdnaOpcode::BufferLoadSbyteD16Hi:
case RdnaOpcode::BufferLoadShortD16Hi:
case RdnaOpcode::BufferStoreByteD16Hi:
case RdnaOpcode::BufferStoreShortD16Hi:
case RdnaOpcode::FlatLoadUbyteD16Hi:
case RdnaOpcode::FlatLoadSbyteD16Hi:
case RdnaOpcode::FlatLoadShortD16Hi:
case RdnaOpcode::FlatStoreByteD16Hi:
case RdnaOpcode::FlatStoreShortD16Hi:
case RdnaOpcode::DsReadU8D16Hi:
case RdnaOpcode::DsReadI8D16Hi:
case RdnaOpcode::DsReadU16D16Hi:
case RdnaOpcode::DsWriteB8D16Hi:
case RdnaOpcode::DsWriteB16D16Hi: operand.sdwaSel = 5u; break;
default: break;
}
return operand;
}
struct MemoryOpcodeInfo {
std::uint32_t encoding;
RdnaOpcode opcode;
@ -54,6 +85,14 @@ constexpr MemoryOpcodeInfo mubufOpcodes[] = {
{0x1du, RdnaOpcode::BufferStoreDwordx2, 2, 32, false, false, false},
{0x1eu, RdnaOpcode::BufferStoreDwordx4, 4, 32, false, false, false},
{0x1fu, RdnaOpcode::BufferStoreDwordx3, 3, 32, false, false, false},
{0x20u, RdnaOpcode::BufferLoadUbyteD16, 1, 8, false, false, false},
{0x21u, RdnaOpcode::BufferLoadUbyteD16Hi, 1, 8, false, false, false},
{0x22u, RdnaOpcode::BufferLoadSbyteD16, 1, 8, true, false, false},
{0x23u, RdnaOpcode::BufferLoadSbyteD16Hi, 1, 8, true, false, false},
{0x24u, RdnaOpcode::BufferLoadShortD16, 1, 16, false, false, false},
{0x25u, RdnaOpcode::BufferLoadShortD16Hi, 1, 16, false, false, false},
{0x19u, RdnaOpcode::BufferStoreByteD16Hi, 1, 8, false, false, false},
{0x1bu, RdnaOpcode::BufferStoreShortD16Hi, 1, 16, false, false, false},
{0x30u, RdnaOpcode::BufferAtomicSwap, 1, 32, false, false, false},
{0x31u, RdnaOpcode::BufferAtomicCmpswap, 1, 32, false, false, false},
{0x32u, RdnaOpcode::BufferAtomicAdd, 1, 32, false, false, false},
@ -97,6 +136,14 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = {
{0x1du, RdnaOpcode::FlatStoreDwordx2, 2, 32, false, false, false},
{0x1eu, RdnaOpcode::FlatStoreDwordx4, 4, 32, false, false, false},
{0x1fu, RdnaOpcode::FlatStoreDwordx3, 3, 32, false, false, false},
{0x20u, RdnaOpcode::FlatLoadUbyteD16, 1, 8, false, false, false},
{0x21u, RdnaOpcode::FlatLoadUbyteD16Hi, 1, 8, false, false, false},
{0x22u, RdnaOpcode::FlatLoadSbyteD16, 1, 8, true, false, false},
{0x23u, RdnaOpcode::FlatLoadSbyteD16Hi, 1, 8, true, false, false},
{0x24u, RdnaOpcode::FlatLoadShortD16, 1, 16, false, false, false},
{0x25u, RdnaOpcode::FlatLoadShortD16Hi, 1, 16, false, false, false},
{0x19u, RdnaOpcode::FlatStoreByteD16Hi, 1, 8, false, false, false},
{0x1bu, RdnaOpcode::FlatStoreShortD16Hi, 1, 16, false, false, false},
};
constexpr MemoryOpcodeInfo dsOpcodes[] = {
@ -160,7 +207,12 @@ constexpr MemoryOpcodeInfo dsOpcodes[] = {
{0x76u, RdnaOpcode::DsReadB64, 2, 32, false, false, false},
{0x77u, RdnaOpcode::DsRead2B64, 4, 32, false, false, false},
{0x78u, RdnaOpcode::DsRead2st64B64, 4, 32, false, false, false},
{0xa0u, RdnaOpcode::DsWriteB8D16Hi, 1, 8, false, false, false},
{0xa1u, RdnaOpcode::DsWriteB16D16Hi, 1, 16, false, false, false},
{0xa2u, RdnaOpcode::DsReadU8D16, 1, 8, false, false, false},
{0xa3u, RdnaOpcode::DsReadU8D16Hi, 1, 8, false, false, false},
{0xa4u, RdnaOpcode::DsReadI8D16, 1, 8, true, false, false},
{0xa5u, RdnaOpcode::DsReadI8D16Hi, 1, 8, true, false, false},
{0xa6u, RdnaOpcode::DsReadU16D16, 1, 16, false, false, false},
{0xa7u, RdnaOpcode::DsReadU16D16Hi, 1, 16, false, false, false},
{0xb0u, RdnaOpcode::DsWriteAddtidB32, 1, 32, false, false, false},
@ -282,6 +334,7 @@ bool isDsWriteOpcode(RdnaOpcode opcode) {
case RdnaOpcode::DsWriteB8:
case RdnaOpcode::DsWriteB16:
case RdnaOpcode::DsWriteB16D16Hi:
case RdnaOpcode::DsWriteB8D16Hi:
case RdnaOpcode::DsWrite2B32:
case RdnaOpcode::DsWrite2st64B32:
case RdnaOpcode::DsWrite2B64:
@ -358,6 +411,8 @@ bool isFlatStoreOpcode(RdnaOpcode opcode) {
switch (opcode) {
case RdnaOpcode::FlatStoreByte:
case RdnaOpcode::FlatStoreShort:
case RdnaOpcode::FlatStoreByteD16Hi:
case RdnaOpcode::FlatStoreShortD16Hi:
case RdnaOpcode::FlatStoreDword:
case RdnaOpcode::FlatStoreDwordx2:
case RdnaOpcode::FlatStoreDwordx3:
@ -503,7 +558,7 @@ RdnaInstruction DecodeRdnaMubuf(std::uint32_t programCounter, std::span<const st
applyMemoryInfo(instruction, info);
setRawWords(instruction, code, wordIndex, 2u);
instruction.destination = vectorRegister(vdata);
instruction.destination = d16Half(vectorRegister(vdata), instruction.op);
instruction.source0 = vectorRegister(vaddr);
instruction.source1 = scalarDescriptorBase(srsrc * 4u, 4u, "MUBUF resource descriptor register range overflow");
instruction.source2 = scalarSource(soffsetCode);
@ -586,7 +641,7 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span<const std
applyMemoryInfo(instruction, info);
setRawWords(instruction, code, wordIndex, 2u);
instruction.destination = vectorRegister(isFlatStoreOpcode(instruction.op) ? data : vdst);
instruction.destination = d16Half(vectorRegister(isFlatStoreOpcode(instruction.op) ? data : vdst), instruction.op);
instruction.source0 = vectorRegister(addr);
if (seg == 0u || saddr == 0x7Du || saddr == 0x7Fu) {
if (addr == 255u) {
@ -654,17 +709,9 @@ RdnaInstruction DecodeRdnaDs(std::uint32_t programCounter, std::span<const std::
instruction.secondaryOffset = offset1 * 512u;
}
instruction.destination = vectorRegister(vdst);
if (instruction.op == RdnaOpcode::DsReadU16D16) {
instruction.destination.sdwaSel = 4u;
} else if (instruction.op == RdnaOpcode::DsReadU16D16Hi) {
instruction.destination.sdwaSel = 5u;
}
instruction.destination = d16Half(vectorRegister(vdst), instruction.op);
instruction.source0 = vectorRegister(addr);
instruction.source1 = vectorRegister(data0);
if (instruction.op == RdnaOpcode::DsWriteB16D16Hi) {
instruction.source1.sdwaSel = 5u;
}
instruction.source1 = isDsWriteOpcode(instruction.op) ? d16Half(vectorRegister(data0), instruction.op) : vectorRegister(data0);
instruction.source2 = vectorRegister(data1);
instruction.sourceCount = dsSourceCount(instruction.op);
return instruction;

View File

@ -631,6 +631,14 @@ bool IsBufferMemoryOpcode(RdnaOpcode opcode) {
case RdnaOpcode::BufferLoadDwordx3:
case RdnaOpcode::BufferStoreByte:
case RdnaOpcode::BufferStoreShort:
case RdnaOpcode::BufferLoadUbyteD16:
case RdnaOpcode::BufferLoadUbyteD16Hi:
case RdnaOpcode::BufferLoadSbyteD16:
case RdnaOpcode::BufferLoadSbyteD16Hi:
case RdnaOpcode::BufferLoadShortD16:
case RdnaOpcode::BufferLoadShortD16Hi:
case RdnaOpcode::BufferStoreByteD16Hi:
case RdnaOpcode::BufferStoreShortD16Hi:
case RdnaOpcode::BufferStoreDwordx2:
case RdnaOpcode::BufferStoreDwordx3:
case RdnaOpcode::BufferAtomicSwap:

View File

@ -53,6 +53,12 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::BufferLoadSbyte:
case RdnaOpcode::BufferLoadUshort:
case RdnaOpcode::BufferLoadSshort:
case RdnaOpcode::BufferLoadUbyteD16:
case RdnaOpcode::BufferLoadUbyteD16Hi:
case RdnaOpcode::BufferLoadSbyteD16:
case RdnaOpcode::BufferLoadSbyteD16Hi:
case RdnaOpcode::BufferLoadShortD16:
case RdnaOpcode::BufferLoadShortD16Hi:
case RdnaOpcode::BufferLoadDword:
case RdnaOpcode::BufferLoadDwordx2:
case RdnaOpcode::BufferLoadDwordx3:
@ -69,6 +75,8 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::BufferStoreFormatXyzw:
case RdnaOpcode::BufferStoreByte:
case RdnaOpcode::BufferStoreShort:
case RdnaOpcode::BufferStoreByteD16Hi:
case RdnaOpcode::BufferStoreShortD16Hi:
case RdnaOpcode::BufferStoreDword:
case RdnaOpcode::BufferStoreDwordx2:
case RdnaOpcode::BufferStoreDwordx3:
@ -114,6 +122,12 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::FlatLoadSbyte:
case RdnaOpcode::FlatLoadUshort:
case RdnaOpcode::FlatLoadSshort:
case RdnaOpcode::FlatLoadUbyteD16:
case RdnaOpcode::FlatLoadUbyteD16Hi:
case RdnaOpcode::FlatLoadSbyteD16:
case RdnaOpcode::FlatLoadSbyteD16Hi:
case RdnaOpcode::FlatLoadShortD16:
case RdnaOpcode::FlatLoadShortD16Hi:
case RdnaOpcode::FlatLoadDword:
case RdnaOpcode::FlatLoadDwordx2:
case RdnaOpcode::FlatLoadDwordx3:
@ -121,6 +135,8 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
return flatLoad(inst);
case RdnaOpcode::FlatStoreByte:
case RdnaOpcode::FlatStoreShort:
case RdnaOpcode::FlatStoreByteD16Hi:
case RdnaOpcode::FlatStoreShortD16Hi:
case RdnaOpcode::FlatStoreDword:
case RdnaOpcode::FlatStoreDwordx2:
case RdnaOpcode::FlatStoreDwordx3:
@ -231,6 +247,10 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::DsReadU16:
case RdnaOpcode::DsReadU16D16:
case RdnaOpcode::DsReadU16D16Hi:
case RdnaOpcode::DsReadU8D16:
case RdnaOpcode::DsReadU8D16Hi:
case RdnaOpcode::DsReadI8D16:
case RdnaOpcode::DsReadI8D16Hi:
case RdnaOpcode::DsReadB32:
case RdnaOpcode::DsReadB64:
case RdnaOpcode::DsReadB96:
@ -244,6 +264,7 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
case RdnaOpcode::DsWriteB8:
case RdnaOpcode::DsWriteB16:
case RdnaOpcode::DsWriteB16D16Hi:
case RdnaOpcode::DsWriteB8D16Hi:
case RdnaOpcode::DsWriteB32:
case RdnaOpcode::DsWriteB64:
case RdnaOpcode::DsWriteB96: