video_core: Even more fixes, close to building now

This commit is contained in:
emufan4568
2022-08-10 19:47:40 +03:00
parent dade6e6797
commit 9200731b56
16 changed files with 394 additions and 208 deletions

View File

@@ -50,14 +50,6 @@ public:
}
}
// We explicitly delete the copy assignment operator here, because the
// default copy assignment would copy the full storage value, rather than
// just the bits relevant to this particular bit field.
// Ideally, we would just implement the copy assignment to copy only the
// relevant bits, but we're prevented from doing that because the savestate
// code expects that this class is trivially copyable.
BitFieldArray& operator=(const BitFieldArray&) = delete;
public:
constexpr bool IsSigned() const { return std::is_signed<T>(); }
constexpr std::size_t StartBit() const { return position; }

View File

@@ -62,6 +62,10 @@ struct AlignedAllocation {
template<typename T>
class ObjectPool {
public:
ObjectPool() {
vacants.reserve(32);
}
template<typename... P>
T* Allocate(P&&... p) {
#ifndef OBJECT_POOL_DEBUG

View File

@@ -112,6 +112,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_pipeline.cpp
renderer_vulkan/vk_pipeline.h
renderer_vulkan/vk_platform.h
renderer_vulkan/vk_renderpass_cache.cpp
renderer_vulkan/vk_renderpass_cache.h
renderer_vulkan/vk_shader_gen.cpp
renderer_vulkan/vk_shader_gen.h
renderer_vulkan/vk_shader.cpp

View File

@@ -59,10 +59,10 @@ public:
virtual ShaderHandle CreateShader(ShaderStage stage, std::string_view name, std::string source) = 0;
// Binds a vertex buffer at a provided offset
virtual void BindVertexBuffer(BufferHandle buffer, std::span<const u32> offsets) = 0;
virtual void BindVertexBuffer(BufferHandle buffer, std::span<const u64> offsets) = 0;
// Binds an index buffer at provided offset
virtual void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u32 offset) = 0;
virtual void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u64 offset) = 0;
// Start a draw operation
virtual void Draw(PipelineHandle pipeline, FramebufferHandle draw_framebuffer, u32 base_vertex,

View File

@@ -16,6 +16,12 @@ enum class MSAASamples : u32 {
x8
};
// States which operation to perform on the framebuffer attachment during rendering
enum class LoadOp : u8 {
Load = 0,
Clear = 1
};
/**
* Information about a framebuffer
*/
@@ -31,9 +37,7 @@ struct FramebufferInfo {
}
};
/**
* A framebuffer is a collection of render targets and their configuration
*/
// A framebuffer is a collection of render targets and their configuration
class FramebufferBase : public IntrusivePtrEnabled<FramebufferBase> {
public:
FramebufferBase(const FramebufferInfo& info) : info(info) {}
@@ -43,8 +47,8 @@ public:
FramebufferBase(const FramebufferBase&) = delete;
FramebufferBase& operator=(const FramebufferBase&) = delete;
// Clears the attachments bound to the framebuffer
virtual void DoClear(Common::Vec4f color, float depth, u8 stencil) = 0;
// Clears the attachments bound to the framebuffer using the last stored clear value
virtual void DoClear() = 0;
// Returns an immutable reference to the color attachment
TextureHandle GetColorAttachment() const {
@@ -61,8 +65,22 @@ public:
draw_rect = rect;
}
LoadOp GetLoadOp() const {
return load_op;
}
void SetLoadOp(LoadOp op) {
load_op = op;
}
void SetClearValues(Common::Vec4f color, float depth, u8 stencil) {
clear_color_value = color;
clear_depth_value = depth;
clear_stencil_value = stencil;
}
// Returns the area of the framebuffer affected by draw operations
Common::Rectangle<u32> GetDrawRect() {
Common::Rectangle<u32> GetDrawRect() const {
return draw_rect;
}
@@ -72,6 +90,10 @@ public:
}
protected:
LoadOp load_op = LoadOp::Load;
Common::Vec4f clear_color_value{};
float clear_depth_value = 0.f;
u8 clear_stencil_value = 0;
Common::Rectangle<u32> draw_rect;
FramebufferInfo info;
};

View File

@@ -39,8 +39,6 @@ enum class BindingType : u32 {
using BindingGroup = BitFieldArray<0, 3, MAX_BINDINGS_IN_GROUP, BindingType>;
static_assert(sizeof(BindingGroup));
/**
* Describes all the resources used in the pipeline
*/
@@ -50,8 +48,6 @@ struct PipelineLayoutInfo {
u8 push_constant_block_size = 0;
};
static_assert(sizeof(PipelineLayoutInfo));
/**
* The pipeline state is tightly packed with bitfields to reduce
* the overhead of hashing as much as possible

View File

@@ -96,7 +96,7 @@ constexpr u32 UTILITY_GROUP = 0;
constexpr u32 TEXTURE_GROUP = 1;
// Rasterizer pipeline layout
static constexpr PipelineLayoutInfo RASTERIZER_PIPELINE_INFO = {
constexpr PipelineLayoutInfo RASTERIZER_PIPELINE_LAYOUT = {
.group_count = 3,
.binding_groups = {
// Uniform + LUT set
@@ -192,6 +192,10 @@ Rasterizer::Rasterizer(Frontend::EmuWindow& emu_window, std::unique_ptr<BackendB
// Create pipeline cache
pipeline_cache = std::make_unique<PipelineCache>(emu_window, backend);
// Initialize the rasterization pipeline info
raster_info.vertex_layout = HardwareVertex::GetVertexLayout();
raster_info.layout = RASTERIZER_PIPELINE_LAYOUT;
// Synchronize pica state
SyncEntireState();
}
@@ -348,7 +352,7 @@ void Rasterizer::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min, u32
VertexLayout layout{};
std::array<bool, 16> enable_attributes{};
std::array<u32, 16> binding_offsets{};
std::array<u64, 16> binding_offsets{};
u32 buffer_offset = 0;
for (const auto& loader : vertex_attributes.attribute_loaders) {
@@ -448,7 +452,7 @@ void Rasterizer::SetupVertexArray(u32 vs_input_size, u32 vs_input_index_min, u32
vertex_buffer->Commit(vs_input_size);
// Bind the vertex buffers with all the bindings
auto offsets = std::span<u32>{binding_offsets.data(), layout.binding_count};
auto offsets = std::span<u64>{binding_offsets.data(), layout.binding_count};
backend->BindVertexBuffer(vertex_buffer, offsets);
}
@@ -587,6 +591,8 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) {
// Retrieve the framebuffer assigned to the surfaces and update raster_info
FramebufferHandle framebuffer = res_cache.GetFramebuffer(color_surface, depth_surface);
framebuffer->SetLoadOp(LoadOp::Load);
raster_info.color_attachment = framebuffer->GetColorAttachment().IsValid() ?
framebuffer->GetColorAttachment()->GetFormat() :
TextureFormat::Undefined;
@@ -815,7 +821,7 @@ bool Rasterizer::Draw(bool accelerate, bool is_indexed) {
// Bind the vertex buffer at the current mapped offset. This effectively means
// that when base_vertex is zero the GPU will start drawing from the current mapped
// offset not the start of the buffer.
const std::array mapped_offset = {vertex_buffer->GetCurrentOffset()};
const std::array<u64, 1> mapped_offset = {vertex_buffer->GetCurrentOffset()};
backend->BindVertexBuffer(vertex_buffer, mapped_offset);
const std::size_t max_vertices = VERTEX_BUFFER_INFO.capacity / sizeof(HardwareVertex);

View File

@@ -262,7 +262,7 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(surface->pixel_format);
const auto color_values = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info) / 255.f;
framebuffer->DoClear(color_values, 0.0f, 0);
framebuffer->SetClearValues(color_values, 0.0f, 0);
} else if (surface->type == SurfaceType::Depth) {
u32 depth_32bit = 0;
float depth_float;
@@ -278,7 +278,7 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C
UNREACHABLE();
}
framebuffer->DoClear({}, depth_float, 0);
framebuffer->SetClearValues({}, depth_float, 0);
} else if (surface->type == SurfaceType::DepthStencil) {
u32 value_32bit;
std::memcpy(&value_32bit, fill_data, sizeof(u32));
@@ -286,9 +286,10 @@ bool RasterizerCache::FillSurface(const Surface& surface, const u8* fill_data, C
float depth_float = (value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
u8 stencil_int = (value_32bit >> 24);
framebuffer->DoClear({}, depth_float, stencil_int);
framebuffer->SetClearValues({}, depth_float, stencil_int);
}
framebuffer->DoClear();
return true;
}

View File

@@ -134,7 +134,7 @@ constexpr VertexLayout ScreenRectVertex::GetVertexLayout() {
}
// Renderer pipeline layout
static constexpr PipelineLayoutInfo RENDERER_PIPELINE_INFO = {
static constexpr PipelineLayoutInfo RENDERER_PIPELINE_LAYOUT = {
.group_count = 2,
.binding_groups = {
BindingGroup{
@@ -167,7 +167,7 @@ DisplayRenderer::DisplayRenderer(Frontend::EmuWindow& window) : render_window(wi
PipelineInfo present_pipeline_info = {
.vertex_layout = ScreenRectVertex::GetVertexLayout(),
.layout = RENDERER_PIPELINE_INFO,
.layout = RENDERER_PIPELINE_LAYOUT,
.color_attachment = TextureFormat::PresentColor,
.depth_attachment = TextureFormat::Undefined
};
@@ -333,9 +333,10 @@ void DisplayRenderer::DrawSingleScreen(u32 screen, bool rotate, float x, float y
const ScreenInfo& screen_info = screen_infos[screen];
const auto& texcoords = screen_info.display_texcoords;
// Clear the swapchain framebuffer
// Set the swapchain framebuffer to clear mode
FramebufferHandle display = backend->GetWindowFramebuffer();
display->DoClear(clear_color, 0.f, 0);
display->SetLoadOp(LoadOp::Clear);
display->SetClearValues(clear_color, 0.f, 0);
// Update viewport and scissor
const auto& color_surface = display->GetColorAttachment();
@@ -360,7 +361,7 @@ void DisplayRenderer::DrawSingleScreen(u32 screen, bool rotate, float x, float y
}
const u32 size = sizeof(ScreenRectVertex) * vertices.size();
const u32 mapped_offset = vertex_buffer->GetCurrentOffset();
const u64 mapped_offset = vertex_buffer->GetCurrentOffset();
auto vertex_data = vertex_buffer->Map(size);
// Copy vertex data

View File

@@ -42,7 +42,7 @@ constexpr vk::IndexType ToVkIndexType(AttribType type) {
Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
instance(window), swapchain(instance, instance.GetSurface()),
scheduler(instance) {
scheduler(instance), renderpass_cache(instance, swapchain) {
// TODO: Properly report GPU hardware
auto& telemetry_session = Core::System::GetInstance().TelemetrySession();
@@ -51,32 +51,9 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
telemetry_session.AddField(user_system, "GPU_Model", "GTX 1650");
telemetry_session.AddField(user_system, "GPU_Vulkan_Version", "Vulkan 1.3");
// Pre-create all needed renderpasses by the renderer
constexpr std::array color_formats = {
vk::Format::eUndefined,
vk::Format::eR8G8B8A8Unorm,
vk::Format::eR8G8B8Unorm,
vk::Format::eR5G5B5A1UnormPack16,
vk::Format::eR5G6B5UnormPack16,
vk::Format::eR4G4B4A4UnormPack16
};
constexpr std::array depth_stencil_formats = {
vk::Format::eUndefined,
vk::Format::eD16Unorm,
vk::Format::eX8D24UnormPack32,
vk::Format::eD24UnormS8Uint,
};
// Create all required renderpasses
for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) {
for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) {
if (color == 0 && depth == 0) continue;
u32 index = color * MAX_COLOR_FORMATS + depth;
renderpass_cache[index] = CreateRenderPass(color_formats[color], depth_stencil_formats[depth]);
}
}
// Create pipeline cache object
vk::Device device = instance.GetDevice();
pipeline_cache = device.createPipelineCache({});
constexpr std::array pool_sizes = {
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 1024},
@@ -93,7 +70,6 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
};
// Create descriptor pools
vk::Device device = instance.GetDevice();
for (u32 pool = 0; pool < SCHEDULER_COMMAND_COUNT; pool++) {
descriptor_pools[pool] = device.createDescriptorPool(pool_info);
}
@@ -101,8 +77,10 @@ Backend::Backend(Frontend::EmuWindow& window) : BackendBase(window),
Backend::~Backend() {
vk::Device device = instance.GetDevice();
for (auto& renderpass : renderpass_cache) {
device.destroyRenderPass(renderpass);
device.destroyPipelineCache(pipeline_cache);
for (u32 pool = 0; pool < SCHEDULER_COMMAND_COUNT; pool++) {
device.destroyDescriptorPool(descriptor_pools[pool]);
}
}
@@ -134,9 +112,10 @@ FramebufferHandle Backend::CreateFramebuffer(FramebufferInfo info) {
// Get renderpass
TextureFormat color = info.color.IsValid() ? info.color->GetFormat() : TextureFormat::Undefined;
TextureFormat depth = info.depth_stencil.IsValid() ? info.depth_stencil->GetFormat() : TextureFormat::Undefined;
vk::RenderPass renderpass = GetRenderPass(color, depth);
vk::RenderPass load_renderpass = GetRenderPass(color, depth, false);
vk::RenderPass clear_renderpass = GetRenderPass(color, depth, true);
return FramebufferHandle{framebuffer_pool.Allocate(instance, info, renderpass)};
return FramebufferHandle{framebuffer_pool.Allocate(instance, info, load_renderpass, clear_renderpass)};
}
TextureHandle Backend::CreateTexture(TextureInfo info) {
@@ -152,12 +131,14 @@ PipelineHandle Backend::CreatePipeline(PipelineType type, PipelineInfo info) {
// Find an owner first
if (auto iter = pipeline_owners.find(info.layout); iter != pipeline_owners.end()) {
return PipelineHandle{pipeline_pool.Allocate(instance, iter->second, type, info, renderpass, cache)};
return PipelineHandle{pipeline_pool.Allocate(instance, iter->second, type, info,
renderpass, pipeline_cache)};
}
// Create the layout
auto result = pipeline_owners.emplace(info.layout, PipelineOwner{instance, info.layout});
return PipelineHandle{pipeline_pool.Allocate(instance, result.first->second, type, info, renderpass, cache)};
return PipelineHandle{pipeline_pool.Allocate(instance, result.first->second, type, info,
renderpass, pipeline_cache)};
}
SamplerHandle Backend::CreateSampler(SamplerInfo info) {
@@ -165,30 +146,35 @@ SamplerHandle Backend::CreateSampler(SamplerInfo info) {
return SamplerHandle{sampler_pool.Allocate(info)};
}
void Backend::BindVertexBuffer(BufferHandle buffer, std::span<const u64> offsets) {
const Buffer* vertex = static_cast<const Buffer*>(buffer.Get());
std::array<vk::Buffer, 16> buffers;
buffers.fill(vertex->GetHandle());
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
command_buffer.bindVertexBuffers(0, offsets.size(), buffers.data(), offsets.data());
}
void Backend::BindIndexBuffer(BufferHandle buffer, AttribType index_type, u64 offset) {
const Buffer* index = static_cast<const Buffer*>(buffer.Get());
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
command_buffer.bindIndexBuffer(index->GetHandle(), 0, ToVkIndexType(index_type));
}
void Backend::Draw(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer,
u32 base_vertex, u32 num_vertices) {
// Bind descriptor sets
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
BindDescriptorSets(pipeline_handle);
// Bind vertex buffer
const Buffer* vertex = static_cast<const Buffer*>(vertex_buffer.Get());
command_buffer.bindVertexBuffers(0, vertex->GetHandle(), vertex->GetBindOffset());
// Begin renderpass
const Framebuffer* framebuffer = static_cast<const Framebuffer*>(draw_framebuffer.Get());
const vk::RenderPassBeginInfo renderpass_begin = {
.renderPass = framebuffer->GetRenderpass(),
.framebuffer = framebuffer->GetHandle(),
.renderArea = ToVkRect2D(framebuffer->GetDrawRectangle()),
.clearValueCount = 0,
.pClearValues = nullptr
};
command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline);
BeginRenderpass(draw_framebuffer);
// Bind pipeline
const Pipeline* pipeline = static_cast<const Pipeline*>(pipeline_handle.Get());
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
command_buffer.bindPipeline(ToVkPipelineBindPoint(pipeline->GetType()), pipeline->GetHandle());
// Submit draw
@@ -199,34 +185,16 @@ void Backend::Draw(PipelineHandle pipeline_handle, FramebufferHandle draw_frameb
}
void Backend::DrawIndexed(PipelineHandle pipeline_handle, FramebufferHandle draw_framebuffer,
BufferHandle vertex_buffer, BufferHandle index_buffer, AttribType index_type,
u32 base_index, u32 num_indices, u32 base_vertex) {
// Bind descriptor sets
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
BindDescriptorSets(pipeline_handle);
// Bind vertex buffer
const Buffer* vertex = static_cast<const Buffer*>(vertex_buffer.Get());
command_buffer.bindVertexBuffers(0, vertex->GetHandle(), vertex->GetBindOffset());
// Bind index buffer
const Buffer* index = static_cast<const Buffer*>(index_buffer.Get());
command_buffer.bindIndexBuffer(index->GetHandle(), index->GetBindOffset(), ToVkIndexType(index_type));
// Begin renderpass
const Framebuffer* framebuffer = static_cast<const Framebuffer*>(draw_framebuffer.Get());
const vk::RenderPassBeginInfo renderpass_begin = {
.renderPass = framebuffer->GetRenderpass(),
.framebuffer = framebuffer->GetHandle(),
.renderArea = ToVkRect2D(framebuffer->GetDrawRectangle()),
.clearValueCount = 0,
.pClearValues = nullptr
};
command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline);
BeginRenderpass(draw_framebuffer);
// Bind pipeline
const Pipeline* pipeline = static_cast<const Pipeline*>(pipeline_handle.Get());
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
command_buffer.bindPipeline(ToVkPipelineBindPoint(pipeline->GetType()), pipeline->GetHandle());
// Submit draw
@@ -237,106 +205,80 @@ void Backend::DrawIndexed(PipelineHandle pipeline_handle, FramebufferHandle draw
}
vk::RenderPass Backend::CreateRenderPass(vk::Format color, vk::Format depth) const {
// Define attachments
const std::array attachments = {
vk::AttachmentDescription{
.format = color,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
.initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.finalLayout = vk::ImageLayout::eColorAttachmentOptimal
},
vk::AttachmentDescription{
.format = depth,
.initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal
}
};
// Our renderpasses only defines one color and depth attachment
const vk::AttachmentReference color_attachment_ref = {
.attachment = 0,
.layout = vk::ImageLayout::eColorAttachmentOptimal
};
const vk::AttachmentReference depth_attachment_ref = {
.attachment = 1,
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
};
const vk::SubpassDependency subpass_dependency = {
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests,
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests,
.srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentWrite,
.dependencyFlags = vk::DependencyFlagBits::eByRegion
};
// We also require only one subpass
const vk::SubpassDescription subpass = {
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment_ref,
.pResolveAttachments = 0,
.pDepthStencilAttachment = &depth_attachment_ref
};
const vk::RenderPassCreateInfo renderpass_info = {
.attachmentCount = 2,
.pAttachments = attachments.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &subpass_dependency
};
// Create the renderpass
vk::Device device = instance.GetDevice();
return device.createRenderPass(renderpass_info);
}
vk::RenderPass Backend::GetRenderPass(TextureFormat color, TextureFormat depth) const {
u32 color_index = color != TextureFormat::Undefined ? static_cast<u32>(color) + 1 : 0;
u32 depth_index = depth != TextureFormat::Undefined ? static_cast<u32>(depth) - 4 : 0;
return renderpass_cache[color_index * MAX_COLOR_FORMATS + depth_index];
vk::RenderPass Backend::GetRenderPass(TextureFormat color, TextureFormat depth, bool is_clear) const {
if (color == TextureFormat::PresentColor) {
return renderpass_cache.GetPresentRenderpass();
} else {
return renderpass_cache.GetRenderpass(color, depth, is_clear);
}
}
void Backend::BindDescriptorSets(PipelineHandle handle) {
Pipeline* pipeline = static_cast<Pipeline*>(handle.Get());
PipelineLayout& pipeline_layout = pipeline->GetOwner();
PipelineOwner& pipeline_owner = pipeline->GetOwner();
// Allocate required descriptor sets
// TODO: Maybe cache them?
u32 pool_index = scheduler.GetCurrentSlotIndex();
const vk::DescriptorSetAllocateInfo alloc_info = {
.descriptorPool = descriptor_pools[pool_index],
.descriptorSetCount = pipeline_layout.GetDescriptorSetLayoutCount(),
.pSetLayouts = pipeline_layout.GetDescriptorSetLayouts()
};
std::array<vk::DescriptorSet, MAX_BINDING_GROUPS> bound_sets;
const u32 set_count = pipeline_owner.GetDescriptorSetLayoutCount();
for (int i = 0; i < set_count; i++) {
if (!pipeline_owner.descriptor_dirty[i]) {
// Get the ready descriptor if it hasn't been modified
bound_sets[i] = pipeline_owner.descriptor_bank[i];
} else {
// Otherwise allocate a new set and update it with the needed data
u32 pool_index = scheduler.GetCurrentSlotIndex();
const vk::DescriptorSetAllocateInfo alloc_info = {
.descriptorPool = descriptor_pools[pool_index],
.descriptorSetCount = 1,
.pSetLayouts = &pipeline_owner.GetDescriptorSetLayouts()[i]
};
vk::Device device = instance.GetDevice();
auto descriptor_sets = device.allocateDescriptorSets(alloc_info);
vk::Device device = instance.GetDevice();
vk::DescriptorSet set = device.allocateDescriptorSets(alloc_info)[0];
device.updateDescriptorSetWithTemplate(set, pipeline_owner.GetUpdateTemplate(i),
pipeline_owner.GetData(i));
// Write data to the descriptor sets
for (u32 set = 0; set < descriptor_sets.size(); set++) {
device.updateDescriptorSetWithTemplate(descriptor_sets[set],
pipeline_layout.GetUpdateTemplate(set),
pipeline_layout.GetData(set));
bound_sets[i] = set;
pipeline_owner.descriptor_bank[i] = set;
pipeline_owner.descriptor_dirty[i] = false;
}
}
// Bind the descriptor sets
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
command_buffer.bindDescriptorSets(ToVkPipelineBindPoint(handle->GetType()), pipeline_layout.GetLayout(),
0, descriptor_sets, {});
command_buffer.bindDescriptorSets(ToVkPipelineBindPoint(handle->GetType()), pipeline_owner.GetLayout(),
0, set_count, bound_sets.data(), 0, nullptr);
}
void Backend::BeginRenderpass(FramebufferHandle draw_framebuffer) {
const Framebuffer* framebuffer = static_cast<const Framebuffer*>(draw_framebuffer.Get());
u32 clear_value_count = 0;
std::array<vk::ClearValue, 2> clear_values{};
if (framebuffer->GetColorAttachment().IsValid()) {
for (int i = 0; i < 4; i++) {
clear_values[clear_value_count++].color.float32[i] = framebuffer->clear_color_value[i];
}
}
if (framebuffer->GetDepthStencilAttachment().IsValid()) {
clear_values[clear_value_count].depthStencil.depth = framebuffer->clear_depth_value;
clear_values[clear_value_count++].depthStencil.stencil = framebuffer->clear_stencil_value;
}
// Use the clear renderpass if the framebuffer was configured so
const vk::RenderPassBeginInfo renderpass_begin = {
.renderPass = framebuffer->GetLoadOp() == LoadOp::Load ?
framebuffer->GetLoadRenderpass() :
framebuffer->GetClearRenderpass(),
.framebuffer = framebuffer->GetHandle(),
.renderArea = ToVkRect2D(framebuffer->GetDrawRect()),
.clearValueCount = clear_value_count,
.pClearValues = clear_values.data()
};
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
command_buffer.beginRenderPass(renderpass_begin, vk::SubpassContents::eInline);
}
} // namespace VideoCore::Vulkan

View File

@@ -10,13 +10,10 @@
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_pipeline.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
namespace VideoCore::Vulkan {
class Texture;
constexpr u32 RENDERPASS_COUNT = (MAX_COLOR_FORMATS + 1) * (MAX_DEPTH_FORMATS + 1);
class Backend final : public VideoCore::BackendBase {
public:
Backend(Frontend::EmuWindow& window);
@@ -38,8 +35,8 @@ public:
SamplerHandle CreateSampler(SamplerInfo info) override;
ShaderHandle CreateShader(ShaderStage stage, std::string_view name, std::string source) override;
void BindVertexBuffer(BufferHandle buffer, std::span<const u32> offsets) override;
void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u32 offset) override;
void BindVertexBuffer(BufferHandle buffer, std::span<const u64> offsets) override;
void BindIndexBuffer(BufferHandle buffer, AttribType index_type, u64 offset) override;
void Draw(PipelineHandle pipeline, FramebufferHandle draw_framebuffer,
u32 base_vertex, u32 num_vertices) override;
@@ -61,21 +58,20 @@ public:
}
private:
vk::RenderPass CreateRenderPass(vk::Format color, vk::Format depth) const;
vk::RenderPass GetRenderPass(TextureFormat color, TextureFormat depth) const;
vk::RenderPass GetRenderPass(TextureFormat color, TextureFormat depth, bool is_clear = false) const;
// Allocates and binds descriptor sets for the provided pipeline
void BindDescriptorSets(PipelineHandle pipeline);
// Begins the renderpass for the provided framebuffer
void BeginRenderpass(FramebufferHandle framebuffer);
private:
Instance instance;
Swapchain swapchain;
CommandScheduler scheduler;
// The formats Citra uses are limited so we can pre-create
// all the renderpasses we will need
std::array<vk::RenderPass, RENDERPASS_COUNT> renderpass_cache;
vk::PipelineCache cache;
RenderpassCache renderpass_cache;
vk::PipelineCache pipeline_cache;
// A cache of pipeline owners
std::unordered_map<PipelineLayoutInfo, PipelineOwner> pipeline_owners;

View File

@@ -57,7 +57,7 @@ Framebuffer::~Framebuffer() {
device.destroyFramebuffer(framebuffer);
}
void Framebuffer::DoClear(Common::Vec4f color, float depth, u8 stencil) {
void Framebuffer::DoClear() {
vk::CommandBuffer command_buffer = scheduler.GetRenderCommandBuffer();
u32 clear_value_count = 0;
@@ -65,7 +65,7 @@ void Framebuffer::DoClear(Common::Vec4f color, float depth, u8 stencil) {
if (info.color.IsValid()) {
vk::ClearColorValue clear_color{};
std::memcpy(clear_color.float32.data(), color.AsArray(), sizeof(float) * 4);
std::memcpy(clear_color.float32.data(), clear_value.color.AsArray(), sizeof(float) * 4);
clear_values[clear_value_count++] = vk::ClearValue {
.color = clear_color
@@ -75,8 +75,8 @@ void Framebuffer::DoClear(Common::Vec4f color, float depth, u8 stencil) {
if (info.depth_stencil.IsValid()) {
clear_values[clear_value_count++] = vk::ClearValue {
.depthStencil = vk::ClearDepthStencilValue {
.depth = depth,
.stencil = stencil
.depth = clear_value.depth,
.stencil = clear_value.stencil
}
};
}

View File

@@ -13,12 +13,13 @@ class Instance;
class CommandScheduler;
class Framebuffer : public VideoCore::FramebufferBase {
friend class Backend;
public:
Framebuffer(Instance& instance, CommandScheduler& scheduler, const FramebufferInfo& info,
vk::RenderPass load_renderpass, vk::RenderPass clear_renderpass);
~Framebuffer() override;
void DoClear(Common::Vec4f color, float depth, u8 stencil) override;
void DoClear() override;
vk::Framebuffer GetHandle() const {
return framebuffer;
@@ -28,6 +29,20 @@ public:
return load_renderpass;
}
vk::RenderPass GetClearRenderpass() const {
return clear_renderpass;
}
u32 GetAttachmentCount() const {
if (info.color.IsValid() && info.depth_stencil.IsValid()) {
return 2;
} else if (!info.color.IsValid() && !info.depth_stencil.IsValid()) {
return 0;
} else {
return 1;
}
}
private:
Instance& instance;
CommandScheduler& scheduler;

View File

@@ -25,6 +25,7 @@ union DescriptorData {
* Functions as the "parent" to a group of pipelines that share the same layout
*/
class PipelineOwner {
friend class Backend;
public:
PipelineOwner(Instance& instance, PipelineLayoutInfo info);
~PipelineOwner();

View File

@@ -0,0 +1,169 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#define VULKAN_HPP_NO_CONSTRUCTORS
#include "common/assert.h"
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
namespace VideoCore::Vulkan {
static constexpr std::array color_formats = {
vk::Format::eUndefined,
vk::Format::eR8G8B8A8Unorm,
vk::Format::eR8G8B8Unorm,
vk::Format::eR5G5B5A1UnormPack16,
vk::Format::eR5G6B5UnormPack16,
vk::Format::eR4G4B4A4UnormPack16
};
static constexpr std::array depth_stencil_formats = {
vk::Format::eUndefined,
vk::Format::eD16Unorm,
vk::Format::eX8D24UnormPack32,
vk::Format::eD24UnormS8Uint,
};
RenderpassCache::RenderpassCache(Instance& instance, Swapchain& swapchain) : instance(instance) {
// Pre-create all needed renderpasses by the renderer
for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) {
for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) {
if (color == 0 && depth == 0) {
continue;
}
vk::Format color_format = instance.GetFormatAlternative(color_formats[color]);
vk::Format depth_stencil_format = instance.GetFormatAlternative(depth_stencil_formats[depth]);
// Construct both load and clear pass
cached_renderpasses[color][depth][0] = CreateRenderPass(color_format, depth_stencil_format,
vk::AttachmentLoadOp::eLoad,
vk::ImageLayout::eShaderReadOnlyOptimal,
vk::ImageLayout::eColorAttachmentOptimal);
cached_renderpasses[color][depth][1] = CreateRenderPass(color_format, depth_stencil_format,
vk::AttachmentLoadOp::eClear,
vk::ImageLayout::eShaderReadOnlyOptimal,
vk::ImageLayout::eColorAttachmentOptimal);
}
}
// Create the present renderpass
present_renderpass = CreateRenderPass(swapchain.GetSurfaceFormat().format, vk::Format::eUndefined,
vk::AttachmentLoadOp::eClear,
vk::ImageLayout::eUndefined,
vk::ImageLayout::ePresentSrcKHR);
}
RenderpassCache::~RenderpassCache() {
vk::Device device = instance.GetDevice();
for (u32 color = 0; color <= MAX_COLOR_FORMATS; color++) {
for (u32 depth = 0; depth <= MAX_DEPTH_FORMATS; depth++) {
if (color == 0 && depth == 0) {
continue;
}
auto& load_pass = cached_renderpasses[color][depth][0];
auto& clear_pass = cached_renderpasses[color][depth][1];
// Destroy renderpasses
device.destroyRenderPass(load_pass);
device.destroyRenderPass(clear_pass);
}
}
device.destroyRenderPass(present_renderpass);
}
vk::RenderPass RenderpassCache::GetRenderpass(TextureFormat color, TextureFormat depth, bool is_clear) const {
const u32 color_index = static_cast<u32>(color);
const u32 depth_index = static_cast<u32>(depth) - MAX_COLOR_FORMATS;
ASSERT(color_index < MAX_COLOR_FORMATS && depth_index < MAX_DEPTH_FORMATS);
return cached_renderpasses[color_index][depth_index][is_clear];
}
vk::RenderPass RenderpassCache::CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op,
vk::ImageLayout initial_layout, vk::ImageLayout final_layout) const {
// Define attachments
u32 attachment_count = 0;
std::array<vk::AttachmentDescription, 2> attachments;
bool use_color = false;
vk::AttachmentReference color_attachment_ref;
bool use_depth = false;
vk::AttachmentReference depth_attachment_ref;
if (color != vk::Format::eUndefined) {
attachments[attachment_count++] = vk::AttachmentDescription{
.format = color,
.loadOp = load_op,
.storeOp = vk::AttachmentStoreOp::eStore,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
.initialLayout = initial_layout,
.finalLayout = final_layout
};
color_attachment_ref = vk::AttachmentReference{
.attachment = 0,
.layout = vk::ImageLayout::eColorAttachmentOptimal
};
}
if (depth != vk::Format::eUndefined) {
attachments[attachment_count++] = vk::AttachmentDescription{
.format = depth,
.loadOp = load_op,
.storeOp = vk::AttachmentStoreOp::eStore,
.initialLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal
};
depth_attachment_ref = vk::AttachmentReference{
.attachment = 1,
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
};
}
const vk::SubpassDependency subpass_dependency = {
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests,
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput |
vk::PipelineStageFlagBits::eEarlyFragmentTests,
.srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentWrite,
.dependencyFlags = vk::DependencyFlagBits::eByRegion
};
// We also require only one subpass
const vk::SubpassDescription subpass = {
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
.inputAttachmentCount = 0,
.pInputAttachments = nullptr,
.colorAttachmentCount = use_color ? 1u : 0u,
.pColorAttachments = &color_attachment_ref,
.pResolveAttachments = 0,
.pDepthStencilAttachment = use_depth ? &depth_attachment_ref : nullptr
};
const vk::RenderPassCreateInfo renderpass_info = {
.attachmentCount = attachment_count,
.pAttachments = attachments.data(),
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &subpass_dependency
};
// Create the renderpass
vk::Device device = instance.GetDevice();
return device.createRenderPass(renderpass_info);
}
} // namespace VideoCore::Vulkan

View File

@@ -0,0 +1,39 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/common/texture.h"
#include "video_core/renderer_vulkan/vk_common.h"
namespace VideoCore::Vulkan {
class Instance;
class Swapchain;
class RenderpassCache {
public:
RenderpassCache(Instance& instance, Swapchain& swapchain);
~RenderpassCache();
vk::RenderPass GetRenderpass(TextureFormat color, TextureFormat depth, bool is_clear) const;
// Returns the special swapchain renderpass
vk::RenderPass GetPresentRenderpass() const {
return present_renderpass;
}
private:
vk::RenderPass CreateRenderPass(vk::Format color, vk::Format depth, vk::AttachmentLoadOp load_op,
vk::ImageLayout initial_layout, vk::ImageLayout final_layout) const;
private:
Instance& instance;
// Special renderpass used for rendering to the swapchain
vk::RenderPass present_renderpass;
// [color_format][depth_format][is_clear_pass]
vk::RenderPass cached_renderpasses[MAX_COLOR_FORMATS][MAX_DEPTH_FORMATS][2];
};
} // namespace VideoCore::Vulkan