diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 2fd90bfd1..218ac4541 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -65,13 +65,33 @@ add_library(common
timestamp.cpp
timestamp.h
types.h
+ vulkan/builders.cpp
+ vulkan/builders.h
+ vulkan/context.cpp
+ vulkan/context.h
+ vulkan/shader_cache.cpp
+ vulkan/shader_cache.h
+ vulkan/shader_compiler.cpp
+ vulkan/shader_compiler.h
+ vulkan/staging_buffer.cpp
+ vulkan/staging_buffer.h
+ vulkan/staging_texture.cpp
+ vulkan/staging_texture.h
+ vulkan/stream_buffer.cpp
+ vulkan/stream_buffer.h
+ vulkan/swap_chain.cpp
+ vulkan/swap_chain.h
+ vulkan/texture.cpp
+ vulkan/texture.h
+ vulkan/util.cpp
+ vulkan/util.h
wav_writer.cpp
wav_writer.h
)
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
-target_link_libraries(common PRIVATE glad libcue Threads::Threads cubeb libchdr)
+target_link_libraries(common PRIVATE glad libcue Threads::Threads cubeb libchdr glslang vulkan-loader)
if(WIN32)
target_sources(common PRIVATE
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index a2c6c2653..de0c5284b 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -78,6 +78,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -117,6 +127,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -129,6 +149,9 @@
{43540154-9e1e-409c-834f-b84be5621388}
+
+ {7f909e29-4808-4bd9-a60c-56c51a3aaec2}
+
{425d6c99-d1c8-43c2-b8ac-4d7b1d941017}
@@ -277,7 +300,7 @@
WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
true
@@ -303,7 +326,7 @@
_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
false
true
@@ -332,7 +355,7 @@
WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
true
@@ -358,7 +381,7 @@
_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)
true
ProgramDatabase
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
Default
false
true
@@ -388,7 +411,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
false
@@ -418,7 +441,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
@@ -448,7 +471,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
stdcpp17
false
@@ -478,7 +501,7 @@
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
true
- $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)
+ $(SolutionDir)dep\glad\include;$(SolutionDir)dep\cubeb\include;$(SolutionDir)dep\libcue\include;$(SolutionDir)dep\libchdr\include;$(SolutionDir)dep\vulkan-loader\include;$(SolutionDir)dep\glslang;$(SolutionDir)src;%(AdditionalIncludeDirectories)
true
true
stdcpp17
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index 55d805ec3..a98013549 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -66,8 +66,38 @@
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
@@ -128,6 +158,36 @@
gl
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
+
+ vulkan
+
@@ -139,5 +199,8 @@
{30251086-81f3-44f5-add4-7ff9a24098ab}
+
+ {642ff5eb-af39-4aab-a42f-6eb8188a11d7}
+
\ No newline at end of file
diff --git a/src/common/vulkan/builders.cpp b/src/common/vulkan/builders.cpp
new file mode 100644
index 000000000..349b9263a
--- /dev/null
+++ b/src/common/vulkan/builders.cpp
@@ -0,0 +1,740 @@
+#include "builders.h"
+#include "../assert.h"
+#include "util.h"
+
+namespace Vulkan {
+
+DescriptorSetLayoutBuilder::DescriptorSetLayoutBuilder()
+{
+ Clear();
+}
+
+void DescriptorSetLayoutBuilder::Clear()
+{
+ m_ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ m_ci.pNext = nullptr;
+ m_ci.flags = 0;
+ m_ci.pBindings = nullptr;
+ m_ci.bindingCount = 0;
+}
+
+VkDescriptorSetLayout DescriptorSetLayoutBuilder::Create(VkDevice device)
+{
+ VkDescriptorSetLayout layout;
+ VkResult res = vkCreateDescriptorSetLayout(device, &m_ci, nullptr, &layout);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateDescriptorSetLayout() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ Clear();
+ return layout;
+}
+
+void DescriptorSetLayoutBuilder::AddBinding(u32 binding, VkDescriptorType dtype, u32 dcount, VkShaderStageFlags stages)
+{
+ Assert(m_ci.bindingCount < MAX_BINDINGS);
+
+ VkDescriptorSetLayoutBinding& b = m_bindings[m_ci.bindingCount];
+ b.binding = binding;
+ b.descriptorType = dtype;
+ b.descriptorCount = dcount;
+ b.stageFlags = stages;
+ b.pImmutableSamplers = nullptr;
+
+ m_ci.pBindings = m_bindings.data();
+ m_ci.bindingCount++;
+}
+
+PipelineLayoutBuilder::PipelineLayoutBuilder()
+{
+ Clear();
+}
+
+void PipelineLayoutBuilder::Clear()
+{
+ m_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ m_ci.pNext = nullptr;
+ m_ci.flags = 0;
+ m_ci.pSetLayouts = nullptr;
+ m_ci.setLayoutCount = 0;
+ m_ci.pPushConstantRanges = nullptr;
+ m_ci.pushConstantRangeCount = 0;
+}
+
+VkPipelineLayout PipelineLayoutBuilder::Create(VkDevice device)
+{
+ VkPipelineLayout layout;
+ VkResult res = vkCreatePipelineLayout(device, &m_ci, nullptr, &layout);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreatePipelineLayout() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ Clear();
+ return layout;
+}
+
+void PipelineLayoutBuilder::AddDescriptorSet(VkDescriptorSetLayout layout)
+{
+ Assert(m_ci.setLayoutCount < MAX_SETS);
+
+ m_sets[m_ci.setLayoutCount] = layout;
+
+ m_ci.setLayoutCount++;
+ m_ci.pSetLayouts = m_sets.data();
+}
+
+void PipelineLayoutBuilder::AddPushConstants(VkShaderStageFlags stages, u32 offset, u32 size)
+{
+ Assert(m_ci.pushConstantRangeCount < MAX_PUSH_CONSTANTS);
+
+ VkPushConstantRange& r = m_push_constants[m_ci.pushConstantRangeCount];
+ r.stageFlags = stages;
+ r.offset = offset;
+ r.size = size;
+
+ m_ci.pushConstantRangeCount++;
+ m_ci.pPushConstantRanges = m_push_constants.data();
+}
+
+GraphicsPipelineBuilder::GraphicsPipelineBuilder()
+{
+ Clear();
+}
+
+void GraphicsPipelineBuilder::Clear()
+{
+ m_ci = {};
+ m_ci.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+
+ m_shader_stages = {};
+
+ m_vertex_input_state = {};
+ m_vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+ m_ci.pVertexInputState = &m_vertex_input_state;
+ m_vertex_attributes = {};
+ m_vertex_buffers = {};
+
+ m_input_assembly = {};
+ m_input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+
+ m_rasterization_state = {};
+ m_rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ m_rasterization_state.lineWidth = 1.0f;
+ m_depth_state = {};
+ m_depth_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+ m_blend_state = {};
+ m_blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ m_blend_attachments = {};
+
+ m_viewport_state = {};
+ m_viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ m_viewport = {};
+ m_scissor = {};
+
+ m_dynamic_state = {};
+ m_dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+ m_dynamic_state_values = {};
+
+ m_multisample_state = {};
+ m_multisample_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+
+ // set defaults
+ SetNoCullRasterizationState();
+ SetNoDepthTestState();
+ SetNoBlendingState();
+ SetPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
+
+ // have to be specified even if dynamic
+ SetViewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f);
+ SetScissorRect(0, 0, 1, 1);
+ SetMultisamples(VK_SAMPLE_COUNT_1_BIT);
+}
+
+VkPipeline GraphicsPipelineBuilder::Create(VkDevice device, VkPipelineCache pipeline_cache, bool clear /* = true */)
+{
+ VkPipeline pipeline;
+ VkResult res = vkCreateGraphicsPipelines(device, pipeline_cache, 1, &m_ci, nullptr, &pipeline);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ if (clear)
+ Clear();
+
+ return pipeline;
+}
+
+void GraphicsPipelineBuilder::SetShaderStage(VkShaderStageFlagBits stage, VkShaderModule module,
+ const char* entry_point)
+{
+ Assert(m_ci.stageCount < MAX_SHADER_STAGES);
+
+ u32 index = 0;
+ for (; index < m_ci.stageCount; index++)
+ {
+ if (m_shader_stages[index].stage == stage)
+ break;
+ }
+ if (index == m_ci.stageCount)
+ {
+ m_ci.stageCount++;
+ m_ci.pStages = m_shader_stages.data();
+ }
+
+ VkPipelineShaderStageCreateInfo& s = m_shader_stages[index];
+ s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ s.stage = stage;
+ s.module = module;
+ s.pName = entry_point;
+}
+
+void GraphicsPipelineBuilder::AddVertexBuffer(u32 binding, u32 stride,
+ VkVertexInputRate input_rate /*= VK_VERTEX_INPUT_RATE_VERTEX*/)
+{
+ Assert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS);
+
+ VkVertexInputBindingDescription& b = m_vertex_buffers[m_vertex_input_state.vertexBindingDescriptionCount];
+ b.binding = binding;
+ b.stride = stride;
+ b.inputRate = input_rate;
+
+ m_vertex_input_state.vertexBindingDescriptionCount++;
+ m_vertex_input_state.pVertexBindingDescriptions = m_vertex_buffers.data();
+ m_ci.pVertexInputState = &m_vertex_input_state;
+}
+
+void GraphicsPipelineBuilder::AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset)
+{
+ Assert(m_vertex_input_state.vertexAttributeDescriptionCount < MAX_VERTEX_BUFFERS);
+
+ VkVertexInputAttributeDescription& a = m_vertex_attributes[m_vertex_input_state.vertexAttributeDescriptionCount];
+ a.location = location;
+ a.binding = binding;
+ a.format = format;
+ a.offset = offset;
+
+ m_vertex_input_state.vertexAttributeDescriptionCount++;
+ m_vertex_input_state.pVertexAttributeDescriptions = m_vertex_attributes.data();
+ m_ci.pVertexInputState = &m_vertex_input_state;
+}
+
+void GraphicsPipelineBuilder::SetPrimitiveTopology(VkPrimitiveTopology topology,
+ bool enable_primitive_restart /*= false*/)
+{
+ m_input_assembly.topology = topology;
+ m_input_assembly.primitiveRestartEnable = enable_primitive_restart;
+
+ m_ci.pInputAssemblyState = &m_input_assembly;
+}
+
+void GraphicsPipelineBuilder::SetRasterizationState(VkPolygonMode polygon_mode, VkCullModeFlags cull_mode,
+ VkFrontFace front_face)
+{
+ m_rasterization_state.polygonMode = polygon_mode;
+ m_rasterization_state.cullMode = cull_mode;
+ m_rasterization_state.frontFace = front_face;
+
+ m_ci.pRasterizationState = &m_rasterization_state;
+}
+
+void GraphicsPipelineBuilder::SetLineWidth(float width)
+{
+ m_rasterization_state.lineWidth = width;
+}
+
+void GraphicsPipelineBuilder::SetNoCullRasterizationState()
+{
+ SetRasterizationState(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
+}
+
+void GraphicsPipelineBuilder::SetDepthState(bool depth_test, bool depth_write, VkCompareOp compare_op)
+{
+ m_depth_state.depthTestEnable = depth_test;
+ m_depth_state.depthWriteEnable = depth_write;
+ m_depth_state.depthCompareOp = compare_op;
+
+ m_ci.pDepthStencilState = &m_depth_state;
+}
+
+void GraphicsPipelineBuilder::SetNoDepthTestState()
+{
+ SetDepthState(false, false, VK_COMPARE_OP_ALWAYS);
+}
+
+void GraphicsPipelineBuilder::SetBlendConstants(float r, float g, float b, float a)
+{
+ m_blend_state.blendConstants[0] = r;
+ m_blend_state.blendConstants[1] = g;
+ m_blend_state.blendConstants[2] = b;
+ m_blend_state.blendConstants[3] = a;
+ m_ci.pColorBlendState = &m_blend_state;
+}
+
+void GraphicsPipelineBuilder::AddBlendAttachment(
+ bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, VkBlendOp op,
+ VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, VkBlendOp alpha_op, VkColorComponentFlags write_mask /* = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT */)
+{
+ Assert(m_blend_state.attachmentCount < MAX_ATTACHMENTS);
+
+ VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[m_blend_state.attachmentCount];
+ bs.blendEnable = blend_enable;
+ bs.srcColorBlendFactor = src_factor;
+ bs.dstColorBlendFactor = dst_factor;
+ bs.colorBlendOp = op;
+ bs.srcAlphaBlendFactor = alpha_src_factor;
+ bs.dstAlphaBlendFactor = alpha_dst_factor;
+ bs.alphaBlendOp = alpha_op;
+ bs.colorWriteMask = write_mask;
+
+ m_blend_state.attachmentCount++;
+ m_blend_state.pAttachments = m_blend_attachments.data();
+ m_ci.pColorBlendState = &m_blend_state;
+}
+
+void GraphicsPipelineBuilder::SetBlendAttachment(
+ u32 attachment, bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, VkBlendOp op,
+ VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, VkBlendOp alpha_op, VkColorComponentFlags write_mask /*= VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT*/)
+{
+ Assert(attachment < MAX_ATTACHMENTS);
+
+ VkPipelineColorBlendAttachmentState& bs = m_blend_attachments[attachment];
+ bs.blendEnable = blend_enable;
+ bs.srcColorBlendFactor = src_factor;
+ bs.dstColorBlendFactor = dst_factor;
+ bs.colorBlendOp = op;
+ bs.srcAlphaBlendFactor = alpha_src_factor;
+ bs.dstAlphaBlendFactor = alpha_dst_factor;
+ bs.alphaBlendOp = alpha_op;
+ bs.colorWriteMask = write_mask;
+
+ if (attachment >= m_blend_state.attachmentCount)
+ {
+ m_blend_state.attachmentCount = attachment + 1u;
+ m_blend_state.pAttachments = m_blend_attachments.data();
+ m_ci.pColorBlendState = &m_blend_state;
+ }
+}
+
+void GraphicsPipelineBuilder::ClearBlendAttachments()
+{
+ m_blend_attachments = {};
+ m_blend_state.attachmentCount = 0;
+}
+
+void GraphicsPipelineBuilder::SetNoBlendingState()
+{
+ ClearBlendAttachments();
+ SetBlendAttachment(0, false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, VK_BLEND_FACTOR_ONE,
+ VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD,
+ VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
+ VK_COLOR_COMPONENT_A_BIT);
+}
+
+void GraphicsPipelineBuilder::AddDynamicState(VkDynamicState state)
+{
+ Assert(m_dynamic_state.dynamicStateCount < MAX_DYNAMIC_STATE);
+
+ m_dynamic_state_values[m_dynamic_state.dynamicStateCount] = state;
+ m_dynamic_state.dynamicStateCount++;
+ m_dynamic_state.pDynamicStates = m_dynamic_state_values.data();
+ m_ci.pDynamicState = &m_dynamic_state;
+}
+
+void GraphicsPipelineBuilder::SetDynamicViewportAndScissorState()
+{
+ AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT);
+ AddDynamicState(VK_DYNAMIC_STATE_SCISSOR);
+}
+
+void GraphicsPipelineBuilder::SetViewport(float x, float y, float width, float height, float min_depth, float max_depth)
+{
+ m_viewport.x = x;
+ m_viewport.y = y;
+ m_viewport.width = width;
+ m_viewport.height = height;
+ m_viewport.minDepth = min_depth;
+ m_viewport.maxDepth = max_depth;
+
+ m_viewport_state.pViewports = &m_viewport;
+ m_viewport_state.viewportCount = 1u;
+ m_ci.pViewportState = &m_viewport_state;
+}
+
+void GraphicsPipelineBuilder::SetScissorRect(s32 x, s32 y, u32 width, u32 height)
+{
+ m_scissor.offset.x = x;
+ m_scissor.offset.y = y;
+ m_scissor.extent.width = width;
+ m_scissor.extent.height = height;
+
+ m_viewport_state.pScissors = &m_scissor;
+ m_viewport_state.scissorCount = 1u;
+ m_ci.pViewportState = &m_viewport_state;
+}
+
+void GraphicsPipelineBuilder::SetMultisamples(VkSampleCountFlagBits samples)
+{
+ m_multisample_state.rasterizationSamples = samples;
+ m_ci.pMultisampleState = &m_multisample_state;
+}
+
+void GraphicsPipelineBuilder::SetPipelineLayout(VkPipelineLayout layout)
+{
+ m_ci.layout = layout;
+}
+
+void GraphicsPipelineBuilder::SetRenderPass(VkRenderPass render_pass, u32 subpass)
+{
+ m_ci.renderPass = render_pass;
+ m_ci.subpass = subpass;
+}
+
+SamplerBuilder::SamplerBuilder()
+{
+ Clear();
+}
+
+void SamplerBuilder::Clear()
+{
+ m_ci = {};
+ m_ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+}
+
+VkSampler SamplerBuilder::Create(VkDevice device, bool clear /* = true */)
+{
+ VkSampler sampler;
+ VkResult res = vkCreateSampler(device, &m_ci, nullptr, &sampler);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateSampler() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ return sampler;
+}
+
+void SamplerBuilder::SetFilter(VkFilter mag_filter, VkFilter min_filter, VkSamplerMipmapMode mip_filter)
+{
+ m_ci.magFilter = mag_filter;
+ m_ci.minFilter = min_filter;
+ m_ci.mipmapMode = mip_filter;
+}
+
+void SamplerBuilder::SetAddressMode(VkSamplerAddressMode u, VkSamplerAddressMode v, VkSamplerAddressMode w)
+{
+ m_ci.addressModeU = u;
+ m_ci.addressModeV = v;
+ m_ci.addressModeW = w;
+}
+
+void SamplerBuilder::SetPointSampler(VkSamplerAddressMode address_mode /* = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER */)
+{
+ Clear();
+ SetFilter(VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST);
+ SetAddressMode(address_mode, address_mode, address_mode);
+}
+
+void SamplerBuilder::SetLinearSampler(bool mipmaps,
+ VkSamplerAddressMode address_mode /* = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER */)
+{
+ Clear();
+ SetFilter(VK_FILTER_LINEAR, VK_FILTER_LINEAR,
+ mipmaps ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
+ SetAddressMode(address_mode, address_mode, address_mode);
+}
+
+DescriptorSetUpdateBuilder::DescriptorSetUpdateBuilder()
+{
+ Clear();
+}
+
+void DescriptorSetUpdateBuilder::Clear()
+{
+ m_writes = {};
+ m_num_writes = 0;
+}
+
+void DescriptorSetUpdateBuilder::Update(VkDevice device, bool clear /*= true*/)
+{
+ Assert(m_num_writes > 0);
+
+ vkUpdateDescriptorSets(device, m_num_writes, (m_num_writes > 0) ? m_writes.data() : nullptr, 0, nullptr);
+
+ if (clear)
+ Clear();
+}
+
+void DescriptorSetUpdateBuilder::AddImageDescriptorWrite(
+ VkDescriptorSet set, u32 binding, VkImageView view,
+ VkImageLayout layout /*= VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL*/)
+{
+ Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS);
+
+ VkDescriptorImageInfo& ii = m_infos[m_num_infos++].image;
+ ii.imageView = view;
+ ii.imageLayout = layout;
+ ii.sampler = VK_NULL_HANDLE;
+
+ VkWriteDescriptorSet& dw = m_writes[m_num_writes++];
+ dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ dw.dstSet = set;
+ dw.dstBinding = binding;
+ dw.descriptorCount = 1;
+ dw.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+ dw.pImageInfo = ⅈ
+}
+
+void DescriptorSetUpdateBuilder::AddSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkSampler sampler)
+{
+ Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS);
+
+ VkDescriptorImageInfo& ii = m_infos[m_num_infos++].image;
+ ii.imageView = VK_NULL_HANDLE;
+ ii.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ ii.sampler = sampler;
+
+ VkWriteDescriptorSet& dw = m_writes[m_num_writes++];
+ dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ dw.dstSet = set;
+ dw.dstBinding = binding;
+ dw.descriptorCount = 1;
+ dw.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
+ dw.pImageInfo = ⅈ
+}
+
+void DescriptorSetUpdateBuilder::AddCombinedImageSamplerDescriptorWrite(
+ VkDescriptorSet set, u32 binding, VkImageView view, VkSampler sampler,
+ VkImageLayout layout /*= VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL*/)
+{
+ Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS);
+
+ VkDescriptorImageInfo& ii = m_infos[m_num_infos++].image;
+ ii.imageView = view;
+ ii.imageLayout = layout;
+ ii.sampler = sampler;
+
+ VkWriteDescriptorSet& dw = m_writes[m_num_writes++];
+ dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ dw.dstSet = set;
+ dw.dstBinding = binding;
+ dw.descriptorCount = 1;
+ dw.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ dw.pImageInfo = ⅈ
+}
+
+void DescriptorSetUpdateBuilder::AddBufferDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype,
+ VkBuffer buffer, u32 offset, u32 size)
+{
+ Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS);
+
+ VkDescriptorBufferInfo& bi = m_infos[m_num_infos++].buffer;
+ bi.buffer = buffer;
+ bi.offset = offset;
+ bi.range = size;
+
+ VkWriteDescriptorSet& dw = m_writes[m_num_writes++];
+ dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ dw.dstSet = set;
+ dw.dstBinding = binding;
+ dw.descriptorCount = 1;
+ dw.descriptorType = dtype;
+ dw.pBufferInfo = &bi;
+}
+
+void DescriptorSetUpdateBuilder::AddBufferViewDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype,
+ VkBufferView view)
+{
+ Assert(m_num_writes < MAX_WRITES && m_num_infos < MAX_INFOS);
+
+ VkBufferView& bi = m_infos[m_num_infos++].buffer_view;
+ bi = view;
+
+ VkWriteDescriptorSet& dw = m_writes[m_num_writes++];
+ dw.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ dw.dstSet = set;
+ dw.dstBinding = binding;
+ dw.descriptorCount = 1;
+ dw.descriptorType = dtype;
+ dw.pTexelBufferView = &bi;
+}
+
+FramebufferBuilder::FramebufferBuilder()
+{
+ Clear();
+}
+
+void FramebufferBuilder::Clear()
+{
+ m_ci = {};
+ m_ci.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ m_images = {};
+}
+
+VkFramebuffer FramebufferBuilder::Create(VkDevice device, bool clear /*= true*/)
+{
+ VkFramebuffer fb;
+ VkResult res = vkCreateFramebuffer(device, &m_ci, nullptr, &fb);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateFramebuffer() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ if (clear)
+ Clear();
+
+ return fb;
+}
+
+void FramebufferBuilder::AddAttachment(VkImageView image)
+{
+ Assert(m_ci.attachmentCount < MAX_ATTACHMENTS);
+
+ m_images[m_ci.attachmentCount] = image;
+
+ m_ci.attachmentCount++;
+ m_ci.pAttachments = m_images.data();
+}
+
+void FramebufferBuilder::SetSize(u32 width, u32 height, u32 layers)
+{
+ m_ci.width = width;
+ m_ci.height = height;
+ m_ci.layers = layers;
+}
+
+void FramebufferBuilder::SetRenderPass(VkRenderPass render_pass)
+{
+ m_ci.renderPass = render_pass;
+}
+
+RenderPassBuilder::RenderPassBuilder()
+{
+ Clear();
+}
+
+void RenderPassBuilder::Clear()
+{
+ m_ci = {};
+ m_ci.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ m_attachments = {};
+ m_attachment_references = {};
+ m_num_attachment_references = 0;
+ m_subpasses = {};
+}
+
+VkRenderPass RenderPassBuilder::Create(VkDevice device, bool clear /*= true*/)
+{
+ VkRenderPass rp;
+ VkResult res = vkCreateRenderPass(device, &m_ci, nullptr, &rp);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateRenderPass() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ return rp;
+}
+
+u32 RenderPassBuilder::AddAttachment(VkFormat format, VkSampleCountFlagBits samples, VkAttachmentLoadOp load_op,
+ VkAttachmentStoreOp store_op, VkImageLayout initial_layout,
+ VkImageLayout final_layout)
+{
+ Assert(m_ci.attachmentCount < MAX_ATTACHMENTS);
+
+ const u32 index = m_ci.attachmentCount;
+ VkAttachmentDescription& ad = m_attachments[index];
+ ad.format = format;
+ ad.samples = samples;
+ ad.loadOp = load_op;
+ ad.storeOp = store_op;
+ ad.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ ad.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ ad.initialLayout = initial_layout;
+ ad.finalLayout = final_layout;
+
+ m_ci.attachmentCount++;
+ m_ci.pAttachments = m_attachments.data();
+
+ return index;
+}
+
+u32 RenderPassBuilder::AddSubpass()
+{
+ Assert(m_ci.subpassCount < MAX_SUBPASSES);
+
+ const u32 index = m_ci.subpassCount;
+ VkSubpassDescription& sp = m_subpasses[index];
+ sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+ m_ci.subpassCount++;
+ m_ci.pSubpasses = m_subpasses.data();
+
+ return index;
+}
+
+void RenderPassBuilder::AddSubpassColorAttachment(u32 subpass, u32 attachment, VkImageLayout layout)
+{
+ Assert(subpass < m_ci.subpassCount && m_num_attachment_references < MAX_ATTACHMENT_REFERENCES);
+
+ VkAttachmentReference& ar = m_attachment_references[m_num_attachment_references++];
+ ar.attachment = attachment;
+ ar.layout = layout;
+
+ VkSubpassDescription& sp = m_subpasses[subpass];
+ if (sp.colorAttachmentCount == 0)
+ sp.pColorAttachments = &ar;
+ sp.colorAttachmentCount++;
+}
+
+void RenderPassBuilder::AddSubpassDepthAttachment(u32 subpass, u32 attachment, VkImageLayout layout)
+{
+ Assert(subpass < m_ci.subpassCount && m_num_attachment_references < MAX_ATTACHMENT_REFERENCES);
+
+ VkAttachmentReference& ar = m_attachment_references[m_num_attachment_references++];
+ ar.attachment = attachment;
+ ar.layout = layout;
+
+ VkSubpassDescription& sp = m_subpasses[subpass];
+ sp.pDepthStencilAttachment = &ar;
+}
+
+BufferViewBuilder::BufferViewBuilder()
+{
+ Clear();
+}
+
+void BufferViewBuilder::Clear()
+{
+ m_ci = {};
+ m_ci.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
+}
+
+VkBufferView BufferViewBuilder::Create(VkDevice device, bool clear /*= true*/)
+{
+ VkBufferView bv;
+ VkResult res = vkCreateBufferView(device, &m_ci, nullptr, &bv);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateBufferView() failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ return bv;
+}
+
+void BufferViewBuilder::Set(VkBuffer buffer, VkFormat format, u32 offset, u32 size)
+{
+ m_ci.buffer = buffer;
+ m_ci.format = format;
+ m_ci.offset = offset;
+ m_ci.range = size;
+}
+
+} // namespace Vulkan
\ No newline at end of file
diff --git a/src/common/vulkan/builders.h b/src/common/vulkan/builders.h
new file mode 100644
index 000000000..cd5032247
--- /dev/null
+++ b/src/common/vulkan/builders.h
@@ -0,0 +1,269 @@
+#pragma once
+#include "../types.h"
+#include "vulkan_loader.h"
+#include
+
+namespace Vulkan {
+
+class DescriptorSetLayoutBuilder
+{
+public:
+ enum : u32
+ {
+ MAX_BINDINGS = 16,
+ };
+
+ DescriptorSetLayoutBuilder();
+
+ void Clear();
+
+ VkDescriptorSetLayout Create(VkDevice device);
+
+ void AddBinding(u32 binding, VkDescriptorType dtype, u32 dcount, VkShaderStageFlags stages);
+
+private:
+ VkDescriptorSetLayoutCreateInfo m_ci{};
+ std::array m_bindings{};
+};
+
+class PipelineLayoutBuilder
+{
+public:
+ enum : u32
+ {
+ MAX_SETS = 8,
+ MAX_PUSH_CONSTANTS = 1
+ };
+
+ PipelineLayoutBuilder();
+
+ void Clear();
+
+ VkPipelineLayout Create(VkDevice device);
+
+ void AddDescriptorSet(VkDescriptorSetLayout layout);
+
+ void AddPushConstants(VkShaderStageFlags stages, u32 offset, u32 size);
+
+private:
+ VkPipelineLayoutCreateInfo m_ci{};
+ std::array m_sets{};
+ std::array m_push_constants{};
+};
+
+class GraphicsPipelineBuilder
+{
+public:
+ enum : u32
+ {
+ MAX_SHADER_STAGES = 3,
+ MAX_VERTEX_ATTRIBUTES = 16,
+ MAX_VERTEX_BUFFERS = 8,
+ MAX_ATTACHMENTS = 2,
+ MAX_DYNAMIC_STATE = 8
+ };
+
+ GraphicsPipelineBuilder();
+
+ void Clear();
+
+ VkPipeline Create(VkDevice device, VkPipelineCache pipeline_cache = VK_NULL_HANDLE, bool clear = true);
+
+ void SetShaderStage(VkShaderStageFlagBits stage, VkShaderModule module, const char* entry_point);
+ void SetVertexShader(VkShaderModule module) { SetShaderStage(VK_SHADER_STAGE_VERTEX_BIT, module, "main"); }
+ void SetGeometryShader(VkShaderModule module) { SetShaderStage(VK_SHADER_STAGE_GEOMETRY_BIT, module, "main"); }
+ void SetFragmentShader(VkShaderModule module) { SetShaderStage(VK_SHADER_STAGE_FRAGMENT_BIT, module, "main"); }
+
+ void AddVertexBuffer(u32 binding, u32 stride, VkVertexInputRate input_rate = VK_VERTEX_INPUT_RATE_VERTEX);
+ void AddVertexAttribute(u32 location, u32 binding, VkFormat format, u32 offset);
+
+ void SetPrimitiveTopology(VkPrimitiveTopology topology, bool enable_primitive_restart = false);
+
+ void SetRasterizationState(VkPolygonMode polygon_mode, VkCullModeFlags cull_mode, VkFrontFace front_face);
+ void SetLineWidth(float width);
+ void SetNoCullRasterizationState();
+
+ void SetDepthState(bool depth_test, bool depth_write, VkCompareOp compare_op);
+ void SetNoDepthTestState();
+
+ void AddBlendAttachment(bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor, VkBlendOp op,
+ VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor, VkBlendOp alpha_op,
+ VkColorComponentFlags write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
+ void SetBlendAttachment(u32 attachment, bool blend_enable, VkBlendFactor src_factor, VkBlendFactor dst_factor,
+ VkBlendOp op, VkBlendFactor alpha_src_factor, VkBlendFactor alpha_dst_factor,
+ VkBlendOp alpha_op,
+ VkColorComponentFlags write_mask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT);
+ void ClearBlendAttachments();
+
+ void SetBlendConstants(float r, float g, float b, float a);
+ void SetNoBlendingState();
+
+ void AddDynamicState(VkDynamicState state);
+
+ void SetDynamicViewportAndScissorState();
+ void SetViewport(float x, float y, float width, float height, float min_depth, float max_depth);
+ void SetScissorRect(s32 x, s32 y, u32 width, u32 height);
+
+ void SetMultisamples(VkSampleCountFlagBits samples);
+
+ void SetPipelineLayout(VkPipelineLayout layout);
+ void SetRenderPass(VkRenderPass render_pass, u32 subpass);
+
+private:
+ VkGraphicsPipelineCreateInfo m_ci;
+ std::array m_shader_stages;
+
+ VkPipelineVertexInputStateCreateInfo m_vertex_input_state;
+ std::array m_vertex_buffers;
+ std::array m_vertex_attributes;
+
+ VkPipelineInputAssemblyStateCreateInfo m_input_assembly;
+
+ VkPipelineRasterizationStateCreateInfo m_rasterization_state;
+ VkPipelineDepthStencilStateCreateInfo m_depth_state;
+
+ VkPipelineColorBlendStateCreateInfo m_blend_state;
+ std::array m_blend_attachments;
+
+ VkPipelineViewportStateCreateInfo m_viewport_state;
+ VkViewport m_viewport;
+ VkRect2D m_scissor;
+
+ VkPipelineDynamicStateCreateInfo m_dynamic_state;
+ std::array m_dynamic_state_values;
+
+ VkPipelineMultisampleStateCreateInfo m_multisample_state;
+};
+
+class SamplerBuilder
+{
+public:
+ SamplerBuilder();
+
+ void Clear();
+
+ VkSampler Create(VkDevice device, bool clear = true);
+
+ void SetFilter(VkFilter mag_filter, VkFilter min_filter, VkSamplerMipmapMode mip_filter);
+ void SetAddressMode(VkSamplerAddressMode u, VkSamplerAddressMode v, VkSamplerAddressMode w);
+
+ void SetPointSampler(VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
+ void SetLinearSampler(bool mipmaps, VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER);
+
+private:
+ VkSamplerCreateInfo m_ci;
+};
+
+class DescriptorSetUpdateBuilder
+{
+ enum : u32
+ {
+ MAX_WRITES = 16,
+ MAX_INFOS = 16,
+ };
+
+public:
+ DescriptorSetUpdateBuilder();
+
+ void Clear();
+
+ void Update(VkDevice device, bool clear = true);
+
+ void AddImageDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view,
+ VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ void AddSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkSampler sampler);
+ void AddCombinedImageSamplerDescriptorWrite(VkDescriptorSet set, u32 binding, VkImageView view, VkSampler sampler,
+ VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ void AddBufferDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype, VkBuffer buffer, u32 offset,
+ u32 size);
+ void AddBufferViewDescriptorWrite(VkDescriptorSet set, u32 binding, VkDescriptorType dtype, VkBufferView view);
+
+private:
+ union InfoUnion
+ {
+ VkDescriptorBufferInfo buffer;
+ VkDescriptorImageInfo image;
+ VkBufferView buffer_view;
+ };
+
+ std::array m_writes;
+ u32 m_num_writes = 0;
+
+ std::array m_infos;
+ u32 m_num_infos = 0;
+};
+
+class FramebufferBuilder
+{
+ enum : u32
+ {
+ MAX_ATTACHMENTS = 2,
+ };
+
+public:
+ FramebufferBuilder();
+
+ void Clear();
+
+ VkFramebuffer Create(VkDevice device, bool clear = true);
+
+ void AddAttachment(VkImageView image);
+
+ void SetSize(u32 width, u32 height, u32 layers);
+
+ void SetRenderPass(VkRenderPass render_pass);
+
+private:
+ VkFramebufferCreateInfo m_ci;
+ std::array m_images;
+};
+
+class RenderPassBuilder
+{
+ enum : u32
+ {
+ MAX_ATTACHMENTS = 2,
+ MAX_ATTACHMENT_REFERENCES = 2,
+ MAX_SUBPASSES = 1,
+ };
+
+public:
+ RenderPassBuilder();
+
+ void Clear();
+
+ VkRenderPass Create(VkDevice device, bool clear = true);
+
+ u32 AddAttachment(VkFormat format, VkSampleCountFlagBits samples, VkAttachmentLoadOp load_op,
+ VkAttachmentStoreOp store_op, VkImageLayout initial_layout, VkImageLayout final_layout);
+
+ u32 AddSubpass();
+ void AddSubpassColorAttachment(u32 subpass, u32 attachment, VkImageLayout layout);
+ void AddSubpassDepthAttachment(u32 subpass, u32 attachment, VkImageLayout layout);
+
+private:
+ VkRenderPassCreateInfo m_ci;
+ std::array m_attachments;
+ std::array m_attachment_references;
+ u32 m_num_attachment_references = 0;
+ std::array m_subpasses;
+};
+
+class BufferViewBuilder
+{
+public:
+ BufferViewBuilder();
+
+ void Clear();
+
+ VkBufferView Create(VkDevice device, bool clear = true);
+
+ void Set(VkBuffer buffer, VkFormat format, u32 offset, u32 size);
+
+private:
+ VkBufferViewCreateInfo m_ci;
+};
+
+} // namespace Vulkan
\ No newline at end of file
diff --git a/src/common/vulkan/context.cpp b/src/common/vulkan/context.cpp
new file mode 100644
index 000000000..13a1111fb
--- /dev/null
+++ b/src/common/vulkan/context.cpp
@@ -0,0 +1,1149 @@
+// Copyright 2016 Dolphin Emulator Project
+// Copyright 2020 DuckStation Emulator Project
+// Licensed under GPLv2+
+// Refer to the LICENSE file included.
+
+#include "context.h"
+#include "../assert.h"
+#include "../log.h"
+#include "../window_info.h"
+#include "swap_chain.h"
+#include "util.h"
+#include
+#include
+#include
+Log_SetChannel(Vulkan::Context);
+
+std::unique_ptr g_vulkan_context;
+
+namespace Vulkan {
+
+Context::Context(VkInstance instance, VkPhysicalDevice physical_device)
+ : m_instance(instance), m_physical_device(physical_device)
+{
+ // Read device physical memory properties, we need it for allocating buffers
+ vkGetPhysicalDeviceProperties(physical_device, &m_device_properties);
+ vkGetPhysicalDeviceMemoryProperties(physical_device, &m_device_memory_properties);
+
+ // Would any drivers be this silly? I hope not...
+ m_device_properties.limits.minUniformBufferOffsetAlignment =
+ std::max(m_device_properties.limits.minUniformBufferOffsetAlignment, static_cast(1));
+ m_device_properties.limits.minTexelBufferOffsetAlignment =
+ std::max(m_device_properties.limits.minTexelBufferOffsetAlignment, static_cast(1));
+ m_device_properties.limits.optimalBufferCopyOffsetAlignment =
+ std::max(m_device_properties.limits.optimalBufferCopyOffsetAlignment, static_cast(1));
+ m_device_properties.limits.optimalBufferCopyRowPitchAlignment =
+ std::max(m_device_properties.limits.optimalBufferCopyRowPitchAlignment, static_cast(1));
+}
+
+Context::~Context()
+{
+ WaitForGPUIdle();
+ DestroyRenderPassCache();
+ DestroyGlobalDescriptorPool();
+ DestroyCommandBuffers();
+
+ if (m_device != VK_NULL_HANDLE)
+ vkDestroyDevice(m_device, nullptr);
+
+ if (m_debug_report_callback != VK_NULL_HANDLE)
+ DisableDebugReports();
+
+ vkDestroyInstance(m_instance, nullptr);
+}
+
+bool Context::CheckValidationLayerAvailablility()
+{
+ u32 extension_count = 0;
+ VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
+ return false;
+ }
+
+ std::vector extension_list(extension_count);
+ res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, extension_list.data());
+ Assert(res == VK_SUCCESS);
+
+ u32 layer_count = 0;
+ res = vkEnumerateInstanceLayerProperties(&layer_count, nullptr);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
+ return false;
+ }
+
+ std::vector layer_list(layer_count);
+ res = vkEnumerateInstanceLayerProperties(&layer_count, layer_list.data());
+ Assert(res == VK_SUCCESS);
+
+ // Check for both VK_EXT_debug_report and VK_LAYER_LUNARG_standard_validation
+ return (std::find_if(extension_list.begin(), extension_list.end(),
+ [](const auto& it) {
+ return strcmp(it.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0;
+ }) != extension_list.end() &&
+ std::find_if(layer_list.begin(), layer_list.end(), [](const auto& it) {
+ return strcmp(it.layerName, "VK_LAYER_LUNARG_standard_validation") == 0;
+ }) != layer_list.end());
+}
+
+VkInstance Context::CreateVulkanInstance(bool enable_surface, bool enable_debug_report, bool enable_validation_layer)
+{
+ ExtensionList enabled_extensions;
+ if (!SelectInstanceExtensions(&enabled_extensions, enable_surface, enable_debug_report))
+ return VK_NULL_HANDLE;
+
+ VkApplicationInfo app_info = {};
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.pNext = nullptr;
+ app_info.pApplicationName = "DuckStation";
+ app_info.applicationVersion = VK_MAKE_VERSION(0, 1, 0);
+ app_info.pEngineName = "DuckStation";
+ app_info.engineVersion = VK_MAKE_VERSION(0, 1, 0);
+ app_info.apiVersion = VK_MAKE_VERSION(1, 0, 0);
+
+ VkInstanceCreateInfo instance_create_info = {};
+ instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instance_create_info.pNext = nullptr;
+ instance_create_info.flags = 0;
+ instance_create_info.pApplicationInfo = &app_info;
+ instance_create_info.enabledExtensionCount = static_cast(enabled_extensions.size());
+ instance_create_info.ppEnabledExtensionNames = enabled_extensions.data();
+ instance_create_info.enabledLayerCount = 0;
+ instance_create_info.ppEnabledLayerNames = nullptr;
+
+ // Enable debug layer on debug builds
+ if (enable_validation_layer)
+ {
+ static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"};
+ instance_create_info.enabledLayerCount = 1;
+ instance_create_info.ppEnabledLayerNames = layer_names;
+ }
+
+ VkInstance instance;
+ VkResult res = vkCreateInstance(&instance_create_info, nullptr, &instance);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateInstance failed: ");
+ return nullptr;
+ }
+
+ return instance;
+}
+
+bool Context::SelectInstanceExtensions(ExtensionList* extension_list, bool enable_surface, bool enable_debug_report)
+{
+ u32 extension_count = 0;
+ VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumerateInstanceExtensionProperties failed: ");
+ return false;
+ }
+
+ if (extension_count == 0)
+ {
+ Log_ErrorPrintf("Vulkan: No extensions supported by instance.");
+ return false;
+ }
+
+ std::vector available_extension_list(extension_count);
+ res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, available_extension_list.data());
+ Assert(res == VK_SUCCESS);
+
+ for (const auto& extension_properties : available_extension_list)
+ Log_InfoPrintf("Available extension: %s", extension_properties.extensionName);
+
+ auto SupportsExtension = [&](const char* name, bool required) {
+ if (std::find_if(available_extension_list.begin(), available_extension_list.end(),
+ [&](const VkExtensionProperties& properties) {
+ return !strcmp(name, properties.extensionName);
+ }) != available_extension_list.end())
+ {
+ Log_InfoPrintf("Enabling extension: %s", name);
+ extension_list->push_back(name);
+ return true;
+ }
+
+ if (required)
+ Log_ErrorPrintf("Vulkan: Missing required extension %s.", name);
+
+ return false;
+ };
+
+ // Common extensions
+ if (enable_surface && !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true))
+ return false;
+
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+ if (enable_surface && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
+ return false;
+#elif defined(VK_USE_PLATFORM_XLIB_KHR)
+ if (enable_surface && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true))
+ return false;
+#elif defined(VK_USE_PLATFORM_XCB_KHR)
+ if (enable_surface && !SupportsExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME, true))
+ return false;
+#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+ if (enable_surface && !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
+ return false;
+#endif
+
+ // VK_EXT_debug_report
+ if (enable_debug_report && !SupportsExtension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, false))
+ Log_WarningPrintf("Vulkan: Debug report requested, but extension is not available.");
+
+ return true;
+}
+
+Context::GPUList Context::EnumerateGPUs(VkInstance instance)
+{
+ u32 gpu_count = 0;
+ VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr);
+ if (res != VK_SUCCESS || gpu_count == 0)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
+ return {};
+ }
+
+ GPUList gpus;
+ gpus.resize(gpu_count);
+
+ res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data());
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
+ return {};
+ }
+
+ return gpus;
+}
+
+Context::GPUNameList Context::EnumerateGPUNames(VkInstance instance)
+{
+ u32 gpu_count = 0;
+ VkResult res = vkEnumeratePhysicalDevices(instance, &gpu_count, nullptr);
+ if (res != VK_SUCCESS || gpu_count == 0)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
+ return {};
+ }
+
+ GPUList gpus;
+ gpus.resize(gpu_count);
+
+ res = vkEnumeratePhysicalDevices(instance, &gpu_count, gpus.data());
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumeratePhysicalDevices failed: ");
+ return {};
+ }
+
+ GPUNameList gpu_names;
+ gpu_names.reserve(gpu_count);
+ for (u32 i = 0; i < gpu_count; i++)
+ {
+ VkPhysicalDeviceProperties props = {};
+ vkGetPhysicalDeviceProperties(gpus[i], &props);
+ gpu_names.emplace_back(props.deviceName);
+ }
+
+ return gpu_names;
+}
+
+bool Context::Create(u32 gpu_index, const WindowInfo* wi, std::unique_ptr* out_swap_chain,
+ bool enable_debug_reports, bool enable_validation_layer)
+{
+ AssertMsg(!g_vulkan_context, "Has no current context");
+
+ if (!Vulkan::LoadVulkanLibrary())
+ {
+ Log_ErrorPrintf("Failed to load Vulkan library");
+ return false;
+ }
+
+ const bool enable_surface = (wi && wi->type != WindowInfo::Type::Surfaceless);
+ VkInstance instance = CreateVulkanInstance(enable_surface, enable_debug_reports, enable_validation_layer);
+ if (instance == VK_NULL_HANDLE)
+ {
+ Vulkan::UnloadVulkanLibrary();
+ return false;
+ }
+
+ if (!Vulkan::LoadVulkanInstanceFunctions(instance))
+ {
+ Log_ErrorPrintf("Failed to load Vulkan instance functions");
+ vkDestroyInstance(instance, nullptr);
+ Vulkan::UnloadVulkanLibrary();
+ return false;
+ }
+
+ GPUList gpus = EnumerateGPUs(instance);
+ if (gpus.empty())
+ {
+ vkDestroyInstance(instance, nullptr);
+ Vulkan::UnloadVulkanLibrary();
+ return false;
+ }
+
+ GPUNameList gpu_names = EnumerateGPUNames(instance);
+ for (u32 i = 0; i < gpu_names.size(); i++)
+ Log_InfoPrintf("GPU %u: %s", static_cast(i), gpu_names[i].c_str());
+
+ if (gpu_index >= gpus.size())
+ {
+ Log_WarningPrintf("GPU index (%u) out of range (%u), using first", gpu_index, static_cast(gpus.size()));
+ gpu_index = 0;
+ }
+
+ VkSurfaceKHR surface = VK_NULL_HANDLE;
+ if (enable_surface && (surface = SwapChain::CreateVulkanSurface(instance, *wi)) == VK_NULL_HANDLE)
+ {
+ vkDestroyInstance(instance, nullptr);
+ Vulkan::UnloadVulkanLibrary();
+ return false;
+ }
+
+ g_vulkan_context.reset(new Context(instance, gpus[gpu_index]));
+
+ // Enable debug reports if the "Host GPU" log category is enabled.
+ if (enable_debug_reports)
+ g_vulkan_context->EnableDebugReports();
+
+ // Attempt to create the device.
+ if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer) ||
+ !g_vulkan_context->CreateGlobalDescriptorPool() || !g_vulkan_context->CreateCommandBuffers() ||
+ (enable_surface && (*out_swap_chain = SwapChain::Create(*wi, surface, true)) == nullptr))
+ {
+ // Since we are destroying the instance, we're also responsible for destroying the surface.
+ if (surface != VK_NULL_HANDLE)
+ vkDestroySurfaceKHR(instance, surface, nullptr);
+
+ Vulkan::UnloadVulkanLibrary();
+ g_vulkan_context.reset();
+ return false;
+ }
+
+ return true;
+}
+
+void Context::Destroy()
+{
+ AssertMsg(g_vulkan_context, "Has context");
+ g_vulkan_context.reset();
+}
+
+bool Context::SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface)
+{
+ u32 extension_count = 0;
+ VkResult res = vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, nullptr);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEnumerateDeviceExtensionProperties failed: ");
+ return false;
+ }
+
+ if (extension_count == 0)
+ {
+ Log_ErrorPrintf("Vulkan: No extensions supported by device.");
+ return false;
+ }
+
+ std::vector available_extension_list(extension_count);
+ res =
+ vkEnumerateDeviceExtensionProperties(m_physical_device, nullptr, &extension_count, available_extension_list.data());
+ Assert(res == VK_SUCCESS);
+
+ for (const auto& extension_properties : available_extension_list)
+ Log_InfoPrintf("Available extension: %s", extension_properties.extensionName);
+
+ auto SupportsExtension = [&](const char* name, bool required) {
+ if (std::find_if(available_extension_list.begin(), available_extension_list.end(),
+ [&](const VkExtensionProperties& properties) {
+ return !strcmp(name, properties.extensionName);
+ }) != available_extension_list.end())
+ {
+ Log_InfoPrintf("Enabling extension: %s", name);
+ extension_list->push_back(name);
+ return true;
+ }
+
+ if (required)
+ Log_ErrorPrintf("Vulkan: Missing required extension %s.", name);
+
+ return false;
+ };
+
+ if (enable_surface && !SupportsExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true))
+ return false;
+
+ return true;
+}
+
+bool Context::SelectDeviceFeatures()
+{
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(m_physical_device, &properties);
+
+ VkPhysicalDeviceFeatures available_features;
+ vkGetPhysicalDeviceFeatures(m_physical_device, &available_features);
+
+ // Enable the features we use.
+ m_device_features.dualSrcBlend = available_features.dualSrcBlend;
+ m_device_features.geometryShader = available_features.geometryShader;
+ m_device_features.fillModeNonSolid = available_features.fillModeNonSolid;
+ return true;
+}
+
+bool Context::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer)
+{
+ u32 queue_family_count;
+ vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr);
+ if (queue_family_count == 0)
+ {
+ Log_ErrorPrintf("No queue families found on specified vulkan physical device.");
+ return false;
+ }
+
+ std::vector queue_family_properties(queue_family_count);
+ vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, queue_family_properties.data());
+ Log_InfoPrintf("%u vulkan queue families", queue_family_count);
+
+ // Find graphics and present queues.
+ m_graphics_queue_family_index = queue_family_count;
+ m_present_queue_family_index = queue_family_count;
+ for (uint32_t i = 0; i < queue_family_count; i++)
+ {
+ VkBool32 graphics_supported = queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT;
+ if (graphics_supported)
+ {
+ m_graphics_queue_family_index = i;
+ // Quit now, no need for a present queue.
+ if (!surface)
+ {
+ break;
+ }
+ }
+
+ if (surface)
+ {
+ VkBool32 present_supported;
+ VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(m_physical_device, i, surface, &present_supported);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
+ return false;
+ }
+
+ if (present_supported)
+ {
+ m_present_queue_family_index = i;
+ }
+
+ // Prefer one queue family index that does both graphics and present.
+ if (graphics_supported && present_supported)
+ {
+ break;
+ }
+ }
+ }
+ if (m_graphics_queue_family_index == queue_family_count)
+ {
+ Log_ErrorPrintf("Vulkan: Failed to find an acceptable graphics queue.");
+ return false;
+ }
+ if (surface && m_present_queue_family_index == queue_family_count)
+ {
+ Log_ErrorPrintf("Vulkan: Failed to find an acceptable present queue.");
+ return false;
+ }
+
+ VkDeviceCreateInfo device_info = {};
+ device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ device_info.pNext = nullptr;
+ device_info.flags = 0;
+
+ static constexpr float queue_priorities[] = {1.0f};
+ VkDeviceQueueCreateInfo graphics_queue_info = {};
+ graphics_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ graphics_queue_info.pNext = nullptr;
+ graphics_queue_info.flags = 0;
+ graphics_queue_info.queueFamilyIndex = m_graphics_queue_family_index;
+ graphics_queue_info.queueCount = 1;
+ graphics_queue_info.pQueuePriorities = queue_priorities;
+
+ VkDeviceQueueCreateInfo present_queue_info = {};
+ present_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ present_queue_info.pNext = nullptr;
+ present_queue_info.flags = 0;
+ present_queue_info.queueFamilyIndex = m_present_queue_family_index;
+ present_queue_info.queueCount = 1;
+ present_queue_info.pQueuePriorities = queue_priorities;
+
+ std::array queue_infos = {{
+ graphics_queue_info,
+ present_queue_info,
+ }};
+
+ device_info.queueCreateInfoCount = 1;
+ if (m_graphics_queue_family_index != m_present_queue_family_index)
+ {
+ device_info.queueCreateInfoCount = 2;
+ }
+ device_info.pQueueCreateInfos = queue_infos.data();
+
+ ExtensionList enabled_extensions;
+ if (!SelectDeviceExtensions(&enabled_extensions, surface != VK_NULL_HANDLE))
+ return false;
+
+ device_info.enabledLayerCount = 0;
+ device_info.ppEnabledLayerNames = nullptr;
+ device_info.enabledExtensionCount = static_cast(enabled_extensions.size());
+ device_info.ppEnabledExtensionNames = enabled_extensions.data();
+
+ // Check for required features before creating.
+ if (!SelectDeviceFeatures())
+ return false;
+
+ device_info.pEnabledFeatures = &m_device_features;
+
+ // Enable debug layer on debug builds
+ if (enable_validation_layer)
+ {
+ static const char* layer_names[] = {"VK_LAYER_LUNARG_standard_validation"};
+ device_info.enabledLayerCount = 1;
+ device_info.ppEnabledLayerNames = layer_names;
+ }
+
+ VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateDevice failed: ");
+ return false;
+ }
+
+ // With the device created, we can fill the remaining entry points.
+ if (!LoadVulkanDeviceFunctions(m_device))
+ return false;
+
+ // Grab the graphics and present queues.
+ vkGetDeviceQueue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue);
+ if (surface)
+ {
+ vkGetDeviceQueue(m_device, m_present_queue_family_index, 0, &m_present_queue);
+ }
+ return true;
+}
+
+bool Context::CreateCommandBuffers()
+{
+ VkResult res;
+
+ for (FrameResources& resources : m_frame_resources)
+ {
+ resources.needs_fence_wait = false;
+
+ VkCommandPoolCreateInfo pool_info = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, 0,
+ m_graphics_queue_family_index};
+ res = vkCreateCommandPool(m_device, &pool_info, nullptr, &resources.command_pool);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: ");
+ return false;
+ }
+
+ VkCommandBufferAllocateInfo buffer_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr,
+ resources.command_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
+
+ res = vkAllocateCommandBuffers(m_device, &buffer_info, &resources.command_buffer);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: ");
+ return false;
+ }
+
+ VkFenceCreateInfo fence_info = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, VK_FENCE_CREATE_SIGNALED_BIT};
+
+ res = vkCreateFence(m_device, &fence_info, nullptr, &resources.fence);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
+ return false;
+ }
+
+ // TODO: A better way to choose the number of descriptors.
+ VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1024},
+ {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1024},
+ {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 16}};
+
+ VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ nullptr,
+ 0,
+ 1024, // TODO: tweak this
+ static_cast(countof(pool_sizes)),
+ pool_sizes};
+
+ res = vkCreateDescriptorPool(m_device, &pool_create_info, nullptr, &resources.descriptor_pool);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: ");
+ return false;
+ }
+ }
+
+ ActivateCommandBuffer(0);
+ return true;
+}
+
+void Context::DestroyCommandBuffers()
+{
+ VkDevice device = m_device;
+
+ for (FrameResources& resources : m_frame_resources)
+ {
+ for (auto& it : resources.cleanup_resources)
+ it();
+ resources.cleanup_resources.clear();
+
+ if (resources.fence != VK_NULL_HANDLE)
+ {
+ vkDestroyFence(m_device, resources.fence, nullptr);
+ resources.fence = VK_NULL_HANDLE;
+ }
+ if (resources.descriptor_pool != VK_NULL_HANDLE)
+ {
+ vkDestroyDescriptorPool(m_device, resources.descriptor_pool, nullptr);
+ resources.descriptor_pool = VK_NULL_HANDLE;
+ }
+ if (resources.command_buffer != VK_NULL_HANDLE)
+ {
+ vkFreeCommandBuffers(m_device, resources.command_pool, 1, &resources.command_buffer);
+ resources.command_buffer = VK_NULL_HANDLE;
+ }
+ if (resources.command_pool != VK_NULL_HANDLE)
+ {
+ vkDestroyCommandPool(m_device, resources.command_pool, nullptr);
+ resources.command_pool = VK_NULL_HANDLE;
+ }
+ }
+}
+
+bool Context::CreateGlobalDescriptorPool()
+{
+ // TODO: A better way to choose the number of descriptors.
+ VkDescriptorPoolSize pool_sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1024},
+ {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1024},
+ {VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 16}};
+
+ VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+ nullptr,
+ VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
+ 1024, // TODO: tweak this
+ static_cast(countof(pool_sizes)),
+ pool_sizes};
+
+ VkResult res = vkCreateDescriptorPool(m_device, &pool_create_info, nullptr, &m_global_descriptor_pool);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: ");
+ return false;
+ }
+
+ return true;
+}
+
+void Context::DestroyGlobalDescriptorPool()
+{
+ if (m_global_descriptor_pool != VK_NULL_HANDLE)
+ {
+ vkDestroyDescriptorPool(m_device, m_global_descriptor_pool, nullptr);
+ m_global_descriptor_pool = VK_NULL_HANDLE;
+ }
+}
+
+void Context::DestroyRenderPassCache()
+{
+ for (auto& it : m_render_pass_cache)
+ vkDestroyRenderPass(m_device, it.second, nullptr);
+
+ m_render_pass_cache.clear();
+}
+
+VkDescriptorSet Context::AllocateDescriptorSet(VkDescriptorSetLayout set_layout)
+{
+ VkDescriptorSetAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr,
+ m_frame_resources[m_current_frame].descriptor_pool, 1, &set_layout};
+
+ VkDescriptorSet descriptor_set;
+ VkResult res = vkAllocateDescriptorSets(m_device, &allocate_info, &descriptor_set);
+ if (res != VK_SUCCESS)
+ {
+ // Failing to allocate a descriptor set is not a fatal error, we can
+ // recover by moving to the next command buffer.
+ return VK_NULL_HANDLE;
+ }
+
+ return descriptor_set;
+}
+
+VkDescriptorSet Context::AllocateGlobalDescriptorSet(VkDescriptorSetLayout set_layout)
+{
+ VkDescriptorSetAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr,
+ m_global_descriptor_pool, 1, &set_layout};
+
+ VkDescriptorSet descriptor_set;
+ VkResult res = vkAllocateDescriptorSets(m_device, &allocate_info, &descriptor_set);
+ if (res != VK_SUCCESS)
+ return VK_NULL_HANDLE;
+
+ return descriptor_set;
+}
+
+void Context::FreeGlobalDescriptorSet(VkDescriptorSet set)
+{
+ vkFreeDescriptorSets(m_device, m_global_descriptor_pool, 1, &set);
+}
+
+void Context::WaitForFenceCounter(u64 fence_counter)
+{
+ if (m_completed_fence_counter >= fence_counter)
+ return;
+
+ // Find the first command buffer which covers this counter value.
+ u32 index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
+ while (index != m_current_frame)
+ {
+ if (m_frame_resources[index].fence_counter >= fence_counter)
+ break;
+
+ index = (index + 1) % NUM_COMMAND_BUFFERS;
+ }
+
+ Assert(index != m_current_frame);
+ WaitForCommandBufferCompletion(index);
+}
+
+void Context::WaitForGPUIdle()
+{
+ vkDeviceWaitIdle(m_device);
+}
+
+void Context::WaitForCommandBufferCompletion(u32 index)
+{
+ // Wait for this command buffer to be completed.
+ VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
+
+ // Clean up any resources for command buffers between the last known completed buffer and this
+ // now-completed command buffer. If we use >2 buffers, this may be more than one buffer.
+ const u64 now_completed_counter = m_frame_resources[index].fence_counter;
+ u32 cleanup_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
+ while (cleanup_index != m_current_frame)
+ {
+ FrameResources& resources = m_frame_resources[cleanup_index];
+ if (resources.fence_counter > now_completed_counter)
+ break;
+
+ if (resources.fence_counter > m_completed_fence_counter)
+ {
+ for (auto& it : resources.cleanup_resources)
+ it();
+ resources.cleanup_resources.clear();
+ }
+
+ cleanup_index = (cleanup_index + 1) % NUM_COMMAND_BUFFERS;
+ }
+
+ m_completed_fence_counter = now_completed_counter;
+}
+
+void Context::SubmitCommandBuffer(VkSemaphore wait_semaphore, VkSemaphore signal_semaphore,
+ VkSwapchainKHR present_swap_chain, uint32_t present_image_index)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+
+ // End the current command buffer.
+ VkResult res = vkEndCommandBuffer(resources.command_buffer);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: ");
+ Panic("Failed to end command buffer");
+ }
+
+ // This command buffer now has commands, so can't be re-used without waiting.
+ resources.needs_fence_wait = true;
+
+ // This may be executed on the worker thread, so don't modify any state of the manager class.
+ uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, 0, nullptr, &wait_bits, 1u,
+ &resources.command_buffer, 0, nullptr};
+
+ if (wait_semaphore != VK_NULL_HANDLE)
+ {
+ submit_info.pWaitSemaphores = &wait_semaphore;
+ submit_info.waitSemaphoreCount = 1;
+ }
+
+ if (signal_semaphore != VK_NULL_HANDLE)
+ {
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &signal_semaphore;
+ }
+
+ res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
+ Panic("Failed to submit command buffer.");
+ }
+
+ // Do we have a swap chain to present?
+ if (present_swap_chain != VK_NULL_HANDLE)
+ {
+ // Should have a signal semaphore.
+ Assert(signal_semaphore != VK_NULL_HANDLE);
+ VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+ nullptr,
+ 1,
+ &signal_semaphore,
+ 1,
+ &present_swap_chain,
+ &present_image_index,
+ nullptr};
+
+ res = vkQueuePresentKHR(m_present_queue, &present_info);
+ if (res != VK_SUCCESS)
+ {
+ // VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
+ if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
+ LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
+
+ m_last_present_failed = true;
+ }
+ }
+}
+
+void Context::MoveToNextCommandBuffer()
+{
+ ActivateCommandBuffer((m_current_frame + 1) % NUM_COMMAND_BUFFERS);
+}
+
+void Context::ActivateCommandBuffer(u32 index)
+{
+ FrameResources& resources = m_frame_resources[index];
+
+ // Wait for the GPU to finish with all resources for this command buffer.
+ if (resources.fence_counter > m_completed_fence_counter)
+ WaitForCommandBufferCompletion(index);
+
+ // Reset fence to unsignaled before starting.
+ VkResult res = vkResetFences(m_device, 1, &resources.fence);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkResetFences failed: ");
+
+ // Reset command pools to beginning since we can re-use the memory now
+ res = vkResetCommandPool(m_device, resources.command_pool, 0);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkResetCommandPool failed: ");
+
+ // Enable commands to be recorded to the two buffers again.
+ VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
+ VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr};
+ res = vkBeginCommandBuffer(resources.command_buffer, &begin_info);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: ");
+
+ // Also can do the same for the descriptor pools
+ res = vkResetDescriptorPool(m_device, resources.descriptor_pool, 0);
+ if (res != VK_SUCCESS)
+ LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: ");
+
+ m_current_frame = index;
+ m_current_command_buffer = resources.command_buffer;
+ resources.fence_counter = m_next_fence_counter++;
+}
+
+void Context::ExecuteCommandBuffer(bool wait_for_completion)
+{
+ // If we're waiting for completion, don't bother waking the worker thread.
+ const u32 current_frame = m_current_frame;
+ SubmitCommandBuffer();
+ MoveToNextCommandBuffer();
+
+ if (wait_for_completion)
+ WaitForCommandBufferCompletion(current_frame);
+}
+
+bool Context::CheckLastPresentFail()
+{
+ bool res = m_last_present_failed;
+ m_last_present_failed = false;
+ return res;
+}
+
+void Context::DeferBufferDestruction(VkBuffer object)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+ resources.cleanup_resources.push_back([this, object]() { vkDestroyBuffer(m_device, object, nullptr); });
+}
+
+void Context::DeferBufferViewDestruction(VkBufferView object)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+ resources.cleanup_resources.push_back([this, object]() { vkDestroyBufferView(m_device, object, nullptr); });
+}
+
+void Context::DeferDeviceMemoryDestruction(VkDeviceMemory object)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+ resources.cleanup_resources.push_back([this, object]() { vkFreeMemory(m_device, object, nullptr); });
+}
+
+void Context::DeferFramebufferDestruction(VkFramebuffer object)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+ resources.cleanup_resources.push_back([this, object]() { vkDestroyFramebuffer(m_device, object, nullptr); });
+}
+
+void Context::DeferImageDestruction(VkImage object)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+ resources.cleanup_resources.push_back([this, object]() { vkDestroyImage(m_device, object, nullptr); });
+}
+
+void Context::DeferImageViewDestruction(VkImageView object)
+{
+ FrameResources& resources = m_frame_resources[m_current_frame];
+ resources.cleanup_resources.push_back([this, object]() { vkDestroyImageView(m_device, object, nullptr); });
+}
+
+static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags,
+ VkDebugReportObjectTypeEXT objectType, uint64_t object,
+ size_t location, int32_t messageCode,
+ const char* pLayerPrefix, const char* pMessage,
+ void* pUserData)
+{
+ LOGLEVEL level;
+ if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
+ level = LOGLEVEL_ERROR;
+ else if (flags & (VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT))
+ level = LOGLEVEL_WARNING;
+ else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
+ level = LOGLEVEL_INFO;
+ else
+ level = LOGLEVEL_DEBUG;
+
+ Log::Writef("Vulkan", __func__, level, "Vulkan debug report: (%s) %s", pLayerPrefix ? pLayerPrefix : "", pMessage);
+ return VK_FALSE;
+}
+
+bool Context::EnableDebugReports()
+{
+ // Already enabled?
+ if (m_debug_report_callback != VK_NULL_HANDLE)
+ return true;
+
+ // Check for presence of the functions before calling
+ if (!vkCreateDebugReportCallbackEXT || !vkDestroyDebugReportCallbackEXT || !vkDebugReportMessageEXT)
+ {
+ return false;
+ }
+
+ VkDebugReportCallbackCreateInfoEXT callback_info = {
+ VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, nullptr,
+ VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
+ VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT,
+ DebugReportCallback, nullptr};
+
+ VkResult res = vkCreateDebugReportCallbackEXT(m_instance, &callback_info, nullptr, &m_debug_report_callback);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateDebugReportCallbackEXT failed: ");
+ return false;
+ }
+
+ return true;
+}
+
+void Context::DisableDebugReports()
+{
+ if (m_debug_report_callback != VK_NULL_HANDLE)
+ {
+ vkDestroyDebugReportCallbackEXT(m_instance, m_debug_report_callback, nullptr);
+ m_debug_report_callback = VK_NULL_HANDLE;
+ }
+}
+
+bool Context::GetMemoryType(u32 bits, VkMemoryPropertyFlags properties, u32* out_type_index)
+{
+ for (u32 i = 0; i < VK_MAX_MEMORY_TYPES; i++)
+ {
+ if ((bits & (1 << i)) != 0)
+ {
+ u32 supported = m_device_memory_properties.memoryTypes[i].propertyFlags & properties;
+ if (supported == properties)
+ {
+ *out_type_index = i;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+u32 Context::GetMemoryType(u32 bits, VkMemoryPropertyFlags properties)
+{
+ u32 type_index = VK_MAX_MEMORY_TYPES;
+ if (!GetMemoryType(bits, properties, &type_index))
+ {
+ Log_ErrorPrintf("Unable to find memory type for %x:%x", bits, properties);
+ Panic("Unable to find memory type");
+ }
+
+ return type_index;
+}
+
+u32 Context::GetUploadMemoryType(u32 bits, bool* is_coherent)
+{
+ // Try for coherent memory first.
+ VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+
+ u32 type_index;
+ if (!GetMemoryType(bits, flags, &type_index))
+ {
+ Log_WarningPrintf("Vulkan: Failed to find a coherent memory type for uploads, this will affect performance.");
+
+ // Try non-coherent memory.
+ flags &= ~VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ if (!GetMemoryType(bits, flags, &type_index))
+ {
+ // We shouldn't have any memory types that aren't host-visible.
+ Panic("Unable to get memory type for upload.");
+ type_index = 0;
+ }
+ }
+
+ if (is_coherent)
+ *is_coherent = ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0);
+
+ return type_index;
+}
+
+u32 Context::GetReadbackMemoryType(u32 bits, bool* is_coherent, bool* is_cached)
+{
+ // Try for cached and coherent memory first.
+ VkMemoryPropertyFlags flags =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+
+ u32 type_index;
+ if (!GetMemoryType(bits, flags, &type_index))
+ {
+ // For readbacks, caching is more important than coherency.
+ flags &= ~VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ if (!GetMemoryType(bits, flags, &type_index))
+ {
+ Log_WarningPrintf("Vulkan: Failed to find a cached memory type for readbacks, this will affect "
+ "performance.");
+
+ // Remove the cached bit as well.
+ flags &= ~VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
+ if (!GetMemoryType(bits, flags, &type_index))
+ {
+ // We shouldn't have any memory types that aren't host-visible.
+ Panic("Unable to get memory type for upload.");
+ type_index = 0;
+ }
+ }
+ }
+
+ if (is_coherent)
+ *is_coherent = ((flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0);
+ if (is_cached)
+ *is_cached = ((flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0);
+
+ return type_index;
+}
+
+VkRenderPass Context::GetRenderPass(VkFormat color_format, VkFormat depth_format, VkSampleCountFlagBits samples,
+ VkAttachmentLoadOp load_op)
+{
+ auto key = std::tie(color_format, depth_format, samples, load_op);
+ auto it = m_render_pass_cache.find(key);
+ if (it != m_render_pass_cache.end())
+ return it->second;
+
+ VkAttachmentReference color_reference;
+ VkAttachmentReference* color_reference_ptr = nullptr;
+ VkAttachmentReference depth_reference;
+ VkAttachmentReference* depth_reference_ptr = nullptr;
+ std::array attachments;
+ u32 num_attachments = 0;
+ if (color_format != VK_FORMAT_UNDEFINED)
+ {
+ attachments[num_attachments] = {0,
+ color_format,
+ samples,
+ load_op,
+ VK_ATTACHMENT_STORE_OP_STORE,
+ VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+ VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
+ color_reference.attachment = num_attachments;
+ color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ color_reference_ptr = &color_reference;
+ num_attachments++;
+ }
+ if (depth_format != VK_FORMAT_UNDEFINED)
+ {
+ attachments[num_attachments] = {0,
+ depth_format,
+ samples,
+ load_op,
+ VK_ATTACHMENT_STORE_OP_STORE,
+ VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+ VK_ATTACHMENT_STORE_OP_DONT_CARE,
+ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+ VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
+ depth_reference.attachment = num_attachments;
+ depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ depth_reference_ptr = &depth_reference;
+ num_attachments++;
+ }
+
+ VkSubpassDescription subpass = {0,
+ VK_PIPELINE_BIND_POINT_GRAPHICS,
+ 0,
+ nullptr,
+ color_reference_ptr ? 1u : 0u,
+ color_reference_ptr ? color_reference_ptr : nullptr,
+ nullptr,
+ depth_reference_ptr,
+ 0,
+ nullptr};
+ VkRenderPassCreateInfo pass_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+ nullptr,
+ 0,
+ num_attachments,
+ attachments.data(),
+ 1,
+ &subpass,
+ 0,
+ nullptr};
+
+ VkRenderPass pass;
+ VkResult res = vkCreateRenderPass(m_device, &pass_info, nullptr, &pass);
+ if (res != VK_SUCCESS)
+ {
+ LOG_VULKAN_ERROR(res, "vkCreateRenderPass failed: ");
+ return VK_NULL_HANDLE;
+ }
+
+ m_render_pass_cache.emplace(key, pass);
+ return pass;
+}
+
+} // namespace Vulkan
diff --git a/src/common/vulkan/context.h b/src/common/vulkan/context.h
new file mode 100644
index 000000000..31163c38e
--- /dev/null
+++ b/src/common/vulkan/context.h
@@ -0,0 +1,229 @@
+// Copyright 2016 Dolphin Emulator Project
+// Copyright 2020 DuckStation Emulator Project
+// Licensed under GPLv2+
+// Refer to the LICENSE file included.
+
+#pragma once
+
+#include "../types.h"
+#include "vulkan_loader.h"
+#include
+#include
+#include