Merge pull request #142 from Wunkolo/vulkan-framebuffer

[Vulkan] Implement framebuffer management
This commit is contained in:
wheremyfoodat 2023-08-27 13:41:43 +03:00 committed by GitHub
commit 80cdf0354f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1986 additions and 198 deletions

View file

@ -1,5 +1,12 @@
#include <map>
#include <optional>
#include "math_util.hpp"
#include "renderer.hpp"
#include "vulkan_api.hpp"
#include "vk_api.hpp"
#include "vk_descriptor_heap.hpp"
#include "vk_descriptor_update_batch.hpp"
#include "vk_sampler_cache.hpp"
class GPU;
@ -10,7 +17,7 @@ class RendererVK final : public Renderer {
vk::UniqueInstance instance = {};
vk::UniqueDebugUtilsMessengerEXT debugMessenger = {};
vk::UniqueSurfaceKHR surface = {};
vk::SurfaceKHR swapchainSurface = {};
vk::PhysicalDevice physicalDevice = {};
@ -32,17 +39,74 @@ class RendererVK final : public Renderer {
std::vector<vk::Image> swapchainImages = {};
std::vector<vk::UniqueImageView> swapchainImageViews = {};
// Per-swapchain-image data
// Each vector is `swapchainImageCount` in size
std::vector<vk::UniqueCommandBuffer> presentCommandBuffers = {};
// This value is the degree of parallelism to allow multiple frames to be in-flight
// aka: "double-buffer"/"triple-buffering"
// Todo: make this a configuration option
static constexpr usize frameBufferingCount = 3;
// Frame-buffering data
// Each vector is `frameBufferingCount` in size
std::vector<vk::UniqueSemaphore> swapImageFreeSemaphore = {};
std::vector<vk::UniqueSemaphore> renderFinishedSemaphore = {};
std::vector<vk::UniqueFence> frameFinishedFences = {};
std::vector<std::vector<vk::UniqueFramebuffer>> frameFramebuffers = {};
std::vector<vk::UniqueCommandBuffer> frameCommandBuffers = {};
const vk::CommandBuffer& getCurrentCommandBuffer() const { return frameCommandBuffers[frameBufferingIndex].get(); }
// Todo:
// Use `{colourBuffer,depthBuffer}Loc` to maintain an std::map-cache of framebuffers
struct Texture {
u32 loc = 0;
u32 sizePerPixel = 0;
std::array<u32, 2> size = {};
vk::Format format;
vk::UniqueImage image;
vk::UniqueDeviceMemory imageMemory;
vk::UniqueImageView imageView;
Math::Rect<u32> getSubRect(u32 inputAddress, u32 width, u32 height) {
// PICA textures have top-left origin, same as Vulkan
const u32 startOffset = (inputAddress - loc) / sizePerPixel;
const u32 x0 = (startOffset % (size[0] * 8)) / 8;
const u32 y0 = (startOffset / (size[0] * 8)) * 8;
return Math::Rect<u32>{x0, y0, x0 + width, y0 + height};
}
};
// Hash(loc, size, format) -> Texture
std::map<u64, Texture> textureCache;
Texture* findRenderTexture(u32 addr);
Texture& getColorRenderTexture(u32 addr, PICA::ColorFmt format, u32 width, u32 height);
Texture& getDepthRenderTexture(u32 addr, PICA::DepthFmt format, u32 width, u32 height);
// Framebuffer for the top/bottom image
std::vector<vk::UniqueImage> screenTexture = {};
std::vector<vk::UniqueImageView> screenTextureViews = {};
std::vector<vk::UniqueFramebuffer> screenTextureFramebuffers = {};
vk::UniqueDeviceMemory framebufferMemory = {};
std::map<u64, vk::UniqueRenderPass> renderPassCache;
vk::RenderPass getRenderPass(vk::Format colorFormat, std::optional<vk::Format> depthFormat);
vk::RenderPass getRenderPass(PICA::ColorFmt colorFormat, std::optional<PICA::DepthFmt> depthFormat);
std::unique_ptr<Vulkan::DescriptorUpdateBatch> descriptorUpdateBatch;
std::unique_ptr<Vulkan::SamplerCache> samplerCache;
// Display pipeline data
std::unique_ptr<Vulkan::DescriptorHeap> displayDescriptorHeap;
vk::UniquePipeline displayPipeline;
vk::UniquePipelineLayout displayPipelineLayout;
std::vector<vk::DescriptorSet> topDisplayPipelineDescriptorSet;
std::vector<vk::DescriptorSet> bottomDisplayPipelineDescriptorSet;
// Recreate the swapchain, possibly re-using the old one in the case of a resize
vk::Result recreateSwapchain(vk::SurfaceKHR surface, vk::Extent2D swapchainExtent);
u64 currentFrame = 0;
u64 frameBufferingIndex = 0;
public:
RendererVK(GPU& gpu, const std::array<u32, regNum>& internalRegs, const std::array<u32, extRegNum>& externalRegs);
~RendererVK() override;

View file

@ -4,7 +4,7 @@
#include <type_traits>
#include <utility>
#include "vulkan_api.hpp"
#include "vk_api.hpp"
namespace Vulkan {

View file

@ -0,0 +1,49 @@
#pragma once
#include <optional>
#include <span>
#include "helpers.hpp"
#include "vk_api.hpp"
namespace Vulkan {
// Implements a basic heap of descriptor sets given a layout of particular
// bindings. Create a descriptor set by providing a list of bindings and it will
// automatically create both the pool, layout, and maintail a heap of descriptor
// sets. Descriptor sets will be reused and recycled. Assume that newly
// allocated descriptor sets are in an undefined state.
class DescriptorHeap {
private:
const vk::Device device;
vk::UniqueDescriptorPool descriptorPool;
vk::UniqueDescriptorSetLayout descriptorSetLayout;
std::vector<vk::UniqueDescriptorSet> descriptorSets;
std::vector<vk::DescriptorSetLayoutBinding> bindings;
std::vector<bool> allocationMap;
explicit DescriptorHeap(vk::Device device);
public:
~DescriptorHeap() = default;
DescriptorHeap(DescriptorHeap&&) = default;
const vk::DescriptorPool& getDescriptorPool() const { return descriptorPool.get(); };
const vk::DescriptorSetLayout& getDescriptorSetLayout() const { return descriptorSetLayout.get(); };
const std::span<const vk::UniqueDescriptorSet> getDescriptorSets() const { return descriptorSets; };
std::span<const vk::DescriptorSetLayoutBinding> getBindings() const { return bindings; };
std::optional<vk::DescriptorSet> allocateDescriptorSet();
bool freeDescriptorSet(vk::DescriptorSet set);
static std::optional<DescriptorHeap> create(
vk::Device device, std::span<const vk::DescriptorSetLayoutBinding> bindings, u16 descriptorHeapCount = 1024
);
};
} // namespace Vulkan

View file

@ -0,0 +1,62 @@
#pragma once
#include <memory>
#include <optional>
#include <variant>
#include "helpers.hpp"
#include "vk_api.hpp"
namespace Vulkan {
// Implements a re-usable structure for batching up descriptor writes with a
// finite amount of space for both convenience and to reduce the overall amount
// of API calls to `vkUpdateDescriptorSets`
class DescriptorUpdateBatch {
private:
const vk::Device device;
const usize descriptorWriteMax;
const usize descriptorCopyMax;
using DescriptorInfoUnion = std::variant<vk::DescriptorImageInfo, vk::DescriptorBufferInfo, vk::BufferView>;
// Todo: Maybe some kind of hash so that these structures can be re-used
// among descriptor writes.
std::unique_ptr<DescriptorInfoUnion[]> descriptorInfos;
std::unique_ptr<vk::WriteDescriptorSet[]> descriptorWrites;
std::unique_ptr<vk::CopyDescriptorSet[]> descriptorCopies;
usize descriptorWriteEnd = 0;
usize descriptorCopyEnd = 0;
DescriptorUpdateBatch(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax)
: device(device), descriptorWriteMax(descriptorWriteMax), descriptorCopyMax(descriptorCopyMax) {}
public:
~DescriptorUpdateBatch() = default;
DescriptorUpdateBatch(DescriptorUpdateBatch&&) = default;
void flush();
void addImage(
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral
);
void addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler);
void addImageSampler(
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler,
vk::ImageLayout imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal
);
void addBuffer(
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size = VK_WHOLE_SIZE
);
void copyBinding(
vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement = 0,
u8 targetArrayElement = 0, u8 descriptorCount = 1
);
static std::optional<DescriptorUpdateBatch> create(vk::Device device, usize descriptorWriteMax = 256, usize descriptorCopyMax = 256);
};
} // namespace Vulkan

View file

@ -0,0 +1,36 @@
#pragma once
#include <span>
#include <type_traits>
#include <utility>
#include "helpers.hpp"
#include "vk_api.hpp"
namespace Vulkan {
// Will try to find a memory type that is suitable for the given requirements.
// Returns -1 if no suitable memory type was found.
s32 findMemoryTypeIndex(
vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties,
vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected
);
// Given an array of valid Vulkan image-handles or buffer-handles, these
// functions will allocate a single block of device-memory for all of them
// and bind them consecutively.
// There may be a case that all the buffers or images cannot be allocated
// to the same device memory due to their required memory-type.
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitImageHeap(
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Image> images,
vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal,
vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected
);
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitBufferHeap(
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Buffer> buffers,
vk::MemoryPropertyFlags memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal,
vk::MemoryPropertyFlags memoryExcludeProperties = vk::MemoryPropertyFlagBits::eProtected
);
} // namespace Vulkan

View file

@ -0,0 +1,12 @@
#pragma once
#include "PICA/gpu.hpp"
#include "helpers.hpp"
#include "vk_api.hpp"
namespace Vulkan {
vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat);
vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat);
} // namespace Vulkan

View file

@ -0,0 +1,28 @@
#pragma once
#include <optional>
#include <unordered_map>
#include "helpers.hpp"
#include "vk_api.hpp"
namespace Vulkan {
// Implements a simple pool of reusable sampler objects
class SamplerCache {
private:
const vk::Device device;
std::unordered_map<std::size_t, vk::UniqueSampler> samplerMap;
explicit SamplerCache(vk::Device device);
public:
~SamplerCache() = default;
SamplerCache(SamplerCache&&) = default;
const vk::Sampler& getSampler(const vk::SamplerCreateInfo& samplerInfo);
static std::optional<SamplerCache> create(vk::Device device);
};
} // namespace Vulkan