[D3D12/Vulkan] Simplify host GPU fence management
Replace the `SubmissionTracker`s with new `GPUCompletionTimeline`s with a more unified interface (using a base class), and without the internal logic for queue ownership transfers since that idea was scrapped during the development of the `Presenter`. Also use this fence management logic for GPU emulation, though without architectural reworks for now, just on the bottom level. Still very messy, but can be cleaned up in further GPU command processor and presenter reworks.
This commit is contained in:
@@ -481,8 +481,8 @@ bool D3D12CommandProcessor::RequestOneUseSingleViewDescriptors(
|
||||
} else {
|
||||
descriptor_index = view_bindless_heap_allocated_++;
|
||||
}
|
||||
view_bindless_one_use_descriptors_.push_back(
|
||||
std::make_pair(descriptor_index, submission_current_));
|
||||
view_bindless_one_use_descriptors_.emplace_back(descriptor_index,
|
||||
GetCurrentSubmission());
|
||||
handles_out[i] =
|
||||
std::make_pair(provider.OffsetViewDescriptor(
|
||||
view_bindless_heap_cpu_start_, descriptor_index),
|
||||
@@ -665,7 +665,8 @@ ID3D12Resource* D3D12CommandProcessor::RequestScratchGPUBuffer(
|
||||
return nullptr;
|
||||
}
|
||||
if (scratch_buffer_ != nullptr) {
|
||||
resources_for_deletion_.emplace_back(submission_current_, scratch_buffer_);
|
||||
resources_for_deletion_.emplace_back(GetCurrentSubmission(),
|
||||
scratch_buffer_);
|
||||
}
|
||||
scratch_buffer_ = buffer;
|
||||
scratch_buffer_size_ = size;
|
||||
@@ -787,22 +788,11 @@ bool D3D12CommandProcessor::SetupContext() {
|
||||
ID3D12Device* device = provider.GetDevice();
|
||||
ID3D12CommandQueue* direct_queue = provider.GetDirectQueue();
|
||||
|
||||
fence_completion_event_ = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
if (fence_completion_event_ == nullptr) {
|
||||
XELOGE("Failed to create the fence completion event");
|
||||
return false;
|
||||
}
|
||||
if (FAILED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE,
|
||||
IID_PPV_ARGS(&submission_fence_)))) {
|
||||
XELOGE("Failed to create the submission fence");
|
||||
return false;
|
||||
}
|
||||
if (FAILED(device->CreateFence(
|
||||
0, D3D12_FENCE_FLAG_NONE,
|
||||
IID_PPV_ARGS(&queue_operations_since_submission_fence_)))) {
|
||||
XELOGE(
|
||||
"Failed to create the fence for awaiting queue operations done since "
|
||||
"the latest submission");
|
||||
completion_timeline_ = ui::d3d12::D3D12GPUCompletionTimeline::Create(device);
|
||||
queue_operations_since_submission_completion_timeline_ =
|
||||
ui::d3d12::D3D12GPUCompletionTimeline::Create(device);
|
||||
if (!completion_timeline_ ||
|
||||
!queue_operations_since_submission_completion_timeline_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1669,21 +1659,10 @@ void D3D12CommandProcessor::ShutdownContext() {
|
||||
frame_completed_ = 0;
|
||||
std::memset(closed_frame_submissions_, 0, sizeof(closed_frame_submissions_));
|
||||
|
||||
// First release the fences since they may reference fence_completion_event_.
|
||||
queue_operations_since_submission_completion_timeline_.reset();
|
||||
|
||||
queue_operations_done_since_submission_signal_ = false;
|
||||
queue_operations_since_submission_fence_last_ = 0;
|
||||
ui::d3d12::util::ReleaseAndNull(queue_operations_since_submission_fence_);
|
||||
|
||||
ui::d3d12::util::ReleaseAndNull(submission_fence_);
|
||||
submission_open_ = false;
|
||||
submission_current_ = 1;
|
||||
submission_completed_ = 0;
|
||||
|
||||
if (fence_completion_event_) {
|
||||
CloseHandle(fence_completion_event_);
|
||||
fence_completion_event_ = nullptr;
|
||||
}
|
||||
completion_timeline_.reset();
|
||||
|
||||
device_removed_ = false;
|
||||
|
||||
@@ -1777,7 +1756,7 @@ void D3D12CommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
||||
fxaa_source_texture_->GetDesc();
|
||||
if (fxaa_source_texture_desc.Width != swap_texture_desc.Width ||
|
||||
fxaa_source_texture_desc.Height != swap_texture_desc.Height) {
|
||||
if (submission_completed_ < fxaa_source_texture_submission_) {
|
||||
if (GetCompletedSubmission() < fxaa_source_texture_submission_) {
|
||||
fxaa_source_texture_->AddRef();
|
||||
resources_for_deletion_.emplace_back(
|
||||
fxaa_source_texture_submission_,
|
||||
@@ -1908,7 +1887,7 @@ void D3D12CommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
||||
.resource_uav_capable();
|
||||
|
||||
if (use_fxaa) {
|
||||
fxaa_source_texture_submission_ = submission_current_;
|
||||
fxaa_source_texture_submission_ = GetCurrentSubmission();
|
||||
}
|
||||
|
||||
ID3D12Resource* apply_gamma_dest =
|
||||
@@ -2634,8 +2613,9 @@ bool D3D12CommandProcessor::IssueCopy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12CommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
|
||||
if (await_submission >= submission_current_) {
|
||||
void D3D12CommandProcessor::CheckSubmissionCompletion(
|
||||
uint64_t await_submission) {
|
||||
if (await_submission >= GetCurrentSubmission()) {
|
||||
if (submission_open_) {
|
||||
EndSubmission(false);
|
||||
}
|
||||
@@ -2644,48 +2624,31 @@ void D3D12CommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
|
||||
// submission, but just in case of a failure, or queue operations being done
|
||||
// outside of a submission, await explicitly.
|
||||
if (queue_operations_done_since_submission_signal_) {
|
||||
UINT64 fence_value = ++queue_operations_since_submission_fence_last_;
|
||||
ID3D12CommandQueue* direct_queue = GetD3D12Provider().GetDirectQueue();
|
||||
if (SUCCEEDED(
|
||||
direct_queue->Signal(queue_operations_since_submission_fence_,
|
||||
fence_value) &&
|
||||
SUCCEEDED(queue_operations_since_submission_fence_
|
||||
->SetEventOnCompletion(fence_value,
|
||||
fence_completion_event_)))) {
|
||||
WaitForSingleObject(fence_completion_event_, INFINITE);
|
||||
if (SUCCEEDED(queue_operations_since_submission_completion_timeline_
|
||||
->SignalAndAdvance(direct_queue) &&
|
||||
queue_operations_since_submission_completion_timeline_
|
||||
->AwaitAllSubmissions())) {
|
||||
queue_operations_done_since_submission_signal_ = false;
|
||||
} else {
|
||||
XELOGE(
|
||||
"Failed to await an out-of-submission queue operation completion "
|
||||
"Direct3D 12 fence");
|
||||
"Failed to await the completion of an out-of-submission "
|
||||
"Direct3D 12 queue operation");
|
||||
}
|
||||
}
|
||||
// A submission won't be ended if it hasn't been started, or if ending
|
||||
// has failed - clamp the index.
|
||||
await_submission = submission_current_ - 1;
|
||||
await_submission = GetCurrentSubmission() - 1;
|
||||
}
|
||||
|
||||
uint64_t submission_completed_before = submission_completed_;
|
||||
submission_completed_ = submission_fence_->GetCompletedValue();
|
||||
if (submission_completed_ < await_submission) {
|
||||
if (SUCCEEDED(submission_fence_->SetEventOnCompletion(
|
||||
await_submission, fence_completion_event_))) {
|
||||
WaitForSingleObject(fence_completion_event_, INFINITE);
|
||||
submission_completed_ = submission_fence_->GetCompletedValue();
|
||||
}
|
||||
}
|
||||
if (submission_completed_ < await_submission) {
|
||||
XELOGE("Failed to await a submission completion Direct3D 12 fence");
|
||||
}
|
||||
if (submission_completed_ <= submission_completed_before) {
|
||||
// Not updated - no need to reclaim or download things.
|
||||
return;
|
||||
}
|
||||
completion_timeline_->AwaitSubmissionAndUpdateCompleted(await_submission);
|
||||
|
||||
const uint64_t completed_submission = GetCompletedSubmission();
|
||||
|
||||
// Reclaim command allocators.
|
||||
while (command_allocator_submitted_first_) {
|
||||
if (command_allocator_submitted_first_->last_usage_submission >
|
||||
submission_completed_) {
|
||||
completed_submission) {
|
||||
break;
|
||||
}
|
||||
if (command_allocator_writable_last_) {
|
||||
@@ -2706,7 +2669,7 @@ void D3D12CommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
|
||||
// Release single-use bindless descriptors.
|
||||
while (!view_bindless_one_use_descriptors_.empty()) {
|
||||
if (view_bindless_one_use_descriptors_.front().second >
|
||||
submission_completed_) {
|
||||
completed_submission) {
|
||||
break;
|
||||
}
|
||||
ReleaseViewBindlessDescriptorImmediately(
|
||||
@@ -2716,7 +2679,7 @@ void D3D12CommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
|
||||
|
||||
// Delete transient resources marked for deletion.
|
||||
while (!resources_for_deletion_.empty()) {
|
||||
if (resources_for_deletion_.front().first > submission_completed_) {
|
||||
if (resources_for_deletion_.front().first > completed_submission) {
|
||||
break;
|
||||
}
|
||||
resources_for_deletion_.front().second->Release();
|
||||
@@ -2729,7 +2692,7 @@ void D3D12CommandProcessor::CheckSubmissionFence(uint64_t await_submission) {
|
||||
|
||||
primitive_processor_->CompletedSubmissionUpdated();
|
||||
|
||||
texture_cache_->CompletedSubmissionUpdated(submission_completed_);
|
||||
texture_cache_->CompletedSubmissionUpdated(completed_submission);
|
||||
}
|
||||
|
||||
bool D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
@@ -2759,7 +2722,7 @@ bool D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
// Check the fence - needed for all kinds of submissions (to reclaim transient
|
||||
// resources early) and specifically for frames (not to queue too many), and
|
||||
// await the availability of the current frame.
|
||||
CheckSubmissionFence(
|
||||
CheckSubmissionCompletion(
|
||||
is_opening_frame
|
||||
? closed_frame_submissions_[frame_current_ % kQueueFrames]
|
||||
: 0);
|
||||
@@ -2775,7 +2738,7 @@ bool D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
for (uint64_t frame = frame_completed_ + 1; frame < frame_current_;
|
||||
++frame) {
|
||||
if (closed_frame_submissions_[frame % kQueueFrames] >
|
||||
submission_completed_) {
|
||||
GetCompletedSubmission()) {
|
||||
break;
|
||||
}
|
||||
frame_completed_ = frame;
|
||||
@@ -2812,7 +2775,7 @@ bool D3D12CommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
|
||||
primitive_processor_->BeginSubmission();
|
||||
|
||||
texture_cache_->BeginSubmission(submission_current_);
|
||||
texture_cache_->BeginSubmission(GetCurrentSubmission());
|
||||
}
|
||||
|
||||
if (is_opening_frame) {
|
||||
@@ -2907,6 +2870,8 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
|
||||
// destroyed between frames.
|
||||
SubmitBarriers();
|
||||
|
||||
// TODO(Triang3l): Error checking.
|
||||
|
||||
ID3D12CommandQueue* direct_queue = provider.GetDirectQueue();
|
||||
|
||||
// Submit the deferred command list.
|
||||
@@ -2923,7 +2888,7 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
|
||||
ID3D12CommandList* execute_command_lists[] = {command_list_};
|
||||
direct_queue->ExecuteCommandLists(1, execute_command_lists);
|
||||
command_allocator_writable_first_->last_usage_submission =
|
||||
submission_current_;
|
||||
GetCurrentSubmission();
|
||||
if (command_allocator_submitted_last_) {
|
||||
command_allocator_submitted_last_->next =
|
||||
command_allocator_writable_first_;
|
||||
@@ -2936,8 +2901,7 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
|
||||
if (!command_allocator_writable_first_) {
|
||||
command_allocator_writable_last_ = nullptr;
|
||||
}
|
||||
|
||||
direct_queue->Signal(submission_fence_, submission_current_++);
|
||||
completion_timeline_->SignalAndAdvance(direct_queue);
|
||||
|
||||
submission_open_ = false;
|
||||
|
||||
@@ -2958,7 +2922,7 @@ bool D3D12CommandProcessor::EndSubmission(bool is_swap) {
|
||||
frame_open_ = false;
|
||||
// Submission already closed now, so minus 1.
|
||||
closed_frame_submissions_[(frame_current_++) % kQueueFrames] =
|
||||
submission_current_ - 1;
|
||||
GetCurrentSubmission() - 1;
|
||||
|
||||
if (cache_clear_requested_ && AwaitAllQueueOperationsCompletion()) {
|
||||
cache_clear_requested_ = false;
|
||||
@@ -3912,7 +3876,7 @@ bool D3D12CommandProcessor::UpdateBindings(const D3D12Shader* vertex_shader,
|
||||
ID3D12DescriptorHeap* sampler_heap_new;
|
||||
if (!sampler_bindless_heaps_overflowed_.empty() &&
|
||||
sampler_bindless_heaps_overflowed_.front().second <=
|
||||
submission_completed_) {
|
||||
GetCompletedSubmission()) {
|
||||
sampler_heap_new = sampler_bindless_heaps_overflowed_.front().first;
|
||||
sampler_bindless_heaps_overflowed_.pop_front();
|
||||
} else {
|
||||
@@ -3934,7 +3898,7 @@ bool D3D12CommandProcessor::UpdateBindings(const D3D12Shader* vertex_shader,
|
||||
// leave the values in an undefined state in case CreateDescriptorHeap
|
||||
// has failed.
|
||||
sampler_bindless_heaps_overflowed_.push_back(std::make_pair(
|
||||
sampler_bindless_heap_current_, submission_current_));
|
||||
sampler_bindless_heap_current_, GetCurrentSubmission()));
|
||||
sampler_bindless_heap_current_ = sampler_heap_new;
|
||||
sampler_bindless_heap_cpu_start_ =
|
||||
sampler_bindless_heap_current_
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/ui/d3d12/d3d12_descriptor_heap_pool.h"
|
||||
#include "xenia/ui/d3d12/d3d12_gpu_completion_timeline.h"
|
||||
#include "xenia/ui/d3d12/d3d12_provider.h"
|
||||
#include "xenia/ui/d3d12/d3d12_upload_buffer_pool.h"
|
||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||
@@ -73,12 +74,16 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||
return deferred_command_list_;
|
||||
}
|
||||
|
||||
uint64_t GetCurrentSubmission() const { return submission_current_; }
|
||||
uint64_t GetCompletedSubmission() const { return submission_completed_; }
|
||||
uint64_t GetCurrentSubmission() const {
|
||||
return completion_timeline_->GetUpcomingSubmission();
|
||||
}
|
||||
uint64_t GetCompletedSubmission() const {
|
||||
return completion_timeline_->GetCompletedSubmissionFromLastUpdate();
|
||||
}
|
||||
|
||||
// Must be called when a subsystem does something like UpdateTileMappings so
|
||||
// it can be awaited in CheckSubmissionFence(submission_current_) if it was
|
||||
// done after the latest ExecuteCommandLists + Signal.
|
||||
// it can be awaited in CheckSubmissionCompletion(GetCurrentSubmission()) if
|
||||
// it was done after the latest ExecuteCommandLists + Signal.
|
||||
void NotifyQueueOperationsDoneDirectly() {
|
||||
queue_operations_done_since_submission_signal_ = true;
|
||||
}
|
||||
@@ -324,9 +329,9 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||
// decay.
|
||||
|
||||
// Rechecks submission number and reclaims per-submission resources. Pass 0 as
|
||||
// the submission to await to simply check status, or pass submission_current_
|
||||
// to wait for all queue operations to be completed.
|
||||
void CheckSubmissionFence(uint64_t await_submission);
|
||||
// the submission to await to simply check status, or pass
|
||||
// GetCurrentSubmission() to wait for all queue operations to be completed.
|
||||
void CheckSubmissionCompletion(uint64_t await_submission);
|
||||
// If is_guest_command is true, a new full frame - with full cleanup of
|
||||
// resources and, if needed, starting capturing - is opened if pending (as
|
||||
// opposed to simply resuming after mid-frame synchronization). Returns
|
||||
@@ -342,8 +347,8 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||
// need to be fulfilled before actually submitting the command list.
|
||||
bool CanEndSubmissionImmediately() const;
|
||||
bool AwaitAllQueueOperationsCompletion() {
|
||||
CheckSubmissionFence(submission_current_);
|
||||
return submission_completed_ + 1 >= submission_current_;
|
||||
CheckSubmissionCompletion(GetCurrentSubmission());
|
||||
return GetCompletedSubmission() + 1u >= GetCurrentSubmission();
|
||||
}
|
||||
// Need to await submission completion before calling.
|
||||
void ClearCommandAllocatorCache();
|
||||
@@ -389,20 +394,15 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||
|
||||
bool cache_clear_requested_ = false;
|
||||
|
||||
HANDLE fence_completion_event_ = nullptr;
|
||||
|
||||
std::unique_ptr<ui::d3d12::D3D12GPUCompletionTimeline> completion_timeline_;
|
||||
bool submission_open_ = false;
|
||||
// Values of submission_fence_.
|
||||
uint64_t submission_current_ = 1;
|
||||
uint64_t submission_completed_ = 0;
|
||||
ID3D12Fence* submission_fence_ = nullptr;
|
||||
|
||||
// For awaiting non-submission queue operations such as UpdateTileMappings in
|
||||
// AwaitAllQueueOperationsCompletion when they're queued after the latest
|
||||
// ExecuteCommandLists + Signal, thus won't be awaited by just awaiting the
|
||||
// submission.
|
||||
ID3D12Fence* queue_operations_since_submission_fence_ = nullptr;
|
||||
uint64_t queue_operations_since_submission_fence_last_ = 0;
|
||||
std::unique_ptr<ui::d3d12::D3D12GPUCompletionTimeline>
|
||||
queue_operations_since_submission_completion_timeline_;
|
||||
bool queue_operations_done_since_submission_signal_ = false;
|
||||
|
||||
bool frame_open_ = false;
|
||||
|
||||
@@ -69,6 +69,9 @@ const VkDescriptorPoolSize
|
||||
VulkanCommandProcessor::VulkanCommandProcessor(
|
||||
VulkanGraphicsSystem* graphics_system, kernel::KernelState* kernel_state)
|
||||
: CommandProcessor(graphics_system, kernel_state),
|
||||
completion_timeline_(static_cast<const ui::vulkan::VulkanProvider*>(
|
||||
graphics_system->provider())
|
||||
->vulkan_device()),
|
||||
deferred_command_buffer_(*this),
|
||||
transient_descriptor_allocator_uniform_buffer_(
|
||||
static_cast<const ui::vulkan::VulkanProvider*>(
|
||||
@@ -1150,26 +1153,17 @@ void VulkanCommandProcessor::ShutdownContext() {
|
||||
dfn.vkDestroySemaphore(device, semaphore.second, nullptr);
|
||||
}
|
||||
submissions_in_flight_semaphores_.clear();
|
||||
for (VkFence& fence : submissions_in_flight_fences_) {
|
||||
dfn.vkDestroyFence(device, fence, nullptr);
|
||||
}
|
||||
submissions_in_flight_fences_.clear();
|
||||
current_submission_wait_stage_masks_.clear();
|
||||
for (VkSemaphore semaphore : current_submission_wait_semaphores_) {
|
||||
dfn.vkDestroySemaphore(device, semaphore, nullptr);
|
||||
}
|
||||
current_submission_wait_semaphores_.clear();
|
||||
submission_completed_ = 0;
|
||||
submission_open_ = false;
|
||||
|
||||
for (VkSemaphore semaphore : semaphores_free_) {
|
||||
dfn.vkDestroySemaphore(device, semaphore, nullptr);
|
||||
}
|
||||
semaphores_free_.clear();
|
||||
for (VkFence fence : fences_free_) {
|
||||
dfn.vkDestroyFence(device, fence, nullptr);
|
||||
}
|
||||
fences_free_.clear();
|
||||
|
||||
device_lost_ = false;
|
||||
|
||||
@@ -1418,7 +1412,8 @@ void VulkanCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr,
|
||||
SwapFramebuffer& new_swap_framebuffer =
|
||||
swap_framebuffers_[swap_framebuffer_new_index];
|
||||
if (new_swap_framebuffer.framebuffer != VK_NULL_HANDLE) {
|
||||
if (submission_completed_ >= new_swap_framebuffer.last_submission) {
|
||||
if (GetCompletedSubmission() >=
|
||||
new_swap_framebuffer.last_submission) {
|
||||
dfn.vkDestroyFramebuffer(device, new_swap_framebuffer.framebuffer,
|
||||
nullptr);
|
||||
} else {
|
||||
@@ -2026,7 +2021,7 @@ VulkanCommandProcessor::AcquireScratchGpuBuffer(
|
||||
return ScratchBufferAcquisition();
|
||||
}
|
||||
|
||||
if (submission_completed_ >= scratch_buffer_last_usage_submission_) {
|
||||
if (GetCompletedSubmission() >= scratch_buffer_last_usage_submission_) {
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
if (scratch_buffer_ != VK_NULL_HANDLE) {
|
||||
@@ -2341,7 +2336,7 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type,
|
||||
texture_cache_->GetSubmissionToAwaitOnSamplerOverflow(
|
||||
samplers_overflowed_count);
|
||||
assert_true(sampler_overflow_await_submission <= GetCurrentSubmission());
|
||||
CheckSubmissionFenceAndDeviceLoss(sampler_overflow_await_submission);
|
||||
CheckSubmissionCompletionAndDeviceLoss(sampler_overflow_await_submission);
|
||||
}
|
||||
|
||||
// Set up the render targets - this may perform dispatches and draws.
|
||||
@@ -2646,7 +2641,7 @@ void VulkanCommandProcessor::InitializeTrace() {
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss(
|
||||
void VulkanCommandProcessor::CheckSubmissionCompletionAndDeviceLoss(
|
||||
uint64_t await_submission) {
|
||||
// Only report once, no need to retry a wait that won't succeed anyway.
|
||||
if (device_lost_) {
|
||||
@@ -2662,69 +2657,23 @@ void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss(
|
||||
await_submission = GetCurrentSubmission() - 1;
|
||||
}
|
||||
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
completion_timeline_.AwaitSubmissionAndUpdateCompleted(await_submission);
|
||||
|
||||
size_t fences_total = submissions_in_flight_fences_.size();
|
||||
size_t fences_awaited = 0;
|
||||
if (await_submission > submission_completed_) {
|
||||
// Await in a blocking way if requested.
|
||||
// TODO(Triang3l): Await only one fence. "Fence signal operations that are
|
||||
// defined by vkQueueSubmit additionally include in the first
|
||||
// synchronization scope all commands that occur earlier in submission
|
||||
// order."
|
||||
VkResult wait_result = dfn.vkWaitForFences(
|
||||
device, uint32_t(await_submission - submission_completed_),
|
||||
submissions_in_flight_fences_.data(), VK_TRUE, UINT64_MAX);
|
||||
if (wait_result == VK_SUCCESS) {
|
||||
fences_awaited += await_submission - submission_completed_;
|
||||
} else {
|
||||
XELOGE("Failed to await submission completion Vulkan fences");
|
||||
if (wait_result == VK_ERROR_DEVICE_LOST) {
|
||||
device_lost_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check how far into the submissions the GPU currently is, in order because
|
||||
// submission themselves can be executed out of order, but Xenia serializes
|
||||
// that for simplicity.
|
||||
while (fences_awaited < fences_total) {
|
||||
VkResult fence_status = dfn.vkWaitForFences(
|
||||
device, 1, &submissions_in_flight_fences_[fences_awaited], VK_TRUE, 0);
|
||||
if (fence_status != VK_SUCCESS) {
|
||||
if (fence_status == VK_ERROR_DEVICE_LOST) {
|
||||
device_lost_ = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
++fences_awaited;
|
||||
}
|
||||
if (device_lost_) {
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
|
||||
if (vulkan_device->IsLost()) {
|
||||
device_lost_ = true;
|
||||
graphics_system_->OnHostGpuLossFromAnyThread(true);
|
||||
return;
|
||||
}
|
||||
if (!fences_awaited) {
|
||||
// Not updated - no need to reclaim or download things.
|
||||
return;
|
||||
}
|
||||
// Reclaim fences.
|
||||
fences_free_.reserve(fences_free_.size() + fences_awaited);
|
||||
auto submissions_in_flight_fences_awaited_end =
|
||||
submissions_in_flight_fences_.cbegin();
|
||||
std::advance(submissions_in_flight_fences_awaited_end, fences_awaited);
|
||||
fences_free_.insert(fences_free_.cend(),
|
||||
submissions_in_flight_fences_.cbegin(),
|
||||
submissions_in_flight_fences_awaited_end);
|
||||
submissions_in_flight_fences_.erase(submissions_in_flight_fences_.cbegin(),
|
||||
submissions_in_flight_fences_awaited_end);
|
||||
submission_completed_ += fences_awaited;
|
||||
|
||||
const uint64_t completed_submission = GetCompletedSubmission();
|
||||
|
||||
// Reclaim semaphores.
|
||||
while (!submissions_in_flight_semaphores_.empty()) {
|
||||
const auto& semaphore_submission =
|
||||
submissions_in_flight_semaphores_.front();
|
||||
if (semaphore_submission.first > submission_completed_) {
|
||||
if (semaphore_submission.first > completed_submission) {
|
||||
break;
|
||||
}
|
||||
semaphores_free_.push_back(semaphore_submission.second);
|
||||
@@ -2734,7 +2683,7 @@ void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss(
|
||||
// Reclaim command pools.
|
||||
while (!command_buffers_submitted_.empty()) {
|
||||
const auto& command_buffer_pair = command_buffers_submitted_.front();
|
||||
if (command_buffer_pair.first > submission_completed_) {
|
||||
if (command_buffer_pair.first > completed_submission) {
|
||||
break;
|
||||
}
|
||||
command_buffers_writable_.push_back(command_buffer_pair.second);
|
||||
@@ -2747,12 +2696,14 @@ void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss(
|
||||
|
||||
render_target_cache_->CompletedSubmissionUpdated();
|
||||
|
||||
texture_cache_->CompletedSubmissionUpdated(submission_completed_);
|
||||
texture_cache_->CompletedSubmissionUpdated(completed_submission);
|
||||
|
||||
// Destroy objects scheduled for destruction.
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
while (!destroy_framebuffers_.empty()) {
|
||||
const auto& destroy_pair = destroy_framebuffers_.front();
|
||||
if (destroy_pair.first > submission_completed_) {
|
||||
if (destroy_pair.first > completed_submission) {
|
||||
break;
|
||||
}
|
||||
dfn.vkDestroyFramebuffer(device, destroy_pair.second, nullptr);
|
||||
@@ -2760,7 +2711,7 @@ void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss(
|
||||
}
|
||||
while (!destroy_buffers_.empty()) {
|
||||
const auto& destroy_pair = destroy_buffers_.front();
|
||||
if (destroy_pair.first > submission_completed_) {
|
||||
if (destroy_pair.first > completed_submission) {
|
||||
break;
|
||||
}
|
||||
dfn.vkDestroyBuffer(device, destroy_pair.second, nullptr);
|
||||
@@ -2768,7 +2719,7 @@ void VulkanCommandProcessor::CheckSubmissionFenceAndDeviceLoss(
|
||||
}
|
||||
while (!destroy_memory_.empty()) {
|
||||
const auto& destroy_pair = destroy_memory_.front();
|
||||
if (destroy_pair.first > submission_completed_) {
|
||||
if (destroy_pair.first > completed_submission) {
|
||||
break;
|
||||
}
|
||||
dfn.vkFreeMemory(device, destroy_pair.second, nullptr);
|
||||
@@ -2798,8 +2749,9 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
is_opening_frame
|
||||
? closed_frame_submissions_[frame_current_ % kMaxFramesInFlight]
|
||||
: 0;
|
||||
CheckSubmissionFenceAndDeviceLoss(await_submission);
|
||||
if (device_lost_ || submission_completed_ < await_submission) {
|
||||
CheckSubmissionCompletionAndDeviceLoss(await_submission);
|
||||
const uint64_t completed_submission = GetCompletedSubmission();
|
||||
if (device_lost_ || completed_submission < await_submission) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2812,7 +2764,7 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
for (uint64_t frame = frame_completed_ + 1; frame < frame_current_;
|
||||
++frame) {
|
||||
if (closed_frame_submissions_[frame % kMaxFramesInFlight] >
|
||||
submission_completed_) {
|
||||
completed_submission) {
|
||||
break;
|
||||
}
|
||||
frame_completed_ = frame;
|
||||
@@ -2925,27 +2877,12 @@ bool VulkanCommandProcessor::BeginSubmission(bool is_guest_command) {
|
||||
}
|
||||
|
||||
bool VulkanCommandProcessor::EndSubmission(bool is_swap) {
|
||||
const ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
ui::vulkan::VulkanDevice* const vulkan_device = GetVulkanDevice();
|
||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
||||
const VkDevice device = vulkan_device->device();
|
||||
|
||||
// Make sure everything needed for submitting exist.
|
||||
if (submission_open_) {
|
||||
if (fences_free_.empty()) {
|
||||
VkFenceCreateInfo fence_create_info;
|
||||
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_create_info.pNext = nullptr;
|
||||
fence_create_info.flags = 0;
|
||||
VkFence fence;
|
||||
if (dfn.vkCreateFence(device, &fence_create_info, nullptr, &fence) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE("Failed to create a Vulkan fence");
|
||||
// Try to submit later. Completely dropping the submission is not
|
||||
// permitted because resources would be left in an undefined state.
|
||||
return false;
|
||||
}
|
||||
fences_free_.push_back(fence);
|
||||
}
|
||||
if (!sparse_memory_binds_.empty() && semaphores_free_.empty()) {
|
||||
VkSemaphoreCreateInfo semaphore_create_info;
|
||||
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
@@ -3088,58 +3025,37 @@ bool VulkanCommandProcessor::EndSubmission(bool is_swap) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkSubmitInfo submit_info;
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
const uint64_t submission_index = GetCurrentSubmission();
|
||||
|
||||
VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
if (!current_submission_wait_semaphores_.empty()) {
|
||||
submit_info.waitSemaphoreCount =
|
||||
uint32_t(current_submission_wait_semaphores_.size());
|
||||
submit_info.pWaitSemaphores = current_submission_wait_semaphores_.data();
|
||||
submit_info.pWaitDstStageMask =
|
||||
current_submission_wait_stage_masks_.data();
|
||||
} else {
|
||||
submit_info.waitSemaphoreCount = 0;
|
||||
submit_info.pWaitSemaphores = nullptr;
|
||||
submit_info.pWaitDstStageMask = nullptr;
|
||||
}
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &command_buffer.buffer;
|
||||
submit_info.signalSemaphoreCount = 0;
|
||||
submit_info.pSignalSemaphores = nullptr;
|
||||
assert_false(fences_free_.empty());
|
||||
VkFence fence = fences_free_.back();
|
||||
if (dfn.vkResetFences(device, 1, &fence) != VK_SUCCESS) {
|
||||
XELOGE("Failed to reset a Vulkan submission fence");
|
||||
return false;
|
||||
}
|
||||
VkResult submit_result;
|
||||
{
|
||||
ui::vulkan::VulkanDevice::Queue::Acquisition queue_acquisition =
|
||||
vulkan_device->AcquireQueue(
|
||||
vulkan_device->queue_family_graphics_compute(), 0);
|
||||
submit_result =
|
||||
dfn.vkQueueSubmit(queue_acquisition.queue(), 1, &submit_info, fence);
|
||||
}
|
||||
const VkResult submit_result = completion_timeline_.AcquireFenceAndSubmit(
|
||||
vulkan_device->queue_family_graphics_compute(), 0, 1, &submit_info);
|
||||
if (submit_result != VK_SUCCESS) {
|
||||
XELOGE("Failed to submit a Vulkan command buffer");
|
||||
if (submit_result == VK_ERROR_DEVICE_LOST && !device_lost_) {
|
||||
XELOGE("Failed to submit a GPU emulation Vulkan command buffer: {}",
|
||||
vk::to_string(vk::Result(submit_result)));
|
||||
if (vulkan_device->IsLost() && !device_lost_) {
|
||||
device_lost_ = true;
|
||||
graphics_system_->OnHostGpuLossFromAnyThread(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
uint64_t submission_current = GetCurrentSubmission();
|
||||
current_submission_wait_stage_masks_.clear();
|
||||
for (VkSemaphore semaphore : current_submission_wait_semaphores_) {
|
||||
submissions_in_flight_semaphores_.emplace_back(submission_current,
|
||||
submissions_in_flight_semaphores_.emplace_back(submission_index,
|
||||
semaphore);
|
||||
}
|
||||
current_submission_wait_semaphores_.clear();
|
||||
command_buffers_submitted_.emplace_back(submission_current, command_buffer);
|
||||
command_buffers_submitted_.emplace_back(submission_index, command_buffer);
|
||||
command_buffers_writable_.pop_back();
|
||||
// Increments the current submission number, going to the next submission.
|
||||
submissions_in_flight_fences_.push_back(fence);
|
||||
fences_free_.pop_back();
|
||||
|
||||
submission_open_ = false;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/ui/vulkan/linked_type_descriptor_set_allocator.h"
|
||||
#include "xenia/ui/vulkan/vulkan_gpu_completion_timeline.h"
|
||||
#include "xenia/ui/vulkan/vulkan_presenter.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
#include "xenia/ui/vulkan/vulkan_upload_buffer_pool.h"
|
||||
@@ -157,10 +158,11 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||
|
||||
bool submission_open() const { return submission_open_; }
|
||||
uint64_t GetCurrentSubmission() const {
|
||||
return submission_completed_ +
|
||||
uint64_t(submissions_in_flight_fences_.size()) + 1;
|
||||
return completion_timeline_.GetUpcomingSubmission();
|
||||
}
|
||||
uint64_t GetCompletedSubmission() const {
|
||||
return completion_timeline_.GetCompletedSubmissionFromLastUpdate();
|
||||
}
|
||||
uint64_t GetCompletedSubmission() const { return submission_completed_; }
|
||||
|
||||
// Sparse binds are:
|
||||
// - In a single submission, all submitted in one vkQueueBindSparse.
|
||||
@@ -403,7 +405,7 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||
// Rechecks submission number and reclaims per-submission resources. Pass 0 as
|
||||
// the submission to await to simply check status, or pass
|
||||
// GetCurrentSubmission() to wait for all queue operations to be completed.
|
||||
void CheckSubmissionFenceAndDeviceLoss(uint64_t await_submission);
|
||||
void CheckSubmissionCompletionAndDeviceLoss(uint64_t await_submission);
|
||||
// If is_guest_command is true, a new full frame - with full cleanup of
|
||||
// resources and, if needed, starting capturing - is opened if pending (as
|
||||
// opposed to simply resuming after mid-frame synchronization). Returns
|
||||
@@ -414,8 +416,9 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||
// successfully, if it has failed, leaves it open.
|
||||
bool EndSubmission(bool is_swap);
|
||||
bool AwaitAllQueueOperationsCompletion() {
|
||||
CheckSubmissionFenceAndDeviceLoss(GetCurrentSubmission());
|
||||
return !submission_open_ && submissions_in_flight_fences_.empty();
|
||||
CheckSubmissionCompletionAndDeviceLoss(GetCurrentSubmission());
|
||||
return !submission_open_ &&
|
||||
GetCompletedSubmission() + 1u >= GetCurrentSubmission();
|
||||
}
|
||||
|
||||
void ClearTransientDescriptorPools();
|
||||
@@ -460,16 +463,14 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||
VkPipelineStageFlags guest_shader_pipeline_stages_ = 0;
|
||||
VkShaderStageFlags guest_shader_vertex_stages_ = 0;
|
||||
|
||||
std::vector<VkFence> fences_free_;
|
||||
std::vector<VkSemaphore> semaphores_free_;
|
||||
|
||||
ui::vulkan::VulkanGPUCompletionTimeline completion_timeline_;
|
||||
bool submission_open_ = false;
|
||||
uint64_t submission_completed_ = 0;
|
||||
// In case vkQueueSubmit fails after something like a successful
|
||||
// vkQueueBindSparse, to wait correctly on the next attempt.
|
||||
std::vector<VkSemaphore> current_submission_wait_semaphores_;
|
||||
std::vector<VkPipelineStageFlags> current_submission_wait_stage_masks_;
|
||||
std::vector<VkFence> submissions_in_flight_fences_;
|
||||
std::deque<std::pair<uint64_t, VkSemaphore>>
|
||||
submissions_in_flight_semaphores_;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user