perf(agc): move color transfers to + gpu resources cache
This commit is contained in:
parent
630d918b4a
commit
7ed9ef1e9b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -359,3 +359,5 @@ if(AGC_BUILD_VISUAL_TEST)
|
|||
add_dependencies(agc_driver_visual_test agc_driver_visual_runtime)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(PostCompile.cmake)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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, ©);
|
||||
context.Function<PFN_vkCmdCopyBufferToImage>("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<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, ©);
|
||||
context.Function<PFN_vkCmdCopyImageToBuffer>("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<unsigned long long>(state.color.address), static_cast<unsigned long long>(state.color.bytes));
|
||||
APS5_LOG_CHARS_OUT("Draw finished");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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, ©);
|
||||
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);
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue