renderer_vulkan: Refactor VulkanState class and switch to dynamic rendering

* Dynamic rendering gives us no performance loss on desktop while removing the need
to cache and manager many renderpass and framebuffer objects
This commit is contained in:
GPUCode
2022-05-06 22:26:57 +03:00
parent 5858bd3116
commit 1f967b4770
7 changed files with 236 additions and 656 deletions

View File

@ -205,7 +205,7 @@ VKTexture RasterizerCacheVulkan::AllocateSurfaceTexture(vk::Format format, u32 w
return texture;
}
/*static bool FillSurface(const Surface& surface, const u8* fill_data,
static bool FillSurface(const Surface& surface, const u8* fill_data,
const Common::Rectangle<u32>& fill_rect) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
@ -223,16 +223,11 @@ VKTexture RasterizerCacheVulkan::AllocateSurfaceTexture(vk::Format format, u32 w
surface->InvalidateAllWatcher();
if (surface->type == SurfaceType::Color || surface->type == SurfaceType::Texture) {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
surface->texture.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
Pica::Texture::TextureInfo tex_info{};
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface->pixel_format);
Common::Vec4<u8> color = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info);
std::array<GLfloat, 4> color_values = {color.x / 255.f, color.y / 255.f, color.z / 255.f,
std::array<float, 4> color_values = {color.x / 255.f, color.y / 255.f, color.z / 255.f,
color.w / 255.f};
state.color_mask.red_enabled = GL_TRUE;
@ -278,7 +273,7 @@ VKTexture RasterizerCacheVulkan::AllocateSurfaceTexture(vk::Format format, u32 w
glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int);
}
return true;
}*/
}
CachedSurface::~CachedSurface() {
if (texture.IsValid()) {

View File

@ -10,15 +10,13 @@
namespace Vulkan {
VKResourceCache::~VKResourceCache()
{
VKResourceCache::~VKResourceCache() {
for (int i = 0; i < DESCRIPTOR_SET_LAYOUT_COUNT; i++) {
g_vk_instace->GetDevice().destroyDescriptorSetLayout(descriptor_layouts[i]);
}
}
bool VKResourceCache::Initialize()
{
bool VKResourceCache::Initialize() {
// Define the descriptor sets we will be using
std::array<vk::DescriptorSetLayoutBinding, 2> ubo_set = {{
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex |
@ -54,58 +52,22 @@ bool VKResourceCache::Initialize()
vk::PipelineLayoutCreateInfo layout_info({}, descriptor_layouts);
pipeline_layout = g_vk_instace->GetDevice().createPipelineLayoutUnique(layout_info);
// Create global texture staging buffer
texture_upload_buffer.Create(MAX_TEXTURE_UPLOAD_BUFFER_SIZE,
vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
vk::BufferUsageFlagBits::eTransferSrc);
return true;
}
vk::Sampler VKResourceCache::GetSampler(const SamplerInfo& info)
{
auto iter = sampler_cache.find(info);
if (iter != sampler_cache.end()) {
return iter->second;
}
// Create texture sampler
auto properties = g_vk_instace->GetPhysicalDevice().getProperties();
auto features = g_vk_instace->GetPhysicalDevice().getFeatures();
vk::SamplerCreateInfo sampler_info
(
{},
info.mag_filter,
info.min_filter,
info.mipmap_mode,
info.wrapping[0], info.wrapping[1], info.wrapping[2],
{},
features.samplerAnisotropy,
properties.limits.maxSamplerAnisotropy,
false,
vk::CompareOp::eAlways,
{},
{},
vk::BorderColor::eFloatTransparentBlack,
false
);
auto sampler = g_vk_instace->GetDevice().createSamplerUnique(sampler_info);
vk::Sampler handle = sampler.get();
// Store it even if it failed
sampler_cache.emplace(info, std::move(sampler));
return handle;
}
vk::RenderPass VKResourceCache::GetRenderPass(vk::Format color_format, vk::Format depth_format,
u32 multisamples, vk::AttachmentLoadOp load_op)
{
vk::SampleCountFlagBits multisamples,
vk::AttachmentLoadOp load_op) {
// Search the cache if we can reuse an already created renderpass
auto key = std::tie(color_format, depth_format, multisamples, load_op);
auto it = render_pass_cache.find(key);
if (it != render_pass_cache.end()) {
return it->second;
RenderPassCacheKey key = {
.color = color_format,
.depth = depth_format,
.samples = multisamples
};
auto it = renderpass_cache.find(key);
if (it != renderpass_cache.end()) {
return it->second.get();
}
// Otherwise create a new one with the parameters provided
@ -120,7 +82,7 @@ vk::RenderPass VKResourceCache::GetRenderPass(vk::Format color_format, vk::Forma
{
{},
color_format,
static_cast<vk::SampleCountFlagBits>(multisamples),
multisamples,
load_op,
vk::AttachmentStoreOp::eStore,
vk::AttachmentLoadOp::eDontCare,
@ -157,7 +119,7 @@ vk::RenderPass VKResourceCache::GetRenderPass(vk::Format color_format, vk::Forma
auto renderpass = g_vk_instace->GetDevice().createRenderPassUnique(renderpass_info);
vk::RenderPass handle = renderpass.get();
render_pass_cache.emplace(key, std::move(renderpass));
renderpass_cache.emplace(key, std::move(renderpass));
return handle;
}
} // namespace Vulkan

View File

@ -15,13 +15,16 @@
namespace Vulkan {
using RenderPassCacheKey = std::tuple<vk::Format, vk::Format, u32, vk::AttachmentLoadOp>;
struct RenderPassCacheKey {
vk::Format color, depth;
vk::SampleCountFlagBits samples;
};
constexpr u32 MAX_TEXTURE_UPLOAD_BUFFER_SIZE = 32 * 1024 * 1024;
constexpr u32 DESCRIPTOR_SET_LAYOUT_COUNT = 3;
class VKResourceCache
{
/// Wrapper class that manages resource caching and storage.
/// It stores pipelines and renderpasses
class VKResourceCache {
public:
VKResourceCache() = default;
~VKResourceCache();
@ -31,9 +34,12 @@ public:
void Shutdown();
// Public interface.
vk::Sampler GetSampler(const SamplerInfo& info);
vk::RenderPass GetRenderPass(vk::Format color_format, vk::Format depth_format, u32 multisamples, vk::AttachmentLoadOp load_op);
vk::PipelineCache GetPipelineCache() const { return pipeline_cache.get(); }
vk::RenderPass GetRenderPass(vk::Format color_format, vk::Format depth_format,
vk::SampleCountFlagBits multisamples,
vk::AttachmentLoadOp load_op);
auto& GetDescriptorLayouts() const { return descriptor_layouts; }
private:
// Descriptor sets
@ -41,8 +47,7 @@ private:
vk::UniquePipelineLayout pipeline_layout;
// Render pass cache
std::unordered_map<RenderPassCacheKey, vk::RenderPass> render_pass_cache;
std::unordered_map<SamplerInfo, vk::Sampler> sampler_cache;
std::unordered_map<RenderPassCacheKey, vk::UniqueRenderPass> renderpass_cache;
vk::UniquePipelineCache pipeline_cache;
std::string pipeline_cache_filename;

View File

@ -3,6 +3,8 @@
// Refer to the license.txt file included.
#include "video_core/renderer_vulkan/vk_state.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_resource_cache.h"
namespace Vulkan {
@ -16,6 +18,11 @@ DirtyState operator |=(DirtyState lhs, DirtyState rhs) {
);
}
bool operator &(DirtyState lhs, DirtyState rhs) {
return static_cast<unsigned>(lhs) &
static_cast<unsigned>(rhs);
}
void VulkanState::Create() {
// Create a dummy texture which can be used in place of a real binding.
VKTexture::Info info = {
@ -27,7 +34,24 @@ void VulkanState::Create() {
};
dummy_texture.Create(info);
//dummy_texture.TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
dummy_texture.TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, g_vk_task_scheduler->GetCommandBuffer());
// Create descriptor pool
// TODO: Choose sizes more wisely
const std::array<vk::DescriptorPoolSize, 3> pool_sizes{{
{ vk::DescriptorType::eUniformBuffer, 32 },
{ vk::DescriptorType::eCombinedImageSampler, 32 },
{ vk::DescriptorType::eStorageTexelBuffer, 32 },
}};
auto& device = g_vk_instace->GetDevice();
vk::DescriptorPoolCreateInfo pool_create_info({}, 1024, pool_sizes);
desc_pool = device.createDescriptorPoolUnique(pool_create_info);
// Create descriptor sets
auto& layouts = g_vk_res_cache->GetDescriptorLayouts();
vk::DescriptorSetAllocateInfo alloc_info(desc_pool.get(), layouts);
descriptor_sets = device.allocateDescriptorSetsUnique(alloc_info);
dirty_flags |= DirtyState::All;
}
@ -38,31 +62,19 @@ void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset) {
}
vertex_buffer = buffer;
vertex_buffer_offset = offset;
vertex_offset = offset;
dirty_flags |= DirtyState::VertexBuffer;
}
void VulkanState::SetFramebuffer(VKFramebuffer* buffer) {
// Should not be changed within a render pass.
//ASSERT(!InRenderPass());
//framebuffer = buffer;
}
void VulkanState::SetPipeline(const VKPipeline* new_pipeline) {
if (new_pipeline == pipeline)
return;
pipeline = new_pipeline;
dirty_flags |= DirtyState::Pipeline;
}
void VulkanState::SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u32 size) {
auto& binding = bindings.ubo[static_cast<u32>(id)];
u32 index = static_cast<u32>(id);
auto& binding = bindings.ubo[index];
if (binding.buffer != buffer->GetBuffer() || binding.range != size)
{
binding.buffer = buffer->GetBuffer();
binding.range = size;
dirty_flags |= DirtyState::Uniform;
bindings.ubo_update[index] = true;
}
}
@ -75,6 +87,7 @@ void VulkanState::SetTexture(TextureID id, VKTexture* texture) {
bindings.texture[index].imageView = texture->GetView();
bindings.texture[index].imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
dirty_flags |= DirtyState::Texture;
bindings.texture_update[index] = true;
}
void VulkanState::SetTexelBuffer(TexelBufferID id, VKBuffer* buffer) {
@ -85,10 +98,7 @@ void VulkanState::SetTexelBuffer(TexelBufferID id, VKBuffer* buffer) {
bindings.lut[index].buffer = buffer->GetBuffer();
dirty_flags |= DirtyState::TexelBuffer;
}
void VulkanState::SetImageTexture(VKTexture* image) {
// TODO
bindings.lut_update[index] = true;
}
void VulkanState::UnbindTexture(VKTexture* image) {
@ -102,468 +112,140 @@ void VulkanState::UnbindTexture(VKTexture* image) {
}
}
void VulkanState::BeginRenderPass()
{
if (InRenderPass())
return;
m_current_render_pass = m_framebuffer->GetLoadRenderPass();
m_framebuffer_render_area = m_framebuffer->GetRect();
VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
m_current_render_pass,
m_framebuffer->GetFB(),
m_framebuffer_render_area,
0,
nullptr};
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
VK_SUBPASS_CONTENTS_INLINE);
void VulkanState::SetAttachments(VKTexture* color, VKTexture* depth_stencil) {
color_attachment = color;
depth_attachment = depth_stencil;
}
void StateTracker::BeginDiscardRenderPass()
{
if (InRenderPass())
return;
m_current_render_pass = m_framebuffer->GetDiscardRenderPass();
m_framebuffer_render_area = m_framebuffer->GetRect();
VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
m_current_render_pass,
m_framebuffer->GetFB(),
m_framebuffer_render_area,
0,
nullptr};
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
VK_SUBPASS_CONTENTS_INLINE);
void VulkanState::SetRenderArea(vk::Rect2D new_render_area) {
render_area = new_render_area;
}
void StateTracker::EndRenderPass()
{
if (!InRenderPass())
return;
vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer());
m_current_render_pass = VK_NULL_HANDLE;
}
void StateTracker::BeginClearRenderPass(const VkRect2D& area, const VkClearValue* clear_values,
u32 num_clear_values)
{
ASSERT(!InRenderPass());
m_current_render_pass = m_framebuffer->GetClearRenderPass();
m_framebuffer_render_area = area;
VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
m_current_render_pass,
m_framebuffer->GetFB(),
m_framebuffer_render_area,
num_clear_values,
clear_values};
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
VK_SUBPASS_CONTENTS_INLINE);
}
void StateTracker::SetViewport(const VkViewport& viewport)
{
if (memcmp(&m_viewport, &viewport, sizeof(viewport)) == 0)
return;
m_viewport = viewport;
m_dirty_flags |= DIRTY_FLAG_VIEWPORT;
}
void StateTracker::SetScissor(const VkRect2D& scissor)
{
if (memcmp(&m_scissor, &scissor, sizeof(scissor)) == 0)
return;
m_scissor = scissor;
m_dirty_flags |= DIRTY_FLAG_SCISSOR;
}
bool StateTracker::Bind()
{
// Must have a pipeline.
if (!m_pipeline)
return false;
// Check the render area if we were in a clear pass.
if (m_current_render_pass == m_framebuffer->GetClearRenderPass() && !IsViewportWithinRenderArea())
EndRenderPass();
// Get a new descriptor set if any parts have changed
if (!UpdateDescriptorSet())
{
// We can fail to allocate descriptors if we exhaust the pool for this command buffer.
WARN_LOG_FMT(VIDEO, "Failed to get a descriptor set, executing buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false, false);
if (!UpdateDescriptorSet())
{
// Something strange going on.
ERROR_LOG_FMT(VIDEO, "Failed to get descriptor set, skipping draw");
return false;
}
}
// Start render pass if not already started
if (!InRenderPass())
BeginRenderPass();
// Re-bind parts of the pipeline
const VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
if (m_dirty_flags & DIRTY_FLAG_VERTEX_BUFFER)
vkCmdBindVertexBuffers(command_buffer, 0, 1, &m_vertex_buffer, &m_vertex_buffer_offset);
if (m_dirty_flags & DIRTY_FLAG_INDEX_BUFFER)
vkCmdBindIndexBuffer(command_buffer, m_index_buffer, m_index_buffer_offset, m_index_type);
if (m_dirty_flags & DIRTY_FLAG_PIPELINE)
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipeline());
if (m_dirty_flags & DIRTY_FLAG_VIEWPORT)
vkCmdSetViewport(command_buffer, 0, 1, &m_viewport);
if (m_dirty_flags & DIRTY_FLAG_SCISSOR)
vkCmdSetScissor(command_buffer, 0, 1, &m_scissor);
m_dirty_flags &= ~(DIRTY_FLAG_VERTEX_BUFFER | DIRTY_FLAG_INDEX_BUFFER | DIRTY_FLAG_PIPELINE |
DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR);
return true;
}
bool StateTracker::BindCompute()
{
if (!m_compute_shader)
return false;
// Can't kick compute in a render pass.
if (InRenderPass())
EndRenderPass();
const VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
if (m_dirty_flags & DIRTY_FLAG_COMPUTE_SHADER)
{
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE,
m_compute_shader->GetComputePipeline());
}
if (!UpdateComputeDescriptorSet())
{
WARN_LOG_FMT(VIDEO, "Failed to get a compute descriptor set, executing buffer");
Renderer::GetInstance()->ExecuteCommandBuffer(false, false);
if (!UpdateComputeDescriptorSet())
{
// Something strange going on.
ERROR_LOG_FMT(VIDEO, "Failed to get descriptor set, skipping dispatch");
return false;
}
}
m_dirty_flags &= ~DIRTY_FLAG_COMPUTE_SHADER;
return true;
}
bool StateTracker::IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const
{
// Check that the viewport does not lie outside the render area.
// If it does, we need to switch to a normal load/store render pass.
s32 left = m_framebuffer_render_area.offset.x;
s32 top = m_framebuffer_render_area.offset.y;
s32 right = left + static_cast<s32>(m_framebuffer_render_area.extent.width);
s32 bottom = top + static_cast<s32>(m_framebuffer_render_area.extent.height);
s32 test_left = x;
s32 test_top = y;
s32 test_right = test_left + static_cast<s32>(width);
s32 test_bottom = test_top + static_cast<s32>(height);
return test_left >= left && test_right <= right && test_top >= top && test_bottom <= bottom;
}
bool StateTracker::IsViewportWithinRenderArea() const
{
return IsWithinRenderArea(static_cast<s32>(m_viewport.x), static_cast<s32>(m_viewport.y),
static_cast<u32>(m_viewport.width),
static_cast<u32>(m_viewport.height));
}
void StateTracker::EndClearRenderPass()
{
if (m_current_render_pass != m_framebuffer->GetClearRenderPass())
return;
// End clear render pass. Bind() will call BeginRenderPass() which
// will switch to the load/store render pass.
EndRenderPass();
}
bool StateTracker::UpdateDescriptorSet()
{
if (m_pipeline->GetUsage() == AbstractPipelineUsage::GX)
return UpdateGXDescriptorSet();
else
return UpdateUtilityDescriptorSet();
}
bool StateTracker::UpdateGXDescriptorSet()
{
const size_t MAX_DESCRIPTOR_WRITES = NUM_UBO_DESCRIPTOR_SET_BINDINGS + // UBO
1 + // Samplers
1; // SSBO
std::array<VkWriteDescriptorSet, MAX_DESCRIPTOR_WRITES> writes;
u32 num_writes = 0;
if (m_dirty_flags & DIRTY_FLAG_GX_UBOS || m_gx_descriptor_sets[0] == VK_NULL_HANDLE)
{
m_gx_descriptor_sets[0] = g_command_buffer_mgr->AllocateDescriptorSet(
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS));
if (m_gx_descriptor_sets[0] == VK_NULL_HANDLE)
return false;
for (size_t i = 0; i < NUM_UBO_DESCRIPTOR_SET_BINDINGS; i++)
{
if (i == UBO_DESCRIPTOR_SET_BINDING_GS &&
!g_ActiveConfig.backend_info.bSupportsGeometryShaders)
{
continue;
}
writes[num_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_gx_descriptor_sets[0],
static_cast<uint32_t>(i),
0,
1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
nullptr,
&m_bindings.gx_ubo_bindings[i],
nullptr};
void VulkanState::BeginRendering() {
if (rendering) {
return;
}
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_GX_UBOS) | DIRTY_FLAG_DESCRIPTOR_SETS;
}
// Make sure attachments are in optimal layout
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
if (color_attachment->GetLayout() != vk::ImageLayout::eColorAttachmentOptimal) {
color_attachment->TransitionLayout(vk::ImageLayout::eColorAttachmentOptimal, command_buffer);
}
if (m_dirty_flags & DIRTY_FLAG_GX_SAMPLERS || m_gx_descriptor_sets[1] == VK_NULL_HANDLE)
{
m_gx_descriptor_sets[1] = g_command_buffer_mgr->AllocateDescriptorSet(
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_STANDARD_SAMPLERS));
if (m_gx_descriptor_sets[1] == VK_NULL_HANDLE)
return false;
if (depth_attachment->GetLayout() != vk::ImageLayout::eDepthStencilAttachmentOptimal) {
depth_attachment->TransitionLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal, command_buffer);
}
writes[num_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_gx_descriptor_sets[1],
0,
0,
static_cast<u32>(NUM_PIXEL_SHADER_SAMPLERS),
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
m_bindings.samplers.data(),
nullptr,
nullptr};
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_GX_SAMPLERS) | DIRTY_FLAG_DESCRIPTOR_SETS;
}
// Begin rendering
vk::RenderingAttachmentInfoKHR color_info(color_attachment->GetView(), color_attachment->GetLayout());
vk::RenderingAttachmentInfoKHR depth_stencil_info(depth_attachment->GetView(), depth_attachment->GetLayout());
if (g_ActiveConfig.backend_info.bSupportsBBox &&
(m_dirty_flags & DIRTY_FLAG_GX_SSBO || m_gx_descriptor_sets[2] == VK_NULL_HANDLE))
{
m_gx_descriptor_sets[2] =
g_command_buffer_mgr->AllocateDescriptorSet(g_object_cache->GetDescriptorSetLayout(
DESCRIPTOR_SET_LAYOUT_STANDARD_SHADER_STORAGE_BUFFERS));
if (m_gx_descriptor_sets[2] == VK_NULL_HANDLE)
return false;
vk::RenderingInfo render_info
(
{}, render_area, 1, {},
color_info,
&depth_stencil_info,
&depth_stencil_info
);
writes[num_writes++] = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, m_gx_descriptor_sets[2], 0, 0, 1,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nullptr, &m_bindings.ssbo, nullptr};
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_GX_SSBO) | DIRTY_FLAG_DESCRIPTOR_SETS;
}
if (num_writes > 0)
vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), num_writes, writes.data(), 0, nullptr);
if (m_dirty_flags & DIRTY_FLAG_DESCRIPTOR_SETS)
{
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
g_ActiveConfig.backend_info.bSupportsBBox ?
NUM_GX_DESCRIPTOR_SETS :
(NUM_GX_DESCRIPTOR_SETS - 1),
m_gx_descriptor_sets.data(),
g_ActiveConfig.backend_info.bSupportsGeometryShaders ?
NUM_UBO_DESCRIPTOR_SET_BINDINGS :
(NUM_UBO_DESCRIPTOR_SET_BINDINGS - 1),
m_bindings.gx_ubo_offsets.data());
m_dirty_flags &= ~(DIRTY_FLAG_DESCRIPTOR_SETS | DIRTY_FLAG_GX_UBO_OFFSETS);
}
else if (m_dirty_flags & DIRTY_FLAG_GX_UBO_OFFSETS)
{
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
1, m_gx_descriptor_sets.data(),
g_ActiveConfig.backend_info.bSupportsGeometryShaders ?
NUM_UBO_DESCRIPTOR_SET_BINDINGS :
(NUM_UBO_DESCRIPTOR_SET_BINDINGS - 1),
m_bindings.gx_ubo_offsets.data());
m_dirty_flags &= ~DIRTY_FLAG_GX_UBO_OFFSETS;
}
return true;
command_buffer.beginRendering(render_info);
rendering = true;
}
bool StateTracker::UpdateUtilityDescriptorSet()
{
// Max number of updates - UBO, Samplers, TexelBuffer
std::array<VkWriteDescriptorSet, 3> dswrites;
u32 writes = 0;
void VulkanState::EndRendering() {
if (!rendering) {
return;
}
// Allocate descriptor sets.
if (m_dirty_flags & DIRTY_FLAG_UTILITY_UBO || m_utility_descriptor_sets[0] == VK_NULL_HANDLE)
{
m_utility_descriptor_sets[0] = g_command_buffer_mgr->AllocateDescriptorSet(
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_UTILITY_UNIFORM_BUFFER));
if (!m_utility_descriptor_sets[0])
return false;
dswrites[writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_utility_descriptor_sets[0],
0,
0,
1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
nullptr,
&m_bindings.utility_ubo_binding,
nullptr};
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_UTILITY_UBO) | DIRTY_FLAG_DESCRIPTOR_SETS;
}
if (m_dirty_flags & DIRTY_FLAG_UTILITY_BINDINGS || m_utility_descriptor_sets[1] == VK_NULL_HANDLE)
{
m_utility_descriptor_sets[1] = g_command_buffer_mgr->AllocateDescriptorSet(
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_UTILITY_SAMPLERS));
if (!m_utility_descriptor_sets[1])
return false;
dswrites[writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_utility_descriptor_sets[1],
0,
0,
NUM_PIXEL_SHADER_SAMPLERS,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
m_bindings.samplers.data(),
nullptr,
nullptr};
dswrites[writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_utility_descriptor_sets[1],
8,
0,
1,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
nullptr,
nullptr,
m_bindings.texel_buffers.data()};
m_dirty_flags = (m_dirty_flags & ~DIRTY_FLAG_UTILITY_BINDINGS) | DIRTY_FLAG_DESCRIPTOR_SETS;
}
if (writes > 0)
vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), writes, dswrites.data(), 0, nullptr);
if (m_dirty_flags & DIRTY_FLAG_DESCRIPTOR_SETS)
{
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
NUM_UTILITY_DESCRIPTOR_SETS, m_utility_descriptor_sets.data(), 1,
&m_bindings.utility_ubo_offset);
m_dirty_flags &= ~(DIRTY_FLAG_DESCRIPTOR_SETS | DIRTY_FLAG_UTILITY_UBO_OFFSET);
}
else if (m_dirty_flags & DIRTY_FLAG_UTILITY_UBO_OFFSET)
{
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
1, m_utility_descriptor_sets.data(), 1, &m_bindings.utility_ubo_offset);
m_dirty_flags &= ~(DIRTY_FLAG_DESCRIPTOR_SETS | DIRTY_FLAG_UTILITY_UBO_OFFSET);
}
return true;
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.endRendering();
rendering = false;
}
bool StateTracker::UpdateComputeDescriptorSet()
{
// Max number of updates - UBO, Samplers, TexelBuffer, Image
std::array<VkWriteDescriptorSet, 4> dswrites;
void VulkanState::SetViewport(vk::Viewport new_viewport) {
if (new_viewport == viewport) {
return;
}
// Allocate descriptor sets.
if (m_dirty_flags & DIRTY_FLAG_COMPUTE_BINDINGS)
{
m_compute_descriptor_set = g_command_buffer_mgr->AllocateDescriptorSet(
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_COMPUTE));
dswrites[0] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_compute_descriptor_set,
0,
0,
1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
nullptr,
&m_bindings.utility_ubo_binding,
nullptr};
dswrites[1] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_compute_descriptor_set,
1,
0,
NUM_COMPUTE_SHADER_SAMPLERS,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
m_bindings.samplers.data(),
nullptr,
nullptr};
dswrites[2] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_compute_descriptor_set,
3,
0,
NUM_COMPUTE_TEXEL_BUFFERS,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
nullptr,
nullptr,
m_bindings.texel_buffers.data()};
dswrites[3] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
m_compute_descriptor_set,
5,
0,
1,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
&m_bindings.image_texture,
nullptr,
nullptr};
viewport = new_viewport;
dirty_flags |= DirtyState::Viewport;
}
vkUpdateDescriptorSets(g_vulkan_context->GetDevice(), static_cast<uint32_t>(dswrites.size()),
dswrites.data(), 0, nullptr);
m_dirty_flags =
(m_dirty_flags & ~DIRTY_FLAG_COMPUTE_BINDINGS) | DIRTY_FLAG_COMPUTE_DESCRIPTOR_SET;
}
void VulkanState::SetScissor(vk::Rect2D new_scissor) {
if (new_scissor == scissor) {
return;
}
if (m_dirty_flags & DIRTY_FLAG_COMPUTE_DESCRIPTOR_SET)
{
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
VK_PIPELINE_BIND_POINT_COMPUTE,
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_COMPUTE), 0, 1,
&m_compute_descriptor_set, 1, &m_bindings.utility_ubo_offset);
m_dirty_flags &= ~DIRTY_FLAG_COMPUTE_DESCRIPTOR_SET;
}
scissor = new_scissor;
dirty_flags |= DirtyState::Scissor;
}
return true;
void VulkanState::Apply() {
// Update resources in descriptor sets if changed
UpdateDescriptorSet();
// Start rendering if not already started
BeginRendering();
// Re-apply dynamic parts of the pipeline
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
if (dirty_flags & DirtyState::VertexBuffer) {
command_buffer.bindVertexBuffers(0, vertex_buffer->GetBuffer(), vertex_offset);
}
if (dirty_flags & DirtyState::IndexBuffer) {
command_buffer.bindIndexBuffer(index_buffer->GetBuffer(), index_offset, vk::IndexType::eUint16);
}
if (dirty_flags & DirtyState::Viewport) {
command_buffer.setViewport(0, viewport);
}
if (dirty_flags & DirtyState::Scissor) {
command_buffer.setScissor(0, scissor);
}
dirty_flags = DirtyState::None;
}
void VulkanState::UpdateDescriptorSet() {
std::vector<vk::WriteDescriptorSet> writes;
auto& device = g_vk_instace->GetDevice();
// Check if any resource has been updated
if (dirty_flags & DirtyState::Uniform) {
for (int i = 0; i < 2; i++) {
if (bindings.ubo_update[i]) {
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eUniformBuffer,
nullptr, &bindings.ubo[i]);
bindings.ubo_update[i] = false;
}
}
}
if (dirty_flags & DirtyState::Texture) {
for (int i = 0; i < 4; i++) {
if (bindings.texture_update[i]) {
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eCombinedImageSampler,
nullptr, &bindings.texture[i]);
bindings.texture_update[i] = false;
}
}
}
if (dirty_flags & DirtyState::TexelBuffer) {
for (int i = 0; i < 3; i++) {
if (bindings.lut_update[i]) {
writes.emplace_back(descriptor_sets[i].get(), i, 0, 1, vk::DescriptorType::eStorageTexelBuffer,
nullptr, &bindings.lut[i]);
bindings.lut_update[i] = false;
}
}
}
if (!writes.empty()) {
device.updateDescriptorSets(writes, {});
}
}
} // namespace Vulkan

View File

@ -6,12 +6,11 @@
#include <array>
#include "video_core/renderer_vulkan/vk_texture.h"
#include "video_core/renderer_vulkan/vk_pipeline.h"
namespace Vulkan {
enum class DirtyState {
All,
None,
Framebuffer,
Pipeline,
Texture,
@ -25,7 +24,9 @@ enum class DirtyState {
Scissor,
CullMode,
VertexBuffer,
Uniform
IndexBuffer,
Uniform,
All
};
enum class UniformID {
@ -57,29 +58,33 @@ public:
/// Configure drawing state
void SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset);
void SetFramebuffer(VKFramebuffer* framebuffer);
void SetPipeline(const VKPipeline* pipeline);
void SetViewport(vk::Viewport viewport);
void SetScissor(vk::Rect2D scissor);
/// Rendering
void SetAttachments(VKTexture* color, VKTexture* depth_stencil);
void SetRenderArea(vk::Rect2D render_area);
void BeginRendering();
void EndRendering();
/// Configure shader resources
void SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u32 size);
void SetTexture(TextureID id, VKTexture* texture);
void SetTexelBuffer(TexelBufferID id, VKBuffer* buffer);
void SetImageTexture(VKTexture* image);
void UnbindTexture(VKTexture* image);
/// Apply all dirty state to the current Vulkan command buffer
void UpdateDescriptorSet();
void Apply();
private:
// Stage which should be applied
DirtyState dirty_flags;
bool rendering = false;
// Input assembly
VKBuffer* vertex_buffer = nullptr;
vk::DeviceSize vertex_buffer_offset = 0;
// Pipeline state
const VKPipeline* pipeline = nullptr;
VKBuffer* vertex_buffer = nullptr, * index_buffer = nullptr;
vk::DeviceSize vertex_offset = 0, index_offset = 0;
// Shader bindings. These describe which resources
// we have bound to the pipeline and at which
@ -89,22 +94,24 @@ private:
struct
{
std::array<vk::DescriptorBufferInfo, 2> ubo;
std::array<bool, 2> ubo_update;
std::array<vk::DescriptorImageInfo, 4> texture;
std::array<bool, 4> texture_update;
std::array<vk::DescriptorBufferInfo, 3> lut;
std::array<bool, 3> lut_update;
} bindings = {};
std::array<vk::DescriptorSet, 3> descriptor_sets = {};
std::vector<vk::UniqueDescriptorSet> descriptor_sets = {};
vk::UniqueDescriptorPool desc_pool;
// Rasterization
vk::Viewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
vk::Viewport viewport = { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
vk::CullModeFlags cull_mode = vk::CullModeFlagBits::eNone;
vk::Rect2D scissor = {{0, 0}, {1, 1}};
vk::Rect2D scissor = { {0, 0}, {1, 1} };
VKTexture dummy_texture;
// Framebuffer
VKFramebuffer* framebuffer = nullptr;
vk::RenderPass current_render_pass = VK_NULL_HANDLE;
vk::Rect2D framebuffer_render_area = {};
// Render attachments
VKTexture* color_attachment = nullptr, * depth_attachment = nullptr;
vk::Rect2D render_area = {};
vk::ColorComponentFlags color_mask;
// Depth

View File

@ -51,11 +51,8 @@ void VKTexture::Create(const Info& info, bool make_staging) {
LOG_CRITICAL(Render_Vulkan, "Unknown texture format {}", texture_info.format);
}
// Make sure the texture size doesn't exceed the global staging buffer size
u32 image_size = texture_info.width * texture_info.height * channels;
assert(image_size <= MAX_TEXTURE_UPLOAD_BUFFER_SIZE);
// Create the texture
u32 image_size = texture_info.width * texture_info.height * channels;
vk::ImageCreateFlags flags;
if (info.view_type == vk::ImageViewType::eCube) {
flags = vk::ImageCreateFlagBits::eCubeCompatible;
@ -103,7 +100,7 @@ void VKTexture::Adopt(vk::Image image, vk::ImageViewCreateInfo view_info) {
texture_view = g_vk_instace->GetDevice().createImageView(view_info);
}
void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer& command_buffer) {
void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer command_buffer) {
struct LayoutInfo {
vk::ImageLayout layout;
vk::AccessFlags access;
@ -209,12 +206,13 @@ void VKTexture::CopyPixels(std::span<u32> new_pixels) {
TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, command_buffer);
}
void VKTexture::BlitTo(Common::Rectangle<u32> srect, VKTexture& dest,
Common::Rectangle<u32> drect, SurfaceParams::SurfaceType type,
vk::CommandBuffer& command_buffer) {
void VKTexture::BlitTo(Common::Rectangle<u32> srect, VKTexture* dest,
Common::Rectangle<u32> drect, SurfaceParams::SurfaceType type) {
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Ensure textures are of the same dimention
assert(texture_info.width == dest.texture_info.width &&
texture_info.height == dest.texture_info.height);
assert(texture_info.width == dest->texture_info.width &&
texture_info.height == dest->texture_info.height);
vk::ImageAspectFlags image_aspect;
switch (type) {
@ -242,73 +240,34 @@ void VKTexture::BlitTo(Common::Rectangle<u32> srect, VKTexture& dest,
// Transition image layouts
TransitionLayout(vk::ImageLayout::eTransferSrcOptimal, command_buffer);
dest.TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
dest->TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
// Perform blit operation
command_buffer.blitImage(texture, vk::ImageLayout::eTransferSrcOptimal, dest.texture,
command_buffer.blitImage(texture, vk::ImageLayout::eTransferSrcOptimal, dest->GetHandle(),
vk::ImageLayout::eTransferDstOptimal, regions, vk::Filter::eNearest);
}
void VKTexture::Fill(Common::Rectangle<u32> region, glm::vec4 color) {
void VKTexture::Fill(Common::Rectangle<u32> region, vk::ImageAspectFlags aspect,
vk::ClearValue value) {
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
TransitionLayout(vk::ImageLayout::eTransferDstOptimal, command_buffer);
}
// End any ongoing rendering operations
g_vk_state->EndRendering();
void VKTexture::Fill(Common::Rectangle<u32> region, glm::vec2 depth_stencil) {
// Set fill area
g_vk_state->SetAttachments(this, nullptr);
}
// Begin clear render
g_vk_state->BeginRendering();
VKFramebuffer::~VKFramebuffer() {
auto deleter = [this]() {
if (framebuffer) {
auto& device = g_vk_instace->GetDevice();
device.destroyFramebuffer(framebuffer);
}
};
vk::Offset2D offset(region.left, region.bottom);
vk::Rect2D rect(offset, { region.GetWidth(), region.GetHeight() });
vk::ClearAttachment clear_info(aspect, 0, value);
vk::ClearRect clear_rect(rect, 0, 1);
command_buffer.clearAttachments(clear_info, clear_rect);
g_vk_task_scheduler->Schedule(deleter);
}
void VKFramebuffer::Create(const Info& info) {
// Make sure that either attachment is valid
assert(info.color || info.depth_stencil);
attachments = { info.color, info.depth_stencil };
auto rect = info.color ? info.color->GetRect() : info.depth_stencil->GetRect();
auto color_format = info.color ? info.color->GetFormat() : vk::Format::eUndefined;
auto depth_format = info.depth_stencil ? info.depth_stencil->GetFormat() : vk::Format::eUndefined;
vk::FramebufferCreateInfo framebuffer_info
(
{},
g_vk_res_cache->GetRenderPass(color_format, depth_format, 1, vk::AttachmentLoadOp::eLoad),
{},
rect.extent.width,
rect.extent.height,
1
);
if (info.color && info.depth_stencil) {
std::array<vk::ImageView, 2> views = { info.color->GetView(), info.depth_stencil->GetView() };
framebuffer_info.setAttachments(views);
}
else {
auto valid = info.color ? info.color : info.depth_stencil;
std::array<vk::ImageView, 1> view = { valid->GetView() };
framebuffer_info.setAttachments(view);
}
framebuffer = g_vk_instace->GetDevice().createFramebuffer(framebuffer_info);
}
void VKFramebuffer::Prepare(vk::CommandBuffer& command_buffer) {
// Transition attachments to their optimal formats for rendering
if (attachments[Attachments::Color]) {
attachments[Attachments::Color]->TransitionLayout(vk::ImageLayout::eColorAttachmentOptimal, command_buffer);
}
if (attachments[Attachments::DepthStencil]) {
attachments[Attachments::DepthStencil]->TransitionLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal, command_buffer);
}
TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, command_buffer);
}
}

View File

@ -52,24 +52,24 @@ public:
void CopyPixels(std::span<u32> pixels);
/// Get Vulkan objects
vk::Image GetImage() const { return texture; }
vk::Image GetHandle() const { return texture; }
vk::ImageView GetView() const { return texture_view; }
vk::Format GetFormat() const { return texture_info.format; }
vk::Rect2D GetRect() const { return vk::Rect2D({}, { texture_info.width, texture_info.height }); }
vk::ImageLayout GetLayout() const { return texture_layout; }
u32 GetSamples() const { return texture_info.multisamples; }
bool IsValid() { return texture; }
/// Used to transition the image to an optimal layout during transfers
void TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer& command_buffer);
void TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer command_buffer);
/// Fill the texture with the values provided
void Fill(Common::Rectangle<u32> region, glm::vec4 color);
void Fill(Common::Rectangle<u32> region, glm::vec2 depth_stencil);
void Fill(Common::Rectangle<u32> region, vk::ImageAspectFlags aspect,
vk::ClearValue value);
/// Copy current texture to another with optionally performing format convesions
void BlitTo(Common::Rectangle<u32> source_rect, VKTexture& dest,
Common::Rectangle<u32> dst_rect, SurfaceParams::SurfaceType type,
vk::CommandBuffer& command_buffer);
void BlitTo(Common::Rectangle<u32> srect, VKTexture* dest,
Common::Rectangle<u32> drect, SurfaceParams::SurfaceType type);
private:
bool cleanup_image = true;
@ -84,34 +84,4 @@ private:
VKBuffer staging;
};
enum Attachments {
Color = 0,
DepthStencil = 1
};
/// Vulkan framebuffer object similar to an FBO in OpenGL
class VKFramebuffer final : public NonCopyable {
public:
struct Info {
VKTexture* color = nullptr;
VKTexture* depth_stencil = nullptr;
};
VKFramebuffer() = default;
~VKFramebuffer();
/// Create Vulkan framebuffer object
void Create(const Info& info);
/// Configure frambuffer for rendering
void Prepare(vk::CommandBuffer& command_buffer);
vk::Rect2D GetRect() const { return vk::Rect2D({}, { width, height }); }
private:
u32 width, height;
vk::Framebuffer framebuffer;
std::array<VKTexture*, 2> attachments;
};
} // namespace Vulkan