perf(agc): move color transfers to + gpu resources cache

This commit is contained in:
boykopovar 2026-09-23 22:32:42 +03:00
parent 630d918b4a
commit 7ed9ef1e9b
31 changed files with 744 additions and 76 deletions

View File

@ -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

View File

@ -359,3 +359,5 @@ if(AGC_BUILD_VISUAL_TEST)
add_dependencies(agc_driver_visual_test agc_driver_visual_runtime)
endif()
endif()
include(PostCompile.cmake)

View File

@ -5,6 +5,7 @@
#include <vulkan/vulkan.h>
#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 <memory>
@ -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<const std::byte> 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<const Graphics::GuestMemorySnapshot> snapshots = {});
void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
private:
Graphics::Context graphicsContext() const;
void present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels);
void present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display = nullptr);
struct State;
std::unique_ptr<State> state;
};

View File

@ -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");

View File

@ -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 <SDL_loadso.h>
#include <SDL_error.h>
@ -72,6 +76,10 @@ struct VulkanDevice::State {
bool samplerAnisotropy = false;
bool textureCompressionBC = false;
std::unique_ptr<Graphics::TextureDetiler> detiler;
std::unique_ptr<Graphics::GpuColorTransfer> colorTransfer;
std::shared_ptr<Graphics::BufferPool> bufferPool;
std::unique_ptr<Graphics::TextureCache> textureCache;
std::unique_ptr<Graphics::PipelineCache> pipelineCache;
VkPhysicalDeviceMeshShaderPropertiesEXT meshLimits{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT};
std::unique_ptr<PresentationScaler> scaler;
@ -151,8 +159,12 @@ struct VulkanDevice::State {
if (device != VK_NULL_HANDLE) {
const auto idle = reinterpret_cast<PFN_vkDeviceWaitIdle>(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<PFN_vkDestroyFence>(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<PFN_vkCreateCommandPool>("vkCreateCommandPool")(state->device, &poolInfo, nullptr, &state->pool), "vkCreateCommandPool");
state->bufferPool = std::make_shared<Graphics::BufferPool>(graphicsContext());
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext());
state->detiler = std::make_unique<Graphics::TextureDetiler>(graphicsContext());
state->textureCache = std::make_unique<Graphics::TextureCache>(graphicsContext());
state->colorTransfer = std::make_unique<Graphics::GpuColorTransfer>(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<const std::byte> 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<const std::byte> 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<unsigned>(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<void>(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<PFN_vkCmdPipelineBarrier>("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<unsigned>(opaque), reinterpret_cast<void*>(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<void*>(state->uploadBuffer), reinterpret_cast<void*>(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<PFN_vkCmdClearColorImage>("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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(state->device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines");
check(state->DeviceFunction<PFN_vkCreateComputePipelines>("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;

View File

@ -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 <array>
#include <memory>
#include <mutex>
#include <optional>
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<BufferAllocation> 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<std::optional<BufferAllocation>, 64> free;
VkDeviceSize retainedBytes = 0;
std::size_t cursor = 0;
static constexpr VkDeviceSize budget = 512ull * 1024 * 1024;
};
std::shared_ptr<BufferPool> GetBufferPool(const Context& context);
}
#endif

View File

@ -6,19 +6,27 @@
#endif
#include <vulkan/vulkan.h>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <string>
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> bufferPool;
TextureCache* textureCache = nullptr;
VkPipelineCache pipelineCache = VK_NULL_HANDLE;
template<typename TFunction>
TFunction Function(const char* name) const {
Require(deviceProc != nullptr, "missing Vulkan device function resolver");
const auto function = reinterpret_cast<TFunction>(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;
}

View File

@ -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 <memory>
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<Buffer> tiled;
std::unique_ptr<Buffer> linear;
std::uint32_t width = 0;
std::uint32_t height = 0;
ColorTileMode mode = ColorTileMode::Linear;
std::unique_ptr<RenderTarget> target;
VkExtent2D targetExtent{};
VkFormat targetFormat = VK_FORMAT_UNDEFINED;
bool targetBlending = false;
};
}
#endif

View File

@ -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<PFN_vkCreatePipelineCache>("vkCreatePipelineCache")(context.device, &info, nullptr, &cache), "vkCreatePipelineCache");
}
~PipelineCache() {
context.Function<PFN_vkDestroyPipelineCache>("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

View File

@ -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<BufferPool> 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;

View File

@ -55,7 +55,7 @@ private:
VkDescriptorSet _set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
std::vector<Allocation> allocations;
std::vector<std::unique_ptr<Texture>> textures;
std::vector<std::shared_ptr<Texture>> textures;
std::vector<std::unique_ptr<Sampler>> samplers;
};

View File

@ -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<const std::byte> 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;
};
}

View File

@ -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 <array>
#include <list>
#include <memory>
#include <vector>
namespace AgcDriver::Graphics {
class TextureCache {
public:
explicit TextureCache(const Context& context);
std::shared_ptr<Texture> Get(std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components);
private:
struct Entry {
std::array<std::uint32_t, 8> descriptor;
std::vector<std::byte> snapshot;
std::shared_ptr<Texture> texture;
};
void trim();
Context context;
std::list<Entry> entries;
std::uint64_t retainedBytes = 0;
static constexpr std::uint64_t budget = 256ull * 1024 * 1024;
};
}
#endif

View File

@ -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<std::pair<std::uint32_t, VkPipeline>> pipelines;
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
std::vector<VkDescriptorPool> descriptorPools;
std::size_t allocatedSets = 0;
};
}

View File

@ -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;
}

View File

@ -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<PFN_vkUnmapMemory>("vkUnmapMemory")), destroyBuffer(context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")), freeMemory(context.Function<PFN_vkFreeMemory>("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<BufferAllocation> 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<BufferPool> GetBufferPool(const Context& context) {
if (!context.bufferPool) context.bufferPool = std::make_shared<BufferPool>(context);
return context.bufferPool;
}
}

View File

@ -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<Buffer> transfer;
std::unique_ptr<RenderTarget> target;
RenderTarget* target = nullptr;
if (state.hasColorTarget) {
APS5_LOG_OUT_DEBUG("Creating color target address=0x%llx bytes=%llu extent=%ux%u", static_cast<unsigned long long>(state.color.address), static_cast<unsigned long long>(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<Buffer>(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<RenderTarget>(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<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, transfer->Handle(), target->Image(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
context.Function<PFN_vkCmdCopyBufferToImage>("vkCmdCopyBufferToImage")(commands, context.colorTransfer->LinearBuffer(), target->Image(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
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<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &reuse, 0, nullptr);
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, target->Image(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, transfer->Handle(), 1, &copy);
context.Function<PFN_vkCmdCopyImageToBuffer>("vkCmdCopyImageToBuffer")(commands, target->Image(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, context.colorTransfer->LinearBuffer(), 1, &copy);
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<unsigned long long>(state.color.address), static_cast<unsigned long long>(state.color.bytes));
APS5_LOG_CHARS_OUT("Draw finished");
}

View File

@ -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 <array>
namespace AgcDriver::Graphics {
GpuColorTransfer::GpuColorTransfer(const Context& context) : context(context) {
VkShaderModule module = VK_NULL_HANDLE;
try {
std::array<VkDescriptorSetLayoutBinding, 2> 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<std::uint32_t>(bindings.size());
descriptorInfo.pBindings = bindings.data();
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("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<PFN_vkCreatePipelineLayout>("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<PFN_vkCreateShaderModule>("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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateComputePipelines color transfer");
context.Function<PFN_vkDestroyShaderModule>("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<PFN_vkCreateDescriptorPool>("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<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &descriptorSet), "vkAllocateDescriptorSets color transfer");
} catch (...) {
if (module) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, module, nullptr);
release();
throw;
}
}
GpuColorTransfer::~GpuColorTransfer() {
release();
}
void GpuColorTransfer::release() noexcept {
if (descriptorPool) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, descriptorPool, nullptr);
if (pipeline) context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline")(context.device, pipeline, nullptr);
if (pipelineLayout) context.Function<PFN_vkDestroyPipelineLayout>("vkDestroyPipelineLayout")(context.device, pipelineLayout, nullptr);
if (descriptorLayout) context.Function<PFN_vkDestroyDescriptorSetLayout>("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<Buffer>(context, layout.Bytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
auto newLinear = std::make_unique<Buffer>(context, layout.LinearBytes(), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
const std::array<VkDescriptorBufferInfo, 2> buffers{{{newTiled->Handle(), 0, layout.Bytes()}, {newLinear->Handle(), 0, layout.LinearBytes()}}};
std::array<VkWriteDescriptorSet, 2> 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<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(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<PFN_vkCmdPipelineBarrier>("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<std::uint32_t, 4> push{width, height, (width + 127u) / 128u, (toTiled ? 1u : 0u) | (swapRedBlue ? 2u : 0u) | (mode == ColorTileMode::RenderTarget ? 4u : 0u)};
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
context.Function<PFN_vkCmdDispatch>("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<RenderTarget>(context, color, blending);
target = std::move(replacement);
targetExtent = color.extent;
targetFormat = color.format;
targetBlending = blending;
}
return *target;
}
}

View File

@ -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<std::byte> 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");

View File

@ -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<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines");
Check(context.Function<PFN_vkCreateGraphicsPipelines>("vkCreateGraphicsPipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &pipeline), "vkCreateGraphicsPipelines");
} catch (...) {
release();
throw;

View File

@ -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<std::byte> 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});
}
}
}

View File

@ -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 <exception>
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<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory buffer");
Check(context.Function<PFN_vkBindBufferMemory>("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<PFN_vkUnmapMemory>("vkUnmapMemory")(context.device, memory);
if (buffer) context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, buffer, nullptr);
if (memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, memory, nullptr);

View File

@ -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<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(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<Texture>(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");

View File

@ -5,6 +5,7 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <vector>
@ -45,7 +46,7 @@ std::uint64_t SliceLinearBytes(const std::vector<TileMipLayout>& 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<const std::byte> 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<const void*>(descriptor.baseAddress), static_cast<std::size_t>(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<PFN_vkGetImageMemoryRequirements>("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<PFN_vkAllocateMemory>("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory texture");
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory");
{
Buffer staging(context, static_cast<std::size_t>(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<std::size_t>(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
detiler.BeginBatch();
CommandBatch batch(context);
const auto commands = batch.Handle();

View File

@ -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 <algorithm>
#include <cstring>
#include <limits>
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<Texture> TextureCache::Get(std::span<const std::uint32_t> words, const GuestTextureResource& resource, VkComponentMapping components) {
Require(words.size() == 8, "texture cache descriptor must contain eight DWORDs");
trim();
std::array<std::uint32_t, 8> 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<const void*>(resource.baseAddress), it->snapshot.size(), 1);
if (std::memcmp(reinterpret_cast<const void*>(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<std::size_t>::max(), "texture cache surface size overflow");
std::vector<std::byte> snapshot(static_cast<std::size_t>(bytes));
GuestMemory::Read(resource.baseAddress, snapshot, 1);
auto texture = std::make_shared<Texture>(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;
}
}

View File

@ -78,7 +78,7 @@ TextureDetiler::~TextureDetiler() {
}
void TextureDetiler::release() noexcept {
if (descriptorPool) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, descriptorPool, nullptr);
for (const auto pool : descriptorPools) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
for (const auto& entry : pipelines) context.Function<PFN_vkDestroyPipeline>("vkDestroyPipeline")(context.device, entry.second, nullptr);
if (module) context.Function<PFN_vkDestroyShaderModule>("vkDestroyShaderModule")(context.device, module, nullptr);
if (pipelineLayout) context.Function<PFN_vkDestroyPipelineLayout>("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<PFN_vkCreateComputePipelines>("vkCreateComputePipelines")(context.device, VK_NULL_HANDLE, 1, &createInfo, nullptr, &result), "vkCreateComputePipelines");
Check(context.Function<PFN_vkCreateComputePipelines>("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<PFN_vkDestroyDescriptorPool>("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<PFN_vkCreateDescriptorPool>("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<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &set), "vkAllocateDescriptorSets");
const auto set = allocateSet();
const VkDescriptorBufferInfo sourceInfo{source, sourceDescriptorOffset, sourceRange};
const VkDescriptorBufferInfo destinationInfo{destination, destinationDescriptorOffset, destinationRange};
std::array<VkWriteDescriptorSet, 2> writes{};

View File

@ -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<PFN_vkResetDescriptorPool>("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<PFN_vkCreateDescriptorPool>("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<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &set), "vkAllocateDescriptorSets texture detiler");
++allocatedSets;
return set;
}
}

View File

@ -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 "$<TARGET_FILE:glslang-standalone>" -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()

View File

@ -1,4 +1,5 @@
#include "BdaShader.hpp"
#include "ColorTransferTests.hpp"
#include <fstream>
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
@ -66,6 +67,9 @@ public:
Check(function<PFN_vkCreateDevice>("vkCreateDevice")(context.physical, &device, nullptr, &context.device), "vkCreateDevice");
context.deviceProc = function<PFN_vkGetDeviceProcAddr>("vkGetDeviceProcAddr");
function<PFN_vkGetPhysicalDeviceMemoryProperties>("vkGetPhysicalDeviceMemoryProperties")(context.physical, &context.memory);
VkPhysicalDeviceProperties properties{};
function<PFN_vkGetPhysicalDeviceProperties>("vkGetPhysicalDeviceProperties")(context.physical, &properties);
context.limits = properties.limits;
context.bufferDeviceAddress = true;
context.Function<PFN_vkGetDeviceQueue>("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<PFN_vkDestroyCommandPool>("vkDestroyCommandPool")(context.device, context.pool, nullptr);
context.bufferPool.reset();
if (context.device != VK_NULL_HANDLE) function<PFN_vkDestroyDevice>("vkDestroyDevice")(context.device, nullptr);
if (instance != VK_NULL_HANDLE) function<PFN_vkDestroyInstance>("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) {

View File

@ -0,0 +1,78 @@
#include "ColorTransferTests.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
#include <algorithm>
#include <bit>
#include <cstring>
#include <memory>
#include <vector>
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<std::byte> 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<std::uintptr_t>(aligned);
std::span<std::byte> guest(static_cast<std::byte*>(aligned), layout.Bytes());
std::fill(guest.begin(), guest.end(), std::byte{0xa5});
std::vector<std::byte> source(layout.LinearBytes());
for (std::size_t i = 0; i < source.size(); ++i) source[i] = static_cast<std::byte>((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<PFN_vkCmdCopyBuffer>("vkCmdCopyBuffer")(commands, transfer.LinearBuffer(), readback.Handle(), 1, &copy);
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<PFN_vkCmdPipelineBarrier>("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<PFN_vkCmdFillBuffer>("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<std::byte> 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);
}

View File

@ -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