diff --git a/README.md b/README.md index d0db327a4..5f9307bab 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Execution reaches `_start`, [stack unwinding](core/libs/prx/libc/src/exception/U The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces validated via [Spirv-Tools](3rdparty/SPIRV-Tools) SPIR-V. -The application reaches the render loop and **successfully presents its first frame** (logo). +The real game reaches the render loop and **successfully presents its first frames** (logo). -Current exception: `AGC graphics: unsupported SPIR-V memory model` on 6 frame. +Current exception: `Could not open file /_sd/SAVEDATA00/-saveindex` on ~71 frame. ## Build diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 8bf0dcbbd..a356e17ed 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -359,3 +359,5 @@ if(AGC_BUILD_VISUAL_TEST) add_dependencies(agc_driver_visual_test agc_driver_visual_runtime) endif() endif() + +include(PostCompile.cmake) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index bbe54659d..41d83cc32 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -5,6 +5,7 @@ #include #include "Recompiler.hpp" #include "prx/libSceAgcDriver/Execution/include/Presentation.hpp" +#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include @@ -23,12 +24,13 @@ public: bool Presentable() const; void PresentClear(std::uint32_t width, std::uint32_t height, bool opaque); void PresentPixels(std::uint32_t width, std::uint32_t height, std::span pixels); + void PresentDisplayBuffer(const DisplayBuffer& buffer); void Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span snapshots = {}); void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span shaders, std::span snapshots = {}); private: Graphics::Context graphicsContext() const; - void present(std::uint32_t width, std::uint32_t height, bool opaque, std::span pixels); + void present(std::uint32_t width, std::uint32_t height, bool opaque, std::span pixels, const DisplayBuffer* display = nullptr); struct State; std::unique_ptr state; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index a8f1c1fac..9664d0cd5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -212,10 +212,8 @@ public: require(buffer->width == window.width && buffer->height == window.height, "display buffer extent differs from output"); presenting->WaitIdle(); timing.Mark("device_idle_wait"); - const auto pixels = ReadDisplayBuffer(*buffer); - timing.Mark("display_read_detile"); - presenting->PresentPixels(window.width, window.height, pixels); - timing.Mark("present_pixels"); + presenting->PresentDisplayBuffer(*buffer); + timing.Mark("present_display_buffer"); } else { presenting->PresentClear(window.width, window.height, opaque); timing.Mark("present_clear"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 49b6a3a6c..c3581b8a2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -4,6 +4,10 @@ #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" +#include "prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp" #include "prx/libc/include/General.hpp" #include #include @@ -72,6 +76,10 @@ struct VulkanDevice::State { bool samplerAnisotropy = false; bool textureCompressionBC = false; std::unique_ptr detiler; + std::unique_ptr colorTransfer; + std::shared_ptr bufferPool; + std::unique_ptr textureCache; + std::unique_ptr pipelineCache; VkPhysicalDeviceMeshShaderPropertiesEXT meshLimits{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT}; std::unique_ptr scaler; @@ -151,8 +159,12 @@ struct VulkanDevice::State { if (device != VK_NULL_HANDLE) { const auto idle = reinterpret_cast(deviceProc(device, "vkDeviceWaitIdle"))(device); if (idle != VK_SUCCESS && idle != VK_ERROR_DEVICE_LOST) std::terminate(); + textureCache.reset(); detiler.reset(); + colorTransfer.reset(); scaler.reset(); + pipelineCache.reset(); + bufferPool.reset(); const auto destroyFence = reinterpret_cast(deviceProc(device, "vkDestroyFence")); if (acquireFence) destroyFence(device, acquireFence, nullptr); if (renderFence) destroyFence(device, renderFence, nullptr); @@ -406,7 +418,11 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; poolInfo.queueFamilyIndex = family; check(state->DeviceFunction("vkCreateCommandPool")(state->device, &poolInfo, nullptr, &state->pool), "vkCreateCommandPool"); + state->bufferPool = std::make_shared(graphicsContext()); + state->pipelineCache = std::make_unique(graphicsContext()); state->detiler = std::make_unique(graphicsContext()); + state->textureCache = std::make_unique(graphicsContext()); + state->colorTransfer = std::make_unique(graphicsContext()); if (window != nullptr) { require(window->getDrawableSize != nullptr, "missing window drawable size query"); std::uint32_t drawableWidth = 0; @@ -534,11 +550,19 @@ void VulkanDevice::PresentPixels(std::uint32_t width, std::uint32_t height, std: present(width, height, true, pixels); } -void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span pixels) { +void VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { + present(buffer.width, buffer.height, true, {}, &buffer); +} + +void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span pixels, const DisplayBuffer* display) { PerformanceTimer timing("Vulkan.Present"); APS5_LOG_OUT_DEBUG("present begin width=%u height=%u opaque=%u pixels=%zu", width, height, static_cast(opaque), pixels.size()); require(state->swapchain != VK_NULL_HANDLE, "device has no swapchain"); require(state->extent.width != 0 && state->extent.height != 0, "output window is minimized"); + if (display != nullptr) { + static_cast(DisplayBufferSize(*display)); + state->colorTransfer->Upload(display->address, width, height, Graphics::ColorTileMode::RenderTarget); + } if (!pixels.empty()) state->Upload(pixels); timing.Mark("pixel_upload"); APS5_LOG_OUT_DEBUG("present source=%s bytes=%zu", pixels.empty() ? "clear" : "pixels", pixels.size()); @@ -580,7 +604,7 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; auto pipelineBarrier = state->DeviceFunction("vkCmdPipelineBarrier"); pipelineBarrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); - if (pixels.empty()) { + if (pixels.empty() && display == nullptr) { APS5_LOG_OUT_DEBUG("Recording swapchain clear opaque=%u image=%p", static_cast(opaque), reinterpret_cast(barrier.image)); VkClearColorValue clear{}; clear.float32[3] = opaque ? 1.0f : 0.0f; @@ -589,7 +613,10 @@ void VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu APS5_LOG_OUT_DEBUG("Recording swapchain scaled blit width=%u height=%u bytes=%zu buffer=%p image=%p", width, height, pixels.size(), reinterpret_cast(state->uploadBuffer), reinterpret_cast(barrier.image)); require(state->scaler != nullptr, "presentation scaler is unavailable"); state->scaler->EnsureSourceImage(width, height); - state->scaler->RecordUpload(commands, state->uploadBuffer); + if (display != nullptr) { + state->colorTransfer->Detile(commands, display->pixelFormat == 0x8000000022000000ull); + } + state->scaler->RecordUpload(commands, display != nullptr ? state->colorTransfer->LinearBuffer() : state->uploadBuffer); VkClearColorValue letterbox{}; letterbox.float32[3] = 1.0f; state->DeviceFunction("vkCmdClearColorImage")(commands, barrier.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &letterbox, 1, &barrier.subresourceRange); @@ -667,7 +694,11 @@ Graphics::Context VulkanDevice::graphicsContext() const { state->fragmentShaderBarycentric, state->samplerAnisotropy, state->textureCompressionBC, - state->detiler.get() + state->detiler.get(), + state->colorTransfer.get(), + state->bufferPool, + state->textureCache.get(), + state->pipelineCache ? state->pipelineCache->Handle() : VK_NULL_HANDLE }; } @@ -736,7 +767,7 @@ void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std pipelineInfo.stage.module = module; pipelineInfo.stage.pName = "main"; pipelineInfo.layout = layout; - check(state->DeviceFunction("vkCreateComputePipelines")(state->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines"); + check(state->DeviceFunction("vkCreateComputePipelines")(state->device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines"); timing.Mark("pipeline_create"); VkCommandBufferAllocateInfo allocation{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; allocation.commandPool = state->pool; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp new file mode 100644 index 000000000..5ef5b4b30 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -0,0 +1,48 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BUFFERPOOL_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_BUFFERPOOL_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +struct BufferAllocation { + VkBuffer buffer; + VkDeviceMemory memory; + void* mapping; + VkDeviceAddress address; + VkDeviceSize allocationBytes; + std::size_t bytes; + VkBufferUsageFlags usage; +}; + +class BufferPool { +public: + explicit BufferPool(const Context& context); + ~BufferPool(); + BufferPool(const BufferPool&) = delete; + BufferPool& operator=(const BufferPool&) = delete; + std::optional Take(std::size_t bytes, VkBufferUsageFlags usage); + void Put(const BufferAllocation& allocation) noexcept; + +private: + void destroy(const BufferAllocation& allocation) noexcept; + VkDevice device; + PFN_vkUnmapMemory unmap; + PFN_vkDestroyBuffer destroyBuffer; + PFN_vkFreeMemory freeMemory; + std::mutex mutex; + std::array, 64> free; + VkDeviceSize retainedBytes = 0; + std::size_t cursor = 0; + static constexpr VkDeviceSize budget = 512ull * 1024 * 1024; +}; + +std::shared_ptr GetBufferPool(const Context& context); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 6b595a940..8a9e836f5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -6,19 +6,27 @@ #endif #include #include +#include #include #include namespace AgcDriver::Graphics { class TextureDetiler; +class GpuColorTransfer; +class BufferPool; +class TextureCache; inline void Require(bool condition, const std::string& reason) { if (!condition) throw std::runtime_error("AGC graphics: " + reason); } inline void Check(VkResult result, const char* operation) { - Require(result == VK_SUCCESS, std::string(operation) + ": Vulkan result " + std::to_string(result)); + if (result != VK_SUCCESS) throw std::runtime_error(std::string("AGC graphics: ") + operation + ": Vulkan result " + std::to_string(result)); +} + +inline void Require(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(std::string("AGC graphics: ") + reason); } struct Context { @@ -42,12 +50,16 @@ struct Context { bool samplerAnisotropy = false; bool textureCompressionBC = false; TextureDetiler* detiler = nullptr; + GpuColorTransfer* colorTransfer = nullptr; + mutable std::shared_ptr bufferPool; + TextureCache* textureCache = nullptr; + VkPipelineCache pipelineCache = VK_NULL_HANDLE; template TFunction Function(const char* name) const { Require(deviceProc != nullptr, "missing Vulkan device function resolver"); const auto function = reinterpret_cast(deviceProc(device, name)); - Require(function != nullptr, std::string("missing Vulkan function: ") + name); + if (function == nullptr) throw std::runtime_error(std::string("AGC graphics: missing Vulkan function: ") + name); return function; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp new file mode 100644 index 000000000..50cf1cc8c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp @@ -0,0 +1,45 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_GPUCOLORTRANSFER_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_GPUCOLORTRANSFER_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include + +namespace AgcDriver::Graphics { + +class GpuColorTransfer { +public: + explicit GpuColorTransfer(const Context& context); + ~GpuColorTransfer(); + GpuColorTransfer(const GpuColorTransfer&) = delete; + GpuColorTransfer& operator=(const GpuColorTransfer&) = delete; + void Upload(std::uint64_t address, std::uint32_t width, std::uint32_t height, ColorTileMode mode); + void Detile(VkCommandBuffer commands, bool swapRedBlue = false); + void Tile(VkCommandBuffer commands); + void WriteBack(std::uint64_t address); + VkBuffer LinearBuffer() const; + RenderTarget& Target(const ColorTarget& color, bool blending); + +private: + void prepare(std::uint32_t width, std::uint32_t height, ColorTileMode mode); + void convert(VkCommandBuffer commands, bool toTiled, bool swapRedBlue); + void release() noexcept; + Context context; + VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + std::unique_ptr tiled; + std::unique_ptr linear; + std::uint32_t width = 0; + std::uint32_t height = 0; + ColorTileMode mode = ColorTileMode::Linear; + std::unique_ptr target; + VkExtent2D targetExtent{}; + VkFormat targetFormat = VK_FORMAT_UNDEFINED; + bool targetBlending = false; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp new file mode 100644 index 000000000..5ef1cdd73 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp @@ -0,0 +1,30 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_PIPELINECACHE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_PIPELINECACHE_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" + +namespace AgcDriver::Graphics { + +class PipelineCache { +public: + explicit PipelineCache(const Context& context) : context(context) { + const VkPipelineCacheCreateInfo info{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO}; + Check(context.Function("vkCreatePipelineCache")(context.device, &info, nullptr, &cache), "vkCreatePipelineCache"); + } + + ~PipelineCache() { + context.Function("vkDestroyPipelineCache")(context.device, cache, nullptr); + } + + PipelineCache(const PipelineCache&) = delete; + PipelineCache& operator=(const PipelineCache&) = delete; + VkPipelineCache Handle() const { return cache; } + +private: + Context context; + VkPipelineCache cache = VK_NULL_HANDLE; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp index defa5b17f..2ff4ca7ba 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp @@ -25,6 +25,9 @@ private: VkDeviceMemory memory = VK_NULL_HANDLE; void* mapping = nullptr; std::size_t size; + VkDeviceSize allocationBytes = 0; + VkBufferUsageFlags usage; + std::shared_ptr cache; }; class RenderTarget { @@ -38,7 +41,7 @@ public: private: void release() noexcept; - const Context& context; + Context context; VkImage image = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 0248fb0f8..789dca14d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -55,7 +55,7 @@ private: VkDescriptorSet _set = VK_NULL_HANDLE; VkDescriptorPool pool = VK_NULL_HANDLE; std::vector allocations; - std::vector> textures; + std::vector> textures; std::vector> samplers; }; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 6b4466cc2..b36be99b7 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -9,20 +9,22 @@ namespace AgcDriver::Graphics { class Texture { public: - Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components); + Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span snapshot); ~Texture(); Texture(const Texture&) = delete; Texture& operator=(const Texture&) = delete; VkImageView View() const; + VkDeviceSize AllocationBytes() const { return allocationBytes; } private: void release() noexcept; - const Context& context; + Context context; VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; + VkDeviceSize allocationBytes = 0; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureCache.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureCache.hpp new file mode 100644 index 000000000..66460d7fb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureCache.hpp @@ -0,0 +1,32 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURECACHE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_TEXTURECACHE_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +class TextureCache { +public: + explicit TextureCache(const Context& context); + std::shared_ptr Get(std::span words, const GuestTextureResource& resource, VkComponentMapping components); + +private: + struct Entry { + std::array descriptor; + std::vector snapshot; + std::shared_ptr texture; + }; + void trim(); + Context context; + std::list entries; + std::uint64_t retainedBytes = 0; + static constexpr std::uint64_t budget = 256ull * 1024 * 1024; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp index c15355a7b..6bdeb68cf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp @@ -18,17 +18,20 @@ namespace AgcDriver::Graphics { TextureDetiler& operator=(const TextureDetiler&) = delete; void Dispatch(VkCommandBuffer commands, TextureTileMode tileMode, std::uint32_t elementBytes, VkBuffer source, std::uint64_t sourceOffset, VkBuffer destination, std::uint64_t destinationOffset, const TileMipLayout& layout); + void BeginBatch(); private: VkPipeline pipeline(TextureTileMode tileMode, std::uint32_t elementBytes); void release() noexcept; + VkDescriptorSet allocateSet(); const Context context; VkDescriptorSetLayout descriptorLayout = VK_NULL_HANDLE; VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; VkShaderModule module = VK_NULL_HANDLE; std::vector> pipelines; - VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + std::vector descriptorPools; + std::size_t allocatedSets = 0; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/ColorTransfer.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/ColorTransfer.comp new file mode 100644 index 000000000..30d6f3238 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/ColorTransfer.comp @@ -0,0 +1,28 @@ +#version 450 + +layout(local_size_x = 8, local_size_y = 8) in; +layout(set = 0, binding = 0, std430) buffer TiledPixels { uint tiled[]; }; +layout(set = 0, binding = 1, std430) buffer LinearPixels { uint linear[]; }; +layout(push_constant) uniform Parameters { + uint width; + uint height; + uint blocksPerRow; + uint mode; +} parameters; + +void main() { + uint x = gl_GlobalInvocationID.x; + uint y = gl_GlobalInvocationID.y; + if (x >= parameters.width || y >= parameters.height) return; + uint linearIndex = y * parameters.width + x; + uint tiledIndex = linearIndex; + if ((parameters.mode & 4u) != 0u) { + uint offset = ((y << 4u) & 0x0070u) ^ ((y << 5u) & 0x0f00u) ^ ((y << 9u) & 0x1000u) ^ ((y << 8u) & 0x4000u) + ^ ((x << 2u) & 0x000cu) ^ ((x << 5u) & 0x0380u) ^ ((x << 4u) & 0x0400u) ^ ((x << 6u) & 0x0800u) ^ ((x << 9u) & 0xa000u); + tiledIndex = ((y / 128u) * parameters.blocksPerRow + x / 128u) * 16384u + offset / 4u; + } + uint pixel = (parameters.mode & 1u) != 0u ? linear[linearIndex] : tiled[tiledIndex]; + if ((parameters.mode & 2u) != 0u) pixel = (pixel & 0xff00ff00u) | ((pixel & 0xffu) << 16u) | ((pixel >> 16u) & 0xffu); + if ((parameters.mode & 1u) != 0u) tiled[tiledIndex] = pixel; + else linear[linearIndex] = pixel; +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp new file mode 100644 index 000000000..d217f906a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -0,0 +1,56 @@ +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" + +namespace AgcDriver::Graphics { + +BufferPool::BufferPool(const Context& context) : device(context.device), unmap(context.Function("vkUnmapMemory")), destroyBuffer(context.Function("vkDestroyBuffer")), freeMemory(context.Function("vkFreeMemory")) {} + +BufferPool::~BufferPool() { + for (const auto& allocation : free) { + if (allocation) destroy(*allocation); + } +} + +void BufferPool::destroy(const BufferAllocation& allocation) noexcept { + unmap(device, allocation.memory); + destroyBuffer(device, allocation.buffer, nullptr); + freeMemory(device, allocation.memory, nullptr); +} + +std::optional BufferPool::Take(std::size_t bytes, VkBufferUsageFlags usage) { + std::lock_guard lock(mutex); + for (auto& allocation : free) { + if (!allocation || allocation->bytes != bytes || allocation->usage != usage) continue; + auto result = allocation; + retainedBytes -= allocation->allocationBytes; + allocation.reset(); + return result; + } + return std::nullopt; +} + +void BufferPool::Put(const BufferAllocation& allocation) noexcept { + std::lock_guard lock(mutex); + if (allocation.allocationBytes > budget) { + destroy(allocation); + return; + } + while (retainedBytes + allocation.allocationBytes > budget || free[cursor].has_value()) { + auto& old = free[cursor]; + if (old) { + retainedBytes -= old->allocationBytes; + destroy(*old); + old.reset(); + } + cursor = (cursor + 1) % free.size(); + } + free[cursor] = allocation; + retainedBytes += allocation.allocationBytes; + cursor = (cursor + 1) % free.size(); +} + +std::shared_ptr GetBufferPool(const Context& context) { + if (!context.bufferPool) context.bufferPool = std::make_shared(context); + return context.bufferPool; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 0ee1bc98d..623426b8b 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/ColorTargetTransfer.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" @@ -108,18 +109,16 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& vertexBuffers.push_back(std::move(buffer)); } timing.Mark("vertex_upload"); - std::unique_ptr transfer; - std::unique_ptr target; + RenderTarget* target = nullptr; if (state.hasColorTarget) { APS5_LOG_OUT_DEBUG("Creating color target address=0x%llx bytes=%llu extent=%ux%u", static_cast(state.color.address), static_cast(state.color.bytes), state.color.extent.width, state.color.extent.height); const ColorTargetLayout colorLayout(state.color.extent.width, state.color.extent.height, state.color.tileMode); - transfer = std::make_unique(context, colorLayout.LinearBytes(), VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - APS5_LOG_CHARS_OUT_DEBUG("Transfer buffer created"); - timing.Mark("color_transfer_allocate"); - ReadColorTarget(state.color, transfer->Bytes()); - timing.Mark("color_read_detile"); + Require(context.colorTransfer != nullptr, "device color transfer is unavailable"); + Require(state.color.bytes == colorLayout.Bytes(), "color target transfer size mismatch"); + context.colorTransfer->Upload(state.color.address, state.color.extent.width, state.color.extent.height, state.color.tileMode); + timing.Mark("color_upload"); APS5_LOG_CHARS_OUT_DEBUG("Color target read from guest memory"); - target = std::make_unique(context, state.color, state.blend.blendEnable != 0); + target = &context.colorTransfer->Target(state.color, state.blend.blendEnable != 0); timing.Mark("render_target_create"); APS5_LOG_CHARS_OUT_DEBUG("RenderTarget created"); } @@ -129,7 +128,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& timing.Mark("shader_resources"); APS5_LOG_CHARS_OUT_DEBUG("ShaderResources created"); APS5_LOG_CHARS_OUT_DEBUG("Creating Pipeline"); - Pipeline pipeline(context, state, target.get(), resources, shaders); + Pipeline pipeline(context, state, target, resources, shaders); timing.Mark("pipeline_create"); APS5_LOG_CHARS_OUT_DEBUG("Pipeline created"); APS5_LOG_CHARS_OUT_DEBUG("Creating CommandBatch"); @@ -146,8 +145,9 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& copy.imageExtent = {state.color.extent.width, state.color.extent.height, 1}; if (state.hasColorTarget) { APS5_LOG_CHARS_OUT_DEBUG("Copying guest color target to Vulkan image"); + context.colorTransfer->Detile(commands); imageBarrier(context, commands, target->Image(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT); - context.Function("vkCmdCopyBufferToImage")(commands, transfer->Handle(), target->Image(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + context.Function("vkCmdCopyBufferToImage")(commands, context.colorTransfer->LinearBuffer(), target->Image(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); imageBarrier(context, commands, target->Image(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); APS5_LOG_CHARS_OUT_DEBUG("Guest color target upload recorded"); } @@ -181,10 +181,11 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& reuse.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; reuse.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; reuse.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - reuse.buffer = transfer->Handle(); - reuse.size = transfer->Bytes().size(); + reuse.buffer = context.colorTransfer->LinearBuffer(); + reuse.size = VK_WHOLE_SIZE; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &reuse, 0, nullptr); - context.Function("vkCmdCopyImageToBuffer")(commands, target->Image(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, transfer->Handle(), 1, ©); + context.Function("vkCmdCopyImageToBuffer")(commands, target->Image(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, context.colorTransfer->LinearBuffer(), 1, ©); + context.colorTransfer->Tile(commands); APS5_LOG_CHARS_OUT_DEBUG("Color target copy-back recorded"); } VkMemoryBarrier download{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; @@ -204,8 +205,8 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& resources.WriteBack(); timing.Mark("resources_writeback"); APS5_LOG_CHARS_OUT_DEBUG("Shader resources WriteBack OK"); - if (state.hasColorTarget) WriteColorTarget(state.color, transfer->Bytes()); - timing.Mark("color_tile_writeback"); + if (state.hasColorTarget) context.colorTransfer->WriteBack(state.color.address); + timing.Mark("color_writeback"); if (state.hasColorTarget) APS5_LOG_OUT_DEBUG("Color target written to guest address=0x%llx bytes=%llu", static_cast(state.color.address), static_cast(state.color.bytes)); APS5_LOG_CHARS_OUT("Draw finished"); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp new file mode 100644 index 000000000..cb7404ace --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GpuColorTransfer.cpp @@ -0,0 +1,147 @@ +#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp" +#include "prx/libSceAgcDriver/Graphics/shaders/ColorTransfer_spv.h" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include + +namespace AgcDriver::Graphics { + +GpuColorTransfer::GpuColorTransfer(const Context& context) : context(context) { + VkShaderModule module = VK_NULL_HANDLE; + try { + std::array bindings{}; + for (std::uint32_t i = 0; i < bindings.size(); ++i) { + bindings[i] = {i, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; + } + VkDescriptorSetLayoutCreateInfo descriptorInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + descriptorInfo.bindingCount = static_cast(bindings.size()); + descriptorInfo.pBindings = bindings.data(); + Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &descriptorInfo, nullptr, &descriptorLayout), "vkCreateDescriptorSetLayout color transfer"); + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, 16}; + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.setLayoutCount = 1; + layoutInfo.pSetLayouts = &descriptorLayout; + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &push; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &pipelineLayout), "vkCreatePipelineLayout color transfer"); + VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + moduleInfo.codeSize = sizeof(COLOR_TRANSFER_SPV); + moduleInfo.pCode = COLOR_TRANSFER_SPV; + Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule color transfer"); + VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineInfo.stage.module = module; + pipelineInfo.stage.pName = "main"; + pipelineInfo.layout = pipelineLayout; + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines color transfer"); + context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + module = VK_NULL_HANDLE; + const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2}; + VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + poolInfo.maxSets = 1; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &size; + Check(context.Function("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &descriptorPool), "vkCreateDescriptorPool color transfer"); + VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocation.descriptorPool = descriptorPool; + allocation.descriptorSetCount = 1; + allocation.pSetLayouts = &descriptorLayout; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &descriptorSet), "vkAllocateDescriptorSets color transfer"); + } catch (...) { + if (module) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + release(); + throw; + } +} + +GpuColorTransfer::~GpuColorTransfer() { + release(); +} + +void GpuColorTransfer::release() noexcept { + if (descriptorPool) context.Function("vkDestroyDescriptorPool")(context.device, descriptorPool, nullptr); + if (pipeline) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); + if (pipelineLayout) context.Function("vkDestroyPipelineLayout")(context.device, pipelineLayout, nullptr); + if (descriptorLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, descriptorLayout, nullptr); +} + +void GpuColorTransfer::prepare(std::uint32_t newWidth, std::uint32_t newHeight, ColorTileMode newMode) { + const ColorTargetLayout layout(newWidth, newHeight, newMode); + Require(layout.Bytes() <= context.limits.maxStorageBufferRange && layout.LinearBytes() <= context.limits.maxStorageBufferRange, "color transfer exceeds storage buffer limits"); + Require((newWidth + 7u) / 8u <= context.limits.maxComputeWorkGroupCount[0] && (newHeight + 7u) / 8u <= context.limits.maxComputeWorkGroupCount[1], "color transfer exceeds workgroup limits"); + if (tiled && width == newWidth && height == newHeight && mode == newMode) return; + auto newTiled = std::make_unique(context, layout.Bytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + auto newLinear = std::make_unique(context, layout.LinearBytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const std::array buffers{{{newTiled->Handle(), 0, layout.Bytes()}, {newLinear->Handle(), 0, layout.LinearBytes()}}}; + std::array writes{}; + for (std::uint32_t i = 0; i < writes.size(); ++i) { + writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writes[i].dstSet = descriptorSet; + writes[i].dstBinding = i; + writes[i].descriptorCount = 1; + writes[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + writes[i].pBufferInfo = &buffers[i]; + } + context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); + tiled = std::move(newTiled); + linear = std::move(newLinear); + width = newWidth; + height = newHeight; + mode = newMode; +} + +void GpuColorTransfer::Upload(std::uint64_t address, std::uint32_t newWidth, std::uint32_t newHeight, ColorTileMode newMode) { + prepare(newWidth, newHeight, newMode); + const ColorTargetLayout layout(width, height, mode); + GuestMemory::Read(address, tiled->Bytes(), layout.Alignment()); +} + +void GpuColorTransfer::convert(VkCommandBuffer commands, bool toTiled, bool swapRedBlue) { + Require(commands != VK_NULL_HANDLE && tiled && linear, "color transfer is not prepared"); + VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + before.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + before.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + const auto barrier = context.Function("vkCmdPipelineBarrier"); + barrier(commands, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &before, 0, nullptr, 0, nullptr); + const std::array push{width, height, (width + 127u) / 128u, (toTiled ? 1u : 0u) | (swapRedBlue ? 2u : 0u) | (mode == ColorTileMode::RenderTarget ? 4u : 0u)}; + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + context.Function("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data()); + context.Function("vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1); + VkMemoryBarrier after{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + after.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + after.dstAccessMask = toTiled ? VK_ACCESS_HOST_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT; + barrier(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, toTiled ? VK_PIPELINE_STAGE_HOST_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &after, 0, nullptr, 0, nullptr); +} + +void GpuColorTransfer::Detile(VkCommandBuffer commands, bool swapRedBlue) { + convert(commands, false, swapRedBlue); +} + +void GpuColorTransfer::Tile(VkCommandBuffer commands) { + convert(commands, true, false); +} + +void GpuColorTransfer::WriteBack(std::uint64_t address) { + Require(tiled != nullptr, "color transfer is not prepared for writeback"); + const ColorTargetLayout layout(width, height, mode); + GuestMemory::Write(address, tiled->Bytes(), layout.Alignment()); +} + +VkBuffer GpuColorTransfer::LinearBuffer() const { + Require(linear != nullptr, "color transfer is not prepared"); + return linear->Handle(); +} + +RenderTarget& GpuColorTransfer::Target(const ColorTarget& color, bool blending) { + if (!target || targetExtent.width != color.extent.width || targetExtent.height != color.extent.height || targetFormat != color.format || targetBlending != blending) { + auto replacement = std::make_unique(context, color, blending); + target = std::move(replacement); + targetExtent = color.extent; + targetFormat = color.format; + targetBlending = blending; + } + return *target; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index cfe75e1d0..09fa6380a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -8,22 +8,6 @@ namespace AgcDriver::Graphics { GuestBufferMemory::GuestBufferMemory(const Context& context) : context(context) {} -void GuestBufferMemory::AcquireRegistered() { - Require(!uploaded && regions.empty() && lease.empty(), "guest allocation lease must precede resource registration"); - lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); - for (const auto& range : lease) { - if (range->writable) { - validate(range->address, range->bytes); - regions.push_back({range->address, range->address + range->bytes, true, {}, nullptr}); - } - else { - std::vector bytes(range->bytes); - GuestMemory::Read(range->address, bytes); - AddSnapshot({range->address, bytes}); - } - } -} - void GuestBufferMemory::validate(std::uint64_t address, std::size_t bytes) const { Require(!uploaded, "guest memory ownership is frozen for GPU execution"); Require(address != 0 && bytes != 0, "empty guest memory range"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index e229b7567..3d55bc878 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -126,7 +126,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const RenderTarge pipelineInfo.pColorBlendState = &blend; pipelineInfo.layout = layout; pipelineInfo.renderPass = renderPass; - Check(context.Function("vkCreateGraphicsPipelines")(context.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); + Check(context.Function("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines"); } catch (...) { release(); throw; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/RegisteredGpuMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/RegisteredGpuMemory.cpp new file mode 100644 index 000000000..058dc9229 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/RegisteredGpuMemory.cpp @@ -0,0 +1,21 @@ +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" + +namespace AgcDriver::Graphics { + +void GuestBufferMemory::AcquireRegistered() { + Require(!uploaded && regions.empty() && lease.empty(), "guest allocation lease must precede resource registration"); + lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + regions.reserve(lease.size()); + for (const auto& range : lease) { + validate(range->address, range->bytes); + std::vector snapshot; + if (!range->writable) { + snapshot.resize(range->bytes); + GuestMemory::Read(range->address, snapshot); + } + regions.push_back({range->address, range->address + range->bytes, range->writable, std::move(snapshot), nullptr}); + } +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index 99d71b95b..d9e41b972 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -1,13 +1,23 @@ #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include namespace AgcDriver::Graphics { -Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage) : context(context), size(size) { +Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage) : context(context), size(size), usage(usage) { Require(size != 0, "zero-sized GPU buffer"); const bool addressable = (usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0; Require(!addressable || context.bufferDeviceAddress, "buffer device address is not enabled"); + cache = GetBufferPool(context); + if (const auto allocation = cache->Take(size, usage)) { + buffer = allocation->buffer; + memory = allocation->memory; + mapping = allocation->mapping; + deviceAddress = allocation->address; + allocationBytes = allocation->allocationBytes; + return; + } try { VkBufferCreateInfo info{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; info.size = size; @@ -20,6 +30,7 @@ Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usag const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, nullptr, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0}; if (addressable) allocation.pNext = &flags; allocation.allocationSize = requirements.size; + allocationBytes = requirements.size; allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); Check(context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory buffer"); Check(context.Function("vkBindBufferMemory")(context.device, buffer, memory, 0), "vkBindBufferMemory"); @@ -36,6 +47,10 @@ Buffer::~Buffer() { } void Buffer::release() noexcept { + if (mapping && buffer && memory && cache) { + cache->Put({buffer, memory, mapping, deviceAddress, allocationBytes, size, usage}); + return; + } if (mapping) context.Function("vkUnmapMemory")(context.device, memory); if (buffer) context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); if (memory) context.Function("vkFreeMemory")(context.device, memory, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index e74b16e5b..3acc1116d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/General.hpp" #include "Optimization/include/Optimization/ShaderStageInputInfo.hpp" @@ -209,13 +210,14 @@ void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding& Require(elementWords == 8, "guest texture descriptor must contain 8 dwords"); Require(binding.imageShape.has_value(), "guest image binding is missing an image shape"); Require(context.detiler != nullptr, "device texture detiler is unavailable"); + Require(context.textureCache != nullptr, "device texture cache is unavailable"); Require(binding.count <= context.limits.maxPerStageDescriptorSampledImages, "shader sampled-image descriptors exceed per-stage limits"); for (std::uint32_t element = 0; element < binding.count; ++element) { const auto words = std::span(binding.guestDescriptor).subspan(static_cast(element) * elementWords, elementWords); const auto resource = DecodeTextureResource(words); Require(MatchesGuestDimension(*binding.imageShape, resource.dimension), "guest texture dimension disagrees with the shader's declared image shape"); const VkComponentMapping components{ComponentSwizzleFor(resource.dstSelX), ComponentSwizzleFor(resource.dstSelY), ComponentSwizzleFor(resource.dstSelZ), ComponentSwizzleFor(resource.dstSelW)}; - textures.push_back(std::make_unique(context, *context.detiler, resource, components)); + textures.push_back(context.textureCache->Get(words, resource, components)); item.imageAllocations.push_back(textures.size() - 1); } Require(textures.size() <= context.limits.maxDescriptorSetSampledImages, "pipeline sampled-image descriptors exceed device limits"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index ea09b3e10..b5979394f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -5,6 +5,7 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include #include +#include #include #include @@ -45,7 +46,7 @@ std::uint64_t SliceLinearBytes(const std::vector& mips) { } -Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components) : context(context) { +Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span snapshot) : context(context) { try { const auto vkFormat = ResolveTextureFormat(descriptor.format); if (IsBlockCompressed(descriptor.format)) { @@ -58,7 +59,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex const auto guestBytes = ComputeSurfaceSize(mips, arrayLayers); const auto guestSliceBytes = guestBytes / arrayLayers; - GuestMemory::CheckRange(reinterpret_cast(descriptor.baseAddress), static_cast(guestBytes), 1); + Require(snapshot.size() == guestBytes, "texture snapshot size mismatch"); const auto sliceLinearBytes = SliceLinearBytes(mips); Require(arrayLayers == 0 || sliceLinearBytes <= UINT64_MAX / arrayLayers, "detiled texture buffer size overflows"); @@ -82,15 +83,17 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex context.Function("vkGetImageMemoryRequirements")(context.device, image, &requirements); VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; allocation.allocationSize = requirements.size; + allocationBytes = requirements.size; allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); Check(context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory texture"); Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory"); { Buffer staging(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - GuestMemory::Read(descriptor.baseAddress, staging.Bytes(), 1); + std::memcpy(staging.Bytes().data(), snapshot.data(), snapshot.size()); Buffer linear(context, static_cast(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + detiler.BeginBatch(); CommandBatch batch(context); const auto commands = batch.Handle(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp new file mode 100644 index 000000000..f09740b05 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp @@ -0,0 +1,56 @@ +#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include +#include +#include + +namespace AgcDriver::Graphics { + +TextureCache::TextureCache(const Context& context) : context(context) { + Require(context.detiler != nullptr, "texture cache requires a device detiler"); +} + +void TextureCache::trim() { + for (auto it = entries.begin(); it != entries.end() && (retainedBytes > budget || entries.size() > 64);) { + if (it->texture.use_count() != 1) { + ++it; + continue; + } + retainedBytes -= it->snapshot.size() + it->texture->AllocationBytes(); + it = entries.erase(it); + } +} + +std::shared_ptr TextureCache::Get(std::span words, const GuestTextureResource& resource, VkComponentMapping components) { + Require(words.size() == 8, "texture cache descriptor must contain eight DWORDs"); + trim(); + std::array key; + std::copy(words.begin(), words.end(), key.begin()); + for (auto it = entries.begin(); it != entries.end(); ++it) { + if (it->descriptor != key) continue; + GuestMemory::CheckRange(reinterpret_cast(resource.baseAddress), it->snapshot.size(), 1); + if (std::memcmp(reinterpret_cast(resource.baseAddress), it->snapshot.data(), it->snapshot.size()) == 0) { + auto result = it->texture; + entries.splice(entries.end(), entries, it); + return result; + } + retainedBytes -= it->snapshot.size() + it->texture->AllocationBytes(); + entries.erase(it); + break; + } + const auto mips = ComputeMipLayout(resource.tileMode, resource.format, resource.width, resource.height, resource.mipCount); + const auto layers = resource.dimension == TextureDimension::k2DArray || resource.dimension == TextureDimension::kCube ? resource.depthOrLastArray + 1u : 1u; + const auto bytes = ComputeSurfaceSize(mips, layers); + Require(bytes != 0 && bytes <= std::numeric_limits::max(), "texture cache surface size overflow"); + std::vector snapshot(static_cast(bytes)); + GuestMemory::Read(resource.baseAddress, snapshot, 1); + auto texture = std::make_shared(context, *context.detiler, resource, components, snapshot); + const auto retained = snapshot.size() + texture->AllocationBytes(); + entries.push_back({key, std::move(snapshot), texture}); + retainedBytes += retained; + trim(); + return texture; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp index 82f042ac0..98ef34965 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp @@ -78,7 +78,7 @@ TextureDetiler::~TextureDetiler() { } void TextureDetiler::release() noexcept { - if (descriptorPool) context.Function("vkDestroyDescriptorPool")(context.device, descriptorPool, nullptr); + for (const auto pool : descriptorPools) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); for (const auto& entry : pipelines) context.Function("vkDestroyPipeline")(context.device, entry.second, nullptr); if (module) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); if (pipelineLayout) context.Function("vkDestroyPipelineLayout")(context.device, pipelineLayout, nullptr); @@ -107,7 +107,7 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem createInfo.stage = stage; createInfo.layout = pipelineLayout; VkPipeline result = VK_NULL_HANDLE; - Check(context.Function("vkCreateComputePipelines")(context.device, VK_NULL_HANDLE, 1, &createInfo, nullptr, &result), "vkCreateComputePipelines"); + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &createInfo, nullptr, &result), "vkCreateComputePipelines"); pipelines.emplace_back(key, result); return result; } @@ -127,24 +127,7 @@ void TextureDetiler::Dispatch(VkCommandBuffer commands, TextureTileMode tileMode const auto sourceRange = (sourceBase + layout.tiledSize + 3) / 4 * 4; const auto destinationRange = (destinationBase + layout.linearSize + 3) / 4 * 4; Require(sourceRange <= context.limits.maxStorageBufferRange && destinationRange <= context.limits.maxStorageBufferRange, "texture detiling buffer range exceeds device limits"); - if (descriptorPool) { - context.Function("vkDestroyDescriptorPool")(context.device, descriptorPool, nullptr); - descriptorPool = VK_NULL_HANDLE; - } - VkDescriptorPoolSize poolSize{}; - poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - poolSize.descriptorCount = 2; - VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; - poolInfo.maxSets = 1; - poolInfo.poolSizeCount = 1; - poolInfo.pPoolSizes = &poolSize; - Check(context.Function("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &descriptorPool), "vkCreateDescriptorPool"); - VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; - allocation.descriptorPool = descriptorPool; - allocation.descriptorSetCount = 1; - allocation.pSetLayouts = &descriptorLayout; - VkDescriptorSet set = VK_NULL_HANDLE; - Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &set), "vkAllocateDescriptorSets"); + const auto set = allocateSet(); const VkDescriptorBufferInfo sourceInfo{source, sourceDescriptorOffset, sourceRange}; const VkDescriptorBufferInfo destinationInfo{destination, destinationDescriptorOffset, destinationRange}; std::array writes{}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp new file mode 100644 index 000000000..e9da522f7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp @@ -0,0 +1,36 @@ +#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" + +namespace AgcDriver::Graphics { + +void TextureDetiler::BeginBatch() { + for (const auto pool : descriptorPools) { + Check(context.Function("vkResetDescriptorPool")(context.device, pool, 0), "vkResetDescriptorPool texture detiler"); + } + allocatedSets = 0; +} + +VkDescriptorSet TextureDetiler::allocateSet() { + constexpr std::uint32_t setsPerPool = 64; + const auto poolIndex = allocatedSets / setsPerPool; + if (poolIndex == descriptorPools.size()) { + const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, setsPerPool * 2}; + VkDescriptorPoolCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + info.maxSets = setsPerPool; + info.poolSizeCount = 1; + info.pPoolSizes = &size; + descriptorPools.reserve(descriptorPools.size() + 1); + VkDescriptorPool pool = VK_NULL_HANDLE; + Check(context.Function("vkCreateDescriptorPool")(context.device, &info, nullptr, &pool), "vkCreateDescriptorPool texture detiler"); + descriptorPools.push_back(pool); + } + VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocation.descriptorPool = descriptorPools.at(poolIndex); + allocation.descriptorSetCount = 1; + allocation.pSetLayouts = &descriptorLayout; + VkDescriptorSet set = VK_NULL_HANDLE; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &set), "vkAllocateDescriptorSets texture detiler"); + ++allocatedSets; + return set; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/PostCompile.cmake b/core/libs/prx/libSceAgcDriver/PostCompile.cmake new file mode 100644 index 000000000..85dcca167 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/PostCompile.cmake @@ -0,0 +1,36 @@ +set(agcColorTransferSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/ColorTransfer.comp) +set(agcColorTransferSpv ${agcTextureDetileGeneratedDir}/ColorTransfer.spv) +set(agcColorTransferHeader ${agcTextureDetileGeneratedDir}/ColorTransfer_spv.h) +add_custom_command( + OUTPUT "${agcColorTransferHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -o "${agcColorTransferSpv}" "${agcColorTransferSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcColorTransferSpv} -DOUTPUT=${agcColorTransferHeader} -DSYMBOL=COLOR_TRANSFER_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcColorTransferSource}" glslang-standalone + VERBATIM +) + +foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests agc_driver_bda_device_tests) + if(TARGET ${agcTarget}) + target_sources(${agcTarget} PRIVATE Graphics/src/BufferPool.cpp) + endif() +endforeach() + +foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests) + if(TARGET ${agcTarget}) + target_sources(${agcTarget} PRIVATE Graphics/src/TextureDetilerDescriptors.cpp Graphics/src/TextureCache.cpp Graphics/src/RegisteredGpuMemory.cpp) + endif() +endforeach() + +foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_bda_device_tests) + if(TARGET ${agcTarget}) + target_sources(${agcTarget} PRIVATE Graphics/src/GpuColorTransfer.cpp ${agcColorTransferHeader}) + endif() +endforeach() + +target_sources(agc_driver_bda_device_tests PRIVATE tests/ColorTransferTests.cpp Graphics/src/ColorTargetLayout.cpp Execution/src/GuestMemory.cpp) +target_include_directories(agc_driver_bda_device_tests PRIVATE ${agcTextureDetileGeneratedRoot}) + +if(TARGET agc_driver_visual_test) + target_sources(agc_driver_visual_test PRIVATE Execution/src/DisplayBuffer.cpp) +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp index 40540c11f..1a578bb94 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp @@ -1,4 +1,5 @@ #include "BdaShader.hpp" +#include "ColorTransferTests.hpp" #include #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" @@ -66,6 +67,9 @@ public: Check(function("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice"); context.deviceProc = function("vkGetDeviceProcAddr"); function("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory); + VkPhysicalDeviceProperties properties{}; + function("vkGetPhysicalDeviceProperties")(context.physical, &properties); + context.limits = properties.limits; context.bufferDeviceAddress = true; context.Function("vkGetDeviceQueue")(context.device, family, 0, &context.queue); VkCommandPoolCreateInfo pool{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; @@ -90,6 +94,7 @@ private: void release() noexcept { if (context.pool != VK_NULL_HANDLE) context.Function("vkDestroyCommandPool")(context.device, context.pool, nullptr); + context.bufferPool.reset(); if (context.device != VK_NULL_HANDLE) function("vkDestroyDevice")(context.device, nullptr); if (instance != VK_NULL_HANDLE) function("vkDestroyInstance")(instance, nullptr); if (library != nullptr) SDL_UnloadObject(library); @@ -119,6 +124,7 @@ int main(int argc, char** argv) { buffer.Bytes()[255] = std::byte{0x5a}; Require(buffer.Bytes()[255] == std::byte{0x5a}, "real BDA buffer mapping failed"); RunBdaExecutionTests(device.GetContext()); + RunColorTransferTests(device.GetContext()); std::cout << "Vulkan BDA allocation and execution tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/ColorTransferTests.cpp b/core/libs/prx/libSceAgcDriver/tests/ColorTransferTests.cpp new file mode 100644 index 000000000..e756c1cf5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/ColorTransferTests.cpp @@ -0,0 +1,78 @@ +#include "ColorTransferTests.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp" +#include +#include +#include +#include +#include + +namespace { + +using namespace AgcDriver::Graphics; + +void checkConversion(const Context& context, std::uint32_t width, std::uint32_t height, ColorTileMode mode, bool swap) { + static_assert(std::endian::native == std::endian::little); + const ColorTargetLayout layout(width, height, mode); + std::vector storage(layout.Bytes() + layout.Alignment()); + void* aligned = storage.data(); + auto available = storage.size(); + Require(std::align(layout.Alignment(), layout.Bytes(), aligned, available) != nullptr, "test surface alignment failed"); + const auto address = reinterpret_cast(aligned); + std::span guest(static_cast(aligned), layout.Bytes()); + std::fill(guest.begin(), guest.end(), std::byte{0xa5}); + std::vector source(layout.LinearBytes()); + for (std::size_t i = 0; i < source.size(); ++i) source[i] = static_cast((i * 73u + i / 257u) & 255u); + layout.Tile(source, guest); + GpuColorTransfer transfer(context); + transfer.Upload(address, width, height, mode); + Buffer readback(context, source.size(), VK_BUFFER_USAGE_TRANSFER_DST_BIT); + { + CommandBatch batch(context); + const auto commands = batch.Handle(); + transfer.Detile(commands, swap); + const VkBufferCopy copy{0, 0, source.size()}; + context.Function("vkCmdCopyBuffer")(commands, transfer.LinearBuffer(), readback.Handle(), 1, ©); + VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT; + context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr); + context.Function("vkCmdFillBuffer")(commands, transfer.LinearBuffer(), 0, source.size(), 0x12345678u); + transfer.Tile(commands); + batch.SubmitAndWait(); + } + auto expectedLinear = source; + if (swap) { + for (std::size_t i = 0; i < expectedLinear.size(); i += 4) std::swap(expectedLinear[i], expectedLinear[i + 2]); + } + Require(std::equal(expectedLinear.begin(), expectedLinear.end(), readback.Bytes().begin()), "GPU detile differs from CPU reference"); + std::vector expectedTiled(layout.Bytes(), std::byte{0xa5}); + const std::uint32_t fill = 0x12345678u; + for (std::size_t i = 0; i < source.size(); i += sizeof(fill)) std::memcpy(source.data() + i, &fill, sizeof(fill)); + layout.Tile(source, expectedTiled); + transfer.WriteBack(address); + Require(std::equal(expectedTiled.begin(), expectedTiled.end(), guest.begin()), "GPU tile changed pixels or surface padding"); +} + +void checkBufferReuse(const Context& context) { + constexpr auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + VkBuffer handle = VK_NULL_HANDLE; + VkDeviceAddress address = 0; + { + Buffer original(context, 12348, usage); + handle = original.Handle(); + address = original.DeviceAddress(); + } + Buffer reused(context, 12348, usage); + Require(reused.Handle() == handle && reused.DeviceAddress() == address, "buffer cache did not reuse a completed allocation"); + Buffer simultaneous(context, 12348, usage); + Require(simultaneous.Handle() != reused.Handle(), "buffer cache reused an active allocation"); +} + +} + +void RunColorTransferTests(const AgcDriver::Graphics::Context& context) { + checkBufferReuse(context); + checkConversion(context, 130, 129, AgcDriver::Graphics::ColorTileMode::RenderTarget, false); + checkConversion(context, 257, 17, AgcDriver::Graphics::ColorTileMode::RenderTarget, true); + checkConversion(context, 192, 13, AgcDriver::Graphics::ColorTileMode::Linear, false); +} diff --git a/core/libs/prx/libSceAgcDriver/tests/ColorTransferTests.hpp b/core/libs/prx/libSceAgcDriver/tests/ColorTransferTests.hpp new file mode 100644 index 000000000..684c52535 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/ColorTransferTests.hpp @@ -0,0 +1,8 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_COLORTRANSFERTESTS_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_COLORTRANSFERTESTS_HPP + +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" + +void RunColorTransferTests(const AgcDriver::Graphics::Context& context); + +#endif