VideoBackends:Vulkan: Move Vulkan calls to a separate thread
This commit is contained in:
parent
24386750e2
commit
ef4cdcc281
|
@ -1285,6 +1285,7 @@
|
|||
<ClCompile Include="VideoBackends\Vulkan\VKPerfQuery.cpp" />
|
||||
<ClCompile Include="VideoBackends\Vulkan\VKPipeline.cpp" />
|
||||
<ClCompile Include="VideoBackends\Vulkan\VKGfx.cpp" />
|
||||
<ClCompile Include="VideoBackends\Vulkan\VKScheduler.cpp" />
|
||||
<ClCompile Include="VideoBackends\Vulkan\VKShader.cpp" />
|
||||
<ClCompile Include="VideoBackends\Vulkan\VKStreamBuffer.cpp" />
|
||||
<ClCompile Include="VideoBackends\Vulkan\VKSwapChain.cpp" />
|
||||
|
|
|
@ -251,8 +251,11 @@ void Gfx::UnbindTexture(const AbstractTexture* texture)
|
|||
D3D::stateman->ApplyTextures();
|
||||
}
|
||||
|
||||
void Gfx::Flush()
|
||||
void Gfx::Flush(FlushType flushType)
|
||||
{
|
||||
if (flushType == FlushType::FlushToWorker)
|
||||
return;
|
||||
|
||||
D3D::context->Flush();
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ public:
|
|||
void SetFullscreen(bool enable_fullscreen) override;
|
||||
bool IsFullscreen() const override;
|
||||
|
||||
void Flush() override;
|
||||
void Flush(FlushType flushType) override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
void OnConfigChanged(u32 bits) override;
|
||||
|
|
|
@ -93,8 +93,11 @@ std::unique_ptr<AbstractPipeline> Gfx::CreatePipeline(const AbstractPipelineConf
|
|||
return DXPipeline::Create(config, cache_data, cache_data_length);
|
||||
}
|
||||
|
||||
void Gfx::Flush()
|
||||
void Gfx::Flush(FlushType flushType)
|
||||
{
|
||||
if (flushType == FlushType::FlushToWorker)
|
||||
return;
|
||||
|
||||
ExecuteCommandList(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ public:
|
|||
const void* cache_data = nullptr,
|
||||
size_t cache_data_length = 0) override;
|
||||
|
||||
void Flush() override;
|
||||
void Flush(FlushType flushType) override;
|
||||
void WaitForGPUIdle() override;
|
||||
|
||||
void ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool color_enable, bool alpha_enable,
|
||||
|
|
|
@ -44,7 +44,7 @@ public:
|
|||
const void* cache_data = nullptr,
|
||||
size_t cache_data_length = 0) override;
|
||||
|
||||
void Flush() override;
|
||||
void Flush(FlushType flushType) override;
|
||||
void WaitForGPUIdle() override;
|
||||
void OnConfigChanged(u32 bits) override;
|
||||
|
||||
|
|
|
@ -266,8 +266,11 @@ std::unique_ptr<AbstractPipeline> Metal::Gfx::CreatePipeline(const AbstractPipel
|
|||
return g_object_cache->CreatePipeline(config);
|
||||
}
|
||||
|
||||
void Metal::Gfx::Flush()
|
||||
void Metal::Gfx::Flush(FlushType flushType)
|
||||
{
|
||||
if (flushType == FlushType::FlushToWorker)
|
||||
return;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
g_state_tracker->FlushEncoders();
|
||||
|
|
|
@ -444,8 +444,11 @@ void OGLGfx::OnConfigChanged(u32 bits)
|
|||
g_sampler_cache->Clear();
|
||||
}
|
||||
|
||||
void OGLGfx::Flush()
|
||||
void OGLGfx::Flush(FlushType flushType)
|
||||
{
|
||||
if (flushType == FlushType::FlushToWorker)
|
||||
return;
|
||||
|
||||
// ensure all commands are sent to the GPU.
|
||||
// Otherwise the driver could batch several frames together.
|
||||
glFlush();
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
void BeginUtilityDrawing() override;
|
||||
void EndUtilityDrawing() override;
|
||||
|
||||
void Flush() override;
|
||||
void Flush(FlushType flushType) override;
|
||||
void WaitForGPUIdle() override;
|
||||
void OnConfigChanged(u32 bits) override;
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ add_library(videovulkan
|
|||
VulkanContext.h
|
||||
VulkanLoader.cpp
|
||||
VulkanLoader.h
|
||||
VKScheduler.h
|
||||
VKScheduler.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(videovulkan
|
||||
|
|
|
@ -19,7 +19,9 @@ namespace Vulkan
|
|||
{
|
||||
CommandBufferManager::CommandBufferManager(VKTimelineSemaphore* semaphore,
|
||||
bool use_threaded_submission)
|
||||
: m_use_threaded_submission(use_threaded_submission), m_semaphore(semaphore)
|
||||
: m_use_threaded_submission(use_threaded_submission),
|
||||
m_state_tracker(std::make_unique<StateTracker>(this)), m_last_present_done(true),
|
||||
m_semaphore(semaphore)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -43,7 +45,7 @@ bool CommandBufferManager::Initialize()
|
|||
if (m_use_threaded_submission && !CreateSubmitThread())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return m_state_tracker->Initialize();
|
||||
}
|
||||
|
||||
void CommandBufferManager::Shutdown()
|
||||
|
@ -99,13 +101,6 @@ bool CommandBufferManager::CreateCommandBuffers()
|
|||
LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &resources.semaphore);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &m_present_semaphore);
|
||||
|
@ -138,9 +133,6 @@ void CommandBufferManager::DestroyCommandBuffers()
|
|||
for (auto& it : resources.cleanup_resources)
|
||||
it();
|
||||
|
||||
if (resources.semaphore != VK_NULL_HANDLE)
|
||||
vkDestroySemaphore(device, resources.semaphore, nullptr);
|
||||
|
||||
if (resources.fence != VK_NULL_HANDLE)
|
||||
vkDestroyFence(device, resources.fence, nullptr);
|
||||
}
|
||||
|
@ -237,7 +229,6 @@ bool CommandBufferManager::CreateSubmitThread()
|
|||
m_submit_thread.Reset("VK submission thread", [this](PendingCommandBufferSubmit submit) {
|
||||
SubmitCommandBuffer(submit.command_buffer_index, submit.present_swap_chain,
|
||||
submit.present_image_index);
|
||||
CmdBufferResources& resources = m_command_buffers[submit.command_buffer_index];
|
||||
});
|
||||
|
||||
return true;
|
||||
|
@ -271,13 +262,14 @@ void CommandBufferManager::CleanupCompletedCommandBuffers()
|
|||
}
|
||||
}
|
||||
|
||||
void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
||||
void CommandBufferManager::SubmitCommandBuffer(u64 fence_counter, bool submit_on_worker_thread,
|
||||
bool wait_for_completion,
|
||||
VkSwapchainKHR present_swap_chain,
|
||||
uint32_t present_image_index)
|
||||
{
|
||||
// End the current command buffer.
|
||||
CmdBufferResources& resources = GetCurrentCmdBufferResources();
|
||||
resources.fence_counter = fence_counter;
|
||||
for (VkCommandBuffer command_buffer : resources.command_buffers)
|
||||
{
|
||||
VkResult res = vkEndCommandBuffer(command_buffer);
|
||||
|
@ -310,13 +302,15 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
|||
if (present_swap_chain != VK_NULL_HANDLE)
|
||||
{
|
||||
m_current_frame = (m_current_frame + 1) % NUM_FRAMES_IN_FLIGHT;
|
||||
const u64 now_completed_counter = m_semaphore->GetCompletedFenceCounter();
|
||||
|
||||
// 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)
|
||||
if (cmd_buffer.frame_index == m_current_frame && cmd_buffer.fence_counter != 0 &&
|
||||
cmd_buffer.fence_counter > now_completed_counter)
|
||||
{
|
||||
m_semaphore->WaitForFenceCounter(cmd_buffer.fence_counter);
|
||||
}
|
||||
|
@ -350,7 +344,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
|||
|
||||
// Switch to next cmdbuffer.
|
||||
BeginCommandBuffer();
|
||||
StateTracker::GetInstance()->InvalidateCachedState();
|
||||
m_state_tracker->InvalidateCachedState();
|
||||
}
|
||||
|
||||
void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
|
||||
|
@ -395,8 +389,9 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
|
|||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
|
||||
PanicAlertFmt("Failed to submit command buffer: {} ({})", VkResultToString(res),
|
||||
static_cast<int>(res));
|
||||
PanicAlertFmt("Failed to submit command buffer: {} ({}), semaphore used: {}, has present sc {}",
|
||||
VkResultToString(res), static_cast<int>(res), resources.semaphore_used,
|
||||
present_swap_chain != VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
m_semaphore->PushPendingFenceValue(resources.fence, resources.fence_counter);
|
||||
|
@ -413,28 +408,27 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
|
|||
&present_swap_chain,
|
||||
&present_image_index,
|
||||
nullptr};
|
||||
|
||||
m_last_present_result = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info);
|
||||
m_last_present_done.Set();
|
||||
if (m_last_present_result != VK_SUCCESS)
|
||||
res = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
|
||||
if (m_last_present_result != VK_ERROR_OUT_OF_DATE_KHR &&
|
||||
m_last_present_result != VK_SUBOPTIMAL_KHR &&
|
||||
m_last_present_result != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
|
||||
if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR &&
|
||||
res != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
|
||||
{
|
||||
LOG_VULKAN_ERROR(m_last_present_result, "vkQueuePresentKHR failed: ");
|
||||
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
|
||||
}
|
||||
|
||||
// Don't treat VK_SUBOPTIMAL_KHR as fatal on Android. Android 10+ requires prerotation.
|
||||
// See https://twitter.com/Themaister/status/1207062674011574273
|
||||
#ifdef VK_USE_PLATFORM_ANDROID_KHR
|
||||
if (m_last_present_result != VK_SUBOPTIMAL_KHR)
|
||||
if (res != VK_SUBOPTIMAL_KHR)
|
||||
m_last_present_failed.Set();
|
||||
#else
|
||||
m_last_present_failed.Set();
|
||||
#endif
|
||||
}
|
||||
m_last_present_result.store(res);
|
||||
m_last_present_done.Set();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,7 +441,9 @@ void CommandBufferManager::BeginCommandBuffer()
|
|||
// Wait for the GPU to finish with all resources for this command buffer.
|
||||
if (resources.fence_counter > m_semaphore->GetCompletedFenceCounter() &&
|
||||
resources.fence_counter != 0)
|
||||
{
|
||||
m_semaphore->WaitForFenceCounter(resources.fence_counter);
|
||||
}
|
||||
|
||||
CleanupCompletedCommandBuffers();
|
||||
|
||||
|
@ -471,10 +467,9 @@ void CommandBufferManager::BeginCommandBuffer()
|
|||
LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: ");
|
||||
}
|
||||
|
||||
// Reset upload command buffer state
|
||||
// Reset command buffer state
|
||||
resources.init_command_buffer_used = false;
|
||||
resources.semaphore_used = false;
|
||||
resources.fence_counter = m_semaphore->BumpNextFenceCounter();
|
||||
resources.frame_index = m_current_frame;
|
||||
m_current_cmd_buffer = next_buffer_index;
|
||||
}
|
||||
|
@ -514,6 +509,4 @@ void CommandBufferManager::DeferImageViewDestruction(VkImageView object)
|
|||
cmd_buffer_resources.cleanup_resources.push_back(
|
||||
[object]() { vkDestroyImageView(g_vulkan_context->GetDevice(), object, nullptr); });
|
||||
}
|
||||
|
||||
std::unique_ptr<CommandBufferManager> g_command_buffer_mgr;
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
namespace Vulkan
|
||||
{
|
||||
class StateTracker;
|
||||
class VKTimelineSemaphore;
|
||||
|
||||
class CommandBufferManager
|
||||
|
@ -61,23 +62,24 @@ public:
|
|||
|
||||
// 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.
|
||||
VkSemaphore GetCurrentCommandBufferSemaphore()
|
||||
void SetWaitSemaphoreForCurrentCommandBuffer(VkSemaphore semaphore)
|
||||
{
|
||||
auto& resources = m_command_buffers[m_current_cmd_buffer];
|
||||
resources.semaphore_used = true;
|
||||
return resources.semaphore;
|
||||
resources.semaphore = semaphore;
|
||||
}
|
||||
|
||||
// Ensure that the worker thread has submitted any previous command buffers and is idle.
|
||||
void WaitForSubmitWorkerThreadIdle();
|
||||
|
||||
void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
|
||||
void SubmitCommandBuffer(u64 fence_counter, bool submit_on_worker_thread,
|
||||
bool wait_for_completion,
|
||||
VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE,
|
||||
uint32_t present_image_index = 0xFFFFFFFF);
|
||||
|
||||
// Was the last present submitted to the queue a failure? If so, we must recreate our swapchain.
|
||||
bool CheckLastPresentFail() { return m_last_present_failed.TestAndClear(); }
|
||||
VkResult GetLastPresentResult() const { return m_last_present_result; }
|
||||
VkResult GetLastPresentResult() const { return m_last_present_result.load(); }
|
||||
bool CheckLastPresentDone() { return m_last_present_done.TestAndClear(); }
|
||||
|
||||
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
|
||||
|
@ -88,6 +90,8 @@ public:
|
|||
void DeferImageDestruction(VkImage object, VmaAllocation alloc);
|
||||
void DeferImageViewDestruction(VkImageView object);
|
||||
|
||||
StateTracker* GetStateTracker() { return m_state_tracker.get(); }
|
||||
|
||||
private:
|
||||
bool CreateCommandBuffers();
|
||||
void DestroyCommandBuffers();
|
||||
|
@ -137,6 +141,10 @@ private:
|
|||
u32 m_current_frame = 0;
|
||||
u32 m_current_cmd_buffer = 0;
|
||||
|
||||
bool m_use_threaded_submission = false;
|
||||
|
||||
std::unique_ptr<StateTracker> m_state_tracker;
|
||||
|
||||
// Threaded command buffer execution
|
||||
struct PendingCommandBufferSubmit
|
||||
{
|
||||
|
@ -148,13 +156,10 @@ private:
|
|||
VkSemaphore m_present_semaphore = VK_NULL_HANDLE;
|
||||
Common::Flag m_last_present_failed;
|
||||
Common::Flag m_last_present_done;
|
||||
VkResult m_last_present_result = VK_SUCCESS;
|
||||
bool m_use_threaded_submission = false;
|
||||
std::atomic<VkResult> m_last_present_result = VK_SUCCESS;
|
||||
u32 m_descriptor_set_count = DESCRIPTOR_SETS_PER_POOL;
|
||||
|
||||
VKTimelineSemaphore* m_semaphore;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<CommandBufferManager> g_command_buffer_mgr;
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
namespace Vulkan
|
||||
{
|
||||
// Number of command buffers.
|
||||
constexpr size_t NUM_COMMAND_BUFFERS = 8;
|
||||
constexpr size_t NUM_COMMAND_BUFFERS = 16;
|
||||
|
||||
// Number of frames in flight, will be used to decide how many descriptor pools are used
|
||||
constexpr size_t NUM_FRAMES_IN_FLIGHT = 2;
|
||||
|
|
|
@ -420,6 +420,8 @@ VkRenderPass ObjectCache::GetRenderPass(VkFormat color_format, VkFormat depth_fo
|
|||
u32 multisamples, VkAttachmentLoadOp load_op,
|
||||
u8 additional_attachment_count)
|
||||
{
|
||||
std::scoped_lock lock(m_render_pass_mutex);
|
||||
|
||||
auto key =
|
||||
std::tie(color_format, depth_format, multisamples, load_op, additional_attachment_count);
|
||||
auto it = m_render_pass_cache.find(key);
|
||||
|
@ -502,6 +504,8 @@ VkRenderPass ObjectCache::GetRenderPass(VkFormat color_format, VkFormat depth_fo
|
|||
|
||||
void ObjectCache::DestroyRenderPassCache()
|
||||
{
|
||||
std::scoped_lock lock(m_render_pass_mutex);
|
||||
|
||||
for (auto& it : m_render_pass_cache)
|
||||
vkDestroyRenderPass(g_vulkan_context->GetDevice(), it.second, nullptr);
|
||||
m_render_pass_cache.clear();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
@ -102,6 +103,9 @@ private:
|
|||
std::unique_ptr<VKTexture> m_dummy_texture;
|
||||
|
||||
// Render pass cache
|
||||
// it's the only part of the object cache that is written after initialization
|
||||
// and accessed from multiple threads, so it has to be protected by a lock
|
||||
std::mutex m_render_pass_mutex;
|
||||
using RenderPassCacheKey = std::tuple<VkFormat, VkFormat, u32, VkAttachmentLoadOp, std::size_t>;
|
||||
std::map<RenderPassCacheKey, VkRenderPass> m_render_pass_cache;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#include "VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
#include "VideoCommon/DriverDetails.h"
|
||||
|
@ -26,7 +27,10 @@ StagingBuffer::~StagingBuffer()
|
|||
if (m_map_pointer)
|
||||
Unmap();
|
||||
|
||||
g_command_buffer_mgr->DeferBufferDestruction(m_buffer, m_alloc);
|
||||
g_scheduler->Record(
|
||||
[c_alloc = m_alloc, c_buffer = m_buffer](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->DeferBufferDestruction(c_buffer, c_alloc);
|
||||
});
|
||||
}
|
||||
|
||||
void StagingBuffer::BufferMemoryBarrier(VkCommandBuffer command_buffer, VkBuffer buffer,
|
||||
|
|
|
@ -17,78 +17,100 @@
|
|||
|
||||
namespace Vulkan
|
||||
{
|
||||
static std::unique_ptr<StateTracker> s_state_tracker;
|
||||
|
||||
StateTracker::StateTracker() = default;
|
||||
|
||||
StateTracker::~StateTracker() = default;
|
||||
|
||||
StateTracker* StateTracker::GetInstance()
|
||||
StateTracker::StateTracker(CommandBufferManager* command_buffer_mgr)
|
||||
: m_command_buffer_mgr(command_buffer_mgr)
|
||||
{
|
||||
return s_state_tracker.get();
|
||||
}
|
||||
|
||||
bool StateTracker::CreateInstance()
|
||||
StateTracker::~StateTracker()
|
||||
{
|
||||
ASSERT(!s_state_tracker);
|
||||
s_state_tracker = std::make_unique<StateTracker>();
|
||||
if (!s_state_tracker->Initialize())
|
||||
{
|
||||
s_state_tracker.reset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StateTracker::DestroyInstance()
|
||||
{
|
||||
if (!s_state_tracker)
|
||||
return;
|
||||
|
||||
// When the dummy texture is destroyed, it unbinds itself, then references itself.
|
||||
// Clear everything out so this doesn't happen.
|
||||
for (auto& it : s_state_tracker->m_bindings.samplers)
|
||||
it.imageView = VK_NULL_HANDLE;
|
||||
for (auto& it : s_state_tracker->m_bindings.image_textures)
|
||||
it.imageView = VK_NULL_HANDLE;
|
||||
s_state_tracker->m_dummy_texture.reset();
|
||||
s_state_tracker->m_dummy_compute_texture.reset();
|
||||
|
||||
s_state_tracker.reset();
|
||||
vkDestroyImageView(g_vulkan_context->GetDevice(), m_dummy_view, nullptr);
|
||||
vmaDestroyImage(g_vulkan_context->GetMemoryAllocator(), m_dummy_image, m_dummy_image_alloc);
|
||||
}
|
||||
|
||||
bool StateTracker::Initialize()
|
||||
{
|
||||
// Create a dummy texture which can be used in place of a real binding.
|
||||
m_dummy_texture = VKTexture::Create(TextureConfig(1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
|
||||
AbstractTextureType::Texture_2DArray),
|
||||
"");
|
||||
if (!m_dummy_texture)
|
||||
VkImageCreateInfo dummy_info;
|
||||
dummy_info.pNext = nullptr;
|
||||
dummy_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
dummy_info.extent = {1, 1, 1};
|
||||
dummy_info.arrayLayers = 1;
|
||||
dummy_info.mipLevels = 1;
|
||||
dummy_info.flags = 0;
|
||||
dummy_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
dummy_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
dummy_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
dummy_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
dummy_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
dummy_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
dummy_info.queueFamilyIndexCount = 0;
|
||||
dummy_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
|
||||
dummy_info.pQueueFamilyIndices = nullptr;
|
||||
|
||||
VmaAllocationCreateInfo alloc_create_info = {};
|
||||
alloc_create_info.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT;
|
||||
alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
alloc_create_info.pool = VK_NULL_HANDLE;
|
||||
alloc_create_info.pUserData = nullptr;
|
||||
alloc_create_info.priority = 0.0;
|
||||
alloc_create_info.requiredFlags = 0;
|
||||
alloc_create_info.preferredFlags = 0;
|
||||
|
||||
VkResult res = vmaCreateImage(g_vulkan_context->GetMemoryAllocator(), &dummy_info,
|
||||
&alloc_create_info, &m_dummy_image, &m_dummy_image_alloc, nullptr);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vmaCreateImage failed: ");
|
||||
return false;
|
||||
m_dummy_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
// Create a dummy compute texture which can be used in place of a real binding
|
||||
m_dummy_compute_texture = VKTexture::Create(
|
||||
TextureConfig(1, 1, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage,
|
||||
AbstractTextureType::Texture_2DArray),
|
||||
"");
|
||||
if (!m_dummy_compute_texture)
|
||||
}
|
||||
|
||||
VkImageViewCreateInfo dummy_view_info;
|
||||
dummy_view_info.pNext = nullptr;
|
||||
dummy_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
dummy_view_info.flags = 0;
|
||||
dummy_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||||
dummy_view_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
dummy_view_info.components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
||||
dummy_view_info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
dummy_view_info.image = m_dummy_image;
|
||||
|
||||
res = vkCreateImageView(g_vulkan_context->GetDevice(), &dummy_view_info, nullptr, &m_dummy_view);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateImageView failed: ");
|
||||
return false;
|
||||
m_dummy_compute_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_GENERAL);
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier img_barrier;
|
||||
img_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
img_barrier.pNext = nullptr;
|
||||
img_barrier.srcAccessMask = 0;
|
||||
img_barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||||
img_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
img_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
img_barrier.image = m_dummy_image;
|
||||
img_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
img_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
|
||||
vkCmdPipelineBarrier(m_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &img_barrier);
|
||||
|
||||
// Initialize all samplers to point by default
|
||||
for (size_t i = 0; i < VideoCommon::MAX_PIXEL_SHADER_SAMPLERS; i++)
|
||||
{
|
||||
m_bindings.samplers[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
m_bindings.samplers[i].imageView = m_dummy_texture->GetView();
|
||||
m_bindings.samplers[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
m_bindings.samplers[i].imageView = m_dummy_view;
|
||||
m_bindings.samplers[i].sampler = g_object_cache->GetPointSampler();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < VideoCommon::MAX_COMPUTE_SHADER_SAMPLERS; i++)
|
||||
{
|
||||
m_bindings.image_textures[i].imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
m_bindings.image_textures[i].imageView = m_dummy_compute_texture->GetView();
|
||||
m_bindings.image_textures[i].imageView = m_dummy_view;
|
||||
m_bindings.image_textures[i].sampler = g_object_cache->GetPointSampler();
|
||||
}
|
||||
|
||||
|
@ -127,11 +149,11 @@ void StateTracker::SetIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexT
|
|||
m_dirty_flags |= DIRTY_FLAG_INDEX_BUFFER;
|
||||
}
|
||||
|
||||
void StateTracker::SetFramebuffer(VKFramebuffer* framebuffer)
|
||||
void StateTracker::SetFramebuffer(FramebufferState framebuffer_state)
|
||||
{
|
||||
// Should not be changed within a render pass.
|
||||
ASSERT(!InRenderPass());
|
||||
m_framebuffer = framebuffer;
|
||||
m_framebuffer_state = framebuffer_state;
|
||||
}
|
||||
|
||||
void StateTracker::SetPipeline(const VKPipeline* pipeline)
|
||||
|
@ -252,8 +274,8 @@ void StateTracker::UnbindTexture(VkImageView view)
|
|||
{
|
||||
if (it.imageView == view)
|
||||
{
|
||||
it.imageView = m_dummy_texture->GetView();
|
||||
it.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
it.imageView = m_dummy_view;
|
||||
it.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,8 +283,8 @@ void StateTracker::UnbindTexture(VkImageView view)
|
|||
{
|
||||
if (it.imageView == view)
|
||||
{
|
||||
it.imageView = m_dummy_compute_texture->GetView();
|
||||
it.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
it.imageView = m_dummy_view;
|
||||
it.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -286,18 +308,18 @@ void StateTracker::BeginRenderPass()
|
|||
if (InRenderPass())
|
||||
return;
|
||||
|
||||
m_current_render_pass = m_framebuffer->GetLoadRenderPass();
|
||||
m_framebuffer_render_area = m_framebuffer->GetRect();
|
||||
m_current_render_pass = m_framebuffer_state.load_render_pass;
|
||||
m_render_area = m_framebuffer_state.render_area;
|
||||
|
||||
VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
nullptr,
|
||||
m_current_render_pass,
|
||||
m_framebuffer->GetFB(),
|
||||
m_framebuffer_render_area,
|
||||
m_framebuffer_state.framebuffer,
|
||||
m_render_area,
|
||||
0,
|
||||
nullptr};
|
||||
|
||||
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
|
||||
vkCmdBeginRenderPass(m_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
}
|
||||
|
||||
|
@ -306,18 +328,18 @@ void StateTracker::BeginDiscardRenderPass()
|
|||
if (InRenderPass())
|
||||
return;
|
||||
|
||||
m_current_render_pass = m_framebuffer->GetDiscardRenderPass();
|
||||
m_framebuffer_render_area = m_framebuffer->GetRect();
|
||||
m_current_render_pass = m_framebuffer_state.discard_render_pass;
|
||||
m_render_area = m_framebuffer_state.render_area;
|
||||
|
||||
VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
nullptr,
|
||||
m_current_render_pass,
|
||||
m_framebuffer->GetFB(),
|
||||
m_framebuffer_render_area,
|
||||
m_framebuffer_state.framebuffer,
|
||||
m_render_area,
|
||||
0,
|
||||
nullptr};
|
||||
|
||||
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
|
||||
vkCmdBeginRenderPass(m_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
}
|
||||
|
||||
|
@ -326,7 +348,7 @@ void StateTracker::EndRenderPass()
|
|||
if (!InRenderPass())
|
||||
return;
|
||||
|
||||
vkCmdEndRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer());
|
||||
vkCmdEndRenderPass(m_command_buffer_mgr->GetCurrentCommandBuffer());
|
||||
m_current_render_pass = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
|
@ -335,18 +357,18 @@ void StateTracker::BeginClearRenderPass(const VkRect2D& area, const VkClearValue
|
|||
{
|
||||
ASSERT(!InRenderPass());
|
||||
|
||||
m_current_render_pass = m_framebuffer->GetClearRenderPass();
|
||||
m_framebuffer_render_area = area;
|
||||
m_current_render_pass = m_framebuffer_state.clear_render_pass;
|
||||
m_render_area = area;
|
||||
|
||||
VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
nullptr,
|
||||
m_current_render_pass,
|
||||
m_framebuffer->GetFB(),
|
||||
m_framebuffer_render_area,
|
||||
m_framebuffer_state.framebuffer,
|
||||
m_render_area,
|
||||
num_clear_values,
|
||||
clear_values};
|
||||
|
||||
vkCmdBeginRenderPass(g_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
|
||||
vkCmdBeginRenderPass(m_command_buffer_mgr->GetCurrentCommandBuffer(), &begin_info,
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
}
|
||||
|
||||
|
@ -375,7 +397,8 @@ bool StateTracker::Bind()
|
|||
return false;
|
||||
|
||||
// Check the render area if we were in a clear pass.
|
||||
if (m_current_render_pass == m_framebuffer->GetClearRenderPass() && !IsViewportWithinRenderArea())
|
||||
if (m_current_render_pass == m_framebuffer_state.clear_render_pass &&
|
||||
!IsViewportWithinRenderArea())
|
||||
EndRenderPass();
|
||||
|
||||
// Get a new descriptor set if any parts have changed
|
||||
|
@ -386,7 +409,7 @@ bool StateTracker::Bind()
|
|||
BeginRenderPass();
|
||||
|
||||
// Re-bind parts of the pipeline
|
||||
const VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
|
||||
const VkCommandBuffer command_buffer = m_command_buffer_mgr->GetCurrentCommandBuffer();
|
||||
const bool needs_vertex_buffer = !g_ActiveConfig.backend_info.bSupportsDynamicVertexLoader ||
|
||||
m_pipeline->GetUsage() != AbstractPipelineUsage::GXUber;
|
||||
if (needs_vertex_buffer && (m_dirty_flags & DIRTY_FLAG_VERTEX_BUFFER))
|
||||
|
@ -421,7 +444,7 @@ bool StateTracker::BindCompute()
|
|||
if (InRenderPass())
|
||||
EndRenderPass();
|
||||
|
||||
const VkCommandBuffer command_buffer = g_command_buffer_mgr->GetCurrentCommandBuffer();
|
||||
const VkCommandBuffer command_buffer = m_command_buffer_mgr->GetCurrentCommandBuffer();
|
||||
if (m_dirty_flags & DIRTY_FLAG_COMPUTE_SHADER)
|
||||
{
|
||||
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE,
|
||||
|
@ -437,10 +460,10 @@ bool StateTracker::IsWithinRenderArea(s32 x, s32 y, u32 width, u32 height) const
|
|||
{
|
||||
// Check that the viewport does not lie outside the render area.
|
||||
// If it does, we need to switch to a normal load/store render pass.
|
||||
s32 left = m_framebuffer_render_area.offset.x;
|
||||
s32 top = m_framebuffer_render_area.offset.y;
|
||||
s32 right = left + static_cast<s32>(m_framebuffer_render_area.extent.width);
|
||||
s32 bottom = top + static_cast<s32>(m_framebuffer_render_area.extent.height);
|
||||
s32 left = m_render_area.offset.x;
|
||||
s32 top = m_render_area.offset.y;
|
||||
s32 right = left + static_cast<s32>(m_render_area.extent.width);
|
||||
s32 bottom = top + static_cast<s32>(m_render_area.extent.height);
|
||||
s32 test_left = x;
|
||||
s32 test_top = y;
|
||||
s32 test_right = test_left + static_cast<s32>(width);
|
||||
|
@ -457,7 +480,7 @@ bool StateTracker::IsViewportWithinRenderArea() const
|
|||
|
||||
void StateTracker::EndClearRenderPass()
|
||||
{
|
||||
if (m_current_render_pass != m_framebuffer->GetClearRenderPass())
|
||||
if (m_current_render_pass != m_framebuffer_state.clear_render_pass)
|
||||
return;
|
||||
|
||||
// End clear render pass. Bind() will call BeginRenderPass() which
|
||||
|
@ -486,7 +509,7 @@ void StateTracker::UpdateGXDescriptorSet()
|
|||
|
||||
if (m_dirty_flags & DIRTY_FLAG_GX_UBOS || m_gx_descriptor_sets[0] == VK_NULL_HANDLE)
|
||||
{
|
||||
m_gx_descriptor_sets[0] = g_command_buffer_mgr->AllocateDescriptorSet(
|
||||
m_gx_descriptor_sets[0] = m_command_buffer_mgr->AllocateDescriptorSet(
|
||||
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_STANDARD_UNIFORM_BUFFERS));
|
||||
|
||||
for (size_t i = 0; i < NUM_UBO_DESCRIPTOR_SET_BINDINGS; i++)
|
||||
|
@ -519,7 +542,7 @@ void StateTracker::UpdateGXDescriptorSet()
|
|||
|
||||
if (m_dirty_flags & DIRTY_FLAG_GX_SAMPLERS || m_gx_descriptor_sets[1] == VK_NULL_HANDLE)
|
||||
{
|
||||
m_gx_descriptor_sets[1] = g_command_buffer_mgr->AllocateDescriptorSet(
|
||||
m_gx_descriptor_sets[1] = m_command_buffer_mgr->AllocateDescriptorSet(
|
||||
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_STANDARD_SAMPLERS));
|
||||
|
||||
writes[num_writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
|
@ -545,7 +568,7 @@ void StateTracker::UpdateGXDescriptorSet()
|
|||
(m_dirty_flags & DIRTY_FLAG_GX_SSBO || m_gx_descriptor_sets[2] == VK_NULL_HANDLE))
|
||||
{
|
||||
m_gx_descriptor_sets[2] =
|
||||
g_command_buffer_mgr->AllocateDescriptorSet(g_object_cache->GetDescriptorSetLayout(
|
||||
m_command_buffer_mgr->AllocateDescriptorSet(g_object_cache->GetDescriptorSetLayout(
|
||||
DESCRIPTOR_SET_LAYOUT_STANDARD_SHADER_STORAGE_BUFFERS));
|
||||
|
||||
writes[num_writes++] = {
|
||||
|
@ -575,7 +598,7 @@ void StateTracker::UpdateGXDescriptorSet()
|
|||
|
||||
if (m_dirty_flags & DIRTY_FLAG_DESCRIPTOR_SETS)
|
||||
{
|
||||
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
vkCmdBindDescriptorSets(m_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
|
||||
needs_ssbo ? NUM_GX_DESCRIPTOR_SETS : (NUM_GX_DESCRIPTOR_SETS - 1),
|
||||
m_gx_descriptor_sets.data(),
|
||||
|
@ -587,7 +610,7 @@ void StateTracker::UpdateGXDescriptorSet()
|
|||
else if (m_dirty_flags & DIRTY_FLAG_GX_UBO_OFFSETS)
|
||||
{
|
||||
vkCmdBindDescriptorSets(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
m_command_buffer_mgr->GetCurrentCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
m_pipeline->GetVkPipelineLayout(), 0, 1, m_gx_descriptor_sets.data(),
|
||||
needs_gs_ubo ? NUM_UBO_DESCRIPTOR_SET_BINDINGS : (NUM_UBO_DESCRIPTOR_SET_BINDINGS - 1),
|
||||
m_bindings.gx_ubo_offsets.data());
|
||||
|
@ -604,7 +627,7 @@ void StateTracker::UpdateUtilityDescriptorSet()
|
|||
// Allocate descriptor sets.
|
||||
if (m_dirty_flags & DIRTY_FLAG_UTILITY_UBO || m_utility_descriptor_sets[0] == VK_NULL_HANDLE)
|
||||
{
|
||||
m_utility_descriptor_sets[0] = g_command_buffer_mgr->AllocateDescriptorSet(
|
||||
m_utility_descriptor_sets[0] = m_command_buffer_mgr->AllocateDescriptorSet(
|
||||
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_UTILITY_UNIFORM_BUFFER));
|
||||
|
||||
dswrites[writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
|
@ -623,7 +646,7 @@ void StateTracker::UpdateUtilityDescriptorSet()
|
|||
|
||||
if (m_dirty_flags & DIRTY_FLAG_UTILITY_BINDINGS || m_utility_descriptor_sets[1] == VK_NULL_HANDLE)
|
||||
{
|
||||
m_utility_descriptor_sets[1] = g_command_buffer_mgr->AllocateDescriptorSet(
|
||||
m_utility_descriptor_sets[1] = m_command_buffer_mgr->AllocateDescriptorSet(
|
||||
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_UTILITY_SAMPLERS));
|
||||
|
||||
dswrites[writes++] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
|
@ -655,7 +678,7 @@ void StateTracker::UpdateUtilityDescriptorSet()
|
|||
|
||||
if (m_dirty_flags & DIRTY_FLAG_DESCRIPTOR_SETS)
|
||||
{
|
||||
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
vkCmdBindDescriptorSets(m_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
|
||||
NUM_UTILITY_DESCRIPTOR_SETS, m_utility_descriptor_sets.data(), 1,
|
||||
&m_bindings.utility_ubo_offset);
|
||||
|
@ -663,7 +686,7 @@ void StateTracker::UpdateUtilityDescriptorSet()
|
|||
}
|
||||
else if (m_dirty_flags & DIRTY_FLAG_UTILITY_UBO_OFFSET)
|
||||
{
|
||||
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
vkCmdBindDescriptorSets(m_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline->GetVkPipelineLayout(), 0,
|
||||
1, m_utility_descriptor_sets.data(), 1, &m_bindings.utility_ubo_offset);
|
||||
m_dirty_flags &= ~(DIRTY_FLAG_DESCRIPTOR_SETS | DIRTY_FLAG_UTILITY_UBO_OFFSET);
|
||||
|
@ -678,7 +701,7 @@ void StateTracker::UpdateComputeDescriptorSet()
|
|||
// Allocate descriptor sets.
|
||||
if (m_dirty_flags & DIRTY_FLAG_COMPUTE_BINDINGS)
|
||||
{
|
||||
m_compute_descriptor_set = g_command_buffer_mgr->AllocateDescriptorSet(
|
||||
m_compute_descriptor_set = m_command_buffer_mgr->AllocateDescriptorSet(
|
||||
g_object_cache->GetDescriptorSetLayout(DESCRIPTOR_SET_LAYOUT_COMPUTE));
|
||||
dswrites[0] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
nullptr,
|
||||
|
@ -729,7 +752,7 @@ void StateTracker::UpdateComputeDescriptorSet()
|
|||
|
||||
if (m_dirty_flags & DIRTY_FLAG_COMPUTE_DESCRIPTOR_SET)
|
||||
{
|
||||
vkCmdBindDescriptorSets(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
vkCmdBindDescriptorSets(m_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_BIND_POINT_COMPUTE,
|
||||
g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_COMPUTE), 0, 1,
|
||||
&m_compute_descriptor_set, 1, &m_bindings.utility_ubo_offset);
|
||||
|
|
|
@ -13,28 +13,31 @@
|
|||
|
||||
namespace Vulkan
|
||||
{
|
||||
class VKFramebuffer;
|
||||
class VKShader;
|
||||
class VKPipeline;
|
||||
class VKTexture;
|
||||
class StreamBuffer;
|
||||
class VertexFormat;
|
||||
class CommandBufferManager;
|
||||
|
||||
struct FramebufferState
|
||||
{
|
||||
VkFramebuffer framebuffer;
|
||||
VkRect2D render_area;
|
||||
VkRenderPass load_render_pass;
|
||||
VkRenderPass discard_render_pass;
|
||||
VkRenderPass clear_render_pass;
|
||||
};
|
||||
|
||||
class StateTracker
|
||||
{
|
||||
public:
|
||||
StateTracker();
|
||||
explicit StateTracker(CommandBufferManager* command_buffer_mgr);
|
||||
~StateTracker();
|
||||
|
||||
static StateTracker* GetInstance();
|
||||
static bool CreateInstance();
|
||||
static void DestroyInstance();
|
||||
bool Initialize();
|
||||
|
||||
VKFramebuffer* GetFramebuffer() const { return m_framebuffer; }
|
||||
const VKPipeline* GetPipeline() const { return m_pipeline; }
|
||||
void SetVertexBuffer(VkBuffer buffer, VkDeviceSize offset, u32 size);
|
||||
void SetIndexBuffer(VkBuffer buffer, VkDeviceSize offset, VkIndexType type);
|
||||
void SetFramebuffer(VKFramebuffer* framebuffer);
|
||||
void SetFramebuffer(FramebufferState framebuffer_state);
|
||||
void SetPipeline(const VKPipeline* pipeline);
|
||||
void SetComputeShader(const VKShader* shader);
|
||||
void SetGXUniformBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size);
|
||||
|
@ -110,8 +113,6 @@ private:
|
|||
DIRTY_FLAG_UTILITY_BINDINGS | DIRTY_FLAG_COMPUTE_BINDINGS
|
||||
};
|
||||
|
||||
bool Initialize();
|
||||
|
||||
// Check that the specified viewport is within the render area.
|
||||
// If not, ends the render pass if it is a clear render pass.
|
||||
bool IsViewportWithinRenderArea() const;
|
||||
|
@ -121,6 +122,8 @@ private:
|
|||
void UpdateUtilityDescriptorSet();
|
||||
void UpdateComputeDescriptorSet();
|
||||
|
||||
CommandBufferManager* m_command_buffer_mgr;
|
||||
|
||||
// Which bindings/state has to be updated before the next draw.
|
||||
u32 m_dirty_flags = 0;
|
||||
|
||||
|
@ -156,12 +159,13 @@ private:
|
|||
VkViewport m_viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
||||
VkRect2D m_scissor = {{0, 0}, {1, 1}};
|
||||
|
||||
// uniform buffers
|
||||
std::unique_ptr<VKTexture> m_dummy_texture;
|
||||
std::unique_ptr<VKTexture> m_dummy_compute_texture;
|
||||
VkImage m_dummy_image;
|
||||
VkImageView m_dummy_view;
|
||||
VmaAllocation m_dummy_image_alloc;
|
||||
|
||||
FramebufferState m_framebuffer_state = {};
|
||||
|
||||
VKFramebuffer* m_framebuffer = nullptr;
|
||||
VkRenderPass m_current_render_pass = VK_NULL_HANDLE;
|
||||
VkRect2D m_framebuffer_render_area = {};
|
||||
VkRect2D m_render_area = {};
|
||||
};
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "VideoBackends/Vulkan/StagingBuffer.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
#include "VideoBackends/Vulkan/VKGfx.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
namespace Vulkan
|
||||
|
@ -33,36 +34,42 @@ bool VKBoundingBox::Initialize()
|
|||
return false;
|
||||
|
||||
// Bind bounding box to state tracker
|
||||
StateTracker::GetInstance()->SetSSBO(m_gpu_buffer, 0, BUFFER_SIZE);
|
||||
g_scheduler->Record([c_gpu_buffer = m_gpu_buffer](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetSSBO(c_gpu_buffer, 0, BUFFER_SIZE);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<BBoxType> VKBoundingBox::Read(u32 index, u32 length)
|
||||
{
|
||||
// Can't be done within a render pass.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
// We can just take a reference here, we'll sync immediately afterwards
|
||||
g_scheduler->Record([&](CommandBufferManager* command_buffer_mgr) {
|
||||
// Can't be done within a render pass.
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
|
||||
// Ensure all writes are completed to the GPU buffer prior to the transfer.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0,
|
||||
BUFFER_SIZE, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
m_readback_buffer->PrepareForGPUWrite(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
// Ensure all writes are completed to the GPU buffer prior to the transfer.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, 0,
|
||||
BUFFER_SIZE, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
// Copy from GPU -> readback buffer.
|
||||
VkBufferCopy region = {0, 0, BUFFER_SIZE};
|
||||
vkCmdCopyBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
|
||||
m_readback_buffer->GetBuffer(), 1, ®ion);
|
||||
m_readback_buffer->PrepareForGPUWrite(command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
// Restore GPU buffer access.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
m_readback_buffer->FlushGPUCache(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
// Copy from GPU -> readback buffer.
|
||||
VkBufferCopy region = {0, 0, BUFFER_SIZE};
|
||||
vkCmdCopyBuffer(command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
|
||||
m_readback_buffer->GetBuffer(), 1, ®ion);
|
||||
|
||||
// Restore GPU buffer access.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, VK_ACCESS_TRANSFER_READ_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
m_readback_buffer->FlushGPUCache(command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
});
|
||||
|
||||
// Wait until these commands complete.
|
||||
VKGfx::GetInstance()->ExecuteCommandBuffer(false, true);
|
||||
|
@ -74,30 +81,38 @@ std::vector<BBoxType> VKBoundingBox::Read(u32 index, u32 length)
|
|||
std::vector<BBoxType> values(length);
|
||||
m_readback_buffer->Read(index * sizeof(BBoxType), values.data(), length * sizeof(BBoxType),
|
||||
false);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
void VKBoundingBox::Write(u32 index, std::span<const BBoxType> values)
|
||||
{
|
||||
// We can't issue vkCmdUpdateBuffer within a render pass.
|
||||
// However, the writes must be serialized, so we can't put it in the init buffer.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
std::array<BBoxType, NUM_BBOX_VALUES> values_copy;
|
||||
std::copy(values.begin(), values.end(), values_copy.begin());
|
||||
|
||||
// Ensure GPU buffer is in a state where it can be transferred to.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, 0,
|
||||
BUFFER_SIZE, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
g_scheduler->Record([c_gpu_buffer = m_gpu_buffer, c_values = values_copy,
|
||||
c_index = index](CommandBufferManager* command_buffer_mgr) {
|
||||
// We can't issue vkCmdUpdateBuffer within a render pass.
|
||||
// However, the writes must be serialized, so we can't put it in the init buffer.
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
|
||||
// Write the values to the GPU buffer
|
||||
vkCmdUpdateBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer,
|
||||
index * sizeof(BBoxType), values.size() * sizeof(BBoxType), values.data());
|
||||
// Ensure GPU buffer is in a state where it can be transferred to.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
command_buffer_mgr->GetCurrentCommandBuffer(), c_gpu_buffer,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, 0,
|
||||
BUFFER_SIZE, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
|
||||
// Restore fragment shader access to the buffer.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
// Write the values to the GPU buffer
|
||||
vkCmdUpdateBuffer(command_buffer_mgr->GetCurrentCommandBuffer(), c_gpu_buffer,
|
||||
c_index * sizeof(BBoxType), c_values.size() * sizeof(BBoxType),
|
||||
c_values.data());
|
||||
|
||||
// Restore fragment shader access to the buffer.
|
||||
StagingBuffer::BufferMemoryBarrier(
|
||||
command_buffer_mgr->GetCurrentCommandBuffer(), c_gpu_buffer, VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE,
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT);
|
||||
});
|
||||
}
|
||||
|
||||
bool VKBoundingBox::CreateGPUBuffer()
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "Common/EnumUtils.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/SmallVector.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/ObjectCache.h"
|
||||
|
@ -25,6 +26,7 @@
|
|||
#include "VideoBackends/Vulkan/VKTexture.h"
|
||||
#include "VideoBackends/Vulkan/VKVertexFormat.h"
|
||||
|
||||
#include "VKScheduler.h"
|
||||
#include "VideoCommon/DriverDetails.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
|
@ -100,109 +102,135 @@ VKGfx::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* dep
|
|||
|
||||
void VKGfx::SetPipeline(const AbstractPipeline* pipeline)
|
||||
{
|
||||
StateTracker::GetInstance()->SetPipeline(static_cast<const VKPipeline*>(pipeline));
|
||||
g_scheduler->Record([c_pipeline = static_cast<const VKPipeline*>(pipeline)](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetPipeline(c_pipeline);
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool color_enable,
|
||||
bool alpha_enable, bool z_enable, u32 color, u32 z)
|
||||
{
|
||||
VkRect2D target_vk_rc = {
|
||||
{target_rc.left, target_rc.top},
|
||||
{static_cast<uint32_t>(target_rc.GetWidth()), static_cast<uint32_t>(target_rc.GetHeight())}};
|
||||
|
||||
// Convert RGBA8 -> floating-point values.
|
||||
VkClearValue clear_color_value = {};
|
||||
VkClearValue clear_depth_value = {};
|
||||
clear_color_value.color.float32[0] = static_cast<float>((color >> 16) & 0xFF) / 255.0f;
|
||||
clear_color_value.color.float32[1] = static_cast<float>((color >> 8) & 0xFF) / 255.0f;
|
||||
clear_color_value.color.float32[2] = static_cast<float>((color >> 0) & 0xFF) / 255.0f;
|
||||
clear_color_value.color.float32[3] = static_cast<float>((color >> 24) & 0xFF) / 255.0f;
|
||||
clear_depth_value.depthStencil.depth = static_cast<float>(z & 0xFFFFFF) / 16777216.0f;
|
||||
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
|
||||
clear_depth_value.depthStencil.depth = 1.0f - clear_depth_value.depthStencil.depth;
|
||||
|
||||
// If we're not in a render pass (start of the frame), we can use a clear render pass
|
||||
// to discard the data, rather than loading and then clearing.
|
||||
bool use_clear_attachments = (color_enable && alpha_enable) || z_enable;
|
||||
bool use_clear_render_pass =
|
||||
!StateTracker::GetInstance()->InRenderPass() && color_enable && alpha_enable && z_enable;
|
||||
|
||||
// The NVIDIA Vulkan driver causes the GPU to lock up, or throw exceptions if MSAA is enabled,
|
||||
// a non-full clear rect is specified, and a clear loadop or vkCmdClearAttachments is used.
|
||||
if (g_ActiveConfig.iMultisamples > 1 &&
|
||||
DriverDetails::HasBug(DriverDetails::BUG_BROKEN_MSAA_CLEAR))
|
||||
{
|
||||
use_clear_render_pass = false;
|
||||
use_clear_attachments = false;
|
||||
}
|
||||
|
||||
// This path cannot be used if the driver implementation doesn't guarantee pixels with no drawn
|
||||
// geometry in "this" renderpass won't be cleared
|
||||
if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_CLEAR_LOADOP_RENDERPASS))
|
||||
use_clear_render_pass = false;
|
||||
|
||||
auto* vk_frame_buffer = static_cast<VKFramebuffer*>(m_current_framebuffer);
|
||||
|
||||
// Fastest path: Use a render pass to clear the buffers.
|
||||
if (use_clear_render_pass)
|
||||
{
|
||||
vk_frame_buffer->SetAndClear(target_vk_rc, clear_color_value, clear_depth_value);
|
||||
return;
|
||||
}
|
||||
g_scheduler->Record(
|
||||
[color, z, color_enable, alpha_enable, z_enable, target_rc,
|
||||
c_fb_color_format = vk_frame_buffer->GetColorFormat(),
|
||||
c_fb_depth_format = vk_frame_buffer->GetDepthFormat(),
|
||||
c_num_additional_color_attachments = vk_frame_buffer->GetNumberOfAdditonalAttachments()](
|
||||
CommandBufferManager* command_buffer_mgr) mutable {
|
||||
VkRect2D target_vk_rc = {{target_rc.left, target_rc.top},
|
||||
{static_cast<uint32_t>(target_rc.GetWidth()),
|
||||
static_cast<uint32_t>(target_rc.GetHeight())}};
|
||||
|
||||
// Fast path: Use vkCmdClearAttachments to clear the buffers within a render path
|
||||
// We can't use this when preserving alpha but clearing color.
|
||||
if (use_clear_attachments)
|
||||
{
|
||||
std::vector<VkClearAttachment> clear_attachments;
|
||||
bool has_color = false;
|
||||
if (color_enable && alpha_enable)
|
||||
{
|
||||
VkClearAttachment clear_attachment;
|
||||
clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
clear_attachment.clearValue = clear_color_value;
|
||||
clear_attachments.push_back(std::move(clear_attachment));
|
||||
color_enable = false;
|
||||
alpha_enable = false;
|
||||
has_color = true;
|
||||
}
|
||||
if (z_enable)
|
||||
{
|
||||
VkClearAttachment clear_attachment;
|
||||
clear_attachment.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
clear_attachment.clearValue = clear_depth_value;
|
||||
clear_attachments.push_back(std::move(clear_attachment));
|
||||
z_enable = false;
|
||||
}
|
||||
if (has_color)
|
||||
{
|
||||
for (std::size_t i = 0; i < vk_frame_buffer->GetNumberOfAdditonalAttachments(); i++)
|
||||
{
|
||||
VkClearAttachment clear_attachment;
|
||||
clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
clear_attachment.clearValue = clear_color_value;
|
||||
clear_attachments.push_back(std::move(clear_attachment));
|
||||
}
|
||||
}
|
||||
if (!clear_attachments.empty())
|
||||
{
|
||||
VkClearRect vk_rect = {target_vk_rc, 0, g_framebuffer_manager->GetEFBLayers()};
|
||||
if (!StateTracker::GetInstance()->IsWithinRenderArea(
|
||||
target_vk_rc.offset.x, target_vk_rc.offset.y, target_vk_rc.extent.width,
|
||||
target_vk_rc.extent.height))
|
||||
{
|
||||
StateTracker::GetInstance()->EndClearRenderPass();
|
||||
}
|
||||
StateTracker::GetInstance()->BeginRenderPass();
|
||||
// Convert RGBA8 -> floating-point values.
|
||||
VkClearValue clear_color_value = {};
|
||||
VkClearValue clear_depth_value = {};
|
||||
clear_color_value.color.float32[0] = static_cast<float>((color >> 16) & 0xFF) / 255.0f;
|
||||
clear_color_value.color.float32[1] = static_cast<float>((color >> 8) & 0xFF) / 255.0f;
|
||||
clear_color_value.color.float32[2] = static_cast<float>((color >> 0) & 0xFF) / 255.0f;
|
||||
clear_color_value.color.float32[3] = static_cast<float>((color >> 24) & 0xFF) / 255.0f;
|
||||
clear_depth_value.depthStencil.depth = static_cast<float>(z & 0xFFFFFF) / 16777216.0f;
|
||||
if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
|
||||
clear_depth_value.depthStencil.depth = 1.0f - clear_depth_value.depthStencil.depth;
|
||||
|
||||
vkCmdClearAttachments(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
static_cast<uint32_t>(clear_attachments.size()),
|
||||
clear_attachments.data(), 1, &vk_rect);
|
||||
}
|
||||
}
|
||||
// If we're not in a render pass (start of the frame), we can use a clear render pass
|
||||
// to discard the data, rather than loading and then clearing.
|
||||
bool use_clear_attachments = (color_enable && alpha_enable) || z_enable;
|
||||
bool use_clear_render_pass = !command_buffer_mgr->GetStateTracker()->InRenderPass() &&
|
||||
color_enable && alpha_enable && z_enable;
|
||||
|
||||
// The NVIDIA Vulkan driver causes the GPU to lock up, or throw exceptions if MSAA is
|
||||
// enabled, a non-full clear rect is specified, and a clear loadop or vkCmdClearAttachments
|
||||
// is used.
|
||||
if (g_ActiveConfig.iMultisamples > 1 &&
|
||||
DriverDetails::HasBug(DriverDetails::BUG_BROKEN_MSAA_CLEAR))
|
||||
{
|
||||
use_clear_render_pass = false;
|
||||
use_clear_attachments = false;
|
||||
}
|
||||
|
||||
// This path cannot be used if the driver implementation doesn't guarantee pixels with no
|
||||
// drawn geometry in "this" renderpass won't be cleared
|
||||
if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_CLEAR_LOADOP_RENDERPASS))
|
||||
use_clear_render_pass = false;
|
||||
|
||||
// Fastest path: Use a render pass to clear the buffers.
|
||||
if (use_clear_render_pass)
|
||||
{
|
||||
std::vector<VkClearValue> clear_values;
|
||||
if (c_fb_color_format != AbstractTextureFormat::Undefined)
|
||||
{
|
||||
clear_values.push_back(clear_color_value);
|
||||
}
|
||||
if (c_fb_depth_format != AbstractTextureFormat::Undefined)
|
||||
{
|
||||
clear_values.push_back(clear_depth_value);
|
||||
}
|
||||
for (std::size_t i = 0; i < c_num_additional_color_attachments; i++)
|
||||
{
|
||||
clear_values.push_back(clear_color_value);
|
||||
}
|
||||
|
||||
command_buffer_mgr->GetStateTracker()->BeginClearRenderPass(
|
||||
target_vk_rc, clear_values.data(), static_cast<u32>(clear_values.size()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast path: Use vkCmdClearAttachments to clear the buffers within a render path
|
||||
// We can't use this when preserving alpha but clearing color.
|
||||
if (use_clear_attachments)
|
||||
{
|
||||
Common::SmallVector<VkClearAttachment, 4> clear_attachments;
|
||||
bool has_color = false;
|
||||
if (color_enable && alpha_enable)
|
||||
{
|
||||
VkClearAttachment clear_attachment;
|
||||
clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
clear_attachment.clearValue = clear_color_value;
|
||||
clear_attachments.push_back(std::move(clear_attachment));
|
||||
color_enable = false;
|
||||
alpha_enable = false;
|
||||
has_color = true;
|
||||
}
|
||||
if (z_enable)
|
||||
{
|
||||
VkClearAttachment clear_attachment;
|
||||
clear_attachment.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
clear_attachment.clearValue = clear_depth_value;
|
||||
clear_attachments.push_back(std::move(clear_attachment));
|
||||
z_enable = false;
|
||||
}
|
||||
if (has_color)
|
||||
{
|
||||
for (std::size_t i = 0; i < c_num_additional_color_attachments; i++)
|
||||
{
|
||||
VkClearAttachment clear_attachment;
|
||||
clear_attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
clear_attachment.colorAttachment = 0;
|
||||
clear_attachment.clearValue = clear_color_value;
|
||||
clear_attachments.push_back(std::move(clear_attachment));
|
||||
}
|
||||
}
|
||||
if (!clear_attachments.empty())
|
||||
{
|
||||
VkClearRect vk_rect = {target_vk_rc, 0, g_framebuffer_manager->GetEFBLayers()};
|
||||
if (!command_buffer_mgr->GetStateTracker()->IsWithinRenderArea(
|
||||
target_vk_rc.offset.x, target_vk_rc.offset.y, target_vk_rc.extent.width,
|
||||
target_vk_rc.extent.height))
|
||||
{
|
||||
command_buffer_mgr->GetStateTracker()->EndClearRenderPass();
|
||||
}
|
||||
command_buffer_mgr->GetStateTracker()->BeginRenderPass();
|
||||
|
||||
vkCmdClearAttachments(command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
static_cast<uint32_t>(clear_attachments.size()),
|
||||
clear_attachments.data(), 1, &vk_rect);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Anything left over for the slow path?
|
||||
if (!color_enable && !alpha_enable && !z_enable)
|
||||
|
@ -211,9 +239,16 @@ void VKGfx::ClearRegion(const MathUtil::Rectangle<int>& target_rc, bool color_en
|
|||
AbstractGfx::ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z);
|
||||
}
|
||||
|
||||
void VKGfx::Flush()
|
||||
void VKGfx::Flush(FlushType flushType)
|
||||
{
|
||||
ExecuteCommandBuffer(true, false);
|
||||
if (flushType == FlushType::FlushToWorker)
|
||||
{
|
||||
g_scheduler->Flush();
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecuteCommandBuffer(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void VKGfx::WaitForGPUIdle()
|
||||
|
@ -223,10 +258,11 @@ void VKGfx::WaitForGPUIdle()
|
|||
|
||||
void VKGfx::BindBackbuffer(const ClearColor& clear_color)
|
||||
{
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
if (!g_command_buffer_mgr->CheckLastPresentDone())
|
||||
g_command_buffer_mgr->WaitForSubmitWorkerThreadIdle();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
if (!g_scheduler->CheckLastPresentDone())
|
||||
g_scheduler->SynchronizeSubmissionThread();
|
||||
|
||||
// Handle host window resizes.
|
||||
CheckForSurfaceChange();
|
||||
|
@ -240,9 +276,21 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color)
|
|||
m_swap_chain->SetNextFullscreenState(m_swap_chain->GetCurrentFullscreenState());
|
||||
}
|
||||
|
||||
const bool present_fail = g_command_buffer_mgr->CheckLastPresentFail();
|
||||
VkResult res = present_fail ? g_command_buffer_mgr->GetLastPresentResult() :
|
||||
m_swap_chain->AcquireNextImage();
|
||||
VkSemaphore semaphore = VK_NULL_HANDLE;
|
||||
VkResult res;
|
||||
const bool present_fail = g_scheduler->CheckLastPresentFail();
|
||||
if (!present_fail)
|
||||
{
|
||||
semaphore = m_swap_chain->GetNextSemaphore();
|
||||
g_scheduler->Record([c_semaphore = semaphore](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->SetWaitSemaphoreForCurrentCommandBuffer(c_semaphore);
|
||||
});
|
||||
res = m_swap_chain->AcquireNextImage(semaphore);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = g_scheduler->GetLastPresentResult();
|
||||
}
|
||||
|
||||
if (res == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT &&
|
||||
!m_swap_chain->GetCurrentFullscreenState())
|
||||
|
@ -253,8 +301,13 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color)
|
|||
res = VK_SUCCESS;
|
||||
if (present_fail)
|
||||
{
|
||||
if (semaphore == VK_NULL_HANDLE)
|
||||
{
|
||||
semaphore = m_swap_chain->GetNextSemaphore();
|
||||
}
|
||||
|
||||
// We still need to acquire an image.
|
||||
res = m_swap_chain->AcquireNextImage();
|
||||
res = m_swap_chain->AcquireNextImage(semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,7 +335,12 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color)
|
|||
m_swap_chain->RecreateSwapChain();
|
||||
}
|
||||
|
||||
res = m_swap_chain->AcquireNextImage();
|
||||
semaphore = m_swap_chain->GetNextSemaphore();
|
||||
g_scheduler->Record([c_semaphore = semaphore](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->SetWaitSemaphoreForCurrentCommandBuffer(c_semaphore);
|
||||
});
|
||||
|
||||
res = m_swap_chain->AcquireNextImage(semaphore);
|
||||
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR)
|
||||
PanicAlertFmt("Failed to grab image from swap chain: {:#010X} {}", Common::ToUnderlying(res),
|
||||
VkResultToString(res));
|
||||
|
@ -292,8 +350,7 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color)
|
|||
// color attachment ready for writing. These transitions must occur outside
|
||||
// a render pass, unless the render pass declares a self-dependency.
|
||||
m_swap_chain->GetCurrentTexture()->OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
|
||||
m_swap_chain->GetCurrentTexture()->TransitionToLayout(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
m_swap_chain->GetCurrentTexture()->TransitionToLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(),
|
||||
ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}});
|
||||
}
|
||||
|
@ -301,22 +358,20 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color)
|
|||
void VKGfx::PresentBackbuffer()
|
||||
{
|
||||
// End drawing to backbuffer
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
|
||||
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
|
||||
// to it have finished before present.
|
||||
m_swap_chain->GetCurrentTexture()->TransitionToLayout(
|
||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
m_swap_chain->GetCurrentTexture()->TransitionToLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||
|
||||
// Submit the current command buffer, signaling rendering finished semaphore when it's done
|
||||
// Because this final command buffer is rendering to the swap chain, we need to wait for
|
||||
// the available semaphore to be signaled before executing the buffer. This final submission
|
||||
// can happen off-thread in the background while we're preparing the next frame.
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, false, m_swap_chain->GetSwapChain(),
|
||||
m_swap_chain->GetCurrentImageIndex());
|
||||
|
||||
// New cmdbuffer, so invalidate state.
|
||||
StateTracker::GetInstance()->InvalidateCachedState();
|
||||
g_scheduler->SubmitCommandBuffer(true, false, m_swap_chain->GetSwapChain(),
|
||||
m_swap_chain->GetCurrentImageIndex());
|
||||
}
|
||||
|
||||
void VKGfx::SetFullscreen(bool enable_fullscreen)
|
||||
|
@ -334,11 +389,7 @@ bool VKGfx::IsFullscreen() const
|
|||
|
||||
void VKGfx::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion)
|
||||
{
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(submit_off_thread, wait_for_completion);
|
||||
|
||||
StateTracker::GetInstance()->InvalidateCachedState();
|
||||
g_scheduler->SubmitCommandBuffer(submit_off_thread, wait_for_completion);
|
||||
}
|
||||
|
||||
void VKGfx::CheckForSurfaceChange()
|
||||
|
@ -346,11 +397,13 @@ void VKGfx::CheckForSurfaceChange()
|
|||
if (!g_presenter->SurfaceChangedTestAndClear() || !m_swap_chain)
|
||||
return;
|
||||
|
||||
g_scheduler->SyncWorker();
|
||||
|
||||
// Submit the current draws up until rendering the XFB.
|
||||
ExecuteCommandBuffer(false, true);
|
||||
|
||||
// Clear the present failed flag, since we don't want to resize after recreating.
|
||||
g_command_buffer_mgr->CheckLastPresentFail();
|
||||
g_scheduler->CheckLastPresentFail();
|
||||
|
||||
// Recreate the surface. If this fails we're in trouble.
|
||||
if (!m_swap_chain->RecreateSurface(g_presenter->GetNewSurfaceHandle()))
|
||||
|
@ -365,6 +418,8 @@ void VKGfx::CheckForSurfaceResize()
|
|||
if (!g_presenter->SurfaceResizedTestAndClear())
|
||||
return;
|
||||
|
||||
g_scheduler->SyncWorker();
|
||||
|
||||
// If we don't have a surface, how can we resize the swap chain?
|
||||
// CheckForSurfaceChange should handle this case.
|
||||
if (!m_swap_chain)
|
||||
|
@ -377,7 +432,7 @@ void VKGfx::CheckForSurfaceResize()
|
|||
ExecuteCommandBuffer(false, true);
|
||||
|
||||
// Clear the present failed flag, since we don't want to resize after recreating.
|
||||
g_command_buffer_mgr->CheckLastPresentFail();
|
||||
g_scheduler->CheckLastPresentFail();
|
||||
|
||||
// Resize the swap chain.
|
||||
m_swap_chain->RecreateSwapChain();
|
||||
|
@ -389,7 +444,11 @@ void VKGfx::OnConfigChanged(u32 bits)
|
|||
AbstractGfx::OnConfigChanged(bits);
|
||||
|
||||
if (bits & CONFIG_CHANGE_BIT_HOST_CONFIG)
|
||||
g_object_cache->ReloadPipelineCache();
|
||||
{
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_manager) {
|
||||
g_object_cache->ReloadPipelineCache();
|
||||
});
|
||||
}
|
||||
|
||||
// For vsync, we need to change the present mode, which means recreating the swap chain.
|
||||
if (m_swap_chain && (bits & CONFIG_CHANGE_BIT_VSYNC))
|
||||
|
@ -420,13 +479,21 @@ void VKGfx::OnSwapChainResized()
|
|||
|
||||
void VKGfx::BindFramebuffer(VKFramebuffer* fb)
|
||||
{
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
|
||||
// Shouldn't be bound as a texture.
|
||||
fb->Unbind();
|
||||
|
||||
fb->TransitionForRender();
|
||||
StateTracker::GetInstance()->SetFramebuffer(fb);
|
||||
|
||||
FramebufferState fb_state = {fb->GetFB(), fb->GetRect(), fb->GetLoadRenderPass(),
|
||||
fb->GetDiscardRenderPass(), fb->GetClearRenderPass()};
|
||||
|
||||
g_scheduler->Record([c_fb_state = fb_state](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetFramebuffer(c_fb_state);
|
||||
});
|
||||
m_current_framebuffer = fb;
|
||||
}
|
||||
|
||||
|
@ -449,7 +516,9 @@ void VKGfx::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer)
|
|||
|
||||
// If we're discarding, begin the discard pass, then switch to a load pass.
|
||||
// This way if the command buffer is flushed, we don't start another discard pass.
|
||||
StateTracker::GetInstance()->BeginDiscardRenderPass();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->BeginDiscardRenderPass();
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, const ClearColor& color_value,
|
||||
|
@ -472,25 +541,31 @@ void VKGfx::SetTexture(u32 index, const AbstractTexture* texture)
|
|||
// Texture should always be in SHADER_READ_ONLY layout prior to use.
|
||||
// This is so we don't need to transition during render passes.
|
||||
const VKTexture* tex = static_cast<const VKTexture*>(texture);
|
||||
|
||||
if (tex)
|
||||
{
|
||||
if (tex->GetLayout() != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
{
|
||||
if (StateTracker::GetInstance()->InRenderPass())
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in VKGfx::SetTexture()");
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
}
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
if (command_buffer_mgr->GetStateTracker()->InRenderPass())
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO, "Transitioning image in render pass in Renderer::SetTexture()");
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
}
|
||||
});
|
||||
|
||||
tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
tex->TransitionToLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
StateTracker::GetInstance()->SetTexture(index, tex->GetView());
|
||||
g_scheduler->Record([c_view = tex->GetView(), index](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetTexture(index, c_view);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
StateTracker::GetInstance()->SetTexture(0, VK_NULL_HANDLE);
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetTexture(0, VK_NULL_HANDLE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,15 +575,17 @@ void VKGfx::SetSamplerState(u32 index, const SamplerState& state)
|
|||
if (m_sampler_states[index] == state)
|
||||
return;
|
||||
|
||||
// Look up new state and replace in state tracker.
|
||||
VkSampler sampler = g_object_cache->GetSampler(state);
|
||||
if (sampler == VK_NULL_HANDLE)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to create sampler");
|
||||
sampler = g_object_cache->GetPointSampler();
|
||||
}
|
||||
g_scheduler->Record([index, c_sampler_state = state](CommandBufferManager* command_buffer_mgr) {
|
||||
// Look up new state and replace in state tracker.
|
||||
VkSampler sampler = g_object_cache->GetSampler(c_sampler_state);
|
||||
if (sampler == VK_NULL_HANDLE)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to create sampler");
|
||||
sampler = g_object_cache->GetPointSampler();
|
||||
}
|
||||
|
||||
StateTracker::GetInstance()->SetSampler(index, sampler);
|
||||
command_buffer_mgr->GetStateTracker()->SetSampler(index, sampler);
|
||||
});
|
||||
m_sampler_states[index] = state;
|
||||
}
|
||||
|
||||
|
@ -517,87 +594,117 @@ void VKGfx::SetComputeImageTexture(u32 index, AbstractTexture* texture, bool rea
|
|||
VKTexture* vk_texture = static_cast<VKTexture*>(texture);
|
||||
if (vk_texture)
|
||||
{
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
StateTracker::GetInstance()->SetImageTexture(index, vk_texture->GetView());
|
||||
vk_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
read ? (write ? VKTexture::ComputeImageLayout::ReadWrite :
|
||||
g_scheduler->Record(
|
||||
[index, c_view = vk_texture->GetView()](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
command_buffer_mgr->GetStateTracker()->SetImageTexture(index, c_view);
|
||||
});
|
||||
|
||||
vk_texture->TransitionToLayout(read ? (write ? VKTexture::ComputeImageLayout::ReadWrite :
|
||||
VKTexture::ComputeImageLayout::ReadOnly) :
|
||||
VKTexture::ComputeImageLayout::WriteOnly);
|
||||
}
|
||||
else
|
||||
{
|
||||
StateTracker::GetInstance()->SetImageTexture(index, VK_NULL_HANDLE);
|
||||
g_scheduler->Record([index](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetImageTexture(index, VK_NULL_HANDLE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void VKGfx::UnbindTexture(const AbstractTexture* texture)
|
||||
{
|
||||
StateTracker::GetInstance()->UnbindTexture(static_cast<const VKTexture*>(texture)->GetView());
|
||||
g_scheduler->Record([c_view = static_cast<const VKTexture*>(texture)->GetView()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->UnbindTexture(c_view);
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::ResetSamplerStates()
|
||||
{
|
||||
// Invalidate all sampler states, next draw will re-initialize them.
|
||||
for (u32 i = 0; i < m_sampler_states.size(); i++)
|
||||
{
|
||||
m_sampler_states[i] = RenderState::GetPointSamplerState();
|
||||
StateTracker::GetInstance()->SetSampler(i, g_object_cache->GetPointSampler());
|
||||
}
|
||||
|
||||
// Invalidate all sampler objects (some will be unused now).
|
||||
g_object_cache->ClearSamplerCache();
|
||||
g_scheduler->Record(
|
||||
[c_sampler_count = m_sampler_states.size()](CommandBufferManager* command_buffer_mgr) {
|
||||
// Invalidate all sampler states, next draw will re-initialize them.
|
||||
for (u32 i = 0; i < c_sampler_count; i++)
|
||||
{
|
||||
command_buffer_mgr->GetStateTracker()->SetSampler(i, g_object_cache->GetPointSampler());
|
||||
}
|
||||
|
||||
// Invalidate all sampler objects (some will be unused now).
|
||||
g_object_cache->ClearSamplerCache();
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::SetScissorRect(const MathUtil::Rectangle<int>& rc)
|
||||
{
|
||||
VkRect2D scissor = {{rc.left, rc.top},
|
||||
{static_cast<u32>(rc.GetWidth()), static_cast<u32>(rc.GetHeight())}};
|
||||
g_scheduler->Record([c_rc = rc](CommandBufferManager* command_buffer_mgr) {
|
||||
VkRect2D scissor = {{c_rc.left, c_rc.top},
|
||||
{static_cast<u32>(c_rc.GetWidth()), static_cast<u32>(c_rc.GetHeight())}};
|
||||
|
||||
// See Vulkan spec for vkCmdSetScissor:
|
||||
// The x and y members of offset must be greater than or equal to 0.
|
||||
if (scissor.offset.x < 0)
|
||||
{
|
||||
scissor.extent.width -= -scissor.offset.x;
|
||||
scissor.offset.x = 0;
|
||||
}
|
||||
if (scissor.offset.y < 0)
|
||||
{
|
||||
scissor.extent.height -= -scissor.offset.y;
|
||||
scissor.offset.y = 0;
|
||||
}
|
||||
StateTracker::GetInstance()->SetScissor(scissor);
|
||||
// See Vulkan spec for vkCmdSetScissor:
|
||||
// The x and y members of offset must be greater than or equal to 0.
|
||||
if (scissor.offset.x < 0)
|
||||
{
|
||||
scissor.extent.width -= -scissor.offset.x;
|
||||
scissor.offset.x = 0;
|
||||
}
|
||||
if (scissor.offset.y < 0)
|
||||
{
|
||||
scissor.extent.height -= -scissor.offset.y;
|
||||
scissor.offset.y = 0;
|
||||
}
|
||||
command_buffer_mgr->GetStateTracker()->SetScissor(scissor);
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::SetViewport(float x, float y, float width, float height, float near_depth,
|
||||
float far_depth)
|
||||
{
|
||||
VkViewport viewport = {x, y, width, height, near_depth, far_depth};
|
||||
StateTracker::GetInstance()->SetViewport(viewport);
|
||||
g_scheduler->Record([viewport](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetViewport(viewport);
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::Draw(u32 base_vertex, u32 num_vertices)
|
||||
{
|
||||
if (!StateTracker::GetInstance()->Bind())
|
||||
return;
|
||||
g_scheduler->Record([base_vertex, num_vertices](CommandBufferManager* command_buffer_mgr) {
|
||||
if (!command_buffer_mgr->GetStateTracker()->Bind())
|
||||
return;
|
||||
|
||||
vkCmdDraw(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0);
|
||||
vkCmdDraw(command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex)
|
||||
{
|
||||
if (!StateTracker::GetInstance()->Bind())
|
||||
return;
|
||||
g_scheduler->Record(
|
||||
[base_vertex, num_indices, base_index](CommandBufferManager* command_buffer_mgr) {
|
||||
if (!command_buffer_mgr->GetStateTracker()->Bind())
|
||||
return;
|
||||
|
||||
vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_indices, 1, base_index,
|
||||
base_vertex, 0);
|
||||
vkCmdDrawIndexed(command_buffer_mgr->GetCurrentCommandBuffer(), num_indices, 1, base_index,
|
||||
base_vertex, 0);
|
||||
});
|
||||
}
|
||||
|
||||
void VKGfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y,
|
||||
u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z)
|
||||
{
|
||||
StateTracker::GetInstance()->SetComputeShader(static_cast<const VKShader*>(shader));
|
||||
if (StateTracker::GetInstance()->BindCompute())
|
||||
vkCmdDispatch(g_command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z);
|
||||
g_scheduler->Record([groups_x, groups_y, groups_z,
|
||||
shader](CommandBufferManager* command_buffer_mgr) {
|
||||
if (!command_buffer_mgr->GetStateTracker()->Bind())
|
||||
return;
|
||||
|
||||
command_buffer_mgr->GetStateTracker()->SetComputeShader(static_cast<const VKShader*>(shader));
|
||||
if (command_buffer_mgr->GetStateTracker()->BindCompute())
|
||||
vkCmdDispatch(command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z);
|
||||
});
|
||||
}
|
||||
|
||||
SurfaceInfo VKGfx::GetSurfaceInfo() const
|
||||
|
|
|
@ -51,7 +51,7 @@ public:
|
|||
|
||||
SwapChain* GetSwapChain() const { return m_swap_chain.get(); }
|
||||
|
||||
void Flush() override;
|
||||
void Flush(FlushType flushType) override;
|
||||
void WaitForGPUIdle() override;
|
||||
void OnConfigChanged(u32 bits) override;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "VideoBackends/Vulkan/VKBoundingBox.h"
|
||||
#include "VideoBackends/Vulkan/VKGfx.h"
|
||||
#include "VideoBackends/Vulkan/VKPerfQuery.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VKSwapChain.h"
|
||||
#include "VideoBackends/Vulkan/VKVertexManager.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
@ -197,17 +198,7 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
|
|||
|
||||
UpdateActiveConfig();
|
||||
|
||||
g_timeline_semaphore = std::make_unique<VKTimelineSemaphore>();
|
||||
|
||||
// Create command buffers. We do this separately because the other classes depend on it.
|
||||
g_command_buffer_mgr = std::make_unique<CommandBufferManager>(g_timeline_semaphore.get(),
|
||||
g_Config.bBackendMultithreading);
|
||||
if (!g_command_buffer_mgr->Initialize())
|
||||
{
|
||||
PanicAlertFmt("Failed to create Vulkan command buffers");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
g_scheduler = std::make_unique<Scheduler>(g_Config.bBackendMultithreading);
|
||||
|
||||
// Remaining classes are also dependent on object cache.
|
||||
g_object_cache = std::make_unique<ObjectCache>();
|
||||
|
@ -218,6 +209,14 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Has to be initialized after the object cache
|
||||
if (!g_scheduler->Initialize())
|
||||
{
|
||||
PanicAlertFmt("Failed to initialize Vulkan scheduler.");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create swap chain. This has to be done early so that the target size is correct for auto-scale.
|
||||
std::unique_ptr<SwapChain> swap_chain;
|
||||
if (surface != VK_NULL_HANDLE)
|
||||
|
@ -231,13 +230,6 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
|
|||
}
|
||||
}
|
||||
|
||||
if (!StateTracker::CreateInstance())
|
||||
{
|
||||
PanicAlertFmt("Failed to create state tracker");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto gfx = std::make_unique<VKGfx>(std::move(swap_chain), wsi.render_surface_scale);
|
||||
auto vertex_manager = std::make_unique<VertexManager>();
|
||||
auto perf_query = std::make_unique<PerfQuery>();
|
||||
|
@ -249,8 +241,8 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
|
|||
|
||||
void VideoBackend::Shutdown()
|
||||
{
|
||||
if (g_command_buffer_mgr)
|
||||
g_command_buffer_mgr->Shutdown();
|
||||
if (g_scheduler)
|
||||
g_scheduler->Shutdown();
|
||||
|
||||
if (g_vulkan_context)
|
||||
vkDeviceWaitIdle(g_vulkan_context->GetDevice());
|
||||
|
@ -261,9 +253,7 @@ void VideoBackend::Shutdown()
|
|||
ShutdownShared();
|
||||
|
||||
g_object_cache.reset();
|
||||
StateTracker::DestroyInstance();
|
||||
g_command_buffer_mgr.reset();
|
||||
g_timeline_semaphore.reset();
|
||||
g_scheduler.reset();
|
||||
g_vulkan_context.reset();
|
||||
UnloadVulkanLibrary();
|
||||
}
|
||||
|
|
|
@ -11,10 +11,11 @@
|
|||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "VKTimelineSemaphore.h"
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
#include "VideoBackends/Vulkan/VKGfx.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VKTimelineSemaphore.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
|
@ -52,9 +53,11 @@ void PerfQuery::EnableQuery(PerfQueryGroup group)
|
|||
if (query_count > m_query_buffer.size() / 2)
|
||||
PartialFlush(query_count == PERF_QUERY_BUFFER_SIZE);
|
||||
|
||||
// Ensure command buffer is ready to go before beginning the query, that way we don't submit
|
||||
// a buffer with open queries.
|
||||
StateTracker::GetInstance()->Bind();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
// Ensure command buffer is ready to go before beginning the query, that way we don't submit
|
||||
// a buffer with open queries.
|
||||
command_buffer_mgr->GetStateTracker()->Bind();
|
||||
});
|
||||
|
||||
if (group == PQG_ZCOMP_ZCOMPLOC || group == PQG_ZCOMP)
|
||||
{
|
||||
|
@ -62,15 +65,16 @@ void PerfQuery::EnableQuery(PerfQueryGroup group)
|
|||
DEBUG_ASSERT(!entry.has_value);
|
||||
entry.has_value = true;
|
||||
entry.query_group = group;
|
||||
g_scheduler->Record([c_query_pool = m_query_pool,
|
||||
c_pos = m_query_next_pos](CommandBufferManager* command_buffer_mgr) {
|
||||
// Use precise queries if supported, otherwise boolean (which will be incorrect).
|
||||
VkQueryControlFlags flags =
|
||||
g_vulkan_context->SupportsPreciseOcclusionQueries() ? VK_QUERY_CONTROL_PRECISE_BIT : 0;
|
||||
|
||||
// Use precise queries if supported, otherwise boolean (which will be incorrect).
|
||||
VkQueryControlFlags flags =
|
||||
g_vulkan_context->SupportsPreciseOcclusionQueries() ? VK_QUERY_CONTROL_PRECISE_BIT : 0;
|
||||
|
||||
// Ensure the query starts within a render pass.
|
||||
StateTracker::GetInstance()->BeginRenderPass();
|
||||
vkCmdBeginQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, m_query_next_pos,
|
||||
flags);
|
||||
// Ensure the query starts within a render pass.
|
||||
command_buffer_mgr->GetStateTracker()->BeginRenderPass();
|
||||
vkCmdBeginQuery(command_buffer_mgr->GetCurrentCommandBuffer(), c_query_pool, c_pos, flags);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,10 +82,13 @@ void PerfQuery::DisableQuery(PerfQueryGroup group)
|
|||
{
|
||||
if (group == PQG_ZCOMP_ZCOMPLOC || group == PQG_ZCOMP)
|
||||
{
|
||||
vkCmdEndQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, m_query_next_pos);
|
||||
ActiveQuery& entry = m_query_buffer[m_query_next_pos];
|
||||
entry.fence_counter = g_command_buffer_mgr->GetCurrentFenceCounter();
|
||||
g_scheduler->Record([c_query_pool = m_query_pool, c_pos = m_query_next_pos
|
||||
|
||||
](CommandBufferManager* command_buffer_mgr) {
|
||||
vkCmdEndQuery(command_buffer_mgr->GetCurrentCommandBuffer(), c_query_pool, c_pos);
|
||||
});
|
||||
ActiveQuery& entry = m_query_buffer[m_query_next_pos];
|
||||
entry.fence_counter = g_scheduler->GetCurrentFenceCounter();
|
||||
m_query_next_pos = (m_query_next_pos + 1) % PERF_QUERY_BUFFER_SIZE;
|
||||
m_query_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
@ -96,10 +103,11 @@ void PerfQuery::ResetQuery()
|
|||
m_results[i].store(0, std::memory_order_relaxed);
|
||||
|
||||
// Reset entire query pool, ensuring all queries are ready to write to.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, 0,
|
||||
PERF_QUERY_BUFFER_SIZE);
|
||||
|
||||
g_scheduler->Record([c_query_pool = m_query_pool](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
vkCmdResetQueryPool(command_buffer_mgr->GetCurrentCommandBuffer(), c_query_pool, 0,
|
||||
PERF_QUERY_BUFFER_SIZE);
|
||||
});
|
||||
std::memset(m_query_buffer.data(), 0, sizeof(ActiveQuery) * m_query_buffer.size());
|
||||
}
|
||||
|
||||
|
@ -163,7 +171,7 @@ bool PerfQuery::CreateQueryPool()
|
|||
|
||||
void PerfQuery::ReadbackQueries()
|
||||
{
|
||||
const u64 completed_fence_counter = g_timeline_semaphore->GetCompletedFenceCounter();
|
||||
const u64 completed_fence_counter = g_scheduler->GetCompletedFenceCounter();
|
||||
|
||||
// Need to save these since ProcessResults will modify them.
|
||||
const u32 outstanding_queries = m_query_count.load(std::memory_order_relaxed);
|
||||
|
@ -204,9 +212,12 @@ void PerfQuery::ReadbackQueries(u32 query_count)
|
|||
if (res != VK_SUCCESS)
|
||||
LOG_VULKAN_ERROR(res, "vkGetQueryPoolResults failed: ");
|
||||
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool,
|
||||
m_query_readback_pos, query_count);
|
||||
g_scheduler->Record([c_query_pool = m_query_pool, c_query_readback_pos = m_query_readback_pos,
|
||||
query_count](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
vkCmdResetQueryPool(command_buffer_mgr->GetCurrentCommandBuffer(), c_query_pool,
|
||||
c_query_readback_pos, query_count);
|
||||
});
|
||||
|
||||
// Remove pending queries.
|
||||
for (u32 i = 0; i < query_count; i++)
|
||||
|
@ -236,8 +247,8 @@ void PerfQuery::ReadbackQueries(u32 query_count)
|
|||
void PerfQuery::PartialFlush(bool blocking)
|
||||
{
|
||||
// Submit a command buffer in the background if the front query is not bound to one.
|
||||
if (blocking || m_query_buffer[m_query_readback_pos].fence_counter ==
|
||||
g_command_buffer_mgr->GetCurrentFenceCounter())
|
||||
if (blocking ||
|
||||
m_query_buffer[m_query_readback_pos].fence_counter == g_scheduler->GetCurrentFenceCounter())
|
||||
{
|
||||
VKGfx::GetInstance()->ExecuteCommandBuffer(true, blocking);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Adapted from the yuzu Emulator Project
|
||||
// which in turn is adapted from DXVK
|
||||
|
||||
#include "VKScheduler.h"
|
||||
#include <Common/Assert.h>
|
||||
#include "Common/Thread.h"
|
||||
#include "StateTracker.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/VKTimelineSemaphore.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
Scheduler::Scheduler(bool use_worker_thread)
|
||||
: m_use_worker_thread(use_worker_thread), m_semaphore(std::make_unique<VKTimelineSemaphore>()),
|
||||
m_command_buffer_manager(std::make_unique<CommandBufferManager>(m_semaphore.get(), true))
|
||||
{
|
||||
AcquireNewChunk();
|
||||
|
||||
if (m_use_worker_thread)
|
||||
{
|
||||
m_submit_loop = std::make_unique<Common::BlockingLoop>();
|
||||
m_worker = std::thread([this]() {
|
||||
Common::SetCurrentThreadName("Vulkan CS Thread");
|
||||
WorkerThread();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Scheduler::~Scheduler()
|
||||
{
|
||||
if (m_use_worker_thread)
|
||||
{
|
||||
SyncWorker();
|
||||
m_submit_loop->Stop();
|
||||
m_worker.join();
|
||||
}
|
||||
|
||||
m_semaphore.reset();
|
||||
m_command_buffer_manager.reset();
|
||||
}
|
||||
|
||||
bool Scheduler::Initialize()
|
||||
{
|
||||
return m_command_buffer_manager->Initialize();
|
||||
}
|
||||
|
||||
void Scheduler::CommandChunk::ExecuteAll(CommandBufferManager* cmdbuf)
|
||||
{
|
||||
auto command = first;
|
||||
while (command != nullptr)
|
||||
{
|
||||
auto next = command->GetNext();
|
||||
command->Execute(cmdbuf);
|
||||
command->~Command();
|
||||
command = next;
|
||||
}
|
||||
command_offset = 0;
|
||||
first = nullptr;
|
||||
last = nullptr;
|
||||
}
|
||||
|
||||
void Scheduler::AcquireNewChunk()
|
||||
{
|
||||
std::scoped_lock lock{m_reserve_mutex};
|
||||
if (m_chunk_reserve.empty())
|
||||
{
|
||||
m_chunk = std::make_unique<CommandChunk>();
|
||||
return;
|
||||
}
|
||||
m_chunk = std::move(m_chunk_reserve.back());
|
||||
m_chunk_reserve.pop_back();
|
||||
}
|
||||
|
||||
void Scheduler::Flush()
|
||||
{
|
||||
if (m_chunk->Empty())
|
||||
return;
|
||||
|
||||
{
|
||||
std::scoped_lock lock{m_work_mutex};
|
||||
m_work_queue.push(std::move(m_chunk));
|
||||
m_submit_loop->Wakeup();
|
||||
}
|
||||
AcquireNewChunk();
|
||||
}
|
||||
|
||||
void Scheduler::SyncWorker()
|
||||
{
|
||||
if (!m_use_worker_thread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Flush();
|
||||
m_submit_loop->Wait();
|
||||
}
|
||||
|
||||
void Scheduler::WorkerThread()
|
||||
{
|
||||
m_submit_loop->Run([this]() {
|
||||
while (true)
|
||||
{
|
||||
std::unique_ptr<CommandChunk> work;
|
||||
{
|
||||
std::scoped_lock lock{m_work_mutex};
|
||||
if (m_work_queue.empty())
|
||||
{
|
||||
m_submit_loop->AllowSleep();
|
||||
return;
|
||||
}
|
||||
work = std::move(m_work_queue.front());
|
||||
m_work_queue.pop();
|
||||
}
|
||||
|
||||
work->ExecuteAll(m_command_buffer_manager.get());
|
||||
std::scoped_lock reserve_lock{m_reserve_mutex};
|
||||
m_chunk_reserve.push_back(std::move(work));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Scheduler::Shutdown()
|
||||
{
|
||||
SyncWorker();
|
||||
SynchronizeSubmissionThread();
|
||||
|
||||
u64 fence_counter = m_semaphore->GetCurrentFenceCounter();
|
||||
if (fence_counter != 0)
|
||||
m_semaphore->WaitForFenceCounter(fence_counter - 1);
|
||||
|
||||
m_command_buffer_manager->Shutdown();
|
||||
}
|
||||
|
||||
void Scheduler::SynchronizeSubmissionThread()
|
||||
{
|
||||
SyncWorker();
|
||||
m_command_buffer_manager->WaitForSubmitWorkerThreadIdle();
|
||||
}
|
||||
|
||||
void Scheduler::WaitForFenceCounter(u64 counter)
|
||||
{
|
||||
if (m_semaphore->GetCompletedFenceCounter() >= counter)
|
||||
return;
|
||||
|
||||
SyncWorker();
|
||||
m_semaphore->WaitForFenceCounter(counter);
|
||||
}
|
||||
|
||||
void Scheduler::SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
|
||||
VkSwapchainKHR present_swap_chain, uint32_t present_image_index)
|
||||
{
|
||||
const u64 fence_counter = m_semaphore->BumpNextFenceCounter();
|
||||
Record([fence_counter, submit_on_worker_thread, wait_for_completion, present_swap_chain,
|
||||
present_image_index](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
command_buffer_mgr->SubmitCommandBuffer(fence_counter, submit_on_worker_thread,
|
||||
wait_for_completion, present_swap_chain,
|
||||
present_image_index);
|
||||
});
|
||||
|
||||
if (wait_for_completion) [[unlikely]]
|
||||
g_scheduler->WaitForFenceCounter(fence_counter);
|
||||
else
|
||||
Flush();
|
||||
}
|
||||
|
||||
u64 Scheduler::GetCompletedFenceCounter() const
|
||||
{
|
||||
return m_semaphore->GetCompletedFenceCounter();
|
||||
}
|
||||
|
||||
u64 Scheduler::GetCurrentFenceCounter() const
|
||||
{
|
||||
return m_semaphore->GetCurrentFenceCounter();
|
||||
}
|
||||
|
||||
std::unique_ptr<Scheduler> g_scheduler;
|
||||
} // namespace Vulkan
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Adapted from the yuzu Emulator Project
|
||||
// which in turn is adapted from DXVK
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <queue>
|
||||
|
||||
#include "CommandBufferManager.h"
|
||||
#include "Common/Align.h"
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/VKTimelineSemaphore.h"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
|
||||
class Scheduler
|
||||
{
|
||||
class Command
|
||||
{
|
||||
public:
|
||||
virtual ~Command() = default;
|
||||
|
||||
virtual void Execute(CommandBufferManager* cmdbuf) = 0;
|
||||
|
||||
Command* GetNext() const { return next; }
|
||||
|
||||
void SetNext(Command* next_) { next = next_; }
|
||||
|
||||
private:
|
||||
Command* next = nullptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class alignas(16) TypedCommand final : public Command
|
||||
{
|
||||
public:
|
||||
explicit TypedCommand(T&& command_) : command{std::move(command_)} {}
|
||||
|
||||
~TypedCommand() override = default;
|
||||
|
||||
TypedCommand(TypedCommand&&) = delete;
|
||||
|
||||
TypedCommand& operator=(TypedCommand&&) = delete;
|
||||
|
||||
void Execute(CommandBufferManager* cmdbuf) override { command(cmdbuf); }
|
||||
|
||||
private:
|
||||
T command;
|
||||
};
|
||||
|
||||
class CommandChunk final
|
||||
{
|
||||
public:
|
||||
void ExecuteAll(CommandBufferManager* cmdbuf);
|
||||
|
||||
template <typename T>
|
||||
bool Record(T& command)
|
||||
{
|
||||
using FuncType = TypedCommand<T>;
|
||||
static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
|
||||
|
||||
command_offset = Common::AlignUp(command_offset, alignof(FuncType));
|
||||
if (command_offset > sizeof(data) - sizeof(FuncType)) [[unlikely]]
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Command* const current_last = last;
|
||||
last = new (data.data() + command_offset) FuncType(std::move(command));
|
||||
|
||||
if (current_last) [[likely]]
|
||||
{
|
||||
current_last->SetNext(last);
|
||||
}
|
||||
else
|
||||
{
|
||||
first = last;
|
||||
}
|
||||
command_offset += sizeof(FuncType);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Empty() const { return command_offset == 0; }
|
||||
|
||||
private:
|
||||
Command* first = nullptr;
|
||||
Command* last = nullptr;
|
||||
|
||||
size_t command_offset = 0;
|
||||
alignas(64) std::array<u8, 0x200> data{};
|
||||
};
|
||||
|
||||
public:
|
||||
Scheduler(bool use_worker_thread);
|
||||
~Scheduler();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void Flush();
|
||||
void SyncWorker();
|
||||
|
||||
void Shutdown();
|
||||
|
||||
template <typename T>
|
||||
void Record(T&& command)
|
||||
{
|
||||
if (!m_use_worker_thread) [[unlikely]]
|
||||
{
|
||||
command(m_command_buffer_manager.get());
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_chunk->Record(command)) [[likely]]
|
||||
return;
|
||||
|
||||
Flush();
|
||||
bool succeeded = m_chunk->Record(command);
|
||||
ASSERT(succeeded);
|
||||
}
|
||||
|
||||
u64 GetCompletedFenceCounter() const;
|
||||
|
||||
u64 GetCurrentFenceCounter() const;
|
||||
|
||||
void WaitForFenceCounter(u64 counter);
|
||||
void SynchronizeSubmissionThread();
|
||||
|
||||
bool CheckLastPresentFail() { return m_command_buffer_manager->CheckLastPresentFail(); }
|
||||
VkResult GetLastPresentResult() const { return m_command_buffer_manager->GetLastPresentResult(); }
|
||||
bool CheckLastPresentDone() { return m_command_buffer_manager->CheckLastPresentDone(); }
|
||||
|
||||
void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
|
||||
VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE,
|
||||
uint32_t present_image_index = 0xFFFFFFFF);
|
||||
|
||||
private:
|
||||
void WorkerThread();
|
||||
void AcquireNewChunk();
|
||||
|
||||
bool m_use_worker_thread;
|
||||
|
||||
std::unique_ptr<VKTimelineSemaphore> m_semaphore;
|
||||
std::unique_ptr<CommandBufferManager> m_command_buffer_manager;
|
||||
|
||||
std::unique_ptr<CommandChunk> m_chunk;
|
||||
|
||||
std::thread m_worker;
|
||||
std::unique_ptr<Common::BlockingLoop> m_submit_loop;
|
||||
|
||||
std::queue<std::unique_ptr<CommandChunk>> m_work_queue;
|
||||
std::mutex m_work_mutex;
|
||||
|
||||
std::vector<std::unique_ptr<CommandChunk>> m_chunk_reserve;
|
||||
std::mutex m_reserve_mutex;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<Scheduler> g_scheduler;
|
||||
} // namespace Vulkan
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "VKTimelineSemaphore.h"
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
namespace Vulkan
|
||||
|
@ -25,7 +26,13 @@ StreamBuffer::~StreamBuffer()
|
|||
{
|
||||
// VMA_ALLOCATION_CREATE_MAPPED_BIT automatically handles unmapping for us
|
||||
if (m_buffer != VK_NULL_HANDLE)
|
||||
g_command_buffer_mgr->DeferBufferDestruction(m_buffer, m_alloc);
|
||||
{
|
||||
g_scheduler->Record(
|
||||
[c_buffer = m_buffer, c_alloc = m_alloc](CommandBufferManager* command_buffer_mgr) {
|
||||
if (c_buffer != VK_NULL_HANDLE)
|
||||
command_buffer_mgr->DeferBufferDestruction(c_buffer, c_alloc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamBuffer> StreamBuffer::Create(VkBufferUsageFlags usage, u32 size)
|
||||
|
@ -76,8 +83,11 @@ bool StreamBuffer::AllocateBuffer()
|
|||
|
||||
// Destroy the backings for the buffer after the command buffer executes
|
||||
// VMA_ALLOCATION_CREATE_MAPPED_BIT automatically handles unmapping for us
|
||||
if (m_buffer != VK_NULL_HANDLE)
|
||||
g_command_buffer_mgr->DeferBufferDestruction(m_buffer, m_alloc);
|
||||
g_scheduler->Record(
|
||||
[c_buffer = m_buffer, c_alloc = m_alloc](CommandBufferManager* command_buffer_mgr) {
|
||||
if (c_buffer != VK_NULL_HANDLE)
|
||||
command_buffer_mgr->DeferBufferDestruction(c_buffer, c_alloc);
|
||||
});
|
||||
|
||||
// Replace with the new buffer
|
||||
m_buffer = buffer;
|
||||
|
@ -176,7 +186,7 @@ void StreamBuffer::UpdateCurrentFencePosition()
|
|||
return;
|
||||
|
||||
// Has the offset changed since the last fence?
|
||||
const u64 counter = g_command_buffer_mgr->GetCurrentFenceCounter();
|
||||
const u64 counter = g_scheduler->GetCurrentFenceCounter();
|
||||
if (!m_tracked_fences.empty() && m_tracked_fences.back().first == counter)
|
||||
{
|
||||
// Still haven't executed a command buffer, so just update the offset.
|
||||
|
@ -194,7 +204,7 @@ void StreamBuffer::UpdateGPUPosition()
|
|||
auto start = m_tracked_fences.begin();
|
||||
auto end = start;
|
||||
|
||||
const u64 completed_counter = g_timeline_semaphore->GetCompletedFenceCounter();
|
||||
const u64 completed_counter = g_scheduler->GetCompletedFenceCounter();
|
||||
while (end != m_tracked_fences.end() && completed_counter >= end->first)
|
||||
{
|
||||
m_current_gpu_position = end->second;
|
||||
|
@ -267,14 +277,13 @@ bool StreamBuffer::WaitForClearSpace(u32 num_bytes)
|
|||
|
||||
// Did any fences satisfy this condition?
|
||||
// Has the command buffer been executed yet? If not, the caller should execute it.
|
||||
if (iter == m_tracked_fences.end() ||
|
||||
iter->first == g_command_buffer_mgr->GetCurrentFenceCounter())
|
||||
if (iter == m_tracked_fences.end() || iter->first == g_scheduler->GetCurrentFenceCounter())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait until this fence is signaled. This will fire the callback, updating the GPU position.
|
||||
g_timeline_semaphore->WaitForFenceCounter(iter->first);
|
||||
g_scheduler->WaitForFenceCounter(iter->first);
|
||||
m_tracked_fences.erase(m_tracked_fences.begin(),
|
||||
m_current_offset == iter->second ? m_tracked_fences.end() : ++iter);
|
||||
m_current_offset = new_offset;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/ObjectCache.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VKTexture.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
#include "VideoCommon/Present.h"
|
||||
|
@ -31,9 +32,11 @@ SwapChain::SwapChain(const WindowSystemInfo& wsi, VkSurfaceKHR surface, bool vsy
|
|||
|
||||
SwapChain::~SwapChain()
|
||||
{
|
||||
g_scheduler->SyncWorker();
|
||||
DestroySwapChainImages();
|
||||
DestroySwapChain();
|
||||
DestroySurface();
|
||||
DestroySemaphores();
|
||||
}
|
||||
|
||||
VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSystemInfo& wsi)
|
||||
|
@ -132,8 +135,11 @@ std::unique_ptr<SwapChain> SwapChain::Create(const WindowSystemInfo& wsi, VkSurf
|
|||
bool vsync)
|
||||
{
|
||||
std::unique_ptr<SwapChain> swap_chain = std::make_unique<SwapChain>(wsi, surface, vsync);
|
||||
if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages())
|
||||
if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages() ||
|
||||
!swap_chain->CreateSemaphores())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return swap_chain;
|
||||
}
|
||||
|
@ -276,6 +282,23 @@ bool SwapChain::SelectPresentMode()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SwapChain::CreateSemaphores()
|
||||
{
|
||||
static constexpr VkSemaphoreCreateInfo semaphore_create_info = {
|
||||
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
|
||||
for (VkSemaphore& semaphore : m_semaphores)
|
||||
{
|
||||
VkResult res = vkCreateSemaphore(g_vulkan_context->GetDevice(), &semaphore_create_info, nullptr,
|
||||
&semaphore);
|
||||
if (res != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SwapChain::CreateSwapChain()
|
||||
{
|
||||
// Look up surface properties to determine image count and dimensions
|
||||
|
@ -492,11 +515,22 @@ void SwapChain::DestroySwapChain()
|
|||
m_swap_chain = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkResult SwapChain::AcquireNextImage()
|
||||
void SwapChain::DestroySemaphores()
|
||||
{
|
||||
VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX,
|
||||
g_command_buffer_mgr->GetCurrentCommandBufferSemaphore(),
|
||||
VK_NULL_HANDLE, &m_current_swap_chain_image_index);
|
||||
for (VkSemaphore semaphore : m_semaphores)
|
||||
{
|
||||
if (semaphore != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroySemaphore(g_vulkan_context->GetDevice(), semaphore, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VkResult SwapChain::AcquireNextImage(VkSemaphore semaphore)
|
||||
{
|
||||
VkResult res =
|
||||
vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, semaphore,
|
||||
VK_NULL_HANDLE, &m_current_swap_chain_image_index);
|
||||
if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR)
|
||||
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: ");
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
@ -50,7 +51,7 @@ public:
|
|||
{
|
||||
return m_swap_chain_images[m_current_swap_chain_image_index].framebuffer.get();
|
||||
}
|
||||
VkResult AcquireNextImage();
|
||||
VkResult AcquireNextImage(VkSemaphore semaphore);
|
||||
|
||||
bool RecreateSurface(void* native_handle);
|
||||
bool ResizeSwapChain();
|
||||
|
@ -70,10 +71,19 @@ public:
|
|||
// Updates the fullscreen state. Must call on-thread.
|
||||
bool SetFullscreenState(bool state);
|
||||
|
||||
VkSemaphore GetNextSemaphore()
|
||||
{
|
||||
m_semaphore_index = (m_semaphore_index + 1) % NUM_COMMAND_BUFFERS;
|
||||
return m_semaphores[m_semaphore_index];
|
||||
}
|
||||
|
||||
private:
|
||||
bool SelectSurfaceFormat();
|
||||
bool SelectPresentMode();
|
||||
|
||||
bool CreateSemaphores();
|
||||
void DestroySemaphores();
|
||||
|
||||
bool CreateSwapChain();
|
||||
void DestroySwapChain();
|
||||
|
||||
|
@ -103,6 +113,9 @@ private:
|
|||
std::vector<SwapChainImage> m_swap_chain_images;
|
||||
u32 m_current_swap_chain_image_index = 0;
|
||||
|
||||
std::array<VkSemaphore, NUM_COMMAND_BUFFERS> m_semaphores = {};
|
||||
u32 m_semaphore_index = 0;
|
||||
|
||||
u32 m_width = 0;
|
||||
u32 m_height = 0;
|
||||
u32 m_layers = 0;
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
#include "VideoBackends/Vulkan/StagingBuffer.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
#include "VideoBackends/Vulkan/VKGfx.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VKStreamBuffer.h"
|
||||
#include "VideoBackends/Vulkan/VKTimelineSemaphore.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
||||
#include "VKTimelineSemaphore.h"
|
||||
|
@ -46,14 +48,17 @@ VKTexture::VKTexture(const TextureConfig& tex_config, VmaAllocation alloc, VkIma
|
|||
|
||||
VKTexture::~VKTexture()
|
||||
{
|
||||
StateTracker::GetInstance()->UnbindTexture(m_view);
|
||||
g_command_buffer_mgr->DeferImageViewDestruction(m_view);
|
||||
g_scheduler->Record([c_view = m_view, c_image = m_image,
|
||||
c_alloc = m_alloc](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->UnbindTexture(c_view);
|
||||
command_buffer_mgr->DeferImageViewDestruction(c_view);
|
||||
|
||||
// If we don't have device memory allocated, the image is not owned by us (e.g. swapchain)
|
||||
if (m_alloc != VK_NULL_HANDLE)
|
||||
{
|
||||
g_command_buffer_mgr->DeferImageDestruction(m_image, m_alloc);
|
||||
}
|
||||
// If we don't have device memory allocated, the image is not owned by us (e.g. swapchain)
|
||||
if (c_alloc != VK_NULL_HANDLE)
|
||||
{
|
||||
command_buffer_mgr->DeferImageDestruction(c_image, c_alloc);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<VKTexture> VKTexture::Create(const TextureConfig& tex_config, std::string_view name)
|
||||
|
@ -293,30 +298,35 @@ void VKTexture::CopyRectangleFromTexture(const AbstractTexture* src,
|
|||
static_cast<u32>(dst_rect.GetHeight()) <= m_config.height,
|
||||
"Dest rect is too large for CopyRectangleFromTexture");
|
||||
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
// Must be called outside of a render pass.
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
|
||||
const u32 copy_layer_count = 1;
|
||||
|
||||
VkImageCopy image_copy = {
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, src_level, src_layer, copy_layer_count},
|
||||
{src_rect.left, src_rect.top, 0},
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, dst_level, dst_layer, copy_layer_count},
|
||||
{dst_rect.left, dst_rect.top, 0},
|
||||
{static_cast<uint32_t>(src_rect.GetWidth()), static_cast<uint32_t>(src_rect.GetHeight()), 1}};
|
||||
|
||||
// Must be called outside of a render pass.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
const VkImageLayout old_src_layout = src_texture->GetLayout();
|
||||
src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
src_texture->TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
vkCmdCopyImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), src_texture->m_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||
g_scheduler->Record([c_src_image = src_texture->GetImage(), c_image = m_image,
|
||||
c_src_level = src_level, c_src_layer = src_layer,
|
||||
c_layers = copy_layer_count, c_src_rect = src_rect, c_dst_level = dst_level,
|
||||
c_dst_layer = dst_layer,
|
||||
c_dst_rect = dst_rect](CommandBufferManager* command_buffer_mgr) {
|
||||
VkImageCopy image_copy = {{VK_IMAGE_ASPECT_COLOR_BIT, c_src_level, c_src_layer, c_layers},
|
||||
{c_src_rect.left, c_src_rect.top, 0},
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, c_dst_level, c_dst_layer, c_layers},
|
||||
{c_dst_rect.left, c_dst_rect.top, 0},
|
||||
{static_cast<uint32_t>(c_src_rect.GetWidth()),
|
||||
static_cast<uint32_t>(c_src_rect.GetHeight()), 1}};
|
||||
|
||||
vkCmdCopyImage(command_buffer_mgr->GetCurrentCommandBuffer(), c_src_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, c_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||
});
|
||||
|
||||
// Only restore the source layout. Destination is restored by FinishedRendering().
|
||||
src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_src_layout);
|
||||
src_texture->TransitionToLayout(old_src_layout);
|
||||
}
|
||||
|
||||
void VKTexture::ResolveFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& rect,
|
||||
|
@ -329,24 +339,28 @@ void VKTexture::ResolveFromTexture(const AbstractTexture* src, const MathUtil::R
|
|||
rect.top + rect.GetHeight() <= static_cast<int>(srcentry->m_config.height));
|
||||
|
||||
// Resolving is considered to be a transfer operation.
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
VkImageLayout old_src_layout = srcentry->m_layout;
|
||||
srcentry->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
srcentry->TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
VkImageResolve resolve = {
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}, // srcSubresource
|
||||
{rect.left, rect.top, 0}, // srcOffset
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}, // dstSubresource
|
||||
{rect.left, rect.top, 0}, // dstOffset
|
||||
{static_cast<u32>(rect.GetWidth()), static_cast<u32>(rect.GetHeight()), 1} // extent
|
||||
};
|
||||
vkCmdResolveImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), srcentry->m_image,
|
||||
srcentry->m_layout, m_image, m_layout, 1, &resolve);
|
||||
g_scheduler->Record([c_src_image = srcentry->GetImage(), c_image = m_image,
|
||||
c_src_layout = srcentry->GetLayout(), c_dst_layout = m_layout, c_rect = rect,
|
||||
c_layer = layer, c_level = level](CommandBufferManager* command_buffer_mgr) {
|
||||
VkImageResolve resolve = {
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, c_level, c_layer, 1}, // srcSubresource
|
||||
{c_rect.left, c_rect.top, 0}, // srcOffset
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, c_level, c_layer, 1}, // dstSubresource
|
||||
{c_rect.left, c_rect.top, 0}, // dstOffset
|
||||
{static_cast<u32>(c_rect.GetWidth()), static_cast<u32>(c_rect.GetHeight()), 1} // extent
|
||||
};
|
||||
vkCmdResolveImage(command_buffer_mgr->GetCurrentCommandBuffer(), c_src_image, c_src_layout,
|
||||
c_image, c_dst_layout, 1, &resolve);
|
||||
});
|
||||
|
||||
srcentry->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_src_layout);
|
||||
srcentry->TransitionToLayout(old_src_layout);
|
||||
}
|
||||
|
||||
void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
|
||||
|
@ -372,8 +386,7 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8*
|
|||
// When the last mip level is uploaded, we transition to SHADER_READ_ONLY, ready for use. This is
|
||||
// because we can't transition in a render pass, and we don't necessarily know when this texture
|
||||
// is going to be used.
|
||||
TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, true);
|
||||
|
||||
// For unaligned textures, we can save some memory in the transfer buffer by skipping the rows
|
||||
// that lie outside of the texture's dimensions.
|
||||
|
@ -424,17 +437,23 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8*
|
|||
temp_buffer->Unmap();
|
||||
}
|
||||
|
||||
// Copy from the streaming buffer to the actual image.
|
||||
VkBufferImageCopy image_copy = {
|
||||
upload_buffer_offset, // VkDeviceSize bufferOffset
|
||||
row_length, // uint32_t bufferRowLength
|
||||
0, // uint32_t bufferImageHeight
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}, // VkImageSubresourceLayers imageSubresource
|
||||
{0, 0, 0}, // VkOffset3D imageOffset
|
||||
{width, height, 1} // VkExtent3D imageExtent
|
||||
};
|
||||
vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentInitCommandBuffer(), upload_buffer,
|
||||
m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||
g_scheduler->Record([c_upload_buffer_offset = upload_buffer_offset, c_row_length = row_length,
|
||||
c_level = level, c_width = width, c_height = height,
|
||||
c_upload_buffer = upload_buffer, c_layer = layer,
|
||||
c_image = m_image](CommandBufferManager* command_buffer_mgr) {
|
||||
// Copy from the streaming buffer to the actual image.
|
||||
VkBufferImageCopy image_copy = {
|
||||
c_upload_buffer_offset, // VkDeviceSize bufferOffset
|
||||
c_row_length, // uint32_t bufferRowLength
|
||||
0, // uint32_t bufferImageHeight
|
||||
{VK_IMAGE_ASPECT_COLOR_BIT, c_level, c_layer,
|
||||
1}, // VkImageSubresourceLayers imageSubresource
|
||||
{0, 0, 0}, // VkOffset3D imageOffset
|
||||
{c_width, c_height, 1} // VkExtent3D imageExtent
|
||||
};
|
||||
vkCmdCopyBufferToImage(command_buffer_mgr->GetCurrentInitCommandBuffer(), c_upload_buffer,
|
||||
c_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||
});
|
||||
|
||||
// Preemptively transition to shader read only after uploading the last mip level, as we're
|
||||
// likely finished with writes to this texture for now. We can't do this in common with a
|
||||
|
@ -442,8 +461,7 @@ void VKTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8*
|
|||
// don't want to interrupt the render pass with calls which were executed ages before.
|
||||
if (level == (m_config.levels - 1) && layer == (m_config.layers - 1))
|
||||
{
|
||||
TransitionToLayout(g_command_buffer_mgr->GetCurrentInitCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
TransitionToLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,9 +470,10 @@ void VKTexture::FinishedRendering()
|
|||
if (m_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
||||
return;
|
||||
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
TransitionToLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
}
|
||||
|
||||
void VKTexture::OverrideImageLayout(VkImageLayout new_layout)
|
||||
|
@ -462,9 +481,9 @@ void VKTexture::OverrideImageLayout(VkImageLayout new_layout)
|
|||
m_layout = new_layout;
|
||||
}
|
||||
|
||||
void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout new_layout) const
|
||||
void VKTexture::TransitionToLayout(VkImageLayout new_layout, bool init_command_buffer) const
|
||||
{
|
||||
if (m_layout == new_layout)
|
||||
if (m_layout == new_layout) [[likely]]
|
||||
return;
|
||||
|
||||
VkImageMemoryBarrier barrier = {
|
||||
|
@ -601,14 +620,19 @@ void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout
|
|||
}
|
||||
m_compute_layout = ComputeImageLayout::Undefined;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1,
|
||||
&barrier);
|
||||
g_scheduler->Record([c_src_stage_mask = srcStageMask, c_dst_stage_mask = dstStageMask,
|
||||
c_barrier = barrier,
|
||||
init_command_buffer](CommandBufferManager* command_buffer_mgr) {
|
||||
vkCmdPipelineBarrier(init_command_buffer ? command_buffer_mgr->GetCurrentInitCommandBuffer() :
|
||||
command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
c_src_stage_mask, c_dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1,
|
||||
&c_barrier);
|
||||
});
|
||||
|
||||
m_layout = new_layout;
|
||||
}
|
||||
|
||||
void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer,
|
||||
ComputeImageLayout new_layout) const
|
||||
void VKTexture::TransitionToLayout(ComputeImageLayout new_layout, bool init_command_buffer) const
|
||||
{
|
||||
ASSERT(new_layout != ComputeImageLayout::Undefined);
|
||||
if (m_compute_layout == new_layout)
|
||||
|
@ -706,8 +730,14 @@ void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer,
|
|||
m_layout = barrier.newLayout;
|
||||
m_compute_layout = new_layout;
|
||||
|
||||
vkCmdPipelineBarrier(command_buffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1,
|
||||
&barrier);
|
||||
g_scheduler->Record([c_src_stage_mask = srcStageMask, c_dst_stage_mask = dstStageMask,
|
||||
c_barrier = barrier,
|
||||
init_command_buffer](CommandBufferManager* command_buffer_mgr) {
|
||||
vkCmdPipelineBarrier(init_command_buffer ? command_buffer_mgr->GetCurrentInitCommandBuffer() :
|
||||
command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
c_src_stage_mask, c_dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1,
|
||||
&c_barrier);
|
||||
});
|
||||
}
|
||||
|
||||
VKStagingTexture::VKStagingTexture(PrivateTag, StagingTextureType type, const TextureConfig& config,
|
||||
|
@ -722,7 +752,11 @@ VKStagingTexture::~VKStagingTexture()
|
|||
{
|
||||
if (m_linear_image != VK_NULL_HANDLE)
|
||||
{
|
||||
g_command_buffer_mgr->DeferImageDestruction(m_linear_image, m_linear_image_alloc);
|
||||
g_scheduler->Record(
|
||||
[c_linear_image = m_linear_image,
|
||||
c_linear_image_alloc = m_linear_image_alloc](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->DeferImageDestruction(c_linear_image, c_linear_image_alloc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -844,12 +878,12 @@ void VKStagingTexture::CopyFromTexture(const AbstractTexture* src,
|
|||
src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src_tex->GetHeight());
|
||||
ASSERT(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width &&
|
||||
dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height);
|
||||
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
|
||||
VkImageLayout old_layout = src_tex->GetLayout();
|
||||
src_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
src_tex->TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
|
||||
// Issue the image->buffer copy, but delay it for now.
|
||||
VkBufferImageCopy image_copy = {};
|
||||
|
@ -872,15 +906,17 @@ void VKStagingTexture::CopyFromTexture(const AbstractTexture* src,
|
|||
image_copy.imageOffset = {0, 0, 0};
|
||||
}
|
||||
|
||||
vkCmdCopyImageToBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), src_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_staging_buffer->GetBuffer(), 1,
|
||||
&image_copy);
|
||||
g_scheduler->Record([c_src_image = src_image, c_dst_buffer = m_staging_buffer->GetBuffer(),
|
||||
c_image_copy = image_copy](CommandBufferManager* command_buffer_mgr) {
|
||||
vkCmdCopyImageToBuffer(command_buffer_mgr->GetCurrentCommandBuffer(), c_src_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, c_dst_buffer, 1, &c_image_copy);
|
||||
});
|
||||
|
||||
// Restore old source texture layout.
|
||||
src_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_layout);
|
||||
src_tex->TransitionToLayout(old_layout);
|
||||
|
||||
m_needs_flush = true;
|
||||
m_flush_fence_counter = g_command_buffer_mgr->GetCurrentFenceCounter();
|
||||
m_flush_fence_counter = g_scheduler->GetCurrentFenceCounter();
|
||||
}
|
||||
|
||||
void VKStagingTexture::CopyFromTextureToLinearImage(const VKTexture* src_tex,
|
||||
|
@ -892,44 +928,49 @@ void VKStagingTexture::CopyFromTextureToLinearImage(const VKTexture* src_tex,
|
|||
// with optimal tiling (VK_IMAGE_TILING_OPTIMAL) to a buffer.
|
||||
// That allocation is very slow, so we just do it ourself and reuse the intermediate image.
|
||||
|
||||
const VkImageAspectFlags aspect = VKTexture::GetImageViewAspectForFormat(src_tex->GetFormat());
|
||||
g_scheduler->Record([c_linear_image = m_linear_image, c_src_image = src_tex->GetImage(),
|
||||
c_src_format = src_tex->GetFormat(), c_src_rect = src_rect,
|
||||
c_dst_rect = dst_rect, c_src_layer = src_layer,
|
||||
c_src_level = src_level](CommandBufferManager* command_buffer_mgr) {
|
||||
const VkImageAspectFlags aspect = VKTexture::GetImageViewAspectForFormat(c_src_format);
|
||||
|
||||
VkImageMemoryBarrier linear_image_barrier = {};
|
||||
linear_image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
linear_image_barrier.pNext = nullptr;
|
||||
linear_image_barrier.srcAccessMask = 0;
|
||||
linear_image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT;
|
||||
linear_image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
linear_image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
linear_image_barrier.image = m_linear_image;
|
||||
linear_image_barrier.subresourceRange = {aspect, 0, 1, 0, 1};
|
||||
vkCmdPipelineBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &linear_image_barrier);
|
||||
VkImageMemoryBarrier linear_image_barrier = {};
|
||||
linear_image_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
linear_image_barrier.pNext = nullptr;
|
||||
linear_image_barrier.srcAccessMask = 0;
|
||||
linear_image_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT;
|
||||
linear_image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
linear_image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
linear_image_barrier.image = c_linear_image;
|
||||
linear_image_barrier.subresourceRange = {aspect, 0, 1, 0, 1};
|
||||
vkCmdPipelineBarrier(command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &linear_image_barrier);
|
||||
|
||||
VkImageBlit blit;
|
||||
blit.srcSubresource = {aspect, src_level, src_layer, 1};
|
||||
blit.dstSubresource.layerCount = 1;
|
||||
blit.dstSubresource.baseArrayLayer = 0;
|
||||
blit.dstSubresource.mipLevel = 0;
|
||||
blit.dstSubresource.aspectMask = linear_image_barrier.subresourceRange.aspectMask;
|
||||
blit.srcOffsets[0] = {src_rect.left, src_rect.top, 0};
|
||||
blit.srcOffsets[1] = {static_cast<s32>(blit.srcOffsets[0].x + src_rect.GetWidth()),
|
||||
static_cast<s32>(blit.srcOffsets[0].y + src_rect.GetHeight()), 1};
|
||||
blit.dstOffsets[0] = {0, 0, 0};
|
||||
blit.dstOffsets[1] = {dst_rect.GetWidth(), dst_rect.GetHeight(), 1u};
|
||||
VkImageBlit blit;
|
||||
blit.srcSubresource = {aspect, c_src_level, c_src_layer, 1};
|
||||
blit.dstSubresource.layerCount = 1;
|
||||
blit.dstSubresource.baseArrayLayer = 0;
|
||||
blit.dstSubresource.mipLevel = 0;
|
||||
blit.dstSubresource.aspectMask = linear_image_barrier.subresourceRange.aspectMask;
|
||||
blit.srcOffsets[0] = {c_src_rect.left, c_src_rect.top, 0};
|
||||
blit.srcOffsets[1] = {static_cast<s32>(blit.srcOffsets[0].x + c_src_rect.GetWidth()),
|
||||
static_cast<s32>(blit.srcOffsets[0].y + c_src_rect.GetHeight()), 1};
|
||||
blit.dstOffsets[0] = {0, 0, 0};
|
||||
blit.dstOffsets[1] = {c_dst_rect.GetWidth(), c_dst_rect.GetHeight(), 1u};
|
||||
|
||||
vkCmdBlitImage(g_command_buffer_mgr->GetCurrentCommandBuffer(), src_tex->GetImage(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_linear_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST);
|
||||
vkCmdBlitImage(command_buffer_mgr->GetCurrentCommandBuffer(), c_src_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, c_linear_image,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_NEAREST);
|
||||
|
||||
linear_image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
linear_image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
linear_image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
linear_image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
linear_image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
linear_image_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
|
||||
vkCmdPipelineBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &linear_image_barrier);
|
||||
vkCmdPipelineBarrier(command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &linear_image_barrier);
|
||||
});
|
||||
}
|
||||
|
||||
void VKStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
|
||||
|
@ -947,32 +988,39 @@ void VKStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, A
|
|||
|
||||
// Flush caches before copying.
|
||||
m_staging_buffer->FlushCPUCache();
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
g_scheduler->Record([](CommandBufferManager* command_buffer_manager) {
|
||||
command_buffer_manager->GetStateTracker()->EndRenderPass();
|
||||
});
|
||||
|
||||
VkImageLayout old_layout = dst_tex->GetLayout();
|
||||
dst_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
dst_tex->TransitionToLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||
|
||||
// Issue the image->buffer copy, but delay it for now.
|
||||
VkBufferImageCopy image_copy = {};
|
||||
image_copy.bufferOffset =
|
||||
static_cast<VkDeviceSize>(static_cast<size_t>(src_rect.top) * m_config.GetStride() +
|
||||
static_cast<size_t>(src_rect.left) * m_texel_size);
|
||||
image_copy.bufferRowLength = static_cast<u32>(m_config.width);
|
||||
image_copy.bufferImageHeight = 0;
|
||||
image_copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, dst_level, dst_layer, 1};
|
||||
image_copy.imageOffset = {dst_rect.left, dst_rect.top, 0};
|
||||
image_copy.imageExtent = {static_cast<u32>(dst_rect.GetWidth()),
|
||||
static_cast<u32>(dst_rect.GetHeight()), 1u};
|
||||
vkCmdCopyBufferToImage(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
m_staging_buffer->GetBuffer(), dst_tex->GetImage(),
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||
g_scheduler->Record(
|
||||
[c_src_rect = src_rect, c_dst_rect = dst_rect, c_dst_layer = dst_layer,
|
||||
c_dst_level = dst_level, c_width = m_config.width, c_stride = m_config.GetStride(),
|
||||
c_texel_size = m_texel_size, c_staging_buffer = m_staging_buffer->GetBuffer(),
|
||||
c_dst_image = dst_tex->GetImage()](CommandBufferManager* command_buffer_mgr) {
|
||||
// Issue the image->buffer copy, but delay it for now.
|
||||
VkBufferImageCopy image_copy = {};
|
||||
image_copy.bufferOffset =
|
||||
static_cast<VkDeviceSize>(static_cast<size_t>(c_src_rect.top) * c_stride +
|
||||
static_cast<size_t>(c_src_rect.left) * c_texel_size);
|
||||
image_copy.bufferRowLength = static_cast<u32>(c_width);
|
||||
image_copy.bufferImageHeight = 0;
|
||||
image_copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, c_dst_level, c_dst_layer, 1};
|
||||
image_copy.imageOffset = {c_dst_rect.left, c_dst_rect.top, 0};
|
||||
image_copy.imageExtent = {static_cast<u32>(c_dst_rect.GetWidth()),
|
||||
static_cast<u32>(c_dst_rect.GetHeight()), 1u};
|
||||
vkCmdCopyBufferToImage(command_buffer_mgr->GetCurrentCommandBuffer(), c_staging_buffer,
|
||||
c_dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy);
|
||||
});
|
||||
|
||||
// Restore old source texture layout.
|
||||
dst_tex->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_layout);
|
||||
dst_tex->TransitionToLayout(old_layout);
|
||||
|
||||
m_needs_flush = true;
|
||||
m_flush_fence_counter = g_command_buffer_mgr->GetCurrentFenceCounter();
|
||||
m_flush_fence_counter = g_scheduler->GetCurrentFenceCounter();
|
||||
}
|
||||
|
||||
bool VKStagingTexture::Map()
|
||||
|
@ -992,7 +1040,7 @@ void VKStagingTexture::Flush()
|
|||
return;
|
||||
|
||||
// Is this copy in the current command buffer?
|
||||
if (g_command_buffer_mgr->GetCurrentFenceCounter() == m_flush_fence_counter)
|
||||
if (g_scheduler->GetCurrentFenceCounter() == m_flush_fence_counter)
|
||||
{
|
||||
// Execute the command buffer and wait for it to finish.
|
||||
VKGfx::GetInstance()->ExecuteCommandBuffer(false, true);
|
||||
|
@ -1000,7 +1048,7 @@ void VKStagingTexture::Flush()
|
|||
else
|
||||
{
|
||||
// Wait for the GPU to finish with it.
|
||||
g_timeline_semaphore->WaitForFenceCounter(m_flush_fence_counter);
|
||||
g_scheduler->WaitForFenceCounter(m_flush_fence_counter);
|
||||
}
|
||||
|
||||
// For readback textures, invalidate the CPU cache as there is new data there.
|
||||
|
@ -1027,7 +1075,9 @@ VKFramebuffer::VKFramebuffer(VKTexture* color_attachment, VKTexture* depth_attac
|
|||
|
||||
VKFramebuffer::~VKFramebuffer()
|
||||
{
|
||||
g_command_buffer_mgr->DeferFramebufferDestruction(m_fb);
|
||||
g_scheduler->Record([c_fb = m_fb](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->DeferFramebufferDestruction(c_fb);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<VKFramebuffer>
|
||||
|
@ -1102,17 +1152,24 @@ void VKFramebuffer::Unbind()
|
|||
{
|
||||
if (m_color_attachment)
|
||||
{
|
||||
StateTracker::GetInstance()->UnbindTexture(
|
||||
static_cast<VKTexture*>(m_color_attachment)->GetView());
|
||||
g_scheduler->Record([c_image_view = static_cast<VKTexture*>(m_color_attachment)->GetView()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->UnbindTexture(c_image_view);
|
||||
});
|
||||
}
|
||||
for (auto* attachment : m_additional_color_attachments)
|
||||
{
|
||||
StateTracker::GetInstance()->UnbindTexture(static_cast<VKTexture*>(attachment)->GetView());
|
||||
g_scheduler->Record([c_image_view = static_cast<VKTexture*>(attachment)->GetView()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->UnbindTexture(c_image_view);
|
||||
});
|
||||
}
|
||||
if (m_depth_attachment)
|
||||
{
|
||||
StateTracker::GetInstance()->UnbindTexture(
|
||||
static_cast<VKTexture*>(m_depth_attachment)->GetView());
|
||||
g_scheduler->Record([c_image_view = static_cast<VKTexture*>(m_depth_attachment)->GetView()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->UnbindTexture(c_image_view);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1121,21 +1178,18 @@ void VKFramebuffer::TransitionForRender()
|
|||
if (m_color_attachment)
|
||||
{
|
||||
static_cast<VKTexture*>(m_color_attachment)
|
||||
->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
->TransitionToLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
for (auto* attachment : m_additional_color_attachments)
|
||||
{
|
||||
static_cast<VKTexture*>(attachment)
|
||||
->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
->TransitionToLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
|
||||
if (m_depth_attachment)
|
||||
{
|
||||
static_cast<VKTexture*>(m_depth_attachment)
|
||||
->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
->TransitionToLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1155,7 +1209,12 @@ void VKFramebuffer::SetAndClear(const VkRect2D& rect, const VkClearValue& color_
|
|||
{
|
||||
clear_values.push_back(color_value);
|
||||
}
|
||||
StateTracker::GetInstance()->BeginClearRenderPass(rect, clear_values.data(),
|
||||
static_cast<u32>(clear_values.size()));
|
||||
|
||||
g_scheduler->Record([c_clear_values = std::move(clear_values),
|
||||
c_rect = rect](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->BeginClearRenderPass(
|
||||
c_rect, c_clear_values.data(), static_cast<u32>(c_clear_values.size()));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -67,8 +67,8 @@ public:
|
|||
// irrelevant and will not be loaded.
|
||||
void OverrideImageLayout(VkImageLayout new_layout);
|
||||
|
||||
void TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout new_layout) const;
|
||||
void TransitionToLayout(VkCommandBuffer command_buffer, ComputeImageLayout new_layout) const;
|
||||
void TransitionToLayout(VkImageLayout new_layout, bool init_command_buffer = false) const;
|
||||
void TransitionToLayout(ComputeImageLayout new_layout, bool init_command_buffer = false) const;
|
||||
|
||||
private:
|
||||
bool CreateView(VkImageViewType type);
|
||||
|
|
|
@ -68,6 +68,4 @@ void VKTimelineSemaphore::PushPendingFenceValue(VkFence fence, u64 fence_counter
|
|||
m_fence_loop->Wakeup();
|
||||
}
|
||||
|
||||
std::unique_ptr<VKTimelineSemaphore> g_timeline_semaphore;
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -23,6 +23,11 @@ public:
|
|||
|
||||
~VKTimelineSemaphore();
|
||||
|
||||
u64 GetCurrentFenceCounter() const
|
||||
{
|
||||
return m_current_fence_counter.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
u64 GetCompletedFenceCounter() const
|
||||
{
|
||||
return m_completed_fence_counter.load(std::memory_order_acquire);
|
||||
|
@ -32,12 +37,12 @@ public:
|
|||
|
||||
void PushPendingFenceValue(VkFence fence, u64 counter);
|
||||
|
||||
u64 BumpNextFenceCounter() { return m_next_fence_counter++; }
|
||||
u64 BumpNextFenceCounter() { return m_current_fence_counter++; }
|
||||
|
||||
private:
|
||||
void FenceThreadFunc();
|
||||
|
||||
u64 m_next_fence_counter = 1;
|
||||
std::atomic<u64> m_current_fence_counter = 1;
|
||||
std::atomic<u64> m_completed_fence_counter = 0;
|
||||
|
||||
std::thread m_fence_thread;
|
||||
|
@ -52,6 +57,4 @@ private:
|
|||
std::condition_variable m_fence_condvar;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<VKTimelineSemaphore> g_timeline_semaphore;
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
#include "VideoBackends/Vulkan/VKGfx.h"
|
||||
#include "VideoBackends/Vulkan/VKScheduler.h"
|
||||
#include "VideoBackends/Vulkan/VKStreamBuffer.h"
|
||||
#include "VideoBackends/Vulkan/VKVertexFormat.h"
|
||||
#include "VideoBackends/Vulkan/VulkanContext.h"
|
||||
|
@ -67,11 +68,11 @@ bool VertexManager::Initialize()
|
|||
|
||||
m_vertex_stream_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
||||
VERTEX_STREAM_BUFFER_SIZE);
|
||||
VERTEX_STREAM_BUFFER_SIZE * 2);
|
||||
m_index_stream_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, INDEX_STREAM_BUFFER_SIZE);
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, INDEX_STREAM_BUFFER_SIZE * 2);
|
||||
m_uniform_stream_buffer =
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, UNIFORM_STREAM_BUFFER_SIZE);
|
||||
StreamBuffer::Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, UNIFORM_STREAM_BUFFER_SIZE * 2);
|
||||
if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_uniform_stream_buffer)
|
||||
{
|
||||
PanicAlertFmt("Failed to allocate streaming buffers");
|
||||
|
@ -121,13 +122,18 @@ bool VertexManager::Initialize()
|
|||
|
||||
// Bind the buffers to all the known spots even if it's not used, to keep the driver happy.
|
||||
UploadAllConstants();
|
||||
StateTracker::GetInstance()->SetUtilityUniformBuffer(m_uniform_stream_buffer->GetBuffer(), 0,
|
||||
sizeof(VertexShaderConstants));
|
||||
for (u32 i = 0; i < NUM_COMPUTE_TEXEL_BUFFERS; i++)
|
||||
{
|
||||
StateTracker::GetInstance()->SetTexelBuffer(i,
|
||||
m_texel_buffer_views[TEXEL_BUFFER_FORMAT_R8_UINT]);
|
||||
}
|
||||
|
||||
g_scheduler->Record([c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_texel_buffer_view = m_texel_buffer_views[TEXEL_BUFFER_FORMAT_R8_UINT]](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetUtilityUniformBuffer(c_buffer, 0,
|
||||
sizeof(VertexShaderConstants));
|
||||
|
||||
for (u32 i = 0; i < NUM_COMPUTE_TEXEL_BUFFERS; i++)
|
||||
{
|
||||
command_buffer_mgr->GetStateTracker()->SetTexelBuffer(i, c_texel_buffer_view);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -189,10 +195,13 @@ void VertexManager::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_in
|
|||
ADDSTAT(g_stats.this_frame.bytes_vertex_streamed, static_cast<int>(vertex_data_size));
|
||||
ADDSTAT(g_stats.this_frame.bytes_index_streamed, static_cast<int>(index_data_size));
|
||||
|
||||
StateTracker::GetInstance()->SetVertexBuffer(m_vertex_stream_buffer->GetBuffer(), 0,
|
||||
VERTEX_STREAM_BUFFER_SIZE);
|
||||
StateTracker::GetInstance()->SetIndexBuffer(m_index_stream_buffer->GetBuffer(), 0,
|
||||
VK_INDEX_TYPE_UINT16);
|
||||
g_scheduler->Record([c_vertex_buffer = m_vertex_stream_buffer->GetBuffer(),
|
||||
c_index_buffer = m_index_stream_buffer->GetBuffer()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetVertexBuffer(c_vertex_buffer, 0,
|
||||
VERTEX_STREAM_BUFFER_SIZE);
|
||||
command_buffer_mgr->GetStateTracker()->SetIndexBuffer(c_index_buffer, 0, VK_INDEX_TYPE_UINT16);
|
||||
});
|
||||
}
|
||||
|
||||
void VertexManager::UploadUniforms()
|
||||
|
@ -210,9 +219,12 @@ void VertexManager::UpdateVertexShaderConstants()
|
|||
if (!vertex_shader_manager.dirty || !ReserveConstantStorage())
|
||||
return;
|
||||
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_VS, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset(), sizeof(VertexShaderConstants));
|
||||
g_scheduler->Record([c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_offset = m_uniform_stream_buffer->GetCurrentOffset()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_VS, c_buffer, c_offset, sizeof(VertexShaderConstants));
|
||||
});
|
||||
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &vertex_shader_manager.constants,
|
||||
sizeof(VertexShaderConstants));
|
||||
m_uniform_stream_buffer->CommitMemory(sizeof(VertexShaderConstants));
|
||||
|
@ -228,9 +240,13 @@ void VertexManager::UpdateGeometryShaderConstants()
|
|||
if (!geometry_shader_manager.dirty || !ReserveConstantStorage())
|
||||
return;
|
||||
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset(), sizeof(GeometryShaderConstants));
|
||||
g_scheduler->Record([c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_offset = m_uniform_stream_buffer->GetCurrentOffset()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_GS, c_buffer, c_offset, sizeof(GeometryShaderConstants));
|
||||
});
|
||||
|
||||
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &geometry_shader_manager.constants,
|
||||
sizeof(GeometryShaderConstants));
|
||||
m_uniform_stream_buffer->CommitMemory(sizeof(GeometryShaderConstants));
|
||||
|
@ -248,9 +264,12 @@ void VertexManager::UpdatePixelShaderConstants()
|
|||
|
||||
if (pixel_shader_manager.dirty)
|
||||
{
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset(), sizeof(PixelShaderConstants));
|
||||
g_scheduler->Record([c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_offset = m_uniform_stream_buffer->GetCurrentOffset()](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS, c_buffer, c_offset, sizeof(PixelShaderConstants));
|
||||
});
|
||||
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), &pixel_shader_manager.constants,
|
||||
sizeof(PixelShaderConstants));
|
||||
m_uniform_stream_buffer->CommitMemory(sizeof(PixelShaderConstants));
|
||||
|
@ -260,10 +279,13 @@ void VertexManager::UpdatePixelShaderConstants()
|
|||
|
||||
if (pixel_shader_manager.custom_constants_dirty)
|
||||
{
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS_CUST, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset(),
|
||||
static_cast<u32>(pixel_shader_manager.custom_constants.size()));
|
||||
g_scheduler->Record([c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_offset = m_uniform_stream_buffer->GetCurrentOffset(),
|
||||
c_size = static_cast<u32>(pixel_shader_manager.custom_constants.size())](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(UBO_DESCRIPTOR_SET_BINDING_PS_CUST,
|
||||
c_buffer, c_offset, c_size);
|
||||
});
|
||||
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(),
|
||||
pixel_shader_manager.custom_constants.data(),
|
||||
pixel_shader_manager.custom_constants.size());
|
||||
|
@ -325,26 +347,32 @@ void VertexManager::UploadAllConstants()
|
|||
auto& geometry_shader_manager = system.GetGeometryShaderManager();
|
||||
|
||||
// Update bindings
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset() + pixel_constants_offset,
|
||||
sizeof(PixelShaderConstants));
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_VS, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset() + vertex_constants_offset,
|
||||
sizeof(VertexShaderConstants));
|
||||
g_scheduler->Record(
|
||||
[c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_ps_offset = m_uniform_stream_buffer->GetCurrentOffset() + pixel_constants_offset,
|
||||
c_vs_offset = m_uniform_stream_buffer->GetCurrentOffset() + vertex_constants_offset,
|
||||
c_ps_custom_offset =
|
||||
m_uniform_stream_buffer->GetCurrentOffset() + custom_pixel_constants_offset,
|
||||
c_has_custom_constants = !pixel_shader_manager.custom_constants.empty(),
|
||||
c_custom_constants_size = custom_constants_size,
|
||||
c_gs_offset = m_uniform_stream_buffer->GetCurrentOffset() +
|
||||
geometry_constants_offset](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS, c_buffer, c_ps_offset, sizeof(PixelShaderConstants));
|
||||
|
||||
if (!pixel_shader_manager.custom_constants.empty())
|
||||
{
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS_CUST, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset() + custom_pixel_constants_offset,
|
||||
custom_constants_size);
|
||||
}
|
||||
StateTracker::GetInstance()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_GS, m_uniform_stream_buffer->GetBuffer(),
|
||||
m_uniform_stream_buffer->GetCurrentOffset() + geometry_constants_offset,
|
||||
sizeof(GeometryShaderConstants));
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_VS, c_buffer, c_vs_offset, sizeof(VertexShaderConstants));
|
||||
|
||||
if (c_has_custom_constants)
|
||||
{
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_PS_CUST, c_buffer, c_ps_custom_offset,
|
||||
c_custom_constants_size);
|
||||
}
|
||||
|
||||
command_buffer_mgr->GetStateTracker()->SetGXUniformBuffer(
|
||||
UBO_DESCRIPTOR_SET_BINDING_VS, c_buffer, c_gs_offset, sizeof(GeometryShaderConstants));
|
||||
});
|
||||
|
||||
// Copy the actual data in
|
||||
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer() + pixel_constants_offset,
|
||||
|
@ -380,8 +408,11 @@ void VertexManager::UploadUtilityUniforms(const void* data, u32 data_size)
|
|||
VKGfx::GetInstance()->ExecuteCommandBuffer(false);
|
||||
}
|
||||
|
||||
StateTracker::GetInstance()->SetUtilityUniformBuffer(
|
||||
m_uniform_stream_buffer->GetBuffer(), m_uniform_stream_buffer->GetCurrentOffset(), data_size);
|
||||
g_scheduler->Record([c_buffer = m_uniform_stream_buffer->GetBuffer(),
|
||||
c_offset = m_uniform_stream_buffer->GetCurrentOffset(),
|
||||
c_size = data_size](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetUtilityUniformBuffer(c_buffer, c_offset, c_size);
|
||||
});
|
||||
std::memcpy(m_uniform_stream_buffer->GetCurrentHostPointer(), data, data_size);
|
||||
m_uniform_stream_buffer->CommitMemory(data_size);
|
||||
ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size);
|
||||
|
@ -410,7 +441,10 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff
|
|||
*out_offset = static_cast<u32>(m_texel_stream_buffer->GetCurrentOffset()) / elem_size;
|
||||
m_texel_stream_buffer->CommitMemory(data_size);
|
||||
ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, data_size);
|
||||
StateTracker::GetInstance()->SetTexelBuffer(0, m_texel_buffer_views[format]);
|
||||
g_scheduler->Record([c_texel_buffer_view =
|
||||
m_texel_buffer_views[format]](CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetTexelBuffer(0, c_texel_buffer_view);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -447,8 +481,13 @@ bool VertexManager::UploadTexelBuffer(const void* data, u32 data_size, TexelBuff
|
|||
|
||||
m_texel_stream_buffer->CommitMemory(palette_byte_offset + palette_size);
|
||||
ADDSTAT(g_stats.this_frame.bytes_uniform_streamed, palette_byte_offset + palette_size);
|
||||
StateTracker::GetInstance()->SetTexelBuffer(0, m_texel_buffer_views[format]);
|
||||
StateTracker::GetInstance()->SetTexelBuffer(1, m_texel_buffer_views[palette_format]);
|
||||
|
||||
g_scheduler->Record([c_texel_buffer_view = m_texel_buffer_views[format],
|
||||
c_palette_texel_buffer_view = m_texel_buffer_views[palette_format]](
|
||||
CommandBufferManager* command_buffer_mgr) {
|
||||
command_buffer_mgr->GetStateTracker()->SetTexelBuffer(0, c_texel_buffer_view);
|
||||
command_buffer_mgr->GetStateTracker()->SetTexelBuffer(1, c_palette_texel_buffer_view);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
} // namespace Vulkan
|
||||
|
|
|
@ -785,7 +785,7 @@ bool VulkanContext::CreateDevice(VkSurfaceKHR surface, bool enable_validation_la
|
|||
bool VulkanContext::CreateAllocator(u32 vk_api_version)
|
||||
{
|
||||
VmaAllocatorCreateInfo allocator_info = {};
|
||||
allocator_info.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
|
||||
allocator_info.flags = 0;
|
||||
allocator_info.physicalDevice = m_physical_device;
|
||||
allocator_info.device = m_device;
|
||||
allocator_info.preferredLargeHeapBlockSize = 64 << 20;
|
||||
|
|
|
@ -41,6 +41,21 @@ class AsyncShaderCompiler;
|
|||
|
||||
using ClearColor = std::array<float, 4>;
|
||||
|
||||
enum class FlushType
|
||||
{
|
||||
/**
|
||||
* Flushes the current batch to the GFX worker thread.
|
||||
* Implementations that do not have a worker thread will do nothing.
|
||||
*/
|
||||
FlushToWorker,
|
||||
|
||||
/*
|
||||
* Flushes the current batch to the GFX worker thread
|
||||
* and to the GPU after that.
|
||||
*/
|
||||
FlushToGPU
|
||||
};
|
||||
|
||||
// AbstractGfx is the root of Dolphin's Graphics API abstraction layer.
|
||||
//
|
||||
// Abstract knows nothing about the internals of the GameCube/Wii, that is all handled elsewhere in
|
||||
|
@ -140,7 +155,7 @@ public:
|
|||
ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect,
|
||||
const AbstractFramebuffer* framebuffer) const;
|
||||
|
||||
virtual void Flush() {}
|
||||
virtual void Flush(FlushType flushType = FlushType::FlushToGPU) {}
|
||||
virtual void WaitForGPUIdle() {}
|
||||
|
||||
// For opengl's glDrawBuffer
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "Core/Host.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "AbstractGfx.h"
|
||||
#include "VideoCommon/AsyncRequests.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
|
@ -388,6 +389,9 @@ void FifoManager::RunGpuLoop()
|
|||
// Make sure VertexManager finishes drawing any primitives it has stored in it's buffer.
|
||||
g_vertex_manager->Flush();
|
||||
g_framebuffer_manager->RefreshPeekCache();
|
||||
|
||||
// Flush to worker thread on multithreaded backends (Vulkan)
|
||||
g_gfx->Flush(FlushType::FlushToWorker);
|
||||
}
|
||||
},
|
||||
100);
|
||||
|
@ -494,6 +498,8 @@ int FifoManager::RunGpuOnCpu(int ticks)
|
|||
// Discard all available ticks as there is nothing to do any more.
|
||||
m_sync_ticks.store(std::min(available_ticks, 0));
|
||||
|
||||
g_gfx->Flush(FlushType::FlushToWorker);
|
||||
|
||||
// If the GPU is idle, drop the handler.
|
||||
if (available_ticks >= 0)
|
||||
return -1;
|
||||
|
|
Loading…
Reference in New Issue