Merge pull request #569 from adriadam10/feat/global-store-addtid
feat(shader): implement global_store_dword_addtid
This commit is contained in:
commit
56acdef2f5
|
|
@ -0,0 +1,179 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libc/include/GuestAllocations.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using AgcDriver::Graphics::Require;
|
||||
using ShaderRecompiler::ShaderStage;
|
||||
|
||||
constexpr std::size_t BlockBytes = 65536;
|
||||
constexpr std::uint32_t Base = 2048;
|
||||
constexpr std::uint32_t Fill = 0x5a5a5a5au;
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 41> AddtidCode{
|
||||
0x800aff08, 0x00000800, 0x820b8009, 0x160800ff, 0x00000101, 0x4a0808ff, 0x00005100, 0x36500083,
|
||||
0x3a520880, 0xbefc0380, 0xdc5c8000, 0x000a2900, 0x3a5208ff, 0x11110000, 0xbefc03c0, 0xdc5c8f00,
|
||||
0x000a2900, 0x3a5208ff, 0x22220000, 0xbefc03ff, 0x0000ffff, 0xdc5c8c00, 0x000a2900, 0x3a5208ff,
|
||||
0x33330000, 0xbefc0380, 0x7daa5082, 0xdc5c8200, 0x000a2900, 0xbefe04c1, 0x3a5208ff, 0x44440000,
|
||||
0xbefc0380, 0xdc5c8800, 0x000a2900, 0x3a5208ff, 0x55550000, 0xbefc0380, 0xdc5c87fc, 0x000a2900,
|
||||
0xbf810000,
|
||||
};
|
||||
|
||||
struct Store {
|
||||
std::int32_t offset;
|
||||
std::uint32_t pattern;
|
||||
bool masked;
|
||||
};
|
||||
|
||||
constexpr std::array<Store, 6> Stores{{
|
||||
{0, 0x00000000u, false},
|
||||
{-256, 0x11110000u, false},
|
||||
{-1024, 0x22220000u, false},
|
||||
{512, 0x33330000u, true},
|
||||
{-2048, 0x44440000u, false},
|
||||
{2044, 0x55550000u, false},
|
||||
}};
|
||||
|
||||
class GuestBlock {
|
||||
public:
|
||||
GuestBlock() {
|
||||
#ifdef _WIN32
|
||||
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
||||
#else
|
||||
block = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
|
||||
#endif
|
||||
Require(block != nullptr, "global store addtid: cannot allocate the guest block");
|
||||
GuestAllocations::Mutation().Add(block, BlockBytes, true, true);
|
||||
}
|
||||
|
||||
~GuestBlock() {
|
||||
GuestAllocations::Mutation().Remove(block);
|
||||
#ifdef _WIN32
|
||||
VirtualFree(block, 0, MEM_RELEASE);
|
||||
#else
|
||||
std::free(block);
|
||||
#endif
|
||||
}
|
||||
|
||||
GuestBlock(const GuestBlock&) = delete;
|
||||
GuestBlock& operator=(const GuestBlock&) = delete;
|
||||
|
||||
std::uint8_t* Data() { return block; }
|
||||
|
||||
private:
|
||||
std::uint8_t* block = nullptr;
|
||||
};
|
||||
|
||||
std::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> Expected(std::uint32_t lanes) {
|
||||
std::vector<std::uint32_t> memory(BlockBytes / 4u, Fill);
|
||||
for (const auto& store : Stores) {
|
||||
for (std::uint32_t lane = 0; lane < lanes; ++lane) {
|
||||
if (store.masked && (lane & 3u) == 2u) continue;
|
||||
memory[(Base + store.offset + lane * 4u) / 4u] = (0x5100u + lane * 0x101u) ^ store.pattern;
|
||||
}
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const std::string& name) {
|
||||
std::vector<std::uint32_t> memory(BlockBytes / 4u, Fill);
|
||||
std::memcpy(guest.Data(), memory.data(), BlockBytes);
|
||||
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(guest.Data()));
|
||||
std::vector<std::uint32_t> userData(10, 0u);
|
||||
userData[8] = static_cast<std::uint32_t>(address);
|
||||
userData[9] = static_cast<std::uint32_t>(address >> 32u);
|
||||
const std::span<const std::uint32_t> code(AddtidCode);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> regions{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0, {false, false, false}, false, 1};
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, regions},
|
||||
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::memcpy(memory.data(), guest.Data(), BlockBytes);
|
||||
const auto expected = Expected(waveSize);
|
||||
for (std::size_t dword = 0; dword < memory.size(); ++dword) {
|
||||
Require(memory[dword] == expected[dword], "global store addtid: " + name + " byte " + std::to_string(dword * 4u) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword]));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckRejected(const AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::string& reason) {
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> regions{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
|
||||
const ShaderRecompiler::ShaderComputeStageInfo compute{{32, 1, 1}, 0, {false, false, false}, false, 1};
|
||||
const std::vector<std::uint32_t> userData(10, 0u);
|
||||
ShaderRecompiler::RecompileRequest request{
|
||||
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
|
||||
{32, 0, userData, compute, std::nullopt, std::nullopt, regions},
|
||||
device.Target(),
|
||||
{0, 0, 0, 128}
|
||||
};
|
||||
request.useCache = false;
|
||||
std::string failure;
|
||||
try {
|
||||
static_cast<void>(ShaderRecompiler::Recompile(request));
|
||||
} catch (const std::exception& error) {
|
||||
failure = error.what();
|
||||
}
|
||||
Require(failure.find(reason) != std::string::npos, "global store addtid: expected '" + reason + "', got '" + failure + "'");
|
||||
}
|
||||
|
||||
void CheckRejections(const AgcDriver::VulkanDevice& device) {
|
||||
alignas(256) static constexpr std::array<std::uint32_t, 3> flatSegment{0xdc5c0000, 0x000a2900, 0xbf810000};
|
||||
alignas(256) static constexpr std::array<std::uint32_t, 3> scratchSegment{0xdc5c4000, 0x000a2900, 0xbf810000};
|
||||
alignas(256) static constexpr std::array<std::uint32_t, 3> noScalarBase{0xdc5c8000, 0x007d2900, 0xbf810000};
|
||||
CheckRejected(device, flatSegment, "global_store_dword_addtid is available only in the global segment");
|
||||
CheckRejected(device, scratchSegment, "global_store_dword_addtid is available only in the global segment");
|
||||
CheckRejected(device, noScalarBase, "global_store_dword_addtid supports only an SGPR pair as base address");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
CheckRejections(*device);
|
||||
if (device->Target().subgroupSize < 32u) {
|
||||
std::printf("skipped, the device's subgroups are narrower than a wave (%u lanes)\n", device->Target().subgroupSize);
|
||||
return VulkanTestSkipped;
|
||||
}
|
||||
GuestBlock guest;
|
||||
Run(*device, guest, 32, device->Target(), "wave32");
|
||||
Run(*device, guest, 64, device->Target(), "wave64");
|
||||
Run(*device, guest, 64, device->ComputeTarget(32), "wave64 split");
|
||||
std::puts("global store addtid tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -832,6 +832,7 @@ enum class RdnaOpcode : std::uint16_t {
|
|||
FlatAtomicFmaxX2,
|
||||
FlatAtomicIncX2,
|
||||
FlatAtomicDecX2,
|
||||
GlobalStoreDwordAddtid,
|
||||
DsAddU32,
|
||||
DsAddRtnU32,
|
||||
DsSubU32,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <bit>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
|
|
@ -170,6 +171,7 @@ constexpr MemoryOpcodeInfo flatOpcodes[] = {
|
|||
{0x0eu, RdnaOpcode::FlatLoadDwordx4, 4, 32, false, false, false},
|
||||
{0x0fu, RdnaOpcode::FlatLoadDwordx3, 3, 32, false, false, false},
|
||||
{0x16u, RdnaOpcode::GlobalLoadDwordAddtid, 1, 32, false, false, false},
|
||||
{0x17u, RdnaOpcode::GlobalStoreDwordAddtid, 1, 32, false, false, false},
|
||||
{0x18u, RdnaOpcode::FlatStoreByte, 1, 8, false, false, false},
|
||||
{0x1au, RdnaOpcode::FlatStoreShort, 1, 16, false, false, false},
|
||||
{0x1cu, RdnaOpcode::FlatStoreDword, 1, 32, false, false, false},
|
||||
|
|
@ -560,7 +562,8 @@ bool isFlatStoreOpcode(RdnaOpcode opcode) {
|
|||
case RdnaOpcode::FlatStoreDword:
|
||||
case RdnaOpcode::FlatStoreDwordx2:
|
||||
case RdnaOpcode::FlatStoreDwordx3:
|
||||
case RdnaOpcode::FlatStoreDwordx4: return true;
|
||||
case RdnaOpcode::FlatStoreDwordx4:
|
||||
case RdnaOpcode::GlobalStoreDwordAddtid: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -831,11 +834,12 @@ RdnaInstruction DecodeRdnaFlat(std::uint32_t programCounter, std::span<const std
|
|||
setRawWords(instruction, code, wordIndex, 2u);
|
||||
|
||||
instruction.destination = d16Half(vectorRegister(isFlatStoreOpcode(instruction.op) ? data : vdst), instruction.op);
|
||||
if (instruction.op == RdnaOpcode::GlobalLoadDwordAddtid) {
|
||||
if (instruction.op == RdnaOpcode::GlobalLoadDwordAddtid || instruction.op == RdnaOpcode::GlobalStoreDwordAddtid) {
|
||||
const std::string name = instruction.op == RdnaOpcode::GlobalLoadDwordAddtid ? "global_load_dword_addtid" : "global_store_dword_addtid";
|
||||
if (seg != 2u) {
|
||||
throw std::runtime_error("global_load_dword_addtid is available only in the global segment");
|
||||
throw std::runtime_error(name + " is available only in the global segment");
|
||||
}
|
||||
instruction.source0 = scalarDescriptorBase(saddr, 2u, "global_load_dword_addtid supports only an SGPR pair as base address");
|
||||
instruction.source0 = scalarDescriptorBase(saddr, 2u, (name + " supports only an SGPR pair as base address").c_str());
|
||||
instruction.sourceCount = 1;
|
||||
return instruction;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ private:
|
|||
bool dsAtomic64(const RdnaInstruction& inst, IrOpcode opcode, bool returnsValue);
|
||||
bool dsAppendConsume(const RdnaInstruction& inst, IrOpcode opcode);
|
||||
bool dsAddtid(const RdnaInstruction& inst, bool write);
|
||||
bool globalLoadAddtid(const RdnaInstruction& inst);
|
||||
bool globalAddtid(const RdnaInstruction& inst, bool write);
|
||||
bool dsSwizzleB32(const RdnaInstruction& inst);
|
||||
bool dsBpermuteB32(const RdnaInstruction& inst);
|
||||
bool dsPermuteB32(const RdnaInstruction& inst);
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ bool TranslationContext::flatLoad(const RdnaInstruction& inst) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool TranslationContext::globalLoadAddtid(const RdnaInstruction& inst) {
|
||||
bool TranslationContext::globalAddtid(const RdnaInstruction& inst, bool write) {
|
||||
MemoryInfo memory = flatMemoryInfoFromInstruction(inst);
|
||||
memory.addressIsFull = false;
|
||||
const IrU32 baseLow = readU32(inst.source0);
|
||||
|
|
@ -106,6 +106,11 @@ bool TranslationContext::globalLoadAddtid(const RdnaInstruction& inst) {
|
|||
IrValue& lane = ir.Emit(IrOpcode::LaneId, IrOpcodeType(IrOpcode::LaneId), {});
|
||||
IrValue& laneOffset = ir.ShiftLeftLogical(lane, ir.Constant(2u));
|
||||
IrValue& active = ir.GetExec();
|
||||
if (write) {
|
||||
const IrU32 data = readU32(inst.destination);
|
||||
(void)ir.Emit(IrOpcode::StoreAddressU32, IrType::Void, {resource, &laneOffset, &ir.Constant(0u), &data.Value(), &active}, addMemoryInfo(memory, inst.programCounter));
|
||||
return true;
|
||||
}
|
||||
IrValue& loaded = ir.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {resource, &laneOffset, &ir.Constant(0u), &active}, addMemoryInfo(memory, inst.programCounter));
|
||||
writeOperand(inst.destination, &loaded);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -197,7 +197,9 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) {
|
|||
case RdnaOpcode::FlatLoadDwordx4:
|
||||
return flatLoad(inst);
|
||||
case RdnaOpcode::GlobalLoadDwordAddtid:
|
||||
return globalLoadAddtid(inst);
|
||||
return globalAddtid(inst, false);
|
||||
case RdnaOpcode::GlobalStoreDwordAddtid:
|
||||
return globalAddtid(inst, true);
|
||||
case RdnaOpcode::FlatStoreByte:
|
||||
case RdnaOpcode::FlatStoreShort:
|
||||
case RdnaOpcode::FlatStoreByteD16Hi:
|
||||
|
|
|
|||
Loading…
Reference in New Issue