renderer_vulkan: Rewrite and rename command buffer manager class

* The old manager class had unnecessary complexity and Dolphin specific stuff
that we didn't need. So this commit completely rewrites the class to make the
implementation simpler and more robust. It does three things:

1. Make use of Vulkan timeline semaphores
2. Remove submission thread
3. Rename VKCommandManager to VKTaskScheduler

* In addition remove Unique handles from textures and buffers and give the
responsibility of their deletion to the scheduler.
This commit is contained in:
GPUCode
2022-05-02 19:24:40 +03:00
parent b6099d5504
commit bc440ce6a3
15 changed files with 360 additions and 574 deletions

View File

@ -73,8 +73,6 @@ add_library(video_core STATIC
renderer_vulkan/renderer_vulkan.h
renderer_vulkan/vk_buffer.cpp
renderer_vulkan/vk_buffer.h
renderer_vulkan/vk_command_manager.cpp
renderer_vulkan/vk_command_manager.h
renderer_vulkan/vk_instance.cpp
renderer_vulkan/vk_instance.h
renderer_vulkan/vk_resource_cache.cpp
@ -92,6 +90,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_surface_params.h
renderer_vulkan/vk_swapchain.cpp
renderer_vulkan/vk_swapchain.h
renderer_vulkan/vk_task_scheduler.cpp
renderer_vulkan/vk_task_scheduler.h
renderer_vulkan/vk_texture.cpp
renderer_vulkan/vk_texture.h
shader/debug_data.h

View File

@ -5,9 +5,8 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "video_core/renderer_vulkan/vk_buffer.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include <algorithm>
#include <type_traits>
#include <cstring>
namespace Vulkan {
@ -49,21 +48,8 @@ void VKBuffer::Create(uint32_t byte_count, vk::MemoryPropertyFlags properties, v
void VKBuffer::CopyBuffer(VKBuffer& src_buffer, VKBuffer& dst_buffer, const vk::BufferCopy& region)
{
auto& device = g_vk_instace->GetDevice();
auto& queue = g_vk_instace->graphics_queue;
vk::CommandBufferAllocateInfo alloc_info(g_vk_instace->command_pool.get(), vk::CommandBufferLevel::ePrimary, 1);
vk::CommandBuffer command_buffer = device.allocateCommandBuffers(alloc_info)[0];
command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
command_buffer.copyBuffer(src_buffer.buffer.get(), dst_buffer.buffer.get(), region);
command_buffer.end();
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
queue.submit(submit_info, nullptr);
queue.waitIdle();
device.freeCommandBuffers(g_vk_instace->command_pool.get(), command_buffer);
}
uint32_t VKBuffer::FindMemoryType(uint32_t type_filter, vk::MemoryPropertyFlags properties)

View File

@ -1,329 +0,0 @@
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "video_core/renderer_vulkan/vk_command_manager.h"
#include "common/assert.h"
#include "common/thread.h"
namespace Vulkan {
VKCommandManager::VKCommandManager(bool use_threaded_submission)
: submit_semaphore(1, 1), use_threaded_submission(use_threaded_submission) {
}
VKCommandManager::~VKCommandManager() {
// If the worker thread is enabled, stop and block until it exits.
if (use_threaded_submission) {
submit_loop->Stop();
submit_thread.join();
}
DestroyCommandBuffers();
}
bool VKCommandManager::Initialize()
{
if (!CreateCommandBuffers()) {
return false;
}
if (use_threaded_submission && !CreateSubmitThread()) {
return false;
}
return true;
}
bool VKCommandManager::CreateCommandBuffers() {
static constexpr vk::SemaphoreCreateInfo semaphore_create_info;
auto device = g_vk_instace->GetDevice();
for (auto& resources : frame_resources) {
resources.init_command_buffer_used = false;
resources.semaphore_used = false;
// Create command pool
vk::CommandPoolCreateInfo pool_info({}, g_vk_instace->GetGraphicsQueueFamilyIndex());
resources.command_pool = device.createCommandPool(pool_info);
// Create command buffers
vk::CommandBufferAllocateInfo buffer_info
(
resources.command_pool,
vk::CommandBufferLevel::ePrimary,
resources.command_buffers.size()
);
resources.command_buffers = device.allocateCommandBuffers(buffer_info);
vk::FenceCreateInfo fence_info(vk::FenceCreateFlagBits::eSignaled);
resources.fence = device.createFence(fence_info);
// TODO: A better way to choose the number of descriptors.
const std::array<vk::DescriptorPoolSize, 3> pool_sizes{{
{ vk::DescriptorType::eUniformBuffer, 32 },
{ vk::DescriptorType::eCombinedImageSampler, 64 },
{ vk::DescriptorType::eStorageTexelBuffer, 64 }
}};
const vk::DescriptorPoolCreateInfo pool_create_info({}, 2048, pool_sizes);
resources.descriptor_pool = device.createDescriptorPool(pool_create_info);
}
// Create present semaphore
present_semaphore = device.createSemaphore(semaphore_create_info);
// Activate the first command buffer. ActivateCommandBuffer moves forward, so start with the last
current_frame = static_cast<u32>(frame_resources.size()) - 1;
BeginCommandBuffer();
return true;
}
void VKCommandManager::DestroyCommandBuffers() {
vk::Device device = g_vk_instace->GetDevice();
for (auto& resources : frame_resources) {
// Destroy command pool which also clears any allocated command buffers
if (resources.command_pool) {
device.destroyCommandPool(resources.command_pool);
}
// Destroy any pending objects.
for (auto& it : resources.cleanup_resources)
it();
// Destroy remaining vulkan objects
if (resources.semaphore) {
device.destroySemaphore(resources.semaphore);
}
if (resources.fence) {
device.destroyFence(resources.fence);
}
if (resources.descriptor_pool) {
device.destroyDescriptorPool(resources.descriptor_pool);
}
}
device.destroySemaphore(present_semaphore);
}
vk::DescriptorSet VKCommandManager::AllocateDescriptorSet(vk::DescriptorSetLayout set_layout) {
vk::DescriptorSetAllocateInfo allocate_info(frame_resources[current_frame].descriptor_pool, set_layout);
return g_vk_instace->GetDevice().allocateDescriptorSets(allocate_info)[0];
}
bool VKCommandManager::CreateSubmitThread() {
submit_loop = std::make_unique<Common::BlockingLoop>();
submit_thread = std::thread([this]() {
Common::SetCurrentThreadName("Vulkan CommandBufferManager SubmitThread");
submit_loop->Run([this]() {
PendingCommandBufferSubmit submit;
{
std::lock_guard<std::mutex> guard(pending_submit_lock);
if (pending_submits.empty())
{
submit_loop->AllowSleep();
return;
}
submit = pending_submits.front();
pending_submits.pop_front();
}
SubmitCommandBuffer(submit.command_buffer_index, submit.present_swap_chain,
submit.present_image_index);
});
});
return true;
}
void VKCommandManager::WaitForWorkerThreadIdle()
{
// Drain the semaphore, then allow another request in the future.
submit_semaphore.Wait();
submit_semaphore.Post();
}
void VKCommandManager::WaitForFenceCounter(u64 fence_counter) {
if (completed_fence_counter >= fence_counter)
return;
// Find the first command buffer which covers this counter value.
u32 index = (current_frame + 1) % COMMAND_BUFFER_COUNT;
while (index != current_frame) {
if (frame_resources[index].fence_counter >= fence_counter)
break;
index = (index + 1) % COMMAND_BUFFER_COUNT;
}
ASSERT(index != current_frame);
WaitForCommandBufferCompletion(index);
}
void VKCommandManager::WaitForCommandBufferCompletion(u32 index) {
// Ensure this command buffer has been submitted.
WaitForWorkerThreadIdle();
// Wait for this command buffer to be completed.
auto result = g_vk_instace->GetDevice().waitForFences(frame_resources[index].fence,
VK_TRUE, UINT64_MAX);
if (result != vk::Result::eSuccess) {
LOG_ERROR(Render_Vulkan, "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 = frame_resources[index].fence_counter;
u32 cleanup_index = (current_frame + 1) % COMMAND_BUFFER_COUNT;
while (cleanup_index != current_frame) {
auto& resources = frame_resources[cleanup_index];
if (resources.fence_counter > now_completed_counter) {
break;
}
if (resources.fence_counter > completed_fence_counter) {
for (auto& it : resources.cleanup_resources)
it();
resources.cleanup_resources.clear();
}
cleanup_index = (cleanup_index + 1) % COMMAND_BUFFER_COUNT;
}
completed_fence_counter = now_completed_counter;
}
void VKCommandManager::SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
vk::SwapchainKHR present_swap_chain,
u32 present_image_index) {
// End the current command buffer.
auto& resources = frame_resources[current_frame];
for (auto& command_buffer : resources.command_buffers) {
command_buffer.end();
}
// Grab the semaphore before submitting command buffer either on-thread or off-thread.
// This prevents a race from occurring where a second command buffer is executed
// before the worker thread has woken and executed the first one yet.
submit_semaphore.Wait();
// Submitting off-thread?
if (use_threaded_submission && submit_on_worker_thread && !wait_for_completion) {
// Push to the pending submit queue.
{
std::lock_guard<std::mutex> guard(pending_submit_lock);
pending_submits.push_back({present_swap_chain, present_image_index, current_frame});
}
// Wake up the worker thread for a single iteration.
submit_loop->Wakeup();
}
else {
// Pass through to normal submission path.
SubmitCommandBuffer(current_frame, present_swap_chain, present_image_index);
if (wait_for_completion) {
WaitForCommandBufferCompletion(current_frame);
}
}
// Switch to next cmdbuffer.
BeginCommandBuffer();
}
void VKCommandManager::SubmitCommandBuffer(u32 command_buffer_index,
vk::SwapchainKHR swapchain,
u32 present_image_index) {
auto& resources = frame_resources[command_buffer_index];
vk::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
vk::SubmitInfo submit_info({}, wait_stage, resources.command_buffers);
// If the init command buffer did not have any commands recorded, don't submit it.
if (!resources.init_command_buffer_used) {
submit_info.setCommandBuffers(resources.command_buffers[1]);
}
if (resources.semaphore_used) {
submit_info.setSignalSemaphores(resources.semaphore);
}
submit_info.setSignalSemaphores(present_semaphore);
g_vk_instace->GetGraphicsQueue().submit(submit_info, resources.fence);
// Should have a signal semaphore.
vk::PresentInfoKHR present_info(present_semaphore, swapchain, present_image_index);
auto last_present_result = g_vk_instace->GetPresentQueue().presentKHR(present_info);
if (last_present_result != vk::Result::eSuccess) {
// eErrorOutOfDateKHR is not fatal, just means we need to recreate our swap chain.
if (last_present_result != vk::Result::eErrorOutOfDateKHR &&
last_present_result != vk::Result::eSuboptimalKHR)
{
LOG_ERROR(Render_Vulkan, "Present queue return error");
}
// Don't treat eSuboptimalKHR as fatal on Android. Android 10+ requires prerotation.
// See https://twitter.com/Themaister/status/1207062674011574273
#ifdef VK_USE_PLATFORANDROID_KHR
if (last_present_result != VK_SUBOPTIMAL_KHR) {
last_present_failed.Set();
}
#else
last_present_failed.Set();
#endif
}
// Command buffer has been queued, so permit the next one.
submit_semaphore.Post();
}
void VKCommandManager::BeginCommandBuffer()
{
// Move to the next command buffer.
const u32 next_buffer_index = (current_frame + 1) % COMMAND_BUFFER_COUNT;
auto& resources = frame_resources[next_buffer_index];
auto& device = g_vk_instace->GetDevice();
// Wait for the GPU to finish with all resources for this command buffer.
if (resources.fence_counter > completed_fence_counter) {
WaitForCommandBufferCompletion(next_buffer_index);
}
// Reset fence to unsignaled before starting.
device.resetFences(resources.fence);
// Reset command pools to beginning since we can re-use the memory now
device.resetCommandPool(resources.command_pool);
vk::CommandBufferBeginInfo begin_info(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
// Enable commands to be recorded to the two buffers again.
for (auto command_buffer : resources.command_buffers) {
command_buffer.begin(begin_info);
}
// Also can do the same for the descriptor pools
device.resetDescriptorPool(resources.descriptor_pool);
// Reset upload command buffer state
resources.init_command_buffer_used = false;
resources.semaphore_used = false;
resources.fence_counter = next_fence_counter++;
current_frame = next_buffer_index;
}
std::unique_ptr<VKCommandManager> g_command_buffer_mgr;
} // namespace Vulkan

View File

@ -1,145 +0,0 @@
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <deque>
#include <functional>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "common/blocking_loop.h"
#include "common/semaphore.h"
#include "video_core/renderer_vulkan/vk_instance.h"
namespace Vulkan {
constexpr u32 COMMAND_BUFFER_COUNT = 2;
class VKCommandManager {
public:
explicit VKCommandManager(bool use_threaded_submission);
~VKCommandManager();
bool Initialize();
// These command buffers are allocated per-frame. They are valid until the command buffer
// is submitted, after that you should call these functions again.
vk::CommandBuffer GetCurrentInitCommandBuffer() {
frame_resources[current_frame].init_command_buffer_used = true;
return frame_resources[current_frame].command_buffers[0];
}
vk::CommandBuffer GetCurrentCommandBuffer() const {
return frame_resources[current_frame].command_buffers[1];
}
vk::DescriptorPool GetCurrentDescriptorPool() const {
return frame_resources[current_frame].descriptor_pool;
}
// Allocates a descriptors set from the pool reserved for the current frame.
vk::DescriptorSet AllocateDescriptorSet(vk::DescriptorSetLayout set_layout);
// Fence "counters" are used to track which commands have been completed by the GPU.
// If the last completed fence counter is greater or equal to N, it means that the work
// associated counter N has been completed by the GPU. The value of N to associate with
// commands can be retreived by calling GetCurrentFenceCounter().
u64 GetCompletedFenceCounter() const { return completed_fence_counter; }
// Gets the fence that will be signaled when the currently executing command buffer is
// queued and executed. Do not wait for this fence before the buffer is executed.
u64 GetCurrentFenceCounter() const { return frame_resources[current_frame].fence_counter; }
// Returns the semaphore for the current command buffer, which can be used to ensure the
// swap chain image is ready before the command buffer executes.
vk::Semaphore GetCurrentCommandBufferSemaphore() {
frame_resources[current_frame].semaphore_used = true;
return frame_resources[current_frame].semaphore;
}
// Ensure that the worker thread has submitted any previous command buffers and is idle.
void WaitForWorkerThreadIdle();
// Wait for a fence to be completed.
// Also invokes callbacks for completion.
void WaitForFenceCounter(u64 fence_counter);
void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
vk::SwapchainKHR present_swap_chain = VK_NULL_HANDLE,
u32 present_image_index = -1);
// Was the last present submitted to the queue a failure? If so, we must recreate our swapchain.
bool CheckLastPresentFail() { return last_present_failed.TestAndClear(); }
vk::Result GetLastPresentResult() const { return last_present_result; }
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
// is next re-used, and the GPU has finished working with the specified resource.
template <typename VulkanObject>
void DestroyResource(VulkanObject object);
private:
void BeginCommandBuffer();
bool CreateCommandBuffers();
void DestroyCommandBuffers();
bool CreateSubmitThread();
void WaitForCommandBufferCompletion(u32 command_buffer_index);
void SubmitCommandBuffer(u32 command_buffer_index, vk::SwapchainKHR present_swap_chain,
u32 present_image_index);
private:
struct FrameResources {
// [0] - Init (upload) command buffer, [1] - draw command buffer
std::vector<vk::CommandBuffer> command_buffers = {};
std::vector<std::function<void()>> cleanup_resources;
vk::CommandPool command_pool;
vk::DescriptorPool descriptor_pool;
vk::Fence fence;
vk::Semaphore semaphore;
u64 fence_counter = 0;
bool init_command_buffer_used = false;
bool semaphore_used = false;
};
struct PendingCommandBufferSubmit {
vk::SwapchainKHR present_swap_chain;
u32 present_image_index;
u32 command_buffer_index;
};
u64 next_fence_counter = 1;
u64 completed_fence_counter = 0;
std::array<FrameResources, COMMAND_BUFFER_COUNT> frame_resources;
u32 current_frame = 0;
// Threaded command buffer execution
// Semaphore determines when a command buffer can be queued
Common::Semaphore submit_semaphore;
std::thread submit_thread;
std::unique_ptr<Common::BlockingLoop> submit_loop;
std::deque<PendingCommandBufferSubmit> pending_submits;
std::mutex pending_submit_lock;
Common::Flag last_present_failed;
vk::Semaphore present_semaphore;
vk::Result last_present_result = vk::Result::eSuccess;
bool use_threaded_submission = false;
};
template <typename VulkanObject>
void VKCommandManager::DestroyResource(VulkanObject object) {
auto& resources = frame_resources[current_frame];
auto deleter = [object]() { g_vk_instace->GetDevice().destroy(object); };
resources.cleanup_resources.push_back(deleter);
}
extern std::unique_ptr<VKCommandManager> g_command_buffer_mgr;
} // namespace Vulkan

View File

@ -90,6 +90,7 @@ bool VKInstance::CreateDevice(vk::SurfaceKHR surface, bool validation_enabled) {
// Set device features
device_info.setPEnabledFeatures(&device_features);
device_info.setPNext(&new_features);
// Enable debug layer on debug builds
if (validation_enabled) {
@ -130,6 +131,9 @@ bool VKInstance::FindFeatures()
device_features.depthClamp = available_features.depthClamp;
device_features.textureCompressionBC = available_features.textureCompressionBC;
// Enable timeline semaphore support
new_features.timelineSemaphore = true;
return true;
}

View File

@ -53,6 +53,7 @@ public:
// Extensions and features
std::vector<const char*> device_extensions;
vk::PhysicalDeviceFeatures device_features{};
vk::PhysicalDeviceVulkan12Features new_features{};
};
extern std::unique_ptr<VKInstance> g_vk_instace;

View File

@ -870,7 +870,7 @@ static Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams&
return match_surface;
}
RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
RasterizerCacheVulkan::RasterizerCacheVulkan() {
resolution_scale_factor = VideoCore::GetResolutionScaleFactor();
texture_filterer = std::make_unique<TextureFilterer>(Settings::values.texture_filter_name,
resolution_scale_factor);
@ -882,7 +882,7 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
draw_framebuffer.Create();
}
RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
RasterizerCacheVulkan::~RasterizerCacheVulkan() {
#ifndef ANDROID
// This is for switching renderers, which is unsupported on Android, and costly on shutdown
ClearAll(false);
@ -890,7 +890,7 @@ RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
}
MICROPROFILE_DEFINE(OpenGL_BlitSurface, "OpenGL", "BlitSurface", MP_RGB(128, 192, 64));
bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface,
bool RasterizerCacheVulkan::BlitSurfaces(const Surface& src_surface,
const Common::Rectangle<u32>& src_rect,
const Surface& dst_surface,
const Common::Rectangle<u32>& dst_rect) {
@ -906,7 +906,7 @@ bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface,
draw_framebuffer.handle);
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
Surface RasterizerCacheVulkan::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
bool load_if_create) {
if (params.addr == 0 || params.height * params.width == 0) {
return nullptr;
@ -954,7 +954,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatc
return surface;
}
SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params,
SurfaceRect_Tuple RasterizerCacheVulkan::GetSurfaceSubRect(const SurfaceParams& params,
ScaleMatch match_res_scale,
bool load_if_create) {
if (params.addr == 0 || params.height * params.width == 0) {
@ -1033,14 +1033,14 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams&
return std::make_tuple(surface, surface->GetScaledSubRect(params));
}
Surface RasterizerCacheOpenGL::GetTextureSurface(
Surface RasterizerCacheVulkan::GetTextureSurface(
const Pica::TexturingRegs::FullTextureConfig& config) {
Pica::Texture::TextureInfo info =
Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format);
return GetTextureSurface(info, config.config.lod.max_level);
}
Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info,
Surface RasterizerCacheVulkan::GetTextureSurface(const Pica::Texture::TextureInfo& info,
u32 max_level) {
if (info.physical_address == 0) {
return nullptr;
@ -1172,7 +1172,7 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
return surface;
}
const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCubeConfig& config) {
const CachedTextureCube& RasterizerCacheVulkan::GetTextureCube(const TextureCubeConfig& config) {
auto& cube = texture_cube_cache[config];
struct Face {
@ -1265,7 +1265,7 @@ const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCube
return cube;
}
SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
SurfaceSurfaceRect_Tuple RasterizerCacheVulkan::GetFramebufferSurfaces(
bool using_color_fb, bool using_depth_fb, const Common::Rectangle<s32>& viewport_rect) {
const auto& regs = Pica::g_state.regs;
const auto& config = regs.framebuffer.framebuffer;
@ -1357,7 +1357,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
return std::make_tuple(color_surface, depth_surface, fb_rect);
}
Surface RasterizerCacheOpenGL::GetFillSurface(const GPU::Regs::MemoryFillConfig& config) {
Surface RasterizerCacheVulkan::GetFillSurface(const GPU::Regs::MemoryFillConfig& config) {
Surface new_surface = std::make_shared<CachedSurface>(*this);
new_surface->addr = config.GetStartAddress();
@ -1379,7 +1379,7 @@ Surface RasterizerCacheOpenGL::GetFillSurface(const GPU::Regs::MemoryFillConfig&
return new_surface;
}
SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) {
SurfaceRect_Tuple RasterizerCacheVulkan::GetTexCopySurface(const SurfaceParams& params) {
Common::Rectangle<u32> rect{};
Surface match_surface = FindMatch<MatchFlags::TexCopy | MatchFlags::Invalid>(
@ -1406,7 +1406,7 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams&
return std::make_tuple(match_surface, rect);
}
void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface,
void RasterizerCacheVulkan::DuplicateSurface(const Surface& src_surface,
const Surface& dest_surface) {
ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end);
@ -1427,7 +1427,7 @@ void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface,
}
}
void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, u32 size) {
void RasterizerCacheVulkan::ValidateSurface(const Surface& surface, PAddr addr, u32 size) {
if (size == 0)
return;
@ -1493,7 +1493,7 @@ void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr,
}
}
bool RasterizerCacheOpenGL::NoUnimplementedReinterpretations(const Surface& surface,
bool RasterizerCacheVulkan::NoUnimplementedReinterpretations(const Surface& surface,
SurfaceParams& params,
const SurfaceInterval& interval) {
static constexpr std::array<PixelFormat, 17> all_formats{
@ -1522,7 +1522,7 @@ bool RasterizerCacheOpenGL::NoUnimplementedReinterpretations(const Surface& surf
return implemented;
}
bool RasterizerCacheOpenGL::IntervalHasInvalidPixelFormat(SurfaceParams& params,
bool RasterizerCacheVulkan::IntervalHasInvalidPixelFormat(SurfaceParams& params,
const SurfaceInterval& interval) {
params.pixel_format = PixelFormat::Invalid;
for (const auto& set : RangeFromInterval(surface_cache, interval))
@ -1534,7 +1534,7 @@ bool RasterizerCacheOpenGL::IntervalHasInvalidPixelFormat(SurfaceParams& params,
return false;
}
bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface,
bool RasterizerCacheVulkan::ValidateByReinterpretation(const Surface& surface,
SurfaceParams& params,
const SurfaceInterval& interval) {
auto [cvt_begin, cvt_end] =
@ -1583,7 +1583,7 @@ bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface,
return false;
}
void RasterizerCacheOpenGL::ClearAll(bool flush) {
void RasterizerCacheVulkan::ClearAll(bool flush) {
const auto flush_interval = PageMap::interval_type::right_open(0x0, 0xFFFFFFFF);
// Force flush all surfaces from the cache
if (flush) {
@ -1607,7 +1607,7 @@ void RasterizerCacheOpenGL::ClearAll(bool flush) {
remove_surfaces.clear();
}
void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surface) {
void RasterizerCacheVulkan::FlushRegion(PAddr addr, u32 size, Surface flush_surface) {
std::lock_guard lock{mutex};
if (size == 0)
@ -1641,11 +1641,11 @@ void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surf
dirty_regions -= flushed_intervals;
}
void RasterizerCacheOpenGL::FlushAll() {
void RasterizerCacheVulkan::FlushAll() {
FlushRegion(0, 0xFFFFFFFF);
}
void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner) {
void RasterizerCacheVulkan::InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner) {
std::lock_guard lock{mutex};
if (size == 0)
@ -1709,7 +1709,7 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface
remove_surfaces.clear();
}
Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
Surface RasterizerCacheVulkan::CreateSurface(const SurfaceParams& params) {
Surface surface = std::make_shared<CachedSurface>(*this);
static_cast<SurfaceParams&>(*surface) = params;
@ -1722,7 +1722,7 @@ Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) {
return surface;
}
void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
void RasterizerCacheVulkan::RegisterSurface(const Surface& surface) {
std::lock_guard lock{mutex};
if (surface->registered) {
@ -1733,7 +1733,7 @@ void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
UpdatePagesCachedCount(surface->addr, surface->size, 1);
}
void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
void RasterizerCacheVulkan::UnregisterSurface(const Surface& surface) {
std::lock_guard lock{mutex};
if (!surface->registered) {
@ -1744,7 +1744,7 @@ void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}});
}
void RasterizerCacheOpenGL::UpdatePagesCachedCount(PAddr addr, u32 size, int delta) {
void RasterizerCacheVulkan::UpdatePagesCachedCount(PAddr addr, u32 size, int delta) {
const u32 num_pages =
((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1;
const u32 page_start = addr >> Memory::PAGE_BITS;

View File

@ -32,8 +32,7 @@ void VulkanState::Create() {
dirty_flags |= DirtyState::All;
}
void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset)
{
void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset) {
if (vertex_buffer == buffer) {
return;
}
@ -43,15 +42,13 @@ void VulkanState::SetVertexBuffer(VKBuffer* buffer, vk::DeviceSize offset)
dirty_flags |= DirtyState::VertexBuffer;
}
void VulkanState::SetFramebuffer(VKFramebuffer* buffer)
{
void VulkanState::SetFramebuffer(VKFramebuffer* buffer) {
// Should not be changed within a render pass.
//ASSERT(!InRenderPass());
//framebuffer = buffer;
}
void VulkanState::SetPipeline(const VKPipeline* new_pipeline)
{
void VulkanState::SetPipeline(const VKPipeline* new_pipeline) {
if (new_pipeline == pipeline)
return;
@ -59,8 +56,7 @@ void VulkanState::SetPipeline(const VKPipeline* new_pipeline)
dirty_flags |= DirtyState::Pipeline;
}
void VulkanState::SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u32 size)
{
void VulkanState::SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u32 size) {
auto& binding = bindings.ubo[static_cast<u32>(id)];
if (binding.buffer != buffer->GetBuffer() || binding.range != size)
{
@ -70,8 +66,7 @@ void VulkanState::SetUniformBuffer(UniformID id, VKBuffer* buffer, u32 offset, u
}
}
void VulkanState::SetTexture(TextureID id, VKTexture* texture)
{
void VulkanState::SetTexture(TextureID id, VKTexture* texture) {
u32 index = static_cast<u32>(id);
if (bindings.texture[index].imageView == texture->GetView()) {
return;
@ -82,8 +77,7 @@ void VulkanState::SetTexture(TextureID id, VKTexture* texture)
dirty_flags |= DirtyState::Texture;
}
void VulkanState::SetTexelBuffer(TexelBufferID id, VKBuffer* buffer)
{
void VulkanState::SetTexelBuffer(TexelBufferID id, VKBuffer* buffer) {
u32 index = static_cast<u32>(id);
if (bindings.lut[index].buffer == buffer->GetBuffer()) {
return;
@ -93,11 +87,21 @@ void VulkanState::SetTexelBuffer(TexelBufferID id, VKBuffer* buffer)
dirty_flags |= DirtyState::TexelBuffer;
}
void VulkanState::SetImageTexture(VKTexture* image)
{
void VulkanState::SetImageTexture(VKTexture* image) {
// TODO
}
void VulkanState::UnbindTexture(VKTexture* image) {
// Search the texture bindings for the view
// and replace it with the dummy texture if found
for (auto& it : bindings.texture) {
if (it.imageView == image->GetView()) {
it.imageView = dummy_texture.GetView();
it.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
}
}
}
void VulkanState::BeginRenderPass()
{
if (InRenderPass())

View File

@ -65,6 +65,7 @@ public:
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 Apply();
@ -80,7 +81,11 @@ private:
// Pipeline state
const VKPipeline* pipeline = nullptr;
// Shader bindings
// Shader bindings. These describe which resources
// we have bound to the pipeline and at which
// bind points. When the state is applied the
// descriptor sets are updated with the new
// resources
struct
{
std::array<vk::DescriptorBufferInfo, 2> ubo;

View File

@ -57,22 +57,29 @@ bool VKSwapChain::Create(u32 width, u32 height, bool vsync_enabled) {
// If an old swapchain exists, destroy it and move the new one to its place.
// Synchronization is the responsibility of the caller, not us
if (!!swapchain) {
swapchain_images.clear();
if (swapchain.get()) {
swapchain.swap(new_swapchain);
}
// Create sync objects if not already created
if (!image_available.get()) {
image_available = g_vk_instace->GetDevice().createSemaphoreUnique({});
}
// Create framebuffer and image views
swapchain_images.clear();
SetupImages();
return true;
}
void VKSwapChain::AcquireNextImage(vk::Semaphore present_semaphore) {
const auto result = g_vk_instace->GetDevice().acquireNextImageKHR(*swapchain,
std::numeric_limits<u64>::max(), present_semaphore,
VK_NULL_HANDLE, &image_index);
// Wait for maximum of 1 second
constexpr u64 ACQUIRE_TIMEOUT = 1000000000;
vk::Semaphore VKSwapChain::AcquireNextImage() {
auto result = g_vk_instace->GetDevice().acquireNextImageKHR(*swapchain, ACQUIRE_TIMEOUT,
image_available.get(), VK_NULL_HANDLE,
&image_index);
switch (result) {
case vk::Result::eSuccess:
break;
@ -86,6 +93,8 @@ void VKSwapChain::AcquireNextImage(vk::Semaphore present_semaphore) {
LOG_ERROR(Render_Vulkan, "acquireNextImageKHR returned unknown result");
break;
}
return image_available.get();
}
void VKSwapChain::Present(vk::Semaphore render_semaphore) {

View File

@ -32,11 +32,11 @@ public:
bool Create(u32 width, u32 height, bool vsync_enabled);
/// Acquire the next image in the swapchain.
void AcquireNextImage(vk::Semaphore present_semaphore);
vk::Semaphore AcquireNextImage();
void Present(vk::Semaphore render_semaphore);
/// Returns true when the swapchain needs to be recreated.
bool NeedsRecreation() const { return IsSubOptimal(); }
bool NeedsRecreation() const { return IsSubOptimal() || IsOutDated(); }
bool IsOutDated() const { return is_outdated; }
bool IsSubOptimal() const { return is_suboptimal; }
bool IsVSyncEnabled() const { return vsync_enabled; }
@ -47,10 +47,9 @@ public:
vk::SurfaceKHR GetSurface() const { return surface; }
vk::SurfaceFormatKHR GetSurfaceFormat() const { return details.format; }
vk::SwapchainKHR GetSwapChain() const { return swapchain.get(); }
vk::Image GetCurrentImage() const { return swapchain_images[image_index].image; }
/// Retrieve current texture and framebuffer
vk::Image GetCurrentImage() { return swapchain_images[image_index].image; }
VKTexture& GetCurrentImage() { return swapchain_images[image_index].image; }
VKFramebuffer& GetCurrentFramebuffer() { return swapchain_images[image_index].framebuffer; }
private:
@ -60,6 +59,7 @@ private:
private:
SwapChainDetails details{};
vk::SurfaceKHR surface;
vk::UniqueSemaphore image_available;
bool vsync_enabled = false;
bool is_outdated = false, is_suboptimal = false;

View File

@ -0,0 +1,158 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "common/assert.h"
#include "common/thread.h"
namespace Vulkan {
VKTaskScheduler::VKTaskScheduler(VKSwapChain* swapchain) : swapchain(swapchain) {
}
VKTaskScheduler::~VKTaskScheduler() {
// Sync the GPU before exiting
SyncToGPU();
}
vk::CommandBuffer VKTaskScheduler::GetCommandBuffer() {
return tasks[current_task].command_buffer;
}
bool VKTaskScheduler::Create() {
auto device = g_vk_instace->GetDevice();
// Create command pool
vk::CommandPoolCreateInfo pool_info(vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
g_vk_instace->GetGraphicsQueueFamilyIndex());
command_pool = device.createCommandPoolUnique(pool_info);
// Create timeline semaphore for syncronization
vk::SemaphoreTypeCreateInfo timeline_info(vk::SemaphoreType::eTimeline, 0);
vk::SemaphoreCreateInfo semaphore_info({}, &timeline_info);
timeline = device.createSemaphoreUnique(semaphore_info);
// Initialize task structures
for (auto& task : tasks) {
// Create command buffers
vk::CommandBufferAllocateInfo buffer_info
(
command_pool.get(),
vk::CommandBufferLevel::ePrimary,
1, task.command_buffer
);
task.command_buffer = device.allocateCommandBuffers(buffer_info)[0];
}
// Create present semaphore
present_semaphore = device.createSemaphoreUnique({});
// Activate the first task.
BeginTask();
return true;
}
void VKTaskScheduler::SyncToGPU(u64 task_index) {
// No need to sync if the GPU already has finished the task
if (tasks[task_index].task_id <= GetGPUTick()) {
return;
}
auto old_gpu_tick = GetGPUTick();
// Wait for the task to complete
vk::SemaphoreWaitInfo wait_info({}, timeline.get(), tasks[task_index].task_id);
auto result = g_vk_instace->GetDevice().waitSemaphores(wait_info, UINT64_MAX);
if (result != vk::Result::eSuccess) {
LOG_CRITICAL(Render_Vulkan, "Failed waiting for timeline semaphore!");
}
auto new_gpu_tick = GetGPUTick();
// Delete all resources that can be freed now
for (auto& task : tasks) {
if (task.task_id > old_gpu_tick && task.task_id <= new_gpu_tick) {
for (auto& deleter : task.cleanups) {
deleter();
}
}
}
}
void VKTaskScheduler::SyncToGPU() {
SyncToGPU(current_task);
}
void VKTaskScheduler::Submit(bool present, bool wait_completion) {
// End the current task recording.
auto& task = tasks[current_task];
task.command_buffer.end();
// When the task completes the timeline will increment to the task id
vk::TimelineSemaphoreSubmitInfo timeline_info({}, task.task_id);
vk::PipelineStageFlags wait_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
vk::SubmitInfo submit_info({}, wait_stage, task.command_buffer, present_semaphore.get(), &timeline_info);
// Wait for new swapchain image
if (present) {
auto available = swapchain->AcquireNextImage();
submit_info.setWaitSemaphores(available);
}
// Submit the command buffer
submit_info.setSignalSemaphores(timeline.get());
g_vk_instace->GetGraphicsQueue().submit(submit_info);
// Present the image when rendering has finished
if (present) {
swapchain->Present(present_semaphore.get());
}
// Block host until the GPU catches up
if (wait_completion) {
SyncToGPU();
}
// Switch to next cmdbuffer.
BeginTask();
}
void VKTaskScheduler::ScheduleDestroy(auto object) {
auto& resources = tasks[current_task];
auto deleter = [object]() { g_vk_instace->GetDevice().destroy(object); };
resources.cleanups.push_back(deleter);
}
void VKTaskScheduler::BeginTask()
{
// Move to the next command buffer.
u32 next_task_index = (current_task + 1) % CONCURRENT_TASK_COUNT;
auto& task = tasks[next_task_index];
auto& device = g_vk_instace->GetDevice();
// Wait for the GPU to finish with all resources for this task.
SyncToGPU(next_task_index);
// Reset command pools to beginning since we can re-use the memory now
device.resetCommandPool(command_pool.get());
vk::CommandBufferBeginInfo begin_info(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
// Enable commands to be recorded to the command buffer again.
task.command_buffer.begin(begin_info);
// Reset upload command buffer state
current_task = next_task_index;
}
std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;
} // namespace Vulkan

View File

@ -0,0 +1,85 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <deque>
#include <functional>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
#include "common/common_types.h"
#include "common/blocking_loop.h"
#include "common/threadsafe_queue.h"
#include "video_core/renderer_vulkan/vk_instance.h"
namespace Vulkan {
/// Number of tasks that can be submitted concurrently. This allows the host
/// to start recording the next frame while the GPU is working on the
/// current one. Larger values can be used with caution, as they can cause
/// frame latency if the CPU is too far ahead of the GPU
constexpr u32 CONCURRENT_TASK_COUNT = 2;
class VKSwapChain;
/// Wrapper class around command buffer execution.
/// Handles synchronization and submission of command buffers
class VKTaskScheduler {
public:
explicit VKTaskScheduler(VKSwapChain* swapchain);
~VKTaskScheduler();
/// Create and initialize the work scheduler
bool Create();
/// Retrieve either of the current frame's command buffers
vk::CommandBuffer GetCommandBuffer();
/// Returns the task id that the CPU is recording
u64 GetCPUTick() const { return tasks[current_task].task_id; }
/// Returns the last known task id to have completed execution in the GPU
u64 GetGPUTick() const { return g_vk_instace->GetDevice().getSemaphoreCounterValue(timeline.get()); }
/// Make the host wait for the GPU to complete
void SyncToGPU();
void SyncToGPU(u64 task_index);
/// Schedule a vulkan object for destruction when the GPU no longer uses it
void ScheduleDestroy(auto object);
/// Submit the current work batch and move to the next frame
void Submit(bool present = true, bool wait_completion = false);
private:
void BeginTask();
private:
struct Task {
std::vector<std::function<void()>> cleanups;
vk::CommandBuffer command_buffer;
u64 task_id = 0;
};
vk::UniqueSemaphore timeline;
vk::UniqueCommandPool command_pool;
u64 last_completed_task_id = 0;
// Each task contains unique resources
std::array<Task, CONCURRENT_TASK_COUNT> tasks;
u32 current_task = CONCURRENT_TASK_COUNT - 1;
// Presentation semaphore
vk::UniqueSemaphore present_semaphore;
VKSwapChain* swapchain = nullptr;
};
extern std::unique_ptr<VKTaskScheduler> g_vk_task_scheduler;
} // namespace Vulkan

View File

@ -5,14 +5,32 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "video_core/renderer_vulkan/vk_texture.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_task_scheduler.h"
#include "video_core/renderer_vulkan/vk_resource_cache.h"
#include "video_core/renderer_vulkan/vk_state.h"
namespace Vulkan {
VKTexture::~VKTexture() {
if (cleanup_image) {
g_vk_instace->GetDevice().destroyImage(texture);
// Make sure to unbind the texture before destroying it
g_vk_state->UnbindTexture(this);
if (cleanup_image && texture) {
g_vk_task_scheduler->ScheduleDestroy(texture);
}
// Schedule deletion of the texture after it's no longer used
// by the GPU
if (texture_view) {
g_vk_task_scheduler->ScheduleDestroy(texture_view);
}
if (texture_memory) {
g_vk_task_scheduler->ScheduleDestroy(texture_memory);
}
if (texture_view) {
g_vk_task_scheduler->ScheduleDestroy(texture_view);
}
}
@ -62,13 +80,13 @@ void VKTexture::Create(const Info& info) {
auto memory_index = VKBuffer::FindMemoryType(requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal);
vk::MemoryAllocateInfo alloc_info(requirements.size, memory_index);
texture_memory = device.allocateMemoryUnique(alloc_info);
device.bindImageMemory(texture, texture_memory.get(), 0);
texture_memory = device.allocateMemory(alloc_info);
device.bindImageMemory(texture, texture_memory, 0);
// Create texture view
vk::ImageViewCreateInfo view_info({}, texture, info.view_type, texture_info.format, {},
vk::ImageSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1));
texture_view = device.createImageViewUnique(view_info);
texture_view = device.createImageView(view_info);
}
void VKTexture::Adopt(vk::Image image, vk::ImageViewCreateInfo view_info) {
@ -77,7 +95,7 @@ void VKTexture::Adopt(vk::Image image, vk::ImageViewCreateInfo view_info) {
texture = image;
// Create image view
texture_view = g_vk_instace->GetDevice().createImageViewUnique(view_info);
texture_view = g_vk_instace->GetDevice().createImageView(view_info);
}
void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer& command_buffer) {
@ -143,8 +161,6 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer&
// Submit pipeline barrier
LayoutInfo source = layout_info(texture_layout), dst = layout_info(new_layout);
command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
vk::ImageMemoryBarrier barrier
(
source.access, dst.access,
@ -157,7 +173,6 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer&
std::array<vk::ImageMemoryBarrier, 1> barriers = { barrier };
command_buffer.pipelineBarrier(source.stage, dst.stage, vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
command_buffer.end();
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
@ -166,19 +181,12 @@ void VKTexture::TransitionLayout(vk::ImageLayout new_layout, vk::CommandBuffer&
}
void VKTexture::CopyPixels(std::span<u32> new_pixels) {
auto& device = g_vk_instace->GetDevice();
auto& queue = g_vk_instace->graphics_queue;
auto command_buffer = g_vk_task_scheduler->GetCommandBuffer();
// Copy pixels to staging buffer
std::memcpy(g_vk_res_cache->GetTextureUploadBuffer().GetHostPointer(),
new_pixels.data(), new_pixels.size() * channels);
// Copy the staging buffer to the image
vk::CommandBufferAllocateInfo alloc_info(g_vk_instace->command_pool.get(), vk::CommandBufferLevel::ePrimary, 1);
vk::CommandBuffer command_buffer = device.allocateCommandBuffers(alloc_info)[0];
command_buffer.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
vk::BufferImageCopy region(0, 0, 0, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), 0,
{ texture_info.width, texture_info.height, 1 });
std::array<vk::BufferImageCopy, 1> regions = { region };
@ -191,14 +199,6 @@ void VKTexture::CopyPixels(std::span<u32> new_pixels) {
// Prepare for shader reads
TransitionLayout(vk::ImageLayout::eShaderReadOnlyOptimal, command_buffer);
command_buffer.end();
vk::SubmitInfo submit_info({}, {}, {}, 1, &command_buffer);
queue.submit(submit_info, nullptr);
/// NOTE: Remove this when the renderer starts working, otherwise it will be very slow
queue.waitIdle();
device.freeCommandBuffers(g_vk_instace->command_pool.get(), command_buffer);
}
void VKTexture::BlitTo(Common::Rectangle<u32> srect, VKTexture& dest,
@ -249,6 +249,12 @@ void VKTexture::Fill(Common::Rectangle<u32> region, glm::vec2 depth_stencil) {
}
VKFramebuffer::~VKFramebuffer() {
if (framebuffer) {
g_vk_task_scheduler->ScheduleDestroy(framebuffer);
}
}
void VKFramebuffer::Create(const Info& info) {
// Make sure that either attachment is valid
assert(info.color || info.depth_stencil);
@ -278,7 +284,7 @@ void VKFramebuffer::Create(const Info& info) {
framebuffer_info.setAttachments(view);
}
framebuffer = g_vk_instace->GetDevice().createFramebufferUnique(framebuffer_info);
framebuffer = g_vk_instace->GetDevice().createFramebuffer(framebuffer_info);
}
void VKFramebuffer::Prepare(vk::CommandBuffer& command_buffer) {

View File

@ -44,14 +44,16 @@ public:
/// Create a new Vulkan texture object
void Create(const Info& info);
/// Create a non-owning texture object
/// Create a non-owning texture object, usefull for image object
/// from the swapchain that are managed by another object
void Adopt(vk::Image image, vk::ImageViewCreateInfo view_info);
/// Copies CPU side pixel data to the GPU texture buffer
void CopyPixels(std::span<u32> pixels);
/// Get Vulkan objects
vk::ImageView& GetView() { return texture_view.get(); }
vk::Image GetImage() 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 }); }
u32 GetSamples() const { return texture_info.multisamples; }
@ -74,8 +76,8 @@ private:
Info texture_info;
vk::ImageLayout texture_layout = vk::ImageLayout::eUndefined;
vk::Image texture;
vk::UniqueImageView texture_view;
vk::UniqueDeviceMemory texture_memory;
vk::ImageView texture_view;
vk::DeviceMemory texture_memory;
u32 channels;
};
@ -93,7 +95,7 @@ public:
};
VKFramebuffer() = default;
~VKFramebuffer() = default;
~VKFramebuffer();
/// Create Vulkan framebuffer object
void Create(const Info& info);
@ -105,7 +107,7 @@ public:
private:
u32 width, height;
vk::UniqueFramebuffer framebuffer;
vk::Framebuffer framebuffer;
std::array<VKTexture*, 2> attachments;
};