mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-12 15:38:59 +01:00
VideoBackends:Vulkan: Decouple available command buffers from frames in flight
This commit is contained in:
parent
e8fa867f14
commit
ed75a58061
@ -51,7 +51,7 @@ bool CommandBufferManager::CreateCommandBuffers()
|
|||||||
VkDevice device = g_vulkan_context->GetDevice();
|
VkDevice device = g_vulkan_context->GetDevice();
|
||||||
VkResult res;
|
VkResult res;
|
||||||
|
|
||||||
for (FrameResources& resources : m_frame_resources)
|
for (CmdBufferResources& resources : m_command_buffers)
|
||||||
{
|
{
|
||||||
resources.init_command_buffer_used = false;
|
resources.init_command_buffer_used = false;
|
||||||
resources.semaphore_used = false;
|
resources.semaphore_used = false;
|
||||||
@ -127,8 +127,8 @@ bool CommandBufferManager::CreateCommandBuffers()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate the first command buffer. ActivateCommandBuffer moves forward, so start with the last
|
// Activate the first command buffer. BeginCommandBuffer moves forward, so start with the last
|
||||||
m_current_frame = static_cast<u32>(m_frame_resources.size()) - 1;
|
m_current_cmd_buffer = static_cast<u32>(m_command_buffers.size()) - 1;
|
||||||
BeginCommandBuffer();
|
BeginCommandBuffer();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ void CommandBufferManager::DestroyCommandBuffers()
|
|||||||
{
|
{
|
||||||
VkDevice device = g_vulkan_context->GetDevice();
|
VkDevice device = g_vulkan_context->GetDevice();
|
||||||
|
|
||||||
for (FrameResources& resources : m_frame_resources)
|
for (CmdBufferResources& resources : m_command_buffers)
|
||||||
{
|
{
|
||||||
// The Vulkan spec section 5.2 says: "When a pool is destroyed, all command buffers allocated
|
// The Vulkan spec section 5.2 says: "When a pool is destroyed, all command buffers allocated
|
||||||
// from the pool are freed.". So we don't need to free the command buffers, just the pools.
|
// from the pool are freed.". So we don't need to free the command buffers, just the pools.
|
||||||
@ -165,9 +165,8 @@ void CommandBufferManager::DestroyCommandBuffers()
|
|||||||
|
|
||||||
VkDescriptorSet CommandBufferManager::AllocateDescriptorSet(VkDescriptorSetLayout set_layout)
|
VkDescriptorSet CommandBufferManager::AllocateDescriptorSet(VkDescriptorSetLayout set_layout)
|
||||||
{
|
{
|
||||||
VkDescriptorSetAllocateInfo allocate_info = {
|
VkDescriptorSetAllocateInfo allocate_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||||
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr,
|
nullptr, GetCurrentDescriptorPool(), 1, &set_layout};
|
||||||
m_frame_resources[m_current_frame].descriptor_pool, 1, &set_layout};
|
|
||||||
|
|
||||||
VkDescriptorSet descriptor_set;
|
VkDescriptorSet descriptor_set;
|
||||||
VkResult res =
|
VkResult res =
|
||||||
@ -236,16 +235,16 @@ void CommandBufferManager::WaitForFenceCounter(u64 fence_counter)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Find the first command buffer which covers this counter value.
|
// Find the first command buffer which covers this counter value.
|
||||||
u32 index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
|
u32 index = (m_current_cmd_buffer + 1) % NUM_COMMAND_BUFFERS;
|
||||||
while (index != m_current_frame)
|
while (index != m_current_cmd_buffer)
|
||||||
{
|
{
|
||||||
if (m_frame_resources[index].fence_counter >= fence_counter)
|
if (m_command_buffers[index].fence_counter >= fence_counter)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
index = (index + 1) % NUM_COMMAND_BUFFERS;
|
index = (index + 1) % NUM_COMMAND_BUFFERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(index != m_current_frame);
|
ASSERT(index != m_current_cmd_buffer);
|
||||||
WaitForCommandBufferCompletion(index);
|
WaitForCommandBufferCompletion(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,27 +253,29 @@ void CommandBufferManager::WaitForCommandBufferCompletion(u32 index)
|
|||||||
// Ensure this command buffer has been submitted.
|
// Ensure this command buffer has been submitted.
|
||||||
WaitForWorkerThreadIdle();
|
WaitForWorkerThreadIdle();
|
||||||
|
|
||||||
|
CmdBufferResources& resources = m_command_buffers[index];
|
||||||
|
|
||||||
// Wait for this command buffer to be completed.
|
// Wait for this command buffer to be completed.
|
||||||
VkResult res = vkWaitForFences(g_vulkan_context->GetDevice(), 1, &m_frame_resources[index].fence,
|
VkResult res =
|
||||||
VK_TRUE, UINT64_MAX);
|
vkWaitForFences(g_vulkan_context->GetDevice(), 1, &resources.fence, VK_TRUE, UINT64_MAX);
|
||||||
if (res != VK_SUCCESS)
|
if (res != VK_SUCCESS)
|
||||||
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
LOG_VULKAN_ERROR(res, "vkWaitForFences failed: ");
|
||||||
|
|
||||||
// Clean up any resources for command buffers between the last known completed buffer and this
|
// 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.
|
// now-completed command buffer. If we use >2 buffers, this may be more than one buffer.
|
||||||
const u64 now_completed_counter = m_frame_resources[index].fence_counter;
|
const u64 now_completed_counter = resources.fence_counter;
|
||||||
u32 cleanup_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
|
u32 cleanup_index = (index + 1) % NUM_COMMAND_BUFFERS;
|
||||||
while (cleanup_index != m_current_frame)
|
while (cleanup_index != index)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[cleanup_index];
|
CmdBufferResources& cleanup_resources = m_command_buffers[cleanup_index];
|
||||||
if (resources.fence_counter > now_completed_counter)
|
if (cleanup_resources.fence_counter > now_completed_counter)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (resources.fence_counter > m_completed_fence_counter)
|
if (cleanup_resources.fence_counter > m_completed_fence_counter)
|
||||||
{
|
{
|
||||||
for (auto& it : resources.cleanup_resources)
|
for (auto& it : cleanup_resources.cleanup_resources)
|
||||||
it();
|
it();
|
||||||
resources.cleanup_resources.clear();
|
cleanup_resources.cleanup_resources.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_index = (cleanup_index + 1) % NUM_COMMAND_BUFFERS;
|
cleanup_index = (cleanup_index + 1) % NUM_COMMAND_BUFFERS;
|
||||||
@ -289,7 +290,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
|||||||
uint32_t present_image_index)
|
uint32_t present_image_index)
|
||||||
{
|
{
|
||||||
// End the current command buffer.
|
// End the current command buffer.
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& resources = GetCurrentCmdBufferResources();
|
||||||
for (VkCommandBuffer command_buffer : resources.command_buffers)
|
for (VkCommandBuffer command_buffer : resources.command_buffers)
|
||||||
{
|
{
|
||||||
VkResult res = vkEndCommandBuffer(command_buffer);
|
VkResult res = vkEndCommandBuffer(command_buffer);
|
||||||
@ -307,7 +308,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
|||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(m_pending_submit_lock);
|
std::lock_guard<std::mutex> guard(m_pending_submit_lock);
|
||||||
m_submit_worker_idle = false;
|
m_submit_worker_idle = false;
|
||||||
m_pending_submits.push_back({present_swap_chain, present_image_index, m_current_frame});
|
m_pending_submits.push_back({present_swap_chain, present_image_index, m_current_cmd_buffer});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wake up the worker thread for a single iteration.
|
// Wake up the worker thread for a single iteration.
|
||||||
@ -318,9 +319,27 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
|||||||
WaitForWorkerThreadIdle();
|
WaitForWorkerThreadIdle();
|
||||||
|
|
||||||
// Pass through to normal submission path.
|
// Pass through to normal submission path.
|
||||||
SubmitCommandBuffer(m_current_frame, present_swap_chain, present_image_index);
|
SubmitCommandBuffer(m_current_cmd_buffer, present_swap_chain, present_image_index);
|
||||||
if (wait_for_completion)
|
if (wait_for_completion)
|
||||||
WaitForCommandBufferCompletion(m_current_frame);
|
WaitForCommandBufferCompletion(m_current_cmd_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (present_swap_chain != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
m_current_frame = (m_current_frame + 1) % NUM_FRAMES_IN_FLIGHT;
|
||||||
|
|
||||||
|
// Wait for all command buffers that used the descriptor pool to finish
|
||||||
|
u32 cmd_buffer_index = (m_current_cmd_buffer + 1) % NUM_COMMAND_BUFFERS;
|
||||||
|
while (cmd_buffer_index != m_current_cmd_buffer)
|
||||||
|
{
|
||||||
|
CmdBufferResources& cmd_buffer = m_command_buffers[cmd_buffer_index];
|
||||||
|
if (cmd_buffer.frame_index == m_current_frame && cmd_buffer.fence_counter != 0 &&
|
||||||
|
cmd_buffer.fence_counter > m_completed_fence_counter)
|
||||||
|
{
|
||||||
|
WaitForCommandBufferCompletion(cmd_buffer_index);
|
||||||
|
}
|
||||||
|
cmd_buffer_index = (cmd_buffer_index + 1) % NUM_COMMAND_BUFFERS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to next cmdbuffer.
|
// Switch to next cmdbuffer.
|
||||||
@ -331,7 +350,7 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
|
|||||||
VkSwapchainKHR present_swap_chain,
|
VkSwapchainKHR present_swap_chain,
|
||||||
u32 present_image_index)
|
u32 present_image_index)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[command_buffer_index];
|
CmdBufferResources& resources = m_command_buffers[command_buffer_index];
|
||||||
|
|
||||||
// This may be executed on the worker thread, so don't modify any state of the manager class.
|
// This may be executed on the worker thread, so don't modify any state of the manager class.
|
||||||
uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
@ -411,8 +430,8 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
|
|||||||
void CommandBufferManager::BeginCommandBuffer()
|
void CommandBufferManager::BeginCommandBuffer()
|
||||||
{
|
{
|
||||||
// Move to the next command buffer.
|
// Move to the next command buffer.
|
||||||
const u32 next_buffer_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS;
|
const u32 next_buffer_index = (m_current_cmd_buffer + 1) % NUM_COMMAND_BUFFERS;
|
||||||
FrameResources& resources = m_frame_resources[next_buffer_index];
|
CmdBufferResources& resources = m_command_buffers[next_buffer_index];
|
||||||
|
|
||||||
// Wait for the GPU to finish with all resources for this command buffer.
|
// Wait for the GPU to finish with all resources for this command buffer.
|
||||||
if (resources.fence_counter > m_completed_fence_counter)
|
if (resources.fence_counter > m_completed_fence_counter)
|
||||||
@ -447,48 +466,49 @@ void CommandBufferManager::BeginCommandBuffer()
|
|||||||
resources.init_command_buffer_used = false;
|
resources.init_command_buffer_used = false;
|
||||||
resources.semaphore_used = false;
|
resources.semaphore_used = false;
|
||||||
resources.fence_counter = m_next_fence_counter++;
|
resources.fence_counter = m_next_fence_counter++;
|
||||||
m_current_frame = next_buffer_index;
|
resources.frame_index = m_current_frame;
|
||||||
|
m_current_cmd_buffer = next_buffer_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferManager::DeferBufferDestruction(VkBuffer object)
|
void CommandBufferManager::DeferBufferDestruction(VkBuffer object)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
resources.cleanup_resources.push_back(
|
cmd_buffer_resources.cleanup_resources.push_back(
|
||||||
[object]() { vkDestroyBuffer(g_vulkan_context->GetDevice(), object, nullptr); });
|
[object]() { vkDestroyBuffer(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferManager::DeferBufferViewDestruction(VkBufferView object)
|
void CommandBufferManager::DeferBufferViewDestruction(VkBufferView object)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
resources.cleanup_resources.push_back(
|
cmd_buffer_resources.cleanup_resources.push_back(
|
||||||
[object]() { vkDestroyBufferView(g_vulkan_context->GetDevice(), object, nullptr); });
|
[object]() { vkDestroyBufferView(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferManager::DeferDeviceMemoryDestruction(VkDeviceMemory object)
|
void CommandBufferManager::DeferDeviceMemoryDestruction(VkDeviceMemory object)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
resources.cleanup_resources.push_back(
|
cmd_buffer_resources.cleanup_resources.push_back(
|
||||||
[object]() { vkFreeMemory(g_vulkan_context->GetDevice(), object, nullptr); });
|
[object]() { vkFreeMemory(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferManager::DeferFramebufferDestruction(VkFramebuffer object)
|
void CommandBufferManager::DeferFramebufferDestruction(VkFramebuffer object)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
resources.cleanup_resources.push_back(
|
cmd_buffer_resources.cleanup_resources.push_back(
|
||||||
[object]() { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), object, nullptr); });
|
[object]() { vkDestroyFramebuffer(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferManager::DeferImageDestruction(VkImage object)
|
void CommandBufferManager::DeferImageDestruction(VkImage object)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
resources.cleanup_resources.push_back(
|
cmd_buffer_resources.cleanup_resources.push_back(
|
||||||
[object]() { vkDestroyImage(g_vulkan_context->GetDevice(), object, nullptr); });
|
[object]() { vkDestroyImage(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandBufferManager::DeferImageViewDestruction(VkImageView object)
|
void CommandBufferManager::DeferImageViewDestruction(VkImageView object)
|
||||||
{
|
{
|
||||||
FrameResources& resources = m_frame_resources[m_current_frame];
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
resources.cleanup_resources.push_back(
|
cmd_buffer_resources.cleanup_resources.push_back(
|
||||||
[object]() { vkDestroyImageView(g_vulkan_context->GetDevice(), object, nullptr); });
|
[object]() { vkDestroyImageView(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,16 +34,19 @@ public:
|
|||||||
// is submitted, after that you should call these functions again.
|
// is submitted, after that you should call these functions again.
|
||||||
VkCommandBuffer GetCurrentInitCommandBuffer()
|
VkCommandBuffer GetCurrentInitCommandBuffer()
|
||||||
{
|
{
|
||||||
m_frame_resources[m_current_frame].init_command_buffer_used = true;
|
CmdBufferResources& cmd_buffer_resources = GetCurrentCmdBufferResources();
|
||||||
return m_frame_resources[m_current_frame].command_buffers[0];
|
cmd_buffer_resources.init_command_buffer_used = true;
|
||||||
|
return cmd_buffer_resources.command_buffers[0];
|
||||||
}
|
}
|
||||||
VkCommandBuffer GetCurrentCommandBuffer() const
|
VkCommandBuffer GetCurrentCommandBuffer() const
|
||||||
{
|
{
|
||||||
return m_frame_resources[m_current_frame].command_buffers[1];
|
const CmdBufferResources& cmd_buffer_resources = m_command_buffers[m_current_cmd_buffer];
|
||||||
|
return cmd_buffer_resources.command_buffers[1];
|
||||||
}
|
}
|
||||||
VkDescriptorPool GetCurrentDescriptorPool() const
|
VkDescriptorPool GetCurrentDescriptorPool() const
|
||||||
{
|
{
|
||||||
return m_frame_resources[m_current_frame].descriptor_pool;
|
const CmdBufferResources& cmd_buffer_resources = m_command_buffers[m_current_cmd_buffer];
|
||||||
|
return cmd_buffer_resources.descriptor_pool;
|
||||||
}
|
}
|
||||||
// Allocates a descriptors set from the pool reserved for the current frame.
|
// Allocates a descriptors set from the pool reserved for the current frame.
|
||||||
VkDescriptorSet AllocateDescriptorSet(VkDescriptorSetLayout set_layout);
|
VkDescriptorSet AllocateDescriptorSet(VkDescriptorSetLayout set_layout);
|
||||||
@ -56,14 +59,19 @@ public:
|
|||||||
|
|
||||||
// Gets the fence that will be signaled when the currently executing command buffer is
|
// 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.
|
// queued and executed. Do not wait for this fence before the buffer is executed.
|
||||||
u64 GetCurrentFenceCounter() const { return m_frame_resources[m_current_frame].fence_counter; }
|
u64 GetCurrentFenceCounter() const
|
||||||
|
{
|
||||||
|
auto& resources = m_command_buffers[m_current_cmd_buffer];
|
||||||
|
return resources.fence_counter;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the semaphore for the current command buffer, which can be used to ensure the
|
// 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.
|
// swap chain image is ready before the command buffer executes.
|
||||||
VkSemaphore GetCurrentCommandBufferSemaphore()
|
VkSemaphore GetCurrentCommandBufferSemaphore()
|
||||||
{
|
{
|
||||||
m_frame_resources[m_current_frame].semaphore_used = true;
|
auto& resources = m_command_buffers[m_current_cmd_buffer];
|
||||||
return m_frame_resources[m_current_frame].semaphore;
|
resources.semaphore_used = true;
|
||||||
|
return resources.semaphore;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the worker thread has submitted any previous command buffers and is idle.
|
// Ensure that the worker thread has submitted any previous command buffers and is idle.
|
||||||
@ -101,7 +109,7 @@ private:
|
|||||||
u32 present_image_index);
|
u32 present_image_index);
|
||||||
void BeginCommandBuffer();
|
void BeginCommandBuffer();
|
||||||
|
|
||||||
struct FrameResources
|
struct CmdBufferResources
|
||||||
{
|
{
|
||||||
// [0] - Init (upload) command buffer, [1] - draw command buffer
|
// [0] - Init (upload) command buffer, [1] - draw command buffer
|
||||||
VkCommandPool command_pool = VK_NULL_HANDLE;
|
VkCommandPool command_pool = VK_NULL_HANDLE;
|
||||||
@ -112,15 +120,22 @@ private:
|
|||||||
u64 fence_counter = 0;
|
u64 fence_counter = 0;
|
||||||
bool init_command_buffer_used = false;
|
bool init_command_buffer_used = false;
|
||||||
bool semaphore_used = false;
|
bool semaphore_used = false;
|
||||||
|
u32 frame_index = 0;
|
||||||
|
|
||||||
std::vector<std::function<void()>> cleanup_resources;
|
std::vector<std::function<void()>> cleanup_resources;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CmdBufferResources& GetCurrentCmdBufferResources()
|
||||||
|
{
|
||||||
|
return m_command_buffers[m_current_cmd_buffer];
|
||||||
|
}
|
||||||
|
|
||||||
u64 m_next_fence_counter = 1;
|
u64 m_next_fence_counter = 1;
|
||||||
u64 m_completed_fence_counter = 0;
|
u64 m_completed_fence_counter = 0;
|
||||||
|
|
||||||
std::array<FrameResources, NUM_COMMAND_BUFFERS> m_frame_resources;
|
std::array<CmdBufferResources, NUM_COMMAND_BUFFERS> m_command_buffers;
|
||||||
u32 m_current_frame = 0;
|
u32 m_current_frame = 0;
|
||||||
|
u32 m_current_cmd_buffer = 0;
|
||||||
|
|
||||||
// Threaded command buffer execution
|
// Threaded command buffer execution
|
||||||
std::thread m_submit_thread;
|
std::thread m_submit_thread;
|
||||||
|
@ -11,10 +11,12 @@
|
|||||||
|
|
||||||
namespace Vulkan
|
namespace Vulkan
|
||||||
{
|
{
|
||||||
// Number of command buffers. Having two allows one buffer to be
|
// Number of command buffers.
|
||||||
// executed whilst another is being built.
|
|
||||||
constexpr size_t NUM_COMMAND_BUFFERS = 2;
|
constexpr size_t NUM_COMMAND_BUFFERS = 2;
|
||||||
|
|
||||||
|
// Number of frames in flight, will be used to decide how many descriptor pools are used
|
||||||
|
constexpr size_t NUM_FRAMES_IN_FLIGHT = 2;
|
||||||
|
|
||||||
// Staging buffer usage - optimize for uploads or readbacks
|
// Staging buffer usage - optimize for uploads or readbacks
|
||||||
enum STAGING_BUFFER_TYPE
|
enum STAGING_BUFFER_TYPE
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user