mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-11 08:39:48 +12:00
Merge pull request #142 from Wunkolo/vulkan-framebuffer
[Vulkan] Implement framebuffer management
This commit is contained in:
commit
80cdf0354f
19 changed files with 1986 additions and 198 deletions
|
@ -249,20 +249,48 @@ if(ENABLE_VULKAN)
|
|||
)
|
||||
|
||||
set(RENDERER_VK_INCLUDE_FILES include/renderer_vk/renderer_vk.hpp
|
||||
include/renderer_vk/vulkan_api.hpp include/renderer_vk/vk_debug.hpp
|
||||
include/renderer_vk/vk_api.hpp include/renderer_vk/vk_debug.hpp
|
||||
include/renderer_vk/vk_descriptor_heap.hpp
|
||||
include/renderer_vk/vk_descriptor_update_batch.hpp
|
||||
include/renderer_vk/vk_sampler_cache.hpp
|
||||
include/renderer_vk/vk_memory.hpp include/renderer_vk/vk_pica.hpp
|
||||
)
|
||||
|
||||
set(RENDERER_VK_SOURCE_FILES src/core/renderer_vk/renderer_vk.cpp
|
||||
src/core/renderer_vk/vulkan_api.cpp src/core/renderer_vk/vk_debug.cpp
|
||||
src/core/renderer_vk/vk_api.cpp src/core/renderer_vk/vk_debug.cpp
|
||||
src/core/renderer_vk/vk_descriptor_heap.cpp
|
||||
src/core/renderer_vk/vk_descriptor_update_batch.cpp
|
||||
src/core/renderer_vk/vk_sampler_cache.cpp
|
||||
src/core/renderer_vk/vk_memory.cpp src/core/renderer_vk/vk_pica.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES ${HEADER_FILES} ${RENDERER_VK_INCLUDE_FILES})
|
||||
source_group("Source Files\\Core\\Vulkan Renderer" FILES ${RENDERER_VK_SOURCE_FILES})
|
||||
|
||||
|
||||
set(RENDERER_VK_HOST_SHADERS_SOURCE
|
||||
"src/host_shaders/vulkan_display.frag"
|
||||
"src/host_shaders/vulkan_display.vert"
|
||||
)
|
||||
|
||||
foreach( HOST_SHADER_SOURCE ${RENDERER_VK_HOST_SHADERS_SOURCE} )
|
||||
get_filename_component( FILE_NAME ${HOST_SHADER_SOURCE} NAME )
|
||||
set( HOST_SHADER_SPIRV "${PROJECT_BINARY_DIR}/host_shaders/${FILE_NAME}.spv" )
|
||||
add_custom_command(
|
||||
OUTPUT ${HOST_SHADER_SPIRV}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||
COMMAND Vulkan::glslangValidator -t --target-env vulkan1.1 -g -V "${PROJECT_SOURCE_DIR}/${HOST_SHADER_SOURCE}" -o ${HOST_SHADER_SPIRV}
|
||||
#COMMAND ${SPIRV_OPT} -O ${HOST_SHADER_SPIRV} -o ${HOST_SHADER_SPIRV}
|
||||
DEPENDS ${HOST_SHADER_SOURCE}
|
||||
)
|
||||
list( APPEND RENDERER_VK_HOST_SHADERS_SPIRV ${HOST_SHADER_SPIRV} )
|
||||
endforeach()
|
||||
|
||||
cmrc_add_resource_library(
|
||||
resources_renderer_vk
|
||||
NAMESPACE RendererVK
|
||||
WHENCE "src/host_shaders/"
|
||||
WHENCE "${PROJECT_BINARY_DIR}/host_shaders/"
|
||||
${RENDERER_VK_HOST_SHADERS_SPIRV}
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "vulkan_api.hpp"
|
||||
#include "vk_api.hpp"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
|
|
49
include/renderer_vk/vk_descriptor_heap.hpp
Normal file
49
include/renderer_vk/vk_descriptor_heap.hpp
Normal 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
|
62
include/renderer_vk/vk_descriptor_update_batch.hpp
Normal file
62
include/renderer_vk/vk_descriptor_update_batch.hpp
Normal 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
|
36
include/renderer_vk/vk_memory.hpp
Normal file
36
include/renderer_vk/vk_memory.hpp
Normal 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
|
12
include/renderer_vk/vk_pica.hpp
Normal file
12
include/renderer_vk/vk_pica.hpp
Normal 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
|
28
include/renderer_vk/vk_sampler_cache.hpp
Normal file
28
include/renderer_vk/vk_sampler_cache.hpp
Normal 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
|
File diff suppressed because it is too large
Load diff
3
src/core/renderer_vk/vk_api.cpp
Normal file
3
src/core/renderer_vk/vk_api.cpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include "renderer_vk/vk_api.hpp"
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;
|
119
src/core/renderer_vk/vk_descriptor_heap.cpp
Normal file
119
src/core/renderer_vk/vk_descriptor_heap.cpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
#include "renderer_vk/vk_descriptor_heap.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
DescriptorHeap::DescriptorHeap(vk::Device device) : device(device) {}
|
||||
|
||||
std::optional<vk::DescriptorSet> DescriptorHeap::allocateDescriptorSet() {
|
||||
// Find a free slot
|
||||
const auto freeSlot = std::find(allocationMap.begin(), allocationMap.end(), false);
|
||||
|
||||
// If there is no free slot, return
|
||||
if (freeSlot == allocationMap.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Mark the slot as allocated
|
||||
*freeSlot = true;
|
||||
|
||||
const u16 index = static_cast<u16>(std::distance(allocationMap.begin(), freeSlot));
|
||||
|
||||
vk::UniqueDescriptorSet& newDescriptorSet = descriptorSets[index];
|
||||
|
||||
if (!newDescriptorSet) {
|
||||
// Descriptor set doesn't exist yet. Allocate a new one
|
||||
vk::DescriptorSetAllocateInfo allocateInfo = {};
|
||||
|
||||
allocateInfo.descriptorPool = descriptorPool.get();
|
||||
allocateInfo.pSetLayouts = &descriptorSetLayout.get();
|
||||
allocateInfo.descriptorSetCount = 1;
|
||||
|
||||
if (auto AllocateResult = device.allocateDescriptorSetsUnique(allocateInfo); AllocateResult.result == vk::Result::eSuccess) {
|
||||
newDescriptorSet = std::move(AllocateResult.value[0]);
|
||||
} else {
|
||||
// Error allocating descriptor set
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return newDescriptorSet.get();
|
||||
}
|
||||
|
||||
bool DescriptorHeap::freeDescriptorSet(vk::DescriptorSet Set) {
|
||||
// Find the descriptor set
|
||||
const auto found =
|
||||
std::find_if(descriptorSets.begin(), descriptorSets.end(), [&Set](const auto& CurSet) -> bool { return CurSet.get() == Set; });
|
||||
|
||||
// If the descriptor set is not found, return
|
||||
if (found == descriptorSets.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the slot as free
|
||||
const u16 index = static_cast<u16>(std::distance(descriptorSets.begin(), found));
|
||||
|
||||
allocationMap[index] = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<DescriptorHeap> DescriptorHeap::create(
|
||||
vk::Device device, std::span<const vk::DescriptorSetLayoutBinding> bindings, u16 descriptorHeapCount
|
||||
) {
|
||||
DescriptorHeap newDescriptorHeap(device);
|
||||
|
||||
// Create a histogram of each of the descriptor types and how many of each
|
||||
// the pool should have
|
||||
// Todo: maybe keep this around as a hash table to do more dynamic
|
||||
// allocations of descriptor sets rather than allocating them all up-front
|
||||
std::vector<vk::DescriptorPoolSize> poolSizes;
|
||||
{
|
||||
std::unordered_map<vk::DescriptorType, u16> descriptorTypeCounts;
|
||||
|
||||
for (const auto& binding : bindings) {
|
||||
descriptorTypeCounts[binding.descriptorType] += binding.descriptorCount;
|
||||
}
|
||||
for (const auto& descriptorTypeCount : descriptorTypeCounts) {
|
||||
poolSizes.push_back(vk::DescriptorPoolSize(descriptorTypeCount.first, descriptorTypeCount.second * descriptorHeapCount));
|
||||
}
|
||||
}
|
||||
|
||||
// Create descriptor pool
|
||||
{
|
||||
vk::DescriptorPoolCreateInfo poolInfo;
|
||||
poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;
|
||||
poolInfo.maxSets = descriptorHeapCount;
|
||||
poolInfo.pPoolSizes = poolSizes.data();
|
||||
poolInfo.poolSizeCount = poolSizes.size();
|
||||
if (auto createResult = device.createDescriptorPoolUnique(poolInfo); createResult.result == vk::Result::eSuccess) {
|
||||
newDescriptorHeap.descriptorPool = std::move(createResult.value);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Create descriptor set layout
|
||||
{
|
||||
vk::DescriptorSetLayoutCreateInfo layoutInfo;
|
||||
layoutInfo.pBindings = bindings.data();
|
||||
layoutInfo.bindingCount = bindings.size();
|
||||
|
||||
if (auto createResult = device.createDescriptorSetLayoutUnique(layoutInfo); createResult.result == vk::Result::eSuccess) {
|
||||
newDescriptorHeap.descriptorSetLayout = std::move(createResult.value);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
newDescriptorHeap.descriptorSets.resize(descriptorHeapCount);
|
||||
newDescriptorHeap.allocationMap.resize(descriptorHeapCount);
|
||||
|
||||
newDescriptorHeap.bindings.assign(bindings.begin(), bindings.end());
|
||||
|
||||
return {std::move(newDescriptorHeap)};
|
||||
}
|
||||
} // namespace Vulkan
|
98
src/core/renderer_vk/vk_descriptor_update_batch.cpp
Normal file
98
src/core/renderer_vk/vk_descriptor_update_batch.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
#include "renderer_vk/vk_descriptor_update_batch.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
void DescriptorUpdateBatch::flush() {
|
||||
device.updateDescriptorSets({std::span(descriptorWrites.get(), descriptorWriteEnd)}, {std::span(descriptorCopies.get(), descriptorCopyEnd)});
|
||||
|
||||
descriptorWriteEnd = 0;
|
||||
descriptorCopyEnd = 0;
|
||||
}
|
||||
|
||||
void DescriptorUpdateBatch::addImage(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::ImageLayout imageLayout) {
|
||||
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||
flush();
|
||||
}
|
||||
|
||||
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(vk::Sampler(), imageView, imageLayout);
|
||||
|
||||
descriptorWrites[descriptorWriteEnd] =
|
||||
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampledImage, &imageInfo, nullptr, nullptr);
|
||||
|
||||
++descriptorWriteEnd;
|
||||
}
|
||||
|
||||
void DescriptorUpdateBatch::addSampler(vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Sampler sampler) {
|
||||
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||
flush();
|
||||
}
|
||||
|
||||
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(sampler, vk::ImageView(), vk::ImageLayout());
|
||||
|
||||
descriptorWrites[descriptorWriteEnd] =
|
||||
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eSampler, &imageInfo, nullptr, nullptr);
|
||||
|
||||
++descriptorWriteEnd;
|
||||
}
|
||||
|
||||
void DescriptorUpdateBatch::addImageSampler(
|
||||
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::ImageView imageView, vk::Sampler sampler, vk::ImageLayout imageLayout
|
||||
) {
|
||||
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||
flush();
|
||||
}
|
||||
|
||||
const auto& imageInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorImageInfo>(sampler, imageView, imageLayout);
|
||||
|
||||
descriptorWrites[descriptorWriteEnd] =
|
||||
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr);
|
||||
|
||||
++descriptorWriteEnd;
|
||||
}
|
||||
|
||||
void DescriptorUpdateBatch::addBuffer(
|
||||
vk::DescriptorSet targetDescriptor, u8 targetBinding, vk::Buffer buffer, vk::DeviceSize offset, vk::DeviceSize size
|
||||
) {
|
||||
if (descriptorWriteEnd >= descriptorWriteMax) {
|
||||
flush();
|
||||
}
|
||||
|
||||
const auto& bufferInfo = descriptorInfos[descriptorWriteEnd].emplace<vk::DescriptorBufferInfo>(buffer, offset, size);
|
||||
|
||||
descriptorWrites[descriptorWriteEnd] =
|
||||
vk::WriteDescriptorSet(targetDescriptor, targetBinding, 0, 1, vk::DescriptorType::eStorageImage, nullptr, &bufferInfo, nullptr);
|
||||
|
||||
++descriptorWriteEnd;
|
||||
}
|
||||
|
||||
void DescriptorUpdateBatch::copyBinding(
|
||||
vk::DescriptorSet sourceDescriptor, vk::DescriptorSet targetDescriptor, u8 sourceBinding, u8 targetBinding, u8 sourceArrayElement,
|
||||
u8 targetArrayElement, u8 descriptorCount
|
||||
) {
|
||||
if (descriptorCopyEnd >= descriptorCopyMax) {
|
||||
flush();
|
||||
}
|
||||
|
||||
descriptorCopies[descriptorCopyEnd] = vk::CopyDescriptorSet(
|
||||
sourceDescriptor, sourceBinding, sourceArrayElement, targetDescriptor, targetBinding, targetArrayElement, descriptorCount
|
||||
);
|
||||
|
||||
++descriptorCopyEnd;
|
||||
}
|
||||
|
||||
std::optional<DescriptorUpdateBatch> DescriptorUpdateBatch::create(vk::Device device, usize descriptorWriteMax, usize descriptorCopyMax)
|
||||
|
||||
{
|
||||
DescriptorUpdateBatch newDescriptorUpdateBatch(device, descriptorWriteMax, descriptorCopyMax);
|
||||
|
||||
newDescriptorUpdateBatch.descriptorInfos = std::make_unique<DescriptorInfoUnion[]>(descriptorWriteMax);
|
||||
newDescriptorUpdateBatch.descriptorWrites = std::make_unique<vk::WriteDescriptorSet[]>(descriptorWriteMax);
|
||||
newDescriptorUpdateBatch.descriptorCopies = std::make_unique<vk::CopyDescriptorSet[]>(descriptorCopyMax);
|
||||
|
||||
return {std::move(newDescriptorUpdateBatch)};
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
174
src/core/renderer_vk/vk_memory.cpp
Normal file
174
src/core/renderer_vk/vk_memory.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include "renderer_vk/vk_memory.hpp"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
static constexpr vk::DeviceSize alignUp(vk::DeviceSize value, std::size_t size) {
|
||||
const vk::DeviceSize mod = static_cast<vk::DeviceSize>(value % size);
|
||||
value -= mod;
|
||||
return static_cast<vk::DeviceSize>(mod == vk::DeviceSize{0} ? value : value + size);
|
||||
}
|
||||
|
||||
// Given a speculative heap-allocation, defined by its current size and
|
||||
// memory-type bits, appends a memory requirements structure to it, updating
|
||||
// both the size and the required memory-type-bits. Returns the offset within
|
||||
// the heap for the current MemoryRequirements Todo: Sun Apr 23 13:28:25 PDT
|
||||
// 2023 Rather than using a running-size of the heap, look at all of the memory
|
||||
// requests and optimally create a packing for all of the offset and alignment
|
||||
// requirements. Such as by satisfying all of the largest alignments first, and
|
||||
// then the smallest, to reduce padding
|
||||
static vk::DeviceSize commitMemoryRequestToHeap(
|
||||
const vk::MemoryRequirements& curMemoryRequirements, vk::DeviceSize& curHeapEnd, u32& curMemoryTypeBits, vk::DeviceSize sizeAlignment
|
||||
) {
|
||||
// Accumulate a mask of all the memory types that satisfies each of the
|
||||
// handles
|
||||
curMemoryTypeBits &= curMemoryRequirements.memoryTypeBits;
|
||||
|
||||
// Pad up the memory sizes so they are not considered aliasing
|
||||
const vk::DeviceSize curMemoryOffset = alignUp(curHeapEnd, curMemoryRequirements.alignment);
|
||||
// Pad the size by the required size-alignment.
|
||||
// Intended for BufferImageGranularity
|
||||
const vk::DeviceSize curMemorySize = alignUp(curMemoryRequirements.size, sizeAlignment);
|
||||
|
||||
curHeapEnd = (curMemoryOffset + curMemorySize);
|
||||
return curMemoryOffset;
|
||||
}
|
||||
|
||||
s32 findMemoryTypeIndex(
|
||||
vk::PhysicalDevice physicalDevice, u32 memoryTypeMask, vk::MemoryPropertyFlags memoryProperties,
|
||||
vk::MemoryPropertyFlags memoryExcludeProperties
|
||||
) {
|
||||
const vk::PhysicalDeviceMemoryProperties deviceMemoryProperties = physicalDevice.getMemoryProperties();
|
||||
// Iterate the physical device's memory types until we find a match
|
||||
for (std::size_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) {
|
||||
if(
|
||||
// Is within memory type mask
|
||||
(((memoryTypeMask >> i) & 0b1) == 0b1) &&
|
||||
// Has property flags
|
||||
(deviceMemoryProperties.memoryTypes[i].propertyFlags
|
||||
& memoryProperties)
|
||||
== memoryProperties
|
||||
&&
|
||||
// None of the excluded properties are enabled
|
||||
!(deviceMemoryProperties.memoryTypes[i].propertyFlags
|
||||
& memoryExcludeProperties) )
|
||||
{
|
||||
return static_cast<u32>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitImageHeap(
|
||||
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Image> images, vk::MemoryPropertyFlags memoryProperties,
|
||||
vk::MemoryPropertyFlags memoryExcludeProperties
|
||||
) {
|
||||
vk::MemoryAllocateInfo imageHeapAllocInfo = {};
|
||||
u32 imageHeapMemoryTypeBits = 0xFFFFFFFF;
|
||||
std::vector<vk::BindImageMemoryInfo> imageHeapBinds;
|
||||
|
||||
const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity;
|
||||
|
||||
for (const vk::Image& curImage : images) {
|
||||
const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap(
|
||||
device.getImageMemoryRequirements(curImage), imageHeapAllocInfo.allocationSize, imageHeapMemoryTypeBits, bufferImageGranularity
|
||||
);
|
||||
|
||||
if (imageHeapMemoryTypeBits == 0) {
|
||||
// No possible memory heap for all of the images to share
|
||||
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
// Put nullptr for the device memory for now
|
||||
imageHeapBinds.emplace_back(vk::BindImageMemoryInfo{curImage, nullptr, curBindOffset});
|
||||
}
|
||||
|
||||
const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, imageHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties);
|
||||
|
||||
if (memoryTypeIndex < 0) {
|
||||
// Unable to find a memory heap that satisfies all the images
|
||||
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
imageHeapAllocInfo.memoryTypeIndex = memoryTypeIndex;
|
||||
|
||||
vk::UniqueDeviceMemory imageHeapMemory = {};
|
||||
|
||||
if (auto allocResult = device.allocateMemoryUnique(imageHeapAllocInfo); allocResult.result == vk::Result::eSuccess) {
|
||||
imageHeapMemory = std::move(allocResult.value);
|
||||
} else {
|
||||
return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
// Assign the device memory to the bindings
|
||||
for (vk::BindImageMemoryInfo& curBind : imageHeapBinds) {
|
||||
curBind.memory = imageHeapMemory.get();
|
||||
}
|
||||
|
||||
// Now bind them all in one call
|
||||
if (const vk::Result bindResult = device.bindImageMemory2(imageHeapBinds); bindResult == vk::Result::eSuccess) {
|
||||
// Binding memory succeeded
|
||||
} else {
|
||||
return std::make_tuple(bindResult, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
return std::make_tuple(vk::Result::eSuccess, std::move(imageHeapMemory));
|
||||
}
|
||||
|
||||
std::tuple<vk::Result, vk::UniqueDeviceMemory> commitBufferHeap(
|
||||
vk::Device device, vk::PhysicalDevice physicalDevice, const std::span<const vk::Buffer> buffers, vk::MemoryPropertyFlags memoryProperties,
|
||||
vk::MemoryPropertyFlags memoryExcludeProperties
|
||||
) {
|
||||
vk::MemoryAllocateInfo bufferHeapAllocInfo = {};
|
||||
u32 bufferHeapMemoryTypeBits = 0xFFFFFFFF;
|
||||
std::vector<vk::BindBufferMemoryInfo> bufferHeapBinds;
|
||||
|
||||
const vk::DeviceSize bufferImageGranularity = physicalDevice.getProperties().limits.bufferImageGranularity;
|
||||
|
||||
for (const vk::Buffer& curBuffer : buffers) {
|
||||
const vk::DeviceSize curBindOffset = commitMemoryRequestToHeap(
|
||||
device.getBufferMemoryRequirements(curBuffer), bufferHeapAllocInfo.allocationSize, bufferHeapMemoryTypeBits, bufferImageGranularity
|
||||
);
|
||||
|
||||
if (bufferHeapMemoryTypeBits == 0) {
|
||||
// No possible memory heap for all of the buffers to share
|
||||
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
// Put nullptr for the device memory for now
|
||||
bufferHeapBinds.emplace_back(vk::BindBufferMemoryInfo{curBuffer, nullptr, curBindOffset});
|
||||
}
|
||||
|
||||
const s32 memoryTypeIndex = findMemoryTypeIndex(physicalDevice, bufferHeapMemoryTypeBits, memoryProperties, memoryExcludeProperties);
|
||||
|
||||
if (memoryTypeIndex < 0) {
|
||||
// Unable to find a memory heap that satisfies all the buffers
|
||||
return std::make_tuple(vk::Result::eErrorOutOfDeviceMemory, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
bufferHeapAllocInfo.memoryTypeIndex = memoryTypeIndex;
|
||||
|
||||
vk::UniqueDeviceMemory bufferHeapMemory = {};
|
||||
|
||||
if (auto allocResult = device.allocateMemoryUnique(bufferHeapAllocInfo); allocResult.result == vk::Result::eSuccess) {
|
||||
bufferHeapMemory = std::move(allocResult.value);
|
||||
} else {
|
||||
return std::make_tuple(allocResult.result, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
// Assign the device memory to the bindings
|
||||
for (vk::BindBufferMemoryInfo& curBind : bufferHeapBinds) {
|
||||
curBind.memory = bufferHeapMemory.get();
|
||||
}
|
||||
|
||||
// Now bind them all in one call
|
||||
if (const vk::Result bindResult = device.bindBufferMemory2(bufferHeapBinds); bindResult == vk::Result::eSuccess) {
|
||||
// Binding memory succeeded
|
||||
} else {
|
||||
return std::make_tuple(bindResult, vk::UniqueDeviceMemory());
|
||||
}
|
||||
|
||||
return std::make_tuple(vk::Result::eSuccess, std::move(bufferHeapMemory));
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
39
src/core/renderer_vk/vk_pica.cpp
Normal file
39
src/core/renderer_vk/vk_pica.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include "renderer_vk/vk_pica.hpp"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
vk::Format colorFormatToVulkan(PICA::ColorFmt colorFormat) {
|
||||
switch (colorFormat) {
|
||||
case PICA::ColorFmt::RGBA8: return vk::Format::eR8G8B8A8Unorm;
|
||||
// VK_FORMAT_R8G8B8A8_UNORM is mandated by the vulkan specification
|
||||
// VK_FORMAT_R8G8B8_UNORM may not be supported
|
||||
// TODO: Detect this!
|
||||
// case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8Unorm;
|
||||
case PICA::ColorFmt::RGB8: return vk::Format::eR8G8B8A8Unorm;
|
||||
case PICA::ColorFmt::RGBA5551: return vk::Format::eR5G5B5A1UnormPack16;
|
||||
case PICA::ColorFmt::RGB565: return vk::Format::eR5G6B5UnormPack16;
|
||||
case PICA::ColorFmt::RGBA4: return vk::Format::eR4G4B4A4UnormPack16;
|
||||
}
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
vk::Format depthFormatToVulkan(PICA::DepthFmt depthFormat) {
|
||||
switch (depthFormat) {
|
||||
// VK_FORMAT_D16_UNORM is mandated by the vulkan specification
|
||||
case PICA::DepthFmt::Depth16: return vk::Format::eD16Unorm;
|
||||
case PICA::DepthFmt::Unknown1: return vk::Format::eUndefined;
|
||||
// The GPU may _not_ support these formats natively
|
||||
// Only one of:
|
||||
// VK_FORMAT_X8_D24_UNORM_PACK32 and VK_FORMAT_D32_SFLOAT
|
||||
// and one of:
|
||||
// VK_FORMAT_D24_UNORM_S8_UINT and VK_FORMAT_D32_SFLOAT_S8_UINT
|
||||
// will be supported
|
||||
// TODO: Detect this!
|
||||
// case PICA::DepthFmt::Depth24: return vk::Format::eX8D24UnormPack32;
|
||||
// case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD24UnormS8Uint;
|
||||
case PICA::DepthFmt::Depth24: return vk::Format::eD32Sfloat;
|
||||
case PICA::DepthFmt::Depth24Stencil8: return vk::Format::eD32SfloatS8Uint;
|
||||
}
|
||||
return vk::Format::eUndefined;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
31
src/core/renderer_vk/vk_sampler_cache.cpp
Normal file
31
src/core/renderer_vk/vk_sampler_cache.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "renderer_vk/vk_sampler_cache.hpp"
|
||||
|
||||
#include <vulkan/vulkan_hash.hpp>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
SamplerCache::SamplerCache(vk::Device device) : device(device) {}
|
||||
|
||||
const vk::Sampler& SamplerCache::getSampler(const vk::SamplerCreateInfo& samplerInfo) {
|
||||
const std::size_t samplerHash = std::hash<vk::SamplerCreateInfo>()(samplerInfo);
|
||||
|
||||
// Cache hit
|
||||
if (samplerMap.contains(samplerHash)) {
|
||||
return samplerMap.at(samplerHash).get();
|
||||
}
|
||||
|
||||
if (auto createResult = device.createSamplerUnique(samplerInfo); createResult.result == vk::Result::eSuccess) {
|
||||
return (samplerMap[samplerHash] = std::move(createResult.value)).get();
|
||||
} else {
|
||||
Helpers::panic("Error creating sampler: %s\n", vk::to_string(createResult.result).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<SamplerCache> SamplerCache::create(vk::Device device) {
|
||||
SamplerCache newSamplerCache(device);
|
||||
|
||||
return {std::move(newSamplerCache)};
|
||||
}
|
||||
} // namespace Vulkan
|
|
@ -1,3 +0,0 @@
|
|||
#include "renderer_vk/vulkan_api.hpp"
|
||||
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE;
|
7
src/host_shaders/vulkan_display.frag
Normal file
7
src/host_shaders/vulkan_display.frag
Normal file
|
@ -0,0 +1,7 @@
|
|||
#version 460 core
|
||||
layout(location = 0) in vec2 UV;
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
layout(binding = 0) uniform sampler2D u_texture;
|
||||
|
||||
void main() { FragColor = texture(u_texture, UV); }
|
7
src/host_shaders/vulkan_display.vert
Normal file
7
src/host_shaders/vulkan_display.vert
Normal file
|
@ -0,0 +1,7 @@
|
|||
#version 460 core
|
||||
layout(location = 0) out vec2 UV;
|
||||
|
||||
void main() {
|
||||
UV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(UV * 2.0f + -1.0f, 0.0f, 1.0f);
|
||||
}
|
Loading…
Add table
Reference in a new issue