graphics: add experimental Pro mode and linear PSSR

Co-authored-by: Joshua Jayasingh <joshuajayasingh08@gmail.com>
This commit is contained in:
nmzikandJoshua Jayasingh committed 2026-10-29 07:55:50 +01:00
1 parent 5e837a9bea
commit 651ef73a03
30 files changed
+1340 -278

No files matched your search

+346
View File
@@ -0,0 +1,346 @@
#include "common/abi.h"
#include "graphics/guest_gpu/pm4.h"
#include "graphics/guest_gpu/pssr.h"
#include "loader/symbolDatabase.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
namespace Libs::LibPsml {
void InitPsml_1(Loader::SymbolDatabase *symbols);
}
namespace {
bool g_trinity = true;
}
namespace Loader {
bool SystemContentIsTrinityMode() { return g_trinity; }
} // namespace Loader
namespace {
namespace Pm4 = Libs::Graphics::Pm4;
namespace Pssr = Libs::Graphics::Pssr;
constexpr int ERROR_ALREADY_INITIALIZED = static_cast<int>(0x8a810002u);
constexpr int ERROR_TOO_MANY_CONTEXTS = static_cast<int>(0x8a810004u);
constexpr int ERROR_INVALID_CONTEXT = static_cast<int>(0x8a810005u);
constexpr int ERROR_NO_SPACE = static_cast<int>(0x8a810006u);
constexpr int ERROR_BAD_COMMAND_BUFFER = static_cast<int>(0x8a810007u);
constexpr int ERROR_NULL_ARGUMENT = static_cast<int>(0x8a810009u);
constexpr int ERROR_NULL_TEXTURE = static_cast<int>(0x8a81000au);
constexpr int ERROR_INVALID_TEXTURE = static_cast<int>(0x8a81000bu);
constexpr int ERROR_RENDER_SIZE = static_cast<int>(0x8a81000eu);
constexpr int ERROR_OUTPUT_TEXTURE = static_cast<int>(0x8a81000fu);
constexpr int ERROR_NOT_TRINITY = static_cast<int>(0x8a810012u);
constexpr int ERROR_INVALID_SHARED = static_cast<int>(0x8a810014u);
constexpr uint64_t CANARY = 0xfedcba9876543210;
struct MemoryBlock {
uint64_t offset = 0;
uint64_t size = 0x200000;
};
struct SharedParameters {
uint32_t profile = 0;
uint32_t reserved = 0;
const MemoryBlock *blocks = nullptr;
uint64_t block_count = 0;
uint64_t virtual_address = 0;
};
struct ContextParameters {
uint32_t profile = 0;
uint32_t reserved = 0;
const MemoryBlock *blocks = nullptr;
uint64_t block_count = 0;
uint16_t width = 960;
uint16_t height = 540;
uint32_t reserved1 = 0;
const void *depth_copy = nullptr;
const void *motion_copy = nullptr;
const char *name = nullptr;
void *shared = nullptr;
};
static_assert(offsetof(ContextParameters, shared) == 0x38);
struct CommandSpace {
uint32_t *cmd;
uint32_t dwords;
};
struct DispatchParameters {
const uint32_t *output;
const uint32_t *input;
std::array<uint8_t, 0xe0> unused{};
uint32_t width;
uint32_t height;
};
static_assert(offsetof(DispatchParameters, width) == 0xf0);
struct GuardedHandle {
uint64_t before = CANARY;
void *value = nullptr;
std::array<uint64_t, 128> after;
GuardedHandle() { after.fill(CANARY); }
bool Intact() const {
return before == CANARY &&
std::all_of(after.begin(), after.end(),
[](uint64_t value) { return value == CANARY; });
}
};
using Initialize = int(KYTY_SYSV_ABI *)();
using CreateShared = int(KYTY_SYSV_ABI *)(void **, const SharedParameters *);
using CreateContext = int(KYTY_SYSV_ABI *)(void **, const ContextParameters *);
using Release = int(KYTY_SYSV_ABI *)(void *);
using GetSize = int(KYTY_SYSV_ABI *)(const void *, uint32_t *);
using Dispatch = int(KYTY_SYSV_ABI *)(const void *, const CommandSpace *,
const void *);
using GetBias = int(KYTY_SYSV_ABI *)(const void *, float *, uint32_t, uint32_t);
using Predicate = int(KYTY_SYSV_ABI *)(const void *);
Loader::SymbolDatabase g_symbols;
template <typename T> T Function(const char *nid) {
const auto *symbol = g_symbols.FindByNid(nid, Loader::SymbolType::Func);
EXPECT_NE(symbol, nullptr);
return symbol == nullptr ? nullptr : reinterpret_cast<T>(symbol->vaddr);
}
std::array<uint32_t, 8> Texture(uint32_t width, uint32_t height,
uint32_t address) {
std::array<uint32_t, 8> result{};
result[0] = address;
result[1] = (((width - 1) & 3) << 30) | (36u << 20);
result[2] = ((width - 1) >> 2) | ((height - 1) << 14);
result[3] = (9u << 28) | (1u << 20) | 0xfacu;
return result;
}
class PsmlTest : public testing::Test {
protected:
static void SetUpTestSuite() {
Libs::LibPsml::InitPsml_1(&g_symbols);
g_trinity = false;
ASSERT_EQ(Function<Initialize>("3WVD91e12ZQ")(), ERROR_NOT_TRINITY);
g_trinity = true;
ASSERT_EQ(Function<Initialize>("3WVD91e12ZQ")(), 0);
}
void SetUp() override {
SharedParameters params;
params.blocks = blocks.data();
params.block_count = blocks.size();
GuardedHandle result;
ASSERT_EQ(Function<CreateShared>("eWoKNeB6V-k")(&result.value, &params), 0);
ASSERT_TRUE(result.Intact());
ASSERT_GT(reinterpret_cast<uintptr_t>(result.value), 0x10000u);
shared = result.value;
context_params.blocks = blocks.data();
context_params.block_count = 1;
context_params.shared = shared;
result.value = nullptr;
ASSERT_EQ(
Function<CreateContext>("gxv3i+MTEzU")(&result.value, &context_params),
0);
ASSERT_TRUE(result.Intact());
ASSERT_GT(reinterpret_cast<uintptr_t>(result.value), 0x10000u);
context = result.value;
}
void TearDown() override {
for (auto *extra : extra_contexts) {
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(extra), 0);
}
if (context != nullptr) {
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), 0);
}
if (shared != nullptr) {
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), 0);
}
}
std::array<MemoryBlock, 52> blocks;
ContextParameters context_params;
void *shared = nullptr;
void *context = nullptr;
std::vector<void *> extra_contexts;
};
TEST_F(PsmlTest, HandlesPreserveAdjacentMemoryAndRejectReleasedObjects) {
EXPECT_EQ(Function<Initialize>("3WVD91e12ZQ")(), ERROR_ALREADY_INITIALIZED);
uint32_t size = 0;
EXPECT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &size), 0);
EXPECT_EQ(size, Pssr::RequestDwords + 1);
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), ERROR_INVALID_CONTEXT);
EXPECT_EQ(Function<Predicate>("LXq+6mIxpCw")(context), 0);
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), 0);
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(context), ERROR_INVALID_CONTEXT);
EXPECT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &size),
ERROR_INVALID_CONTEXT);
EXPECT_EQ(Function<Predicate>("LXq+6mIxpCw")(context), ERROR_INVALID_CONTEXT);
context = nullptr;
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), 0);
EXPECT_EQ(Function<Release>("jEevBXmagOQ")(shared), ERROR_INVALID_SHARED);
shared = nullptr;
EXPECT_EQ(
Function<GetSize>("AHalTX9wFZY")(reinterpret_cast<void *>(1), &size),
ERROR_INVALID_CONTEXT);
}
TEST_F(PsmlTest, LegacyContextVariantsUseTheirOwnLayoutsAndOutputResolution) {
context_params.shared = reinterpret_cast<void *>(1);
GuardedHandle result;
ASSERT_EQ(
Function<CreateContext>("gxv3i+MTEzU")(&result.value, &context_params),
ERROR_INVALID_SHARED);
ASSERT_TRUE(result.Intact());
ASSERT_EQ(result.value, nullptr);
ASSERT_EQ(
Function<CreateContext>("vUk2pWMx3KQ")(&result.value, &context_params),
0);
ASSERT_TRUE(result.Intact());
extra_contexts.push_back(result.value);
float bias = 0;
EXPECT_EQ(Function<GetBias>("GHna9-DvnUk")(result.value, &bias, 480, 270), 0);
EXPECT_FLOAT_EQ(bias, -2.0f);
ASSERT_EQ(
Function<CreateContext>("2ecEbQaf9VU")(&result.value, &context_params),
0);
ASSERT_TRUE(result.Intact());
extra_contexts.push_back(result.value);
EXPECT_EQ(Function<GetBias>("GHna9-DvnUk")(result.value, &bias, 1920, 1080),
0);
EXPECT_FLOAT_EQ(bias, -2.0f);
}
TEST_F(PsmlTest, CapsLiveContextsAndReleasesCapacity) {
for (int i = 0; i < 3; ++i) {
void *extra = nullptr;
ASSERT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&extra, &context_params),
0);
extra_contexts.push_back(extra);
}
void *fifth = nullptr;
EXPECT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&fifth, &context_params),
ERROR_TOO_MANY_CONTEXTS);
EXPECT_EQ(fifth, nullptr);
EXPECT_EQ(Function<Release>("JaLBe0P3jSU")(extra_contexts.back()), 0);
extra_contexts.pop_back();
ASSERT_EQ(Function<CreateContext>("gxv3i+MTEzU")(&fifth, &context_params), 0);
extra_contexts.push_back(fifth);
}
TEST_F(PsmlTest, DispatchSnapshotsFixedSlotsAndActiveExtent) {
auto output = Texture(960, 540, 0x10000);
auto input = Texture(1920, 1080, 0x20000);
DispatchParameters params{output.data(), input.data(), {}, 640, 360};
std::array<uint32_t, 32> command;
command.fill(0xdeadbeef);
const CommandSpace space{command.data() + 1, 28};
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params), 0);
EXPECT_EQ(KYTY_PM4_LEN(command[1]), space.dwords);
EXPECT_EQ(KYTY_PM4_R(command[1]), Pm4::R_PSSR);
EXPECT_EQ((command[1] >> 8) & 0xffu, Pm4::IT_NOP);
Pssr::Request request;
std::memcpy(&request, command.data() + 2, sizeof(request));
EXPECT_EQ(std::memcmp(request.output, output.data(), sizeof(request.output)),
0);
EXPECT_EQ(std::memcmp(request.input, input.data(), sizeof(request.input)), 0);
EXPECT_EQ(request.width, 640u);
EXPECT_EQ(request.height, 360u);
EXPECT_EQ(command.front(), 0xdeadbeefu);
EXPECT_EQ(command[29], 0xdeadbeefu);
for (size_t i = 2 + Pssr::RequestDwords; i < 29; ++i) {
EXPECT_EQ(command[i], 0u);
}
output.fill(0);
input.fill(0);
params = {};
Pssr::Request snapshot;
std::memcpy(&snapshot, command.data() + 2, sizeof(snapshot));
EXPECT_EQ(std::memcmp(&snapshot, &request, sizeof(request)), 0);
}
TEST_F(PsmlTest, DispatchAcceptsEqualSizedBuffersAndValidatesWithoutWriting) {
auto output = Texture(960, 540, 0x10000);
auto input = Texture(960, 540, 0x20000);
DispatchParameters params{output.data(), input.data(), {}, 960, 540};
uint32_t dwords = 0;
ASSERT_EQ(Function<GetSize>("AHalTX9wFZY")(context, &dwords), 0);
std::vector<uint32_t> command(dwords, 0xdeadbeef);
CommandSpace space{command.data(), dwords};
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params), 0);
std::fill(command.begin(), command.end(), 0xdeadbeef);
space.dwords = dwords - 1;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_NO_SPACE);
space.dwords = 0x4002;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_BAD_COMMAND_BUFFER);
space.dwords = dwords;
params.width = 961;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_RENDER_SIZE);
params.width = 960;
output = Texture(960, 539, 0x10000);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_OUTPUT_TEXTURE);
params.input = nullptr;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_NULL_TEXTURE);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, nullptr),
ERROR_NULL_ARGUMENT);
EXPECT_TRUE(std::all_of(command.begin(), command.end(),
[](uint32_t value) { return value == 0xdeadbeef; }));
}
TEST_F(PsmlTest, DispatchChecksDescriptorContractAndSelectedMip) {
auto output = Texture(960, 540, 0x10000);
auto input = Texture(1920, 1080, 0x20000);
input[3] |= (1u << 12) | (1u << 16);
input[5] |= 1u << 4;
DispatchParameters params{output.data(), input.data(), {}, 960, 540};
std::array<uint32_t, Pssr::RequestDwords + 1> command{};
const CommandSpace space{command.data(), command.size()};
ASSERT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params), 0);
command.fill(0xdeadbeef);
params.width = 961;
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_RENDER_SIZE);
params.width = 960;
input[1] &= ~(0x1ffu << 20);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_INVALID_TEXTURE);
input = Texture(960, 540, 0x20000);
input[3] = (input[3] & ~(0xfu << 28)) | (14u << 28);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_INVALID_TEXTURE);
input = Texture(960, 540, 0x20000);
output[3] &= ~(0x1fu << 20);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_OUTPUT_TEXTURE);
output = Texture(960, 540, 0x20000);
EXPECT_EQ(Function<Dispatch>("RUNLFro+qok")(context, &space, &params),
ERROR_OUTPUT_TEXTURE);
EXPECT_TRUE(std::all_of(command.begin(), command.end(),
[](uint32_t value) { return value == 0xdeadbeef; }));
}
} // namespace
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+228
View File
@@ -0,0 +1,228 @@
void CheckPssr() {
constexpr const char *name = "PssrLinear";
constexpr uintptr_t base = 0x0000000206a00000ull;
constexpr uint64_t size = 0x100000;
constexpr auto packed = Prospero::BufferFormat::k11_11_10Float;
constexpr auto linear = Prospero::TileMode::kLinear;
constexpr auto tiled = Prospero::TileMode::kStandard256B;
constexpr u32 red = 0x000003c0, green = 0x001e0000, blue = 0x78000000;
const auto descriptor = [](uint64_t address, u32 width, u32 height,
Prospero::BufferFormat format, Prospero::TileMode tile,
u32 swizzle = DstSel(4, 5, 6, 7)) {
ShaderTextureResource result{};
result.fields[0] = static_cast<u32>(address >> 8);
result.fields[1] = static_cast<u32>(address >> 40) |
(static_cast<u32>(format) << 20) | (((width - 1) & 3) << 30);
result.fields[2] = ((width - 1) >> 2) | ((height - 1) << 14);
result.fields[3] = swizzle | (static_cast<u32>(tile) << 20) |
(static_cast<u32>(Prospero::ImageType::kColor2D) << 28);
result.fields[5] = 0x00700000;
return result;
};
EnsureRuntimeContext();
GraphicsInitJmpTables();
int64_t physical = -1;
Require(name, "allocate", LibKernel::Memory::KernelAllocateDirectMemory(
0, LibKernel::Memory::KernelGetDirectMemorySize(), size, 0x10000, 0, &physical) == 0,
"upscaling allocation failed");
void *mapped = reinterpret_cast<void *>(base);
Require(name, "map", LibKernel::Memory::KernelMapDirectMemory(
&mapped, size, 0x3, 0x10, physical, 0x10000) == 0 &&
mapped == reinterpret_cast<void *>(base), "upscaling mapping failed");
std::memset(mapped, 0, size);
RenderContext context(m_runtime_context);
context.InitializeGpu(nullptr);
LibKernel::Memory::InstallGpuResources(&context);
context.GetGpu().SendCommandSync([&] {
CommandProcessor processor(context, 0);
processor.BufferInit();
context.MapMemory(base, size);
auto &executor = context.GetRenderExecutor();
auto &cache = context.GetTextureCache();
auto &scheduler = context.GetCommandScheduler();
const auto resolve = [&](const ShaderTextureResource &texture) {
ShaderRecompiler::IR::ImageResource resource{};
resource.resource_class = ShaderRecompiler::IR::ImageResourceClass::Sampled;
resource.numeric_class = Prospero::TextureNumericClass::Float;
resource.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
resource.read = true;
ShaderRecompiler::IR::DescriptorValue value{};
value.dword_count = 8;
std::copy_n(texture.fields, 8, value.dwords.begin());
return executor.ResolveTexture(resource, value);
};
const auto write_linear = [&](const ShaderTextureResource &texture,
std::span<const u32> texels) {
auto info = MakeTilingImage(texture.Format(), texture.Width5() + 1u,
texture.Height5() + 1u, 1, 1, linear, 0, false);
const u32 words_per_texel = info.bytes_per_block / sizeof(u32);
Require(name, "source size", texels.size() ==
info.extent.width * info.extent.height * words_per_texel,
"source texel fixture size is wrong");
std::vector<u32> words(info.data.size / sizeof(u32));
for (u32 y = 0; y < info.extent.height; ++y) {
std::copy_n(texels.begin() + y * info.extent.width * words_per_texel,
info.extent.width * words_per_texel,
words.begin() + y * info.mip_layout[0].pitch * words_per_texel);
}
Require(name, "invalidate source", context.InvalidateMemory(
texture.Base40(), info.data.size), "source fixture is outside mapped memory");
LibKernel::Memory::WriteBacking(texture.Base40(), words.data(), info.data.size);
};
const auto run = [&](const ShaderTextureResource &input,
const ShaderTextureResource &output, u32 width, u32 height) {
Pssr::Request request{};
std::copy_n(input.fields, 8, request.input);
std::copy_n(output.fields, 8, request.output);
request.width = width;
request.height = height;
// The reserved packet includes trailing words that the parser must consume.
constexpr u32 reserved_words = Pssr::RequestDwords + 9;
std::vector<u32> packet(reserved_words, 0xdeadbeef);
packet[0] = KYTY_PM4(reserved_words, Pm4::IT_NOP, Pm4::R_PSSR);
std::memcpy(packet.data() + 1, &request, sizeof(request));
u32 sentinel = 0;
const auto address = reinterpret_cast<uint64_t>(&sentinel);
packet.insert(packet.end(), {KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0), 0,
static_cast<u32>(address), static_cast<u32>(address >> 32), 33});
Pm4Execution execution;
Require(name, "packet and suffix", processor.Process(execution, packet) ==
Pm4ProcessResult::Complete && sentinel == 33,
"upscaling packet did not consume its reserved payload before the suffix");
const auto binding = resolve(output);
return ReadCachedTexel(name, context, binding.image_id, {},
{output.Width5() + 1u, output.Height5() + 1u, 1},
binding.desc.view_info.base_layer, binding.desc.view_info.base_level);
};
auto input = descriptor(base, 2, 1, packed, linear);
auto output = descriptor(base + 0x10000, 4, 1, packed, tiled);
write_linear(input, std::array<u32, 2>{red, green});
Require(name, "CPU-first bilinear source", run(input, output, 2, 1) ==
std::vector<u32>{red, 0x3a0 | (0x340 << 11), 0x340 | (0x3a0 << 11), green},
"first-use source upload or linear interpolation is wrong");
write_linear(input, std::array<u32, 2>{blue, blue});
Require(name, "CPU source refresh", run(input, output, 2, 1) ==
std::vector<u32>(4, blue), "reused source retained stale GPU texels");
input = descriptor(base + 0x20000, 2, 1, packed, linear);
output = descriptor(base + 0x30000, 2, 1, packed, tiled);
write_linear(input, std::array<u32, 2>{red, green});
Require(name, "same-size copy", run(input, output, 2, 1) ==
std::vector<u32>{red, green}, "same-size upscaling lost source pixels");
input = descriptor(base + 0x40000, 8, 4, packed, linear);
output = descriptor(base + 0x50000, 8, 4, packed, tiled);
std::vector<u32> cropped(32, blue);
cropped[0] = cropped[1] = cropped[8] = cropped[9] = red;
write_linear(input, cropped);
Require(name, "active rectangle", run(input, output, 2, 2) ==
std::vector<u32>(32, red),
"equal allocation extents bypassed scaling or sampled outside the active rectangle");
input = descriptor(base + 0x60000, 2, 1, packed, linear, DstSel(6, 5, 4, 7));
output = descriptor(base + 0x70000, 2, 1, packed, tiled);
write_linear(input, std::array<u32, 2>{red, green});
Require(name, "sampled view swizzle", run(input, output, 2, 1) ==
std::vector<u32>{blue, green}, "sampled descriptor swizzle was ignored");
output.fields[3] = (output.fields[3] & ~0xfffu) | DstSel(6, 5, 4, 7);
Require(name, "storage swizzle", run(input, output, 2, 1) ==
std::vector<u32>{red, green}, "output channel mapping was ignored");
input = descriptor(base + 0x80000, 2, 1,
Prospero::BufferFormat::k16_16_16_16Float, linear);
output = descriptor(base + 0x90000, 2, 1, packed, tiled);
write_linear(input, std::array<u32, 4>{0x00003c00, 0x3c000000,
0x3c000000, 0x3c000000});
Require(name, "float16 input", run(input, output, 2, 1) ==
std::vector<u32>{red, green}, "float16 input conversion failed");
ImageDesc parent{};
parent.type = BindingType::Texture;
parent.info = MakeTilingImage(packed, 4, 2, 1, 2, tiled, 0, false);
parent.info.data.address = base + 0xa0000;
parent.info.pixel_format = vk::Format::eB10G11R11UfloatPack32;
parent.view_info.format = parent.info.pixel_format;
parent.view_info.type = vk::ImageViewType::e2DArray;
parent.view_info.layer_count = 2;
const auto parent_id = cache.FindImage(parent);
(void)cache.FindTexture(parent_id, parent);
vk::ClearValue clear{};
clear.color.float32 = std::array<float, 4>{1, 0, 0, 1};
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, clear);
clear.color.float32 = std::array<float, 4>{0, 0, 1, 1};
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear);
input = descriptor(parent.info.data.address, 4, 2, packed, tiled);
output = descriptor(parent.info.data.address + parent.info.tiled_slice_stride,
4, 2, packed, tiled);
const auto resolved_output = resolve(output);
Require(name, "parent array resolution", resolved_output.image_id == parent_id &&
resolved_output.desc.view_info.base_layer == 1,
"standalone output did not resolve into the existing array's second layer");
Require(name, "resolved output layer", run(input, output, 4, 2) ==
std::vector<u32>(8, red), "upscaling wrote layer zero instead of the resolved layer");
Require(name, "source layer preserved", ReadCachedTexel(name, context, parent_id,
{}, {4, 2, 1}) == std::vector<u32>(8, red),
"upscaling modified the source layer of its shared native image");
clear.color.float32 = std::array<float, 4>{0, 1, 0, 1};
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), parent_id,
{vk::ImageAspectFlagBits::eColor, 0, 1, 1, 1}, clear);
Require(name, "resolved source layer", run(output, input, 4, 2) ==
std::vector<u32>(8, green), "upscaling read layer zero instead of the resolved layer");
input = descriptor(base + 0xc0000, 2, 1, packed, linear);
output = descriptor(base + 0xd0000, 2, 1, packed, tiled);
write_linear(input, std::array<u32, 2>{red, green});
ImageDesc raw{};
raw.type = BindingType::Texture;
raw.info = MakeTilingImage(Prospero::BufferFormat::k32UInt, 2, 1, 1, 1,
linear, 0, false);
raw.info.data.address = input.Base40();
raw.info.pixel_format = vk::Format::eR32Uint;
raw.view_info.format = raw.info.pixel_format;
const auto raw_id = cache.FindImage(raw);
(void)cache.FindTexture(raw_id, raw);
const auto float_binding = resolve(input);
Require(name, "reinterpreted sampled view", float_binding.image_id == raw_id &&
cache.GetImage(raw_id).backing.format == vk::Format::eR32Uint &&
float_binding.desc.view_info.format == vk::Format::eB10G11R11UfloatPack32,
"float sampled descriptor did not reuse the integer native allocation");
Require(name, "sampled view format", run(input, output, 2, 1) ==
std::vector<u32>{red, green}, "upscaling used the allocation format instead of its view");
ImageDesc mip_parent{};
mip_parent.type = BindingType::Texture;
mip_parent.info = MakeTilingImage(packed, 128, 64, 2, 1, tiled, 0, false);
mip_parent.info.data.address = base + 0xe0000;
mip_parent.info.pixel_format = vk::Format::eB10G11R11UfloatPack32;
mip_parent.view_info.format = mip_parent.info.pixel_format;
mip_parent.view_info.level_count = 2;
const auto mip_parent_id = cache.FindImage(mip_parent);
(void)cache.FindTexture(mip_parent_id, mip_parent);
clear.color.float32 = std::array<float, 4>{0, 0, 1, 1};
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), mip_parent_id,
{vk::ImageAspectFlagBits::eColor, 0, 2, 0, 1}, clear);
clear.color.float32 = std::array<float, 4>{0, 1, 0, 1};
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), mip_parent_id,
{vk::ImageAspectFlagBits::eColor, 1, 1, 0, 1}, clear);
input = descriptor(mip_parent.info.data.address + mip_parent.info.mip_layout[1].offset,
64, 32, packed, tiled);
output = descriptor(base + 0xf0000, 64, 32, packed, tiled);
const auto resolved_mip = resolve(input);
Require(name, "parent mip resolution", resolved_mip.image_id == mip_parent_id &&
resolved_mip.desc.view_info.base_level == 1,
"standalone source did not resolve into the existing image's second mip");
Require(name, "resolved source mip", run(input, output, 64, 32) ==
std::vector<u32>(64 * 32, green),
"upscaling read mip zero instead of the resolved mip");
context.UnmapMemory(base, size);
scheduler.Finish();
});
LibKernel::Memory::InstallGpuResources(nullptr);
context.ShutdownGpu();
Require(name, "release", LibKernel::Memory::KernelCheckedReleaseDirectMemory(
physical, size) == 0, "upscaling allocation release failed");
std::printf("[gpu] %-32s ok\n", name);
}
+17 -5
View File
@@ -16,6 +16,7 @@
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pm4.h"
#include "graphics/guest_gpu/pssr.h"
#include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/hostMemory.h"
#include "graphics/host_gpu/memoryTracker.h"
@@ -21825,6 +21826,8 @@ public:
case_index, format_cases);
}
#include "PssrGpuTests.inc"
private:
bool m_rasterization_supported = true;
u32 m_skipped_cases = 0;
@@ -51364,13 +51367,16 @@ void CheckPm4IndirectUconfigRegisters(RenderContext &renderer) {
static_cast<uint32_t>(address), static_cast<uint32_t>(address >> 32u),
0x80000000u, 1u,
};
Pm4Execution execution;
const bool handler_present =
g_hw_uc_indirect_func[Pm4::UC_PARAMETER_OVERSUBSCRIPTION] != nullptr;
Require("Pm4IndirectUconfigRegisters", "indirect UCONFIG register stream",
handler_present &&
processor.Process(execution, command) == Pm4ProcessResult::Complete,
"UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT");
for (uint32_t mode = 0; mode <= 1; ++mode) {
command[0] = KYTY_PM4(5, Pm4::IT_SET_UCONFIG_REG_INDIRECT, mode);
Pm4Execution execution;
Require("Pm4IndirectUconfigRegisters", "indirect UCONFIG register stream",
handler_present &&
processor.Process(execution, command) == Pm4ProcessResult::Complete,
"UCONFIG register was rejected by SET_UCONFIG_REG_INDIRECT");
}
std::printf("[host] %-32s ok\n", "Pm4IndirectUconfigRegisters");
}
@@ -53835,6 +53841,12 @@ int main(int argc, char **argv) {
CheckPm4ContextStateOperations(vulkan.RuntimeRenderer());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--pssr-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckPssr();
CheckPm4IndirectUconfigRegisters(vulkan.RuntimeRenderer());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--pm4-wait-only") == 0) {
VulkanHarness vulkan;
CheckPm4AcquireMemPackets(vulkan.RuntimeRenderer());
+129
View File
@@ -0,0 +1,129 @@
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "common/threads.h"
#include "common/virtualMemory.h"
#include "graphics/guest_gpu/pm4.h"
#include "kernel/memory.h"
#include "libs/agc.h"
#include "loader/symbolDatabase.h"
#include "loader/systemContent.h"
#include <gtest/gtest.h>
#include <array>
#include <filesystem>
#include <fstream>
namespace Libs {
void InitLibKernel_1(Loader::SymbolDatabase*);
}
namespace {
namespace Pm4 = Libs::Graphics::Pm4;
TEST(Trinity, ModeQueriesPacketsAndMemoryAgree) {
static Common::Subsystems subsystems;
Common::VirtualMemory::Init();
Common::InitializeThreads();
subsystems.Initialize<Config::Lifecycle>();
Config::ConfigOptions options;
options.printf_direction = Config::LogDirection::Silent;
Config::Load(options);
subsystems.Initialize<Log::Lifecycle>();
Loader::SymbolDatabase symbols;
Libs::InitLibKernel_1(&symbols);
const auto* query = symbols.FindByNid("tU5e3f9gSiU", Loader::SymbolType::Func);
const auto* operation = symbols.FindByNid("NH6xARDOVv8", Loader::SymbolType::Func);
ASSERT_NE(query, nullptr);
ASSERT_NE(operation, nullptr);
const auto is_trinity = reinterpret_cast<int(KYTY_SYSV_ABI*)()>(query->vaddr);
const auto operation_mode = reinterpret_cast<int(KYTY_SYSV_ABI*)(int*, int*)>(operation->vaddr);
struct CommandBuffer {
uint32_t* bottom;
uint32_t* top;
uint32_t* cursor_up;
uint32_t* cursor_down;
void* callback;
void* user_data;
uint32_t reserved_dw;
};
const auto path = std::filesystem::temp_directory_path() /
("kyty_trinity_" + std::to_string(reinterpret_cast<uintptr_t>(&symbols)) + ".json");
struct Case { bool enabled; uint32_t attribute; bool expected; };
for (const auto test: {Case {false, 0, false}, Case {false, 1u << 22, false}, Case {true, 0, false},
Case {true, 1u << 21, false}, Case {true, 1u << 22, true}}) {
options.pro_enabled = test.enabled;
Config::Load(options);
{
std::ofstream file(path);
file << "{\"attribute3\":" << test.attribute
<< R"(,"kernel":{"flexibleMemorySize":3221225472,"cpuPageTableSize":134217728,"gpuPageTableSize":0},"amm":{"pagetableMemorySizeInMib":96}})";
ASSERT_TRUE(file.good());
}
testing::internal::CaptureStdout();
Loader::SystemContentLoadParamSfo(path);
std::filesystem::remove(path);
EXPECT_EQ(Loader::SystemContentIsTrinityMode(), test.expected);
EXPECT_EQ(is_trinity(), test.expected ? 1 : 0);
int mode = -1, submode = -1;
EXPECT_EQ(operation_mode(&mode, &submode), 0);
EXPECT_EQ(mode, test.expected ? 3 : 2);
EXPECT_EQ(submode, 0);
uint8_t graphics_mode = 0xff;
Libs::Graphics::Gen5::AgcGetIsTrinityMode(&graphics_mode);
EXPECT_EQ(graphics_mode, test.expected ? 1 : 0);
std::array<uint32_t, 5> packet {};
CommandBuffer buffer {packet.data(), packet.data() + packet.size(), packet.data(),
packet.data() + packet.size(), nullptr, nullptr, 0};
Libs::Graphics::Gen5::AgcDcbSetUcRegistersIndirect(
reinterpret_cast<Libs::Graphics::Gen5::CommandBuffer*>(&buffer), nullptr, 0);
EXPECT_EQ(packet[0], KYTY_PM4(5, Pm4::IT_SET_UCONFIG_REG_INDIRECT,
test.expected ? 1 : 0));
const auto console = testing::internal::GetCapturedStdout();
if (test.enabled) {
EXPECT_EQ(console, test.expected ? "[PS5] title Trinity support=yes\n"
: "[PS5] title Trinity support=no\n");
} else {
EXPECT_TRUE(console.empty()) << console;
}
}
using namespace Libs::LibKernel::Memory;
subsystems.Initialize<Lifecycle>();
constexpr uint64_t mib = 1024 * 1024;
constexpr uint64_t backing = (12800 + 1216 - 64 - 96) * mib;
constexpr uint64_t flexible = 3072 * mib;
EXPECT_EQ(TestGuestBackingSize(), backing);
EXPECT_EQ(KernelGetDirectMemorySize(), backing - flexible);
size_t configured = 0;
ASSERT_EQ(KernelConfiguredFlexibleMemorySize(&configured), 0);
EXPECT_EQ(configured, flexible);
int64_t available_start = -1;
size_t available_size = 0;
ASSERT_EQ(KernelAvailableDirectMemorySize(0, backing - flexible, 0x4000,
&available_start, &available_size), 0);
EXPECT_EQ(available_start, 0);
EXPECT_EQ(available_size, backing - flexible);
int64_t physical = -1;
ASSERT_EQ(KernelAllocateDirectMemory(backing - flexible - 0x4000, backing - flexible,
0x4000, 0x4000, 0, &physical), 0);
void* mapped = nullptr;
ASSERT_EQ(KernelMapDirectMemory(&mapped, 0x4000, 2, 0, physical, 0), 0);
*static_cast<volatile uint64_t*>(mapped) = 0x123456789abcdef0ull;
EXPECT_EQ(*static_cast<volatile uint64_t*>(mapped), 0x123456789abcdef0ull);
EXPECT_EQ(KernelMunmap(reinterpret_cast<uint64_t>(mapped), 0x4000), 0);
EXPECT_EQ(KernelCheckedReleaseDirectMemory(physical, 0x4000), 0);
}
} // namespace
int RunTrinityTests() {
int argc = 1;
char name[] = "trinity_tests";
char* argv[] = {name, nullptr};
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
+5
View File
@@ -5708,7 +5708,12 @@ void TestTlsStubsPreserveFlags() {
}
#endif
int RunTrinityTests();
int main(int argc, char** argv) {
if (argc == 2 && std::strcmp(argv[1], "--trinity-only") == 0) {
return RunTrinityTests();
}
InitSubsystems();
if (argc == 2 && std::strcmp(argv[1], "--memory-bottlenecks-only") == 0) {
RunTest(TestFlexibleZeroingTracksBackingLifetime);