diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp index 1d9cdb4590..5e872f1dec 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp @@ -54,8 +54,8 @@ std::size_t MultisampledDccBytes(const ColorTarget& target); VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span dcc, std::span cmask); bool MultisampledFillClearColor(const ColorTarget& target, std::uint32_t fill, VkClearColorValue& clear); bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colorFill, const ColorTarget& use, VkClearColorValue& clear); -VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target); std::shared_ptr MultisampleTargetTexture(const Context& context, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D); +VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image = nullptr); void MaterializeMultisampleTargetClear(const Context& context, const ColorTarget& target); VkImageView MultisampleTargetStorageView(const Context& context, const GuestTextureResource& resource); bool MultisampleTargetStorable(const Context& context, const ColorTarget& target); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 1f63a8d086..6ff07e4236 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1602,6 +1602,7 @@ struct RecordedDraw { std::vector> targets; std::vector targetSlices; std::vector> proxies; + std::vector multisampleImages; const IndirectRecord* indirect = nullptr; std::span moved; bool listed = false; @@ -1798,12 +1799,14 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam passReads.emplace_back(args->arguments, args->arguments + args->RangeBytes()); if (args->countIndirect) passReads.emplace_back(args->countAddress, args->countAddress + 4); } + passReads.insert(passReads.end(), inputs.inPlaceReads.begin(), inputs.inPlaceReads.end()); const auto passImages = resources.StorageImages(); std::vector> passAttachments; passAttachments.reserve(record.targets.size()); for (const auto& target : record.targets) { if (target != nullptr) passAttachments.emplace_back(target->Image(), true); } + for (const auto image : record.multisampleImages) passAttachments.emplace_back(image, true); const bool unknownMemory = addressBased || resources.UsesAddressTable() || resources.HasCopiedWrites(); static const bool addressReadPasses = std::getenv("APS5_NO_ADDRESS_READ_PASSES") == nullptr; const bool readOnlyBda = addressReadPasses && resources.BdaReadOnly(); @@ -2026,6 +2029,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& TileMipLayout mip{}; std::unique_ptr target; bool multisampled = false; + VkImage multisampleImage = VK_NULL_HANDLE; // APS5_DUMP_TARGETS: the rendered linear pixels, read back for target_
_.raw. std::unique_ptr dump; }; @@ -2042,7 +2046,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& if (color.samples > 1) { binding.multisampled = true; timer.phase(PhaseSetup); - targetViews.push_back(MultisampleTargetView(context, color)); + targetViews.push_back(MultisampleTargetView(context, color, &binding.multisampleImage)); timer.phase(PhaseReadTarget); continue; } @@ -2323,6 +2327,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& for (const auto& binding : targets) record.targetSlices.push_back(binding.color.arraySlice); for (const auto& binding : targets) { if (binding.proxied) record.proxies.push_back(binding.resident); + if (binding.multisampled) record.multisampleImages.push_back(binding.multisampleImage); } record.indirect = args != nullptr ? &indirect : nullptr; record.listed = listed; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp index bc604f9db6..25fd1eafc5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp @@ -709,9 +709,11 @@ bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colo return true; } -VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target) { +VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image) { std::lock_guard lock(targetsMutex()); - return acquire(context, target).view; + const auto& acquired = acquire(context, target); + if (image != nullptr) *image = acquired.image; + return acquired.view; } namespace { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/MultisampledPassOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampledPassOrder.cpp new file mode 100644 index 0000000000..2075a2e2c8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MultisampledPassOrder.cpp @@ -0,0 +1,149 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using namespace AgcDriver::Graphics; +using DepthFastClearHarness::Compile; +using DepthFastClearHarness::Height; +using DepthFastClearHarness::Shaders; +using DepthFastClearHarness::Triangles; +using DepthFastClearHarness::Width; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Samples = 4; + +alignas(4096) std::array Multisampled{}; +alignas(4096) std::array DepthA{}; +alignas(4096) std::array DepthB{}; + +struct Barrier { + VkPipelineStageFlags source; + VkPipelineStageFlags destination; + std::vector memory; + std::vector images; +}; + +class Capture { +public: + static Context Install(const Context& base) { + Require(resolver == nullptr, "nested barrier capture"); + resolver = base.deviceProc; + device = base.device; + Context captured = base; + captured.bufferPool.reset(); + captured.deviceProc = resolve; + captured.functions = nullptr; + return captured; + } + static std::vector Take() { return std::exchange(barriers, {}); } + +private: + static PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice handle, const char* name) { + if (std::string_view(name) == "vkCmdPipelineBarrier") return reinterpret_cast(pipelineBarrier); + return resolver(handle, name); + } + static VKAPI_ATTR void VKAPI_CALL pipelineBarrier(VkCommandBuffer commands, VkPipelineStageFlags source, VkPipelineStageFlags destination, VkDependencyFlags dependencies, std::uint32_t memoryCount, const VkMemoryBarrier* memory, std::uint32_t bufferCount, const VkBufferMemoryBarrier* buffers, std::uint32_t imageCount, const VkImageMemoryBarrier* images) { + barriers.push_back({source, destination, std::vector(memory, memory + memoryCount), std::vector(images, images + imageCount)}); + reinterpret_cast(resolver(device, "vkCmdPipelineBarrier"))(commands, source, destination, dependencies, memoryCount, memory, bufferCount, buffers, imageCount, images); + } + + static inline PFN_vkGetDeviceProcAddr resolver = nullptr; + static inline VkDevice device = VK_NULL_HANDLE; + static inline std::vector barriers; +}; + +bool OrdersColorWrites(const std::vector& barriers) { + return std::any_of(barriers.begin(), barriers.end(), [](const Barrier& barrier) { + return barrier.source == VK_PIPELINE_STAGE_ALL_COMMANDS_BIT && barrier.destination == VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT && barrier.images.empty() && barrier.memory.size() == 1 && (barrier.memory[0].srcAccessMask & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0 && (barrier.memory[0].dstAccessMask & VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT) != 0; + }); +} + +void Draw(const Context& context, AgcDriver::VulkanDevice& device, const Shaders& compiled, std::uint64_t depth) { + const std::array shaders{{ + {ShaderStage::Vertex, &compiled.vertex, 0}, + {ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())} + }}; + State state{}; + state.stages = {ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {reinterpret_cast(Multisampled.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Multisampled.size(), 0xe4u}; + state.color.samples = Samples; + state.colors = {state.color}; + state.hasColorTarget = true; + state.depth = DepthTarget{depth, 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0, false, Samples}; + state.depthTest = true; + state.depthWrite = true; + state.depthCompare = VK_COMPARE_OP_LESS; + state.samples = Samples; + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangles().size()), 0, 1, 0, false}; + AgcDriver::Graphics::Draw(context, state, draw, shaders); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto base = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + if ((base.limits.framebufferColorSampleCounts & Samples) == 0 || (base.limits.framebufferDepthSampleCounts & Samples) == 0) { + std::puts("skipped, the device cannot render four-sample color and depth"); + return VulkanTestSkipped; + } + if (!Recorder::MergeBarriers()) { + std::puts("skipped, barrier elision is off"); + return VulkanTestSkipped; + } + const auto shaders = Compile(*device); + device->WaitIdle(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto captured = Capture::Install(base); + Recorder recorder(captured); + recorder.Activate(); + const auto depthA = reinterpret_cast(DepthA.data()); + const auto depthB = reinterpret_cast(DepthB.data()); + Draw(captured, *device, shaders, depthA); + Draw(captured, *device, shaders, depthB); + Capture::Take(); + Draw(captured, *device, shaders, depthA); + const auto between = Capture::Take(); + Require(OrdersColorWrites(between), "multisampled pass order: two draws sharing a multisampled color target while switching depth targets recorded no barrier ordering the first draw's color writes before the second"); + recorder.Submit(); + recorder.Sync(); + std::puts("multisampled pass order tests passed (ordering asserted on the recorded barriers; the multisampled image is not resolved to guest memory)"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PassInputHazard.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PassInputHazard.cpp new file mode 100644 index 0000000000..66e6aff3eb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PassInputHazard.cpp @@ -0,0 +1,241 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "DepthFastClearHarness.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::ColorTargetLayout; +using AgcDriver::Graphics::ColorTileMode; +using DepthFastClearHarness::BufferDescriptor; +using DepthFastClearHarness::Height; +using DepthFastClearHarness::Require; +using DepthFastClearHarness::Triangles; +using DepthFastClearHarness::VertexCode; +using DepthFastClearHarness::Width; +using ShaderRecompiler::ShaderStage; + +constexpr std::size_t BlockBytes = 65536; +const ColorTargetLayout TargetLayout(Width, Height, ColorTileMode::RenderTarget, 4); + +alignas(4096) std::array FirstDepth{}; +alignas(4096) std::array SecondDepth{}; + +alignas(256) constexpr std::array CoverCode{0x7e0002f2, 0xf800180f, 0x00000000, 0xbf810000}; +alignas(256) constexpr std::array StoreCode{0x7e0a0280, 0x7e0c0280, 0xe0702000, 0x80010506, 0x7e0e02f2, 0xf800180f, 0x07070707, 0xbf810000}; + +struct Block { + explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { +#ifdef _WIN32 + if (watched) { + data = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes)); + GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes); + } else { + data = static_cast(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + } +#else + if (watched) { + void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(raw != MAP_FAILED, "pass input hazard: cannot map a block"); + const auto begin = reinterpret_cast(raw); + const auto aligned = (begin + BlockBytes - 1) & ~(static_cast(BlockBytes) - 1); + if (aligned != begin) munmap(raw, aligned - begin); + if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast(aligned + bytes), begin + BlockBytes - aligned); + data = reinterpret_cast(aligned); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes); + } else { + data = static_cast(std::aligned_alloc(BlockBytes, bytes)); + } +#endif + Require(data != nullptr, "pass input hazard: cannot allocate a block"); + std::memset(data, 0, bytes); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + + ~Block() { + AgcDriver::Graphics::StorageTexture::FlushPending(Address(), bytes, nullptr, "test release"); + { + GuestAllocations::Mutation mutation; + mutation.Remove(data); + } +#ifdef _WIN32 + if (watched) { + GuestArena::GuestArenaReset_nid_postfix(data, bytes); + GuestArena::GuestArenaRelease_nid_postfix(data, bytes); + } else { + VirtualFree(data, 0, MEM_RELEASE); + } +#else + if (watched) { + munmap(data, bytes); + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes); + } else { + std::free(data); + } +#endif + } + + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + + std::uint64_t Address() const { return reinterpret_cast(data); } + + std::size_t bytes; + bool watched; + std::uint8_t* data = nullptr; +}; + +struct Program { + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult pixel; + std::uint32_t vertexPush = 0; +}; + +Program Compile(AgcDriver::VulkanDevice& device, const void* vertices, std::span pixelCode, const void* stored) { + const auto target = device.Target(); + Program program; + std::vector vertexUserData(4, 0u); + const auto vertexBuffer = BufferDescriptor(vertices, 16u, static_cast(Triangles().size())); + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); + const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + ShaderRecompiler::RecompileRequest vertex{ + {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {32, 0, vertexUserData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, vertexMemory}, + target, + {0, 0, 0, 64} + }; + vertex.useCache = false; + program.vertex = ShaderRecompiler::Recompile(vertex); + program.vertexPush = static_cast(program.vertex.pushConstants.size()); + + ShaderRecompiler::ShaderPixelStageInfo pixel{}; + pixel.wave32 = true; + pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY); + pixel.posX = true; + pixel.posY = true; + pixel.targetOutputMode[0] = 9; + pixel.targetExportMapping.fill(0xe4u); + std::vector pixelUserData(8, 0u); + if (stored != nullptr) { + const auto storeBuffer = BufferDescriptor(stored, 4u, 1u); + std::copy(storeBuffer.begin(), storeBuffer.end(), pixelUserData.begin() + 4); + } + const std::array pixelMemory{{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}}; + ShaderRecompiler::RecompileRequest fragment{ + {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, + {32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, + target, + PixelPushLayout(program.vertexPush, target) + }; + fragment.useCache = false; + program.pixel = ShaderRecompiler::Recompile(fragment); + return program; +} + +void Draw(AgcDriver::VulkanDevice& device, const Program& program, const Block& target, const float* depth, const std::uint32_t* indices) { + const std::array shaders{{ + {ShaderStage::Vertex, &program.vertex, 0}, + {ShaderStage::Fragment, &program.pixel, PixelPushOffset(program.vertexPush, device.Target())} + }}; + AgcDriver::Graphics::State state{}; + state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; + state.color = {target.Address(), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, TargetLayout.Bytes(), 0xe4u, ColorTileMode::RenderTarget}; + state.colors = {state.color}; + state.hasColorTarget = true; + if (depth != nullptr) { + state.depth = AgcDriver::Graphics::DepthTarget{reinterpret_cast(depth), 0, {Width, Height}, VK_FORMAT_D32_SFLOAT, 1.0f, 0}; + state.depthTest = true; + state.depthWrite = true; + state.depthCompare = VK_COMPARE_OP_ALWAYS; + } + state.renderExtent = {Width, Height}; + state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + state.viewport = {0, static_cast(Height), static_cast(Width), -static_cast(Height), 0, 1}; + state.negativeOneToOne = false; + state.scissor = {{0, 0}, {Width, Height}}; + state.cullMode = VK_CULL_MODE_NONE; + state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + state.blend.colorWriteMask = 15; + state.blends = {state.blend}; + state.blendConstants = {}; + const AgcDriver::Pm4::DrawParameters draw{reinterpret_cast(indices), static_cast(Triangles().size()), indices != nullptr ? 4u : 0u, 1, 0, indices != nullptr}; + device.Draw(state, draw, shaders); +} + +bool WriterFenced(std::uint64_t address, std::size_t bytes) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "pass input hazard: no active recorder"); + const std::array, 1> writes{{{address, address + bytes}}}; + return recorder->StartDrawPass(0x5eed, false, false, AgcDriver::Graphics::PassAccess{{}, writes, {}, {}, false, false}).barrier; +} + +std::size_t Covered(AgcDriver::VulkanDevice& device, const Block& target) { + AgcDriver::Graphics::StorageTexture::FlushPending(target.Address(), target.bytes, nullptr, "test read-back"); + device.WaitIdle(); + std::vector stored(TargetLayout.Bytes()); + AgcDriver::GuestMemory::Read(target.Address(), std::as_writable_bytes(std::span(stored)), 1); + return static_cast(std::count(stored.begin(), stored.end(), std::uint8_t{0xff})); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const Block inputs(BlockBytes); + auto* indices = reinterpret_cast(inputs.data); + constexpr std::array triangle{0u, 1u, 2u}; + const auto reader = Compile(*device, Triangles().data(), CoverCode, nullptr); + const auto writer = Compile(*device, Triangles().data(), StoreCode, indices + 1); + constexpr std::size_t all = std::size_t{Width} * Height * 4u; + const std::array, 3> depths{{{nullptr, nullptr}, {FirstDepth.data(), nullptr}, {FirstDepth.data(), SecondDepth.data()}}}; + for (const auto& [readDepth, writeDepth] : depths) { + const Block read(TargetLayout.Bytes()); + const Block written(TargetLayout.Bytes()); + std::memcpy(indices, triangle.data(), sizeof(triangle)); + const auto variant = std::string(readDepth == nullptr ? "without depth" : writeDepth == nullptr ? "with depth on the reader" : "with a depth target each"); + Draw(*device, reader, read, readDepth, indices); + Require(WriterFenced(inputs.Address(), sizeof(triangle)), "pass input hazard " + variant + ": a pass writing the indices an open pass reads in place took no barrier"); + Draw(*device, reader, read, readDepth, indices); + Draw(*device, writer, written, writeDepth, nullptr); + Require(Covered(*device, read) == all, "pass input hazard " + variant + ": the draw reading its indices in place did not cover its target, a later pass overwrote them first"); + Require(Covered(*device, written) == all, "pass input hazard " + variant + ": the storing draw did not cover its target"); + std::array stored{}; + AgcDriver::GuestMemory::Read(inputs.Address(), std::as_writable_bytes(std::span(stored)), 4); + Require(stored == std::array{0u, 0u, 2u}, "pass input hazard " + variant + ": the storing draw's write did not reach the indices"); + } + std::puts("pass input hazard tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +}