#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "Cover_vert_spv.h" #include "Cover_frag_spv.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using namespace AgcDriver::Graphics; constexpr int VulkanTestSkipped = 77; constexpr std::uint32_t Width = 61; constexpr std::uint32_t Height = 13; constexpr std::uint32_t Stale = 0x5a5a5a5au; constexpr std::uint32_t Clear = 0x11223344u; constexpr std::uint32_t Drawn = 0xffffffffu; alignas(4096) std::array SurfaceStorage{}; const std::span Surface{reinterpret_cast((reinterpret_cast(SurfaceStorage.data()) + 65535u) & ~std::uintptr_t{65535u}), 65536}; alignas(4096) std::array Cmask{}; ColorTarget target(ColorTileMode mode) { const ColorTargetLayout layout(Width, Height, mode, 4); ColorTarget color{}; color.address = reinterpret_cast(Surface.data()); color.extent = {Width, Height}; color.format = VK_FORMAT_R8G8B8A8_UNORM; color.bytes = layout.Bytes(); color.componentMapping = 0xe4u; color.tileMode = mode; color.elementBytes = 4; color.clearWords = {Clear, 0}; color.surfaceAddress = color.address; color.surfaceExtent = color.extent; color.cmaskAddress = reinterpret_cast(Cmask.data()); color.cmaskBytes = CmaskLayout(Width, Height).Bytes(); return color; } void prepare(const ColorTarget& color, const std::function& cleared, std::uint8_t code = 0) { std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); StorageTexture::FlushPending(color.address, Surface.size(), nullptr, "test"); std::vector stale(Surface.size()); for (std::size_t offset = 0; offset < stale.size(); offset += 4) std::memcpy(stale.data() + offset, &Stale, 4); AgcDriver::GuestMemory::Write(color.address, stale); const CmaskLayout layout(Width, Height); std::vector mask(Cmask.size(), std::byte{0xff}); for (std::uint32_t tileY = 0; tileY < layout.TilesY(); ++tileY) { for (std::uint32_t tileX = 0; tileX < layout.TilesX(); ++tileX) { if (!cleared(tileX, tileY)) continue; const auto nibble = layout.Nibble(tileX, tileY); mask[nibble / 2u] &= std::byte{static_cast((nibble % 2u) == 0 ? 0xf0u : 0x0fu)}; mask[nibble / 2u] |= std::byte{static_cast((nibble % 2u) == 0 ? code : code << 4u)}; } } AgcDriver::GuestMemory::Write(color.cmaskAddress, mask); } void draw(AgcDriver::VulkanDevice& device, const ColorTarget& color, VkRect2D scissor) { ShaderRecompiler::RecompileResult vertex; vertex.spirv = std::vector(std::begin(COVER_VERT_SPV), std::end(COVER_VERT_SPV)); ShaderRecompiler::RecompileResult fragment; fragment.spirv = std::vector(std::begin(COVER_FRAG_SPV), std::end(COVER_FRAG_SPV)); const std::array shaders{{ {ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, 0} }}; State state{}; state.stages = {ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt}; state.color = color; state.colors = {color}; state.hasColorTarget = true; state.renderExtent = {Width, Height}; state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; state.viewport = {0, 0, static_cast(Width), static_cast(Height), 0, 1}; state.negativeOneToOne = false; state.scissor = scissor; state.cullMode = VK_CULL_MODE_NONE; state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; state.blend.colorWriteMask = 15; state.blends = {state.blend}; state.blendConstants = {}; std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); device.Draw(state, {0, 3, 0, 1, 0, false}, shaders); device.WaitIdle(); } void expect(AgcDriver::VulkanDevice& device, const ColorTarget& color, const std::function& texel, const std::string& what) { std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); StorageTexture::FlushPending(color.address, color.bytes, nullptr, "test"); device.WaitIdle(); const ColorTargetLayout layout(Width, Height, color.tileMode, 4); for (std::uint32_t y = 0; y < Height; ++y) { for (std::uint32_t x = 0; x < Width; ++x) { std::uint32_t stored = 0; std::memcpy(&stored, Surface.data() + layout.Offset(x, y), 4); Require(stored == texel(x, y), what + ": texel (" + std::to_string(x) + ", " + std::to_string(y) + ") holds " + std::to_string(stored)); } } Require(std::all_of(Cmask.begin(), Cmask.end(), [](std::uint8_t value) { return value == 0xffu; }), what + ": the CMASK was not left expanded"); } void eliminate(AgcDriver::VulkanDevice& device, const ColorTarget& color) { std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); device.ColorMetadataPass({ColorMetadataPass::Mode::EliminateFastClear, {color}}); } void run(AgcDriver::VulkanDevice& device, ColorTileMode mode, const std::string& name) { const auto color = target(mode); prepare(color, [](std::uint32_t, std::uint32_t) { return true; }); draw(device, color, {{0, 0}, {20, Height}}); expect(device, color, [](std::uint32_t x, std::uint32_t) { return x < 20 ? Drawn : Clear; }, name + " fast-cleared target drawn over its left part"); draw(device, color, {{40, 0}, {Width - 40, Height}}); expect(device, color, [](std::uint32_t x, std::uint32_t) { return x < 20 || x >= 40 ? Drawn : Clear; }, name + " fast-cleared target drawn again over its right part"); prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return (tileX == 0 && tileY == 0) || (tileX == 7 && tileY == 1); }); draw(device, color, {{0, 0}, {20, Height}}); expect(device, color, [](std::uint32_t x, std::uint32_t y) { return x < 20 ? Drawn : x >= 56 && y >= 8 ? Clear : Stale; }, name + " partly fast-cleared target drawn over its left part"); prepare(color, [](std::uint32_t, std::uint32_t) { return true; }); eliminate(device, color); expect(device, color, [](std::uint32_t, std::uint32_t) { return Clear; }, name + " fast-cleared target eliminated without a draw"); prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return tileX == 7 && tileY == 1; }); eliminate(device, color); expect(device, color, [](std::uint32_t x, std::uint32_t y) { return x >= 56 && y >= 8 ? Clear : Stale; }, name + " partly fast-cleared edge tile eliminated"); prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return (tileX + tileY) % 2u == 0; }); eliminate(device, color); expect(device, color, [](std::uint32_t x, std::uint32_t y) { return (x / 8u + y / 8u) % 2u == 0 ? Clear : Stale; }, name + " checkerboard of fast-cleared tiles eliminated"); prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return tileX >= 3 && tileY == 1; }); draw(device, color, {{0, 0}, {Width, 4}}); expect(device, color, [](std::uint32_t x, std::uint32_t y) { return y < 4 ? Drawn : y >= 8 && x >= 24 ? Clear : Stale; }, name + " fast-cleared bottom edge tiles under a draw of the top rows"); } void invalidCode(AgcDriver::VulkanDevice& device) { const auto color = target(ColorTileMode::RenderTarget); prepare(color, [](std::uint32_t tileX, std::uint32_t tileY) { return tileX == 2 && tileY == 0; }, 3); eliminate(device, color); { std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); device.WaitIdle(); } prepare(color, [](std::uint32_t, std::uint32_t) { return true; }); try { eliminate(device, color); } catch (const std::runtime_error& error) { Require(std::string(error.what()).find("were recorded, which are not a single-sample fast-clear code") != std::string::npos, std::string("unexpected failure: ") + error.what()); return; } throw std::runtime_error("a CMASK code 3 read by the GPU pass did not throw"); } } int main() { try { std::unique_ptr device; try { device = std::make_unique(); } catch (const std::exception& error) { if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw; std::printf("skipped, no usable Vulkan device: %s\n", error.what()); return VulkanTestSkipped; } { GuestAllocations::Mutation mutation; mutation.Add(SurfaceStorage.data(), SurfaceStorage.size(), true, true, true); mutation.Add(Cmask.data(), Cmask.size(), true, true, true); } run(*device, ColorTileMode::Linear, "linear"); run(*device, ColorTileMode::RenderTarget, "SW_64KB_R_X"); invalidCode(*device); device->WaitIdle(); device.reset(); { GuestAllocations::Mutation mutation; mutation.Remove(SurfaceStorage.data()); mutation.Remove(Cmask.data()); } std::cout << "CMASK fast clear draw and eliminate tests passed\n"; return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } }