Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ constexpr char TrailerMagic[8] = {'b', 'a', 'r', 'e', 'f', 'o', 'o', 't'};
constexpr std::size_t TrailerCodeBytesOffset = sizeof(TrailerMagic) + 3 * sizeof(std::uint32_t);
constexpr std::uint32_t Rsrc1VgprMask = 0x3fu;
constexpr std::uint32_t Rsrc1SgprMask = 0xfu << 6u;
constexpr std::uint32_t Rsrc2OffchipLdsEnable = 1u << 18u;
constexpr std::uint32_t Rsrc2UserSgprMask = (0x1fu << 1u) | (1u << 27u);

bool ValidHalves(const Shader* front, const Shader* back) {
Expand Down Expand Up @@ -127,8 +128,8 @@ std::uint32_t MergeRsrc1(std::uint32_t back, std::uint32_t front) {
}

std::uint32_t MergeRsrc2(const char* function, std::uint32_t back, std::uint32_t front) {
if ((front & ~Rsrc2UserSgprMask & ~back) != 0) Fail(function, "front half requires hardware state the back half does not enable");
return (back & ~Rsrc2UserSgprMask) | (front & Rsrc2UserSgprMask);
if ((front & ~(Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable) & ~back) != 0) Fail(function, "front half requires hardware state the back half does not enable");
return (back & ~Rsrc2UserSgprMask) | (front & (Rsrc2UserSgprMask | Rsrc2OffchipLdsEnable));
}

const ShaderRegister* FindHalfRegister(const Shader* shader, std::uint32_t offset) {
Expand Down
28 changes: 28 additions & 0 deletions core/libs/prx/libSceAgc/tests/ShaderFusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,33 @@ void testScratchReuse() {
check(reinterpret_cast<std::uintptr_t>(changed.code) != firstCode && word(reinterpret_cast<const void*>(firstCode), 64) == SMovS0, "changed halves reused the earlier fused program");
}

void testOffchipLdsEnable() {
constexpr std::uint32_t offchip = 1u << 18u;
for (const auto frontEnable : {0u, offchip}) {
for (const auto backEnable : {0u, offchip}) {
Halves halves;
halves.frontSh[2].value |= frontEnable;
halves.backSh[3].value |= backEnable;
SizeAlign size{};
check(sceAgcGetFusedShaderSize_0080(&size, &halves.front, &halves.back) == 0, "offchip LDS enable rejected by the size query");
std::vector<std::uint8_t> storage(size.m_size + 512, 0xcd);
Shader fused{};
check(sceAgcFuseShaderHalves_0200(&fused, &halves.front, &halves.back, aligned(storage, 256)) == 0, "offchip LDS enable rejected by fusion");
const auto* resource = find(fused, 0x08bu);
check(resource != nullptr && resource->value == ((6u << 1u) | (1u << 7u) | (1u << 15u) | frontEnable | backEnable), "fusion lost offchip LDS enable or changed the back-half state or front user SGPRs");
}
}
Halves reported;
reported.frontSh[2].value = 0x00070004u;
reported.backSh[3].value = 0x00ab0020u;
SizeAlign size{};
check(sceAgcGetFusedShaderSize_0080(&size, &reported.front, &reported.back) == 0, "reported shader resources rejected by size query");
std::vector<std::uint8_t> storage(size.m_size + 512, 0xcd);
Shader fused{};
check(sceAgcFuseShaderHalves_0200(&fused, &reported.front, &reported.back, aligned(storage, 256)) == 0, "reported shader resources rejected by fusion");
check(find(fused, 0x08bu)->value == 0x00af0004u, "reported shader resources did not retain the front offchip LDS enable");
}

void testRejections() {
Halves halves;
SizeAlign size{};
Expand Down Expand Up @@ -277,6 +304,7 @@ int main() {
testFusion(4);
testFusion(0, SSwappcNullS6);
testScratchReuse();
testOffchipLdsEnable();
testRejections();
testHullHalves();
LibcRunShutdown_nid_postfix();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ inline Registers BuildInitialContextRegisters() {
{0x105, 0}, {0x106, 0}, {0x107, 0}, {0x108, 0},
{0x10b, 0}, {0x10c, 0}, {0x10d, 0},
{0x1b1, 0}, {0x1b3, 0}, {0x1b4, 0}, {0x1b6, 0}, {0x1c3, 0}, {0x1c4, 0}, {0x1c5, 0},
{0x1ff, 0}, {0x292, 2}, {0x293, 0}, {0x29b, 0},
{0x1ff, 0}, {0x280, 0x00080008}, {0x281, 0xffff0000}, {0x292, 2}, {0x293, 0}, {0x29b, 0},
{0x2ce, 0}, {0x2d3, 0}, {0x2d5, 0}, {0x2d6, 0}, {0x2db, 0},
{0x2dc, 0xaa00}, {0x2df, 0}, {0x2e4, 0}, {0x2f8, 0}, {0x2f9, 0x2d},
{0x30e, 0xffffffff}, {0x30f, 0xffffffff}, {0x313, 0x6000},
Expand Down
6 changes: 6 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,22 @@ void testRegisterFile() {

void testContextAndBases() {
AgcDriver::QueueState state;
check(state.context.contains(0x280) && state.context.at(0x280) == 0x00080008u && state.context.contains(0x281) && state.context.at(0x281) == 0xffff0000u, "initial point context differs from the SDK defaults");
execute(state, makePacket(0x69, {0x280, 0x00180018u, 0x00400008u}));
check(state.context.at(0x280) == 0x00180018u && state.context.at(0x281) == 0x00400008u, "explicit point size and limits were not stored");
for (const auto offset : {0x10bu, 0x10cu, 0x10du}) execute(state, makePacket(0x69, {offset, 0xffffffffu}));
execute(state, makePacket(0x12, {0}));
for (const auto offset : {0x10bu, 0x10cu, 0x10du}) check(state.context.at(offset) == 0, "CLEAR_STATE did not restore stencil defaults");
check(state.context.at(0x280) == 0x00080008u && state.context.at(0x281) == 0xffff0000u, "CLEAR_STATE did not restore point size and limits");
execute(state, makePacket(0x69, {0x10, 17}));
execute(state, makePacket(0x76, {0x20c, 2}));
execute(state, makePacket(0x10, {3, 0}, 0x68));
check(state.context == AgcDriver::InitialContextRegisters() && state.shader.at(0x20c) == 2, "push-clear reset wrong state");
expectFailure([&] { execute(state, makePacket(0x10, {1, 0}, 0x68)); }, "already pushed");
execute(state, makePacket(0x69, {0x10, 19}));
execute(state, makePacket(0x69, {0x280, 0x00100010u, 0x00800010u}));
execute(state, makePacket(0x10, {2, 0}, 0x68));
check(state.context.at(0x280) == 0x00080008u && state.context.at(0x281) == 0xffff0000u, "context pop did not restore saved point size and limits");
check(state.context.at(0x10) == 17, "pop did not restore context");
expectFailure([&] { execute(state, makePacket(0x10, {2, 0}, 0x68)); }, "not been pushed");
alignas(8) std::array<std::uint32_t, 4> arguments{7, 8, 9, 0};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstring>
#include <iostream>
#include <span>
#include <vector>

namespace {
using AgcDriver::Graphics::Require;
constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Words = 8;
alignas(256) std::array<std::uint32_t, Threads * Words> buffer{};
alignas(256) std::array<std::byte, 65536> texture{};
constexpr std::array<std::uint32_t, 4> Coordinates{0u, 1u, 3u, 4u};
constexpr std::array<std::uint32_t, 4> Samples{0u, 1u, 7u, 0xffffffffu};
constexpr std::array<std::uint32_t, 9> UnormExpected{0x0u, 0x3b808081u, 0x3c40c0c1u, 0x3e808081u, 0x3e828283u, 0x3e868687u, 0x3f40c0c1u, 0x3f41c1c2u, 0x3f43c3c4u};
constexpr std::array<std::uint32_t, 9> SrgbExpected{0x0u, 0x399f0000u, 0x3a6f0000u, 0x3d520000u, 0x3d590000u, 0x3d660000u, 0x3f070000u, 0x3f090000u, 0x3f0c0000u};
alignas(256) constexpr std::array<std::uint32_t, 14> MsaaCode{
0x34020085u, 0xe0301000u, 0x80000201u, 0xe0301004u, 0x80000301u, 0xe0301008u, 0x80000401u,
0xbf8c3f70u, 0xf0000130u, 0x00010802u, 0xbf8c3f70u, 0xe0701010u, 0x80000801u, 0xbf810000u
};
alignas(256) constexpr auto PlaneCode = [] {
auto code = MsaaCode;
code[8] = 0xf0000108u;
return code;
}();

std::array<std::uint32_t, 8> TextureDescriptor(std::uint32_t format) {
const auto address = reinterpret_cast<std::uintptr_t>(texture.data());
return {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (format << 20u) | 0xc0000000u, 0xc000u, 0x90000facu, 0u, 0u, 0u, 0u};
}

void FillTexture(const std::array<std::uint32_t, 8>& descriptor) {
const auto layout = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(descriptor));
Require(layout.guestBytes <= texture.size(), "MSAA-address texture exceeds its storage");
texture.fill(std::byte{0xee});
const auto& mip = layout.mips.at(0);
for (std::uint32_t y = 0; y < 4; ++y) {
for (std::uint32_t x = 0; x < 4; ++x) {
const auto value = 0x12340000u + y * 64u + x;
std::memcpy(texture.data() + mip.tiledOffset + y * mip.pitchBytes + x * 4u, &value, sizeof(value));
}
}
}

std::array<std::uint32_t, Threads> Load(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& image, std::span<const std::uint32_t> code, std::uint32_t wave) {
buffer.fill(0xdeadbeefu);
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
buffer[lane * Words] = Coordinates[lane % 4u];
buffer[lane * Words + 1u] = Coordinates[(lane / 4u) % 4u];
buffer[lane * Words + 2u] = Samples[lane / 16u];
}
const auto address = reinterpret_cast<std::uintptr_t>(buffer.data());
std::vector<std::uint32_t> data{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), static_cast<std::uint32_t>(buffer.size() * 4u), 0x31016facu};
data.insert(data.end(), image.begin(), image.end());
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}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderRecompiler::ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{wave, 0, data, compute, std::nullopt, std::nullopt, memory}, device.Target(), {0, 0, 0, 128}
};
request.useCache = false;
const auto compiled = ShaderRecompiler::Recompile(request);
device.Dispatch(compiled, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
std::array<std::uint32_t, Threads> result{};
for (std::uint32_t lane = 0; lane < Threads; ++lane) result[lane] = buffer[lane * Words + 4u];
return result;
}

void Run(AgcDriver::VulkanDevice& device) {
for (const auto format : {20u, 22u, 56u, 130u}) {
const auto descriptor = TextureDescriptor(format);
FillTexture(descriptor);
for (const auto wave : {32u, 64u}) {
const auto plain = Load(device, descriptor, PlaneCode, wave);
const auto msaa = Load(device, descriptor, MsaaCode, wave);
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
Require(msaa[lane] == plain[lane], "MSAA-address load differs from the measured 2D control at lane " + std::to_string(lane));
const auto x = Coordinates[lane % 4u];
const auto y = Coordinates[(lane / 4u) % 4u];
if (x == 4u || y == 4u) Require(msaa[lane] == 0u, "MSAA-address out-of-range load did not read zero");
else if (format == 20u || format == 22u) Require(msaa[lane] == 0x12340000u + y * 64u + x, "MSAA-address load lost the oracle texel marker");
else {
const auto index = ((lane / 4u) % 4u) * 3u + lane % 4u;
const auto expected = std::bit_cast<float>((format == 56u ? UnormExpected : SrgbExpected)[index]);
const auto actual = std::bit_cast<float>(msaa[lane]);
if (format == 130u) {
const auto encoded = [](float linear) { return std::lround((linear <= 0.0031308f ? linear * 12.92f : 1.055f * std::pow(linear, 1.0f / 2.4f) - 0.055f) * 255.0f); };
Require(std::isfinite(actual) && encoded(actual) == encoded(expected), "MSAA-address sRGB load changed the oracle's encoded texel");
} else Require(std::abs(actual - expected) < 0.0001f, "MSAA-address normalized load differs from the Radeon oracle");
}
}
}
}
}
}

int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Run(*device);
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
Loading