Merge branch 'master' into vulkan
This commit is contained in:
68
src/xenia/ui/vulkan/functions/device_1_0.inc
Normal file
68
src/xenia/ui/vulkan/functions/device_1_0.inc
Normal file
@@ -0,0 +1,68 @@
|
||||
// Vulkan 1.0 core device functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkAllocateCommandBuffers)
|
||||
XE_UI_VULKAN_FUNCTION(vkAllocateDescriptorSets)
|
||||
XE_UI_VULKAN_FUNCTION(vkAllocateMemory)
|
||||
XE_UI_VULKAN_FUNCTION(vkBeginCommandBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkBindBufferMemory)
|
||||
XE_UI_VULKAN_FUNCTION(vkBindImageMemory)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBeginRenderPass)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindDescriptorSets)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindIndexBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindPipeline)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdBindVertexBuffers)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdClearAttachments)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdClearColorImage)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyBufferToImage)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdCopyImageToBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdDraw)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdDrawIndexed)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdEndRenderPass)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdPipelineBarrier)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdPushConstants)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdSetScissor)
|
||||
XE_UI_VULKAN_FUNCTION(vkCmdSetViewport)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateCommandPool)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateDescriptorPool)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateDescriptorSetLayout)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateFence)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateFramebuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateGraphicsPipelines)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateImage)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateImageView)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreatePipelineLayout)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateRenderPass)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateSampler)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateSemaphore)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateShaderModule)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyCommandPool)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyDescriptorPool)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyDescriptorSetLayout)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyFence)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyFramebuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyImage)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyImageView)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyPipeline)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyPipelineLayout)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyRenderPass)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroySampler)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroySemaphore)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyShaderModule)
|
||||
XE_UI_VULKAN_FUNCTION(vkEndCommandBuffer)
|
||||
XE_UI_VULKAN_FUNCTION(vkFlushMappedMemoryRanges)
|
||||
XE_UI_VULKAN_FUNCTION(vkFreeMemory)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetBufferMemoryRequirements)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetDeviceQueue)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetFenceStatus)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetImageMemoryRequirements)
|
||||
XE_UI_VULKAN_FUNCTION(vkMapMemory)
|
||||
XE_UI_VULKAN_FUNCTION(vkResetCommandPool)
|
||||
XE_UI_VULKAN_FUNCTION(vkResetDescriptorPool)
|
||||
XE_UI_VULKAN_FUNCTION(vkResetFences)
|
||||
XE_UI_VULKAN_FUNCTION(vkQueueBindSparse)
|
||||
XE_UI_VULKAN_FUNCTION(vkQueueSubmit)
|
||||
XE_UI_VULKAN_FUNCTION(vkUnmapMemory)
|
||||
XE_UI_VULKAN_FUNCTION(vkUpdateDescriptorSets)
|
||||
XE_UI_VULKAN_FUNCTION(vkWaitForFences)
|
||||
6
src/xenia/ui/vulkan/functions/device_khr_swapchain.inc
Normal file
6
src/xenia/ui/vulkan/functions/device_khr_swapchain.inc
Normal file
@@ -0,0 +1,6 @@
|
||||
// VK_KHR_swapchain functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkAcquireNextImageKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateSwapchainKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroySwapchainKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetSwapchainImagesKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkQueuePresentKHR)
|
||||
10
src/xenia/ui/vulkan/functions/instance_1_0.inc
Normal file
10
src/xenia/ui/vulkan/functions/instance_1_0.inc
Normal file
@@ -0,0 +1,10 @@
|
||||
// Vulkan 1.0 core instance functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateDevice)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyDevice)
|
||||
XE_UI_VULKAN_FUNCTION(vkEnumerateDeviceExtensionProperties)
|
||||
XE_UI_VULKAN_FUNCTION(vkEnumeratePhysicalDevices)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetDeviceProcAddr)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceFeatures)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceMemoryProperties)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceProperties)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties)
|
||||
@@ -0,0 +1,4 @@
|
||||
// VK_EXT_debug_utils functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateDebugUtilsMessengerEXT)
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroyDebugUtilsMessengerEXT)
|
||||
XE_UI_VULKAN_FUNCTION(vkSetDebugUtilsObjectNameEXT)
|
||||
@@ -0,0 +1,2 @@
|
||||
// VK_KHR_android_surface functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateAndroidSurfaceKHR)
|
||||
@@ -0,0 +1,4 @@
|
||||
// VK_KHR_get_physical_device_properties2 functions used in Xenia.
|
||||
// Promoted to Vulkan 1.1 core.
|
||||
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceProperties2KHR,
|
||||
vkGetPhysicalDeviceProperties2)
|
||||
6
src/xenia/ui/vulkan/functions/instance_khr_surface.inc
Normal file
6
src/xenia/ui/vulkan/functions/instance_khr_surface.inc
Normal file
@@ -0,0 +1,6 @@
|
||||
// VK_KHR_surface functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkDestroySurfaceKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfaceCapabilitiesKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfaceFormatsKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfacePresentModesKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceSurfaceSupportKHR)
|
||||
@@ -0,0 +1,3 @@
|
||||
// VK_KHR_win32_surface functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateWin32SurfaceKHR)
|
||||
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceWin32PresentationSupportKHR)
|
||||
@@ -0,0 +1,2 @@
|
||||
// VK_KHR_xcb_surface functions used in Xenia.
|
||||
XE_UI_VULKAN_FUNCTION(vkCreateXcbSurfaceKHR)
|
||||
@@ -11,6 +11,7 @@ project("xenia-ui-vulkan")
|
||||
"xenia-ui",
|
||||
})
|
||||
local_platform_files()
|
||||
local_platform_files("functions")
|
||||
files({
|
||||
"../shaders/bytecode/vulkan_spirv/*.h",
|
||||
})
|
||||
@@ -35,3 +36,10 @@ project("xenia-ui-window-vulkan-demo")
|
||||
resincludedirs({
|
||||
project_root,
|
||||
})
|
||||
|
||||
filter("platforms:Linux")
|
||||
links({
|
||||
"X11",
|
||||
"xcb",
|
||||
"X11-xcb",
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,145 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
||||
#define XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/ui/graphics_context.h"
|
||||
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
|
||||
#define FINE_GRAINED_DRAW_SCOPES 1
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
class VulkanContext : public GraphicsContext {
|
||||
public:
|
||||
~VulkanContext() override;
|
||||
|
||||
ImmediateDrawer* immediate_drawer() override;
|
||||
|
||||
bool WasLost() override;
|
||||
|
||||
bool BeginSwap() override;
|
||||
void EndSwap() override;
|
||||
|
||||
std::unique_ptr<RawImage> Capture() override;
|
||||
|
||||
VulkanProvider& GetVulkanProvider() const {
|
||||
return static_cast<VulkanProvider&>(*provider_);
|
||||
}
|
||||
|
||||
void RequestSurfaceRecreation();
|
||||
|
||||
VkCommandBuffer GetSwapCommandBuffer() const {
|
||||
return swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount]
|
||||
.command_buffer;
|
||||
}
|
||||
VkCommandBuffer AcquireSwapSetupCommandBuffer();
|
||||
uint64_t swap_submission_current() const { return swap_submission_current_; }
|
||||
uint64_t swap_submission_completed() const {
|
||||
return swap_submission_completed_;
|
||||
}
|
||||
|
||||
const VkSurfaceFormatKHR& swap_surface_format() const {
|
||||
return swap_surface_format_;
|
||||
}
|
||||
VkRenderPass swap_render_pass() const { return swap_render_pass_; }
|
||||
|
||||
private:
|
||||
friend class VulkanProvider;
|
||||
explicit VulkanContext(VulkanProvider* provider, Window* target_window);
|
||||
bool Initialize();
|
||||
|
||||
private:
|
||||
void Shutdown();
|
||||
|
||||
bool AwaitSwapSubmissionsCompletion(uint64_t awaited_submission,
|
||||
bool ignore_result);
|
||||
void AwaitAllSwapSubmissionsCompletion() {
|
||||
// Current starts from 1, so subtracting 1 can't result in a negative value.
|
||||
AwaitSwapSubmissionsCompletion(swap_submission_current_ - 1, true);
|
||||
}
|
||||
|
||||
// AwaitAllSwapSubmissionsCompletion must be called before. As this can be
|
||||
// used in swapchain creation or in shutdown,
|
||||
// swap_swapchain_or_surface_recreation_needed_ won't be set by this.
|
||||
void DestroySwapchainFramebuffers();
|
||||
|
||||
bool context_lost_ = false;
|
||||
|
||||
// Actual image count may be less, depending on what the surface can provide.
|
||||
static constexpr uint32_t kSwapchainMaxImageCount = 3;
|
||||
|
||||
// Because of the nature of Vulkan fences (that they belong only to their
|
||||
// specific submission, not the submission and all prior submissions), ALL
|
||||
// fences since the last completed submission to the needed submission should
|
||||
// individually be checked, not just the last one. However, this submission
|
||||
// number abstraction hides the loosely ordered design of Vulkan submissions
|
||||
// (it's okay to wait first for completion of A, then of B, no matter if they
|
||||
// are actually completed in AB or in BA order).
|
||||
|
||||
// May be used infrequently, so allocated on demand (to only keep 1 rather
|
||||
// than 3).
|
||||
std::pair<VkCommandPool, VkCommandBuffer>
|
||||
swap_setup_command_buffers_[kSwapchainMaxImageCount];
|
||||
uint32_t swap_setup_command_buffers_allocated_count_ = 0;
|
||||
uint32_t swap_setup_command_buffers_free_bits_ = 0;
|
||||
|
||||
struct SwapSubmission {
|
||||
// One pool per frame, with resetting the pool itself rather than individual
|
||||
// command buffers (resetting command buffers themselves is not recommended
|
||||
// by Arm since it makes the pool unable to use a single big allocation), as
|
||||
// recommended by Nvidia (Direct3D 12-like way):
|
||||
// https://developer.nvidia.com/sites/default/files/akamai/gameworks/blog/munich/mschott_vulkan_multi_threading.pdf
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
// One pair of semaphores per frame because queue operations may be done out
|
||||
// of order.
|
||||
VkSemaphore image_acquisition_semaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore render_completion_semaphore = VK_NULL_HANDLE;
|
||||
VkCommandPool command_pool = VK_NULL_HANDLE;
|
||||
VkCommandBuffer command_buffer;
|
||||
uint32_t setup_command_buffer_index = UINT32_MAX;
|
||||
};
|
||||
SwapSubmission swap_submissions_[kSwapchainMaxImageCount];
|
||||
uint64_t swap_submission_current_ = 1;
|
||||
uint64_t swap_submission_completed_ = 0;
|
||||
|
||||
VkSurfaceKHR swap_surface_ = VK_NULL_HANDLE;
|
||||
VkSurfaceFormatKHR swap_surface_format_ = {VK_FORMAT_UNDEFINED,
|
||||
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
|
||||
VkPresentModeKHR swap_surface_present_mode_;
|
||||
VkRenderPass swap_render_pass_ = VK_NULL_HANDLE;
|
||||
VkSwapchainKHR swap_swapchain_ = VK_NULL_HANDLE;
|
||||
VkExtent2D swap_swapchain_extent_;
|
||||
std::vector<VkImageView> swap_swapchain_image_views_;
|
||||
std::vector<VkFramebuffer> swap_swapchain_framebuffers_;
|
||||
|
||||
uint32_t swap_swapchain_image_current_ = UINT32_MAX;
|
||||
|
||||
// Attempts to recreate the swapchain will only be made in BeginSwap if this
|
||||
// is true (set when something relevant is changed), so if creation fails,
|
||||
// there won't be attempts every frame again and again.
|
||||
bool swap_swapchain_or_surface_recreation_needed_ = false;
|
||||
|
||||
std::unique_ptr<VulkanImmediateDrawer> immediate_drawer_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
||||
@@ -17,14 +17,14 @@
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/ui/vulkan/vulkan_context.h"
|
||||
#include "xenia/ui/vulkan/vulkan_presenter.h"
|
||||
#include "xenia/ui/vulkan/vulkan_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
// Generated with `xb genspirv`.
|
||||
// Generated with `xb buildshaders`.
|
||||
#include "xenia/ui/shaders/bytecode/vulkan_spirv/immediate_frag.h"
|
||||
#include "xenia/ui/shaders/bytecode/vulkan_spirv/immediate_vert.h"
|
||||
|
||||
@@ -34,15 +34,61 @@ VulkanImmediateDrawer::VulkanImmediateTexture::~VulkanImmediateTexture() {
|
||||
}
|
||||
}
|
||||
|
||||
VulkanImmediateDrawer::VulkanImmediateDrawer(VulkanContext& graphics_context)
|
||||
: ImmediateDrawer(&graphics_context), context_(graphics_context) {}
|
||||
VulkanImmediateDrawer::~VulkanImmediateDrawer() {
|
||||
// Await GPU usage completion of all draws and texture uploads (which happen
|
||||
// before draws).
|
||||
auto vulkan_presenter = static_cast<VulkanPresenter*>(presenter());
|
||||
if (vulkan_presenter) {
|
||||
vulkan_presenter->AwaitUISubmissionCompletionFromUIThread(
|
||||
last_paint_submission_index_);
|
||||
}
|
||||
|
||||
VulkanImmediateDrawer::~VulkanImmediateDrawer() { Shutdown(); }
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_line_);
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_triangle_);
|
||||
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device,
|
||||
pipeline_layout_);
|
||||
|
||||
for (auto& deleted_texture : textures_deleted_) {
|
||||
DestroyTextureResource(deleted_texture.first);
|
||||
}
|
||||
for (SubmittedTextureUploadBuffer& submitted_texture_upload_buffer :
|
||||
texture_upload_buffers_submitted_) {
|
||||
dfn.vkDestroyBuffer(device, submitted_texture_upload_buffer.buffer,
|
||||
nullptr);
|
||||
dfn.vkFreeMemory(device, submitted_texture_upload_buffer.buffer_memory,
|
||||
nullptr);
|
||||
}
|
||||
for (PendingTextureUpload& pending_texture_upload :
|
||||
texture_uploads_pending_) {
|
||||
dfn.vkDestroyBuffer(device, pending_texture_upload.buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, pending_texture_upload.buffer_memory, nullptr);
|
||||
}
|
||||
for (VulkanImmediateTexture* texture : textures_) {
|
||||
if (texture->immediate_drawer_ != this) {
|
||||
continue;
|
||||
}
|
||||
texture->immediate_drawer_ = nullptr;
|
||||
DestroyTextureResource(texture->resource_);
|
||||
}
|
||||
if (white_texture_.image != VK_NULL_HANDLE) {
|
||||
DestroyTextureResource(white_texture_);
|
||||
}
|
||||
|
||||
for (TextureDescriptorPool* pool : texture_descriptor_pools_) {
|
||||
dfn.vkDestroyDescriptorPool(device, pool->pool, nullptr);
|
||||
delete pool;
|
||||
}
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, device,
|
||||
texture_descriptor_set_layout_);
|
||||
}
|
||||
|
||||
bool VulkanImmediateDrawer::Initialize() {
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
VkDescriptorSetLayoutBinding texture_descriptor_set_layout_binding;
|
||||
texture_descriptor_set_layout_binding.binding = 0;
|
||||
@@ -64,9 +110,8 @@ bool VulkanImmediateDrawer::Initialize() {
|
||||
device, &texture_descriptor_set_layout_create_info, nullptr,
|
||||
&texture_descriptor_set_layout_) != VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to create the immediate drawer Vulkan combined image sampler "
|
||||
"VulkanImmediateDrawer: Failed to create the combined image sampler "
|
||||
"descriptor set layout");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -76,11 +121,14 @@ bool VulkanImmediateDrawer::Initialize() {
|
||||
if (!CreateTextureResource(1, 1, ImmediateTextureFilter::kNearest, false,
|
||||
nullptr, white_texture_,
|
||||
white_texture_pending_upload_index)) {
|
||||
XELOGE("Failed to create a blank texture for the Vulkan immediate drawer");
|
||||
Shutdown();
|
||||
XELOGE("VulkanImmediateDrawer: Failed to create a blank texture");
|
||||
return false;
|
||||
}
|
||||
|
||||
vertex_buffer_pool_ = std::make_unique<VulkanUploadBufferPool>(
|
||||
provider_,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
||||
|
||||
VkPushConstantRange push_constant_ranges[1];
|
||||
push_constant_ranges[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
push_constant_ranges[0].offset = offsetof(PushConstants, vertex);
|
||||
@@ -97,77 +145,16 @@ bool VulkanImmediateDrawer::Initialize() {
|
||||
pipeline_layout_create_info.pPushConstantRanges = push_constant_ranges;
|
||||
if (dfn.vkCreatePipelineLayout(device, &pipeline_layout_create_info, nullptr,
|
||||
&pipeline_layout_) != VK_SUCCESS) {
|
||||
XELOGE("Failed to create the immediate drawer Vulkan pipeline layout");
|
||||
Shutdown();
|
||||
XELOGE("VulkanImmediateDrawer: Failed to create the pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
vertex_buffer_pool_ = std::make_unique<VulkanUploadBufferPool>(
|
||||
provider,
|
||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
||||
|
||||
// Reset the current state.
|
||||
current_command_buffer_ = VK_NULL_HANDLE;
|
||||
batch_open_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanImmediateDrawer::Shutdown() {
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_line_);
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_triangle_);
|
||||
|
||||
vertex_buffer_pool_.reset();
|
||||
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipelineLayout, device,
|
||||
pipeline_layout_);
|
||||
|
||||
for (auto& deleted_texture : textures_deleted_) {
|
||||
DestroyTextureResource(deleted_texture.first);
|
||||
}
|
||||
textures_deleted_.clear();
|
||||
for (SubmittedTextureUploadBuffer& submitted_texture_upload_buffer :
|
||||
texture_upload_buffers_submitted_) {
|
||||
dfn.vkDestroyBuffer(device, submitted_texture_upload_buffer.buffer,
|
||||
nullptr);
|
||||
dfn.vkFreeMemory(device, submitted_texture_upload_buffer.buffer_memory,
|
||||
nullptr);
|
||||
}
|
||||
texture_upload_buffers_submitted_.clear();
|
||||
for (PendingTextureUpload& pending_texture_upload :
|
||||
texture_uploads_pending_) {
|
||||
dfn.vkDestroyBuffer(device, pending_texture_upload.buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, pending_texture_upload.buffer_memory, nullptr);
|
||||
}
|
||||
texture_uploads_pending_.clear();
|
||||
for (VulkanImmediateTexture* texture : textures_) {
|
||||
if (texture->immediate_drawer_ != this) {
|
||||
continue;
|
||||
}
|
||||
texture->immediate_drawer_ = nullptr;
|
||||
DestroyTextureResource(texture->resource_);
|
||||
}
|
||||
textures_.clear();
|
||||
if (white_texture_.image != VK_NULL_HANDLE) {
|
||||
DestroyTextureResource(white_texture_);
|
||||
white_texture_.image = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
texture_descriptor_pool_recycled_first_ = nullptr;
|
||||
texture_descriptor_pool_unallocated_first_ = nullptr;
|
||||
for (TextureDescriptorPool* pool : texture_descriptor_pools_) {
|
||||
dfn.vkDestroyDescriptorPool(device, pool->pool, nullptr);
|
||||
delete pool;
|
||||
}
|
||||
texture_descriptor_pools_.clear();
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyDescriptorSetLayout, device,
|
||||
texture_descriptor_set_layout_);
|
||||
}
|
||||
|
||||
std::unique_ptr<ImmediateTexture> VulkanImmediateDrawer::CreateTexture(
|
||||
uint32_t width, uint32_t height, ImmediateTextureFilter filter,
|
||||
bool is_repeated, const uint8_t* data) {
|
||||
@@ -187,26 +174,30 @@ std::unique_ptr<ImmediateTexture> VulkanImmediateDrawer::CreateTexture(
|
||||
return std::move(texture);
|
||||
}
|
||||
|
||||
void VulkanImmediateDrawer::Begin(int render_target_width,
|
||||
int render_target_height) {
|
||||
assert_true(current_command_buffer_ == VK_NULL_HANDLE);
|
||||
void VulkanImmediateDrawer::Begin(UIDrawContext& ui_draw_context,
|
||||
float coordinate_space_width,
|
||||
float coordinate_space_height) {
|
||||
ImmediateDrawer::Begin(ui_draw_context, coordinate_space_width,
|
||||
coordinate_space_height);
|
||||
|
||||
assert_false(batch_open_);
|
||||
|
||||
if (!EnsurePipelinesCreated()) {
|
||||
return;
|
||||
}
|
||||
const VulkanUIDrawContext& vulkan_ui_draw_context =
|
||||
static_cast<const VulkanUIDrawContext&>(ui_draw_context);
|
||||
|
||||
current_command_buffer_ = context_.GetSwapCommandBuffer();
|
||||
// Update the submission index to be used throughout the current immediate
|
||||
// drawer paint.
|
||||
last_paint_submission_index_ =
|
||||
vulkan_ui_draw_context.submission_index_current();
|
||||
last_completed_submission_index_ =
|
||||
vulkan_ui_draw_context.submission_index_completed();
|
||||
|
||||
uint64_t submission_completed = context_.swap_submission_completed();
|
||||
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
// Destroy deleted textures.
|
||||
for (auto it = textures_deleted_.begin(); it != textures_deleted_.end();) {
|
||||
if (it->second > submission_completed) {
|
||||
if (it->second > last_completed_submission_index_) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
@@ -220,7 +211,8 @@ void VulkanImmediateDrawer::Begin(int render_target_width,
|
||||
// Release upload buffers for completed texture uploads.
|
||||
auto erase_texture_uploads_end = texture_upload_buffers_submitted_.begin();
|
||||
while (erase_texture_uploads_end != texture_upload_buffers_submitted_.end()) {
|
||||
if (erase_texture_uploads_end->submission_index > submission_completed) {
|
||||
if (erase_texture_uploads_end->submission_index >
|
||||
last_completed_submission_index_) {
|
||||
break;
|
||||
}
|
||||
dfn.vkDestroyBuffer(device, erase_texture_uploads_end->buffer, nullptr);
|
||||
@@ -230,22 +222,34 @@ void VulkanImmediateDrawer::Begin(int render_target_width,
|
||||
texture_upload_buffers_submitted_.erase(
|
||||
texture_upload_buffers_submitted_.begin(), erase_texture_uploads_end);
|
||||
|
||||
vertex_buffer_pool_->Reclaim(submission_completed);
|
||||
vertex_buffer_pool_->Reclaim(last_completed_submission_index_);
|
||||
|
||||
// Begin drawing.
|
||||
|
||||
// Update the framebuffer attachment format in the pipelines - may await
|
||||
// submission completion if changing.
|
||||
if (!EnsurePipelinesCreatedForCurrentRenderPass()) {
|
||||
// Failed to create the pipelines - don't draw anything.
|
||||
return;
|
||||
}
|
||||
|
||||
VkCommandBuffer draw_command_buffer =
|
||||
vulkan_ui_draw_context.draw_command_buffer();
|
||||
|
||||
current_render_target_extent_.width = uint32_t(render_target_width);
|
||||
current_render_target_extent_.height = uint32_t(render_target_height);
|
||||
VkViewport viewport;
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.width = float(render_target_width);
|
||||
viewport.height = float(render_target_height);
|
||||
viewport.width = float(vulkan_ui_draw_context.render_target_width());
|
||||
viewport.height = float(vulkan_ui_draw_context.render_target_height());
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
dfn.vkCmdSetViewport(current_command_buffer_, 0, 1, &viewport);
|
||||
dfn.vkCmdSetViewport(draw_command_buffer, 0, 1, &viewport);
|
||||
PushConstants::Vertex push_constants_vertex;
|
||||
push_constants_vertex.viewport_size_inv[0] = 1.0f / viewport.width;
|
||||
push_constants_vertex.viewport_size_inv[1] = 1.0f / viewport.height;
|
||||
dfn.vkCmdPushConstants(current_command_buffer_, pipeline_layout_,
|
||||
push_constants_vertex.coordinate_space_size_inv[0] =
|
||||
1.0f / coordinate_space_width;
|
||||
push_constants_vertex.coordinate_space_size_inv[1] =
|
||||
1.0f / coordinate_space_height;
|
||||
dfn.vkCmdPushConstants(draw_command_buffer, pipeline_layout_,
|
||||
VK_SHADER_STAGE_VERTEX_BIT,
|
||||
offsetof(PushConstants, vertex),
|
||||
sizeof(PushConstants::Vertex), &push_constants_vertex);
|
||||
@@ -260,29 +264,28 @@ void VulkanImmediateDrawer::Begin(int render_target_width,
|
||||
|
||||
void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
|
||||
assert_false(batch_open_);
|
||||
if (current_command_buffer_ == VK_NULL_HANDLE) {
|
||||
// No surface, or failed to create the pipelines.
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t submission_current = context_.swap_submission_current();
|
||||
const VulkanProvider::DeviceFunctions& dfn =
|
||||
context_.GetVulkanProvider().dfn();
|
||||
const VulkanUIDrawContext& vulkan_ui_draw_context =
|
||||
*static_cast<const VulkanUIDrawContext*>(ui_draw_context());
|
||||
VkCommandBuffer draw_command_buffer =
|
||||
vulkan_ui_draw_context.draw_command_buffer();
|
||||
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
|
||||
// Bind the vertices.
|
||||
size_t vertex_buffer_size = sizeof(ImmediateVertex) * batch.vertex_count;
|
||||
VkBuffer vertex_buffer;
|
||||
VkDeviceSize vertex_buffer_offset;
|
||||
void* vertex_buffer_mapping = vertex_buffer_pool_->Request(
|
||||
submission_current, vertex_buffer_size, sizeof(float), vertex_buffer,
|
||||
vertex_buffer_offset);
|
||||
last_paint_submission_index_, vertex_buffer_size, sizeof(float),
|
||||
vertex_buffer, vertex_buffer_offset);
|
||||
if (!vertex_buffer_mapping) {
|
||||
XELOGE("Failed to get a buffer for {} vertices in the immediate drawer",
|
||||
XELOGE("VulkanImmediateDrawer: Failed to get a buffer for {} vertices",
|
||||
batch.vertex_count);
|
||||
return;
|
||||
}
|
||||
std::memcpy(vertex_buffer_mapping, batch.vertices, vertex_buffer_size);
|
||||
dfn.vkCmdBindVertexBuffers(current_command_buffer_, 0, 1, &vertex_buffer,
|
||||
dfn.vkCmdBindVertexBuffers(draw_command_buffer, 0, 1, &vertex_buffer,
|
||||
&vertex_buffer_offset);
|
||||
|
||||
// Bind the indices.
|
||||
@@ -292,15 +295,15 @@ void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
|
||||
VkBuffer index_buffer;
|
||||
VkDeviceSize index_buffer_offset;
|
||||
void* index_buffer_mapping = vertex_buffer_pool_->Request(
|
||||
submission_current, index_buffer_size, sizeof(uint16_t), index_buffer,
|
||||
index_buffer_offset);
|
||||
last_paint_submission_index_, index_buffer_size, sizeof(uint16_t),
|
||||
index_buffer, index_buffer_offset);
|
||||
if (!index_buffer_mapping) {
|
||||
XELOGE("Failed to get a buffer for {} indices in the immediate drawer",
|
||||
XELOGE("VulkanImmediateDrawer: Failed to get a buffer for {} indices",
|
||||
batch.index_count);
|
||||
return;
|
||||
}
|
||||
std::memcpy(index_buffer_mapping, batch.indices, index_buffer_size);
|
||||
dfn.vkCmdBindIndexBuffer(current_command_buffer_, index_buffer,
|
||||
dfn.vkCmdBindIndexBuffer(draw_command_buffer, index_buffer,
|
||||
index_buffer_offset, VK_INDEX_TYPE_UINT16);
|
||||
}
|
||||
|
||||
@@ -309,40 +312,18 @@ void VulkanImmediateDrawer::BeginDrawBatch(const ImmediateDrawBatch& batch) {
|
||||
|
||||
void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||
if (!batch_open_) {
|
||||
// No surface, or failed to create the pipelines, or could be an error while
|
||||
// obtaining the vertex and index buffers.
|
||||
// Could have been an error while obtaining the vertex and index buffers,
|
||||
// for instance.
|
||||
return;
|
||||
}
|
||||
|
||||
const VulkanProvider::DeviceFunctions& dfn =
|
||||
context_.GetVulkanProvider().dfn();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
const VulkanUIDrawContext& vulkan_ui_draw_context =
|
||||
*static_cast<const VulkanUIDrawContext*>(ui_draw_context());
|
||||
VkCommandBuffer draw_command_buffer =
|
||||
vulkan_ui_draw_context.draw_command_buffer();
|
||||
|
||||
// Set the scissor rectangle if enabled.
|
||||
VkRect2D scissor;
|
||||
if (draw.scissor) {
|
||||
scissor.offset.x = draw.scissor_rect[0];
|
||||
scissor.offset.y = current_render_target_extent_.height -
|
||||
(draw.scissor_rect[1] + draw.scissor_rect[3]);
|
||||
scissor.extent.width = std::max(draw.scissor_rect[2], 0);
|
||||
scissor.extent.height = std::max(draw.scissor_rect[3], 0);
|
||||
} else {
|
||||
scissor.offset.x = 0;
|
||||
scissor.offset.y = 0;
|
||||
scissor.extent = current_render_target_extent_;
|
||||
}
|
||||
if (!scissor.extent.width || !scissor.extent.height) {
|
||||
// Nothing is visible (used as the default current_scissor_ value also).
|
||||
return;
|
||||
}
|
||||
if (current_scissor_.offset.x != scissor.offset.x ||
|
||||
current_scissor_.offset.y != scissor.offset.y ||
|
||||
current_scissor_.extent.width != scissor.extent.width ||
|
||||
current_scissor_.extent.height != scissor.extent.height) {
|
||||
current_scissor_ = scissor;
|
||||
dfn.vkCmdSetScissor(current_command_buffer_, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
// Bind the pipeline for the current primitive type.
|
||||
// Get the pipeline for the current primitive type.
|
||||
VkPipeline pipeline;
|
||||
switch (draw.primitive_type) {
|
||||
case ImmediatePrimitiveType::kLines:
|
||||
@@ -355,10 +336,35 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||
assert_unhandled_case(draw.primitive_type);
|
||||
return;
|
||||
}
|
||||
if (pipeline == VK_NULL_HANDLE) {
|
||||
// Failed to create the pipeline.
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the scissor rectangle if enabled.
|
||||
VkRect2D scissor;
|
||||
uint32_t scissor_left, scissor_top;
|
||||
if (!ScissorToRenderTarget(draw, scissor_left, scissor_top,
|
||||
scissor.extent.width, scissor.extent.height)) {
|
||||
// Nothing is visible (zero area is used as the default current_scissor_
|
||||
// value also).
|
||||
return;
|
||||
}
|
||||
scissor.offset.x = int32_t(scissor_left);
|
||||
scissor.offset.y = int32_t(scissor_top);
|
||||
if (current_scissor_.offset.x != scissor.offset.x ||
|
||||
current_scissor_.offset.y != scissor.offset.y ||
|
||||
current_scissor_.extent.width != scissor.extent.width ||
|
||||
current_scissor_.extent.height != scissor.extent.height) {
|
||||
current_scissor_ = scissor;
|
||||
dfn.vkCmdSetScissor(draw_command_buffer, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
// Bind the pipeline for the primitive type if the scissor is not empty.
|
||||
if (current_pipeline_ != pipeline) {
|
||||
current_pipeline_ = pipeline;
|
||||
dfn.vkCmdBindPipeline(current_command_buffer_,
|
||||
VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
dfn.vkCmdBindPipeline(draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline);
|
||||
}
|
||||
|
||||
// Bind the texture.
|
||||
@@ -367,7 +373,7 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||
static_cast<VulkanImmediateTexture*>(draw.texture);
|
||||
if (texture && texture->immediate_drawer_ == this) {
|
||||
texture_descriptor_index = texture->resource_.descriptor_index;
|
||||
texture->last_usage_submission_ = context_.swap_submission_current();
|
||||
texture->last_usage_submission_ = last_paint_submission_index_;
|
||||
} else {
|
||||
texture_descriptor_index = white_texture_.descriptor_index;
|
||||
}
|
||||
@@ -376,16 +382,16 @@ void VulkanImmediateDrawer::Draw(const ImmediateDraw& draw) {
|
||||
VkDescriptorSet texture_descriptor_set =
|
||||
GetTextureDescriptor(texture_descriptor_index);
|
||||
dfn.vkCmdBindDescriptorSets(
|
||||
current_command_buffer_, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
pipeline_layout_, 0, 1, &texture_descriptor_set, 0, nullptr);
|
||||
draw_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout_,
|
||||
0, 1, &texture_descriptor_set, 0, nullptr);
|
||||
}
|
||||
|
||||
// Draw.
|
||||
if (batch_has_index_buffer_) {
|
||||
dfn.vkCmdDrawIndexed(current_command_buffer_, draw.count, 1,
|
||||
draw.index_offset, draw.base_vertex, 0);
|
||||
dfn.vkCmdDrawIndexed(draw_command_buffer, draw.count, 1, draw.index_offset,
|
||||
draw.base_vertex, 0);
|
||||
} else {
|
||||
dfn.vkCmdDraw(current_command_buffer_, draw.count, 1, draw.base_vertex, 0);
|
||||
dfn.vkCmdDraw(draw_command_buffer, draw.count, 1, draw.base_vertex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,21 +399,18 @@ void VulkanImmediateDrawer::EndDrawBatch() { batch_open_ = false; }
|
||||
|
||||
void VulkanImmediateDrawer::End() {
|
||||
assert_false(batch_open_);
|
||||
if (current_command_buffer_ == VK_NULL_HANDLE) {
|
||||
// Didn't draw anything because the of some issue or surface not being
|
||||
// available.
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy textures.
|
||||
// Upload textures.
|
||||
if (!texture_uploads_pending_.empty()) {
|
||||
VulkanPresenter& vulkan_presenter =
|
||||
*static_cast<VulkanPresenter*>(presenter());
|
||||
VkCommandBuffer setup_command_buffer =
|
||||
context_.AcquireSwapSetupCommandBuffer();
|
||||
vulkan_presenter.AcquireUISetupCommandBufferFromUIThread();
|
||||
if (setup_command_buffer != VK_NULL_HANDLE) {
|
||||
const VulkanProvider::DeviceFunctions& dfn =
|
||||
context_.GetVulkanProvider().dfn();
|
||||
size_t texture_uploads_pending_count = texture_uploads_pending_.size();
|
||||
uint64_t submission_current = context_.swap_submission_current();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
const VulkanUIDrawContext& vulkan_ui_draw_context =
|
||||
*static_cast<const VulkanUIDrawContext*>(ui_draw_context());
|
||||
|
||||
// Transition to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL.
|
||||
std::vector<VkImageMemoryBarrier> image_memory_barriers;
|
||||
@@ -464,9 +467,10 @@ void VulkanImmediateDrawer::End() {
|
||||
pending_texture_upload.buffer;
|
||||
submitted_texture_upload_buffer.buffer_memory =
|
||||
pending_texture_upload.buffer_memory;
|
||||
submitted_texture_upload_buffer.submission_index = submission_current;
|
||||
submitted_texture_upload_buffer.submission_index =
|
||||
last_paint_submission_index_;
|
||||
} else {
|
||||
// Clearing (initializing the empty image).
|
||||
// Clearing (initializing the special empty image).
|
||||
VkClearColorValue white_clear_value;
|
||||
white_clear_value.float32[0] = 1.0f;
|
||||
white_clear_value.float32[1] = 1.0f;
|
||||
@@ -490,7 +494,7 @@ void VulkanImmediateDrawer::End() {
|
||||
|
||||
if (pending_texture_upload.texture) {
|
||||
pending_texture_upload.texture->last_usage_submission_ =
|
||||
submission_current;
|
||||
last_paint_submission_index_;
|
||||
pending_texture_upload.texture->pending_upload_index_ = SIZE_MAX;
|
||||
}
|
||||
}
|
||||
@@ -504,54 +508,90 @@ void VulkanImmediateDrawer::End() {
|
||||
}
|
||||
|
||||
vertex_buffer_pool_->FlushWrites();
|
||||
current_command_buffer_ = VK_NULL_HANDLE;
|
||||
|
||||
ImmediateDrawer::End();
|
||||
}
|
||||
|
||||
bool VulkanImmediateDrawer::EnsurePipelinesCreated() {
|
||||
VkFormat swap_surface_format = context_.swap_surface_format().format;
|
||||
if (swap_surface_format == pipeline_framebuffer_format_) {
|
||||
void VulkanImmediateDrawer::OnLeavePresenter() {
|
||||
// Leaving the presenter's submission timeline - await GPU usage completion of
|
||||
// all draws and texture uploads (which happen before draws) and reset
|
||||
// submission indices.
|
||||
VulkanPresenter& vulkan_presenter =
|
||||
*static_cast<VulkanPresenter*>(presenter());
|
||||
vulkan_presenter.AwaitUISubmissionCompletionFromUIThread(
|
||||
last_paint_submission_index_);
|
||||
|
||||
for (VulkanImmediateTexture* texture : textures_) {
|
||||
texture->last_usage_submission_ = 0;
|
||||
}
|
||||
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
for (SubmittedTextureUploadBuffer& submitted_texture_upload_buffer :
|
||||
texture_upload_buffers_submitted_) {
|
||||
dfn.vkDestroyBuffer(device, submitted_texture_upload_buffer.buffer,
|
||||
nullptr);
|
||||
dfn.vkFreeMemory(device, submitted_texture_upload_buffer.buffer_memory,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
vertex_buffer_pool_->ChangeSubmissionTimeline();
|
||||
|
||||
last_paint_submission_index_ = 0;
|
||||
last_completed_submission_index_ = 0;
|
||||
}
|
||||
|
||||
bool VulkanImmediateDrawer::EnsurePipelinesCreatedForCurrentRenderPass() {
|
||||
const VulkanUIDrawContext& vulkan_ui_draw_context =
|
||||
*static_cast<const VulkanUIDrawContext*>(ui_draw_context());
|
||||
VkFormat render_pass_format = vulkan_ui_draw_context.render_pass_format();
|
||||
|
||||
if (render_pass_format == pipeline_framebuffer_format_) {
|
||||
// Either created, or failed to create once (don't try to create every
|
||||
// frame).
|
||||
return pipeline_triangle_ != VK_NULL_HANDLE &&
|
||||
pipeline_line_ != VK_NULL_HANDLE;
|
||||
}
|
||||
VkRenderPass swap_render_pass = context_.swap_render_pass();
|
||||
if (swap_surface_format == VK_FORMAT_UNDEFINED ||
|
||||
swap_render_pass == VK_NULL_HANDLE) {
|
||||
// Not ready yet.
|
||||
return false;
|
||||
|
||||
if (last_paint_submission_index_ && pipeline_triangle_ != VK_NULL_HANDLE &&
|
||||
pipeline_line_ != VK_NULL_HANDLE) {
|
||||
// Make sure it's safe to delete the pipelines.
|
||||
VulkanPresenter& vulkan_presenter =
|
||||
*static_cast<VulkanPresenter*>(presenter());
|
||||
vulkan_presenter.AwaitUISubmissionCompletionFromUIThread(
|
||||
last_paint_submission_index_);
|
||||
}
|
||||
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
// Safe to destroy the pipelines now - if the render pass was recreated,
|
||||
// completion of its usage has already been awaited.
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_line_);
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device, pipeline_triangle_);
|
||||
// If creation fails now, don't try to create every frame.
|
||||
pipeline_framebuffer_format_ = swap_surface_format;
|
||||
pipeline_framebuffer_format_ = render_pass_format;
|
||||
|
||||
// Triangle pipeline.
|
||||
|
||||
VkPipelineShaderStageCreateInfo stages[2] = {};
|
||||
stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
stages[0].module = util::CreateShaderModule(provider, immediate_vert,
|
||||
stages[0].module = util::CreateShaderModule(provider_, immediate_vert,
|
||||
sizeof(immediate_vert));
|
||||
if (stages[0].module == VK_NULL_HANDLE) {
|
||||
XELOGE("Failed to create the immediate drawer Vulkan vertex shader module");
|
||||
XELOGE("VulkanImmediateDrawer: Failed to create the vertex shader module");
|
||||
return false;
|
||||
}
|
||||
stages[0].pName = "main";
|
||||
stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
stages[1].module = util::CreateShaderModule(provider, immediate_frag,
|
||||
stages[1].module = util::CreateShaderModule(provider_, immediate_frag,
|
||||
sizeof(immediate_frag));
|
||||
if (stages[1].module == VK_NULL_HANDLE) {
|
||||
XELOGE(
|
||||
"Failed to create the immediate drawer Vulkan fragment shader module");
|
||||
"VulkanImmediateDrawer: Failed to create the fragment shader module");
|
||||
dfn.vkDestroyShaderModule(device, stages[0].module, nullptr);
|
||||
return false;
|
||||
}
|
||||
@@ -621,26 +661,17 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreated() {
|
||||
color_blend_attachment_state.dstColorBlendFactor =
|
||||
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
// Don't change alpha (always 1).
|
||||
color_blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
color_blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
color_blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
color_blend_attachment_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
|
||||
VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT;
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state;
|
||||
color_blend_attachment_state.colorWriteMask =
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
VkPipelineColorBlendStateCreateInfo color_blend_state = {};
|
||||
color_blend_state.sType =
|
||||
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
color_blend_state.pNext = nullptr;
|
||||
color_blend_state.flags = 0;
|
||||
color_blend_state.logicOpEnable = VK_FALSE;
|
||||
color_blend_state.logicOp = VK_LOGIC_OP_NO_OP;
|
||||
color_blend_state.attachmentCount = 1;
|
||||
color_blend_state.pAttachments = &color_blend_attachment_state;
|
||||
color_blend_state.blendConstants[0] = 1.0f;
|
||||
color_blend_state.blendConstants[1] = 1.0f;
|
||||
color_blend_state.blendConstants[2] = 1.0f;
|
||||
color_blend_state.blendConstants[3] = 1.0f;
|
||||
|
||||
static const VkDynamicState dynamic_states[] = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
@@ -669,15 +700,15 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreated() {
|
||||
pipeline_create_info.pColorBlendState = &color_blend_state;
|
||||
pipeline_create_info.pDynamicState = &dynamic_state;
|
||||
pipeline_create_info.layout = pipeline_layout_;
|
||||
pipeline_create_info.renderPass = swap_render_pass;
|
||||
pipeline_create_info.renderPass = vulkan_ui_draw_context.render_pass();
|
||||
pipeline_create_info.subpass = 0;
|
||||
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
|
||||
pipeline_create_info.basePipelineIndex = -1;
|
||||
pipeline_create_info.basePipelineIndex = UINT32_MAX;
|
||||
if (dfn.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1,
|
||||
&pipeline_create_info, nullptr,
|
||||
&pipeline_triangle_) != VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to create the immediate drawer triangle list Vulkan pipeline");
|
||||
"VulkanImmediateDrawer: Failed to create the triangle list pipeline");
|
||||
dfn.vkDestroyShaderModule(device, stages[1].module, nullptr);
|
||||
dfn.vkDestroyShaderModule(device, stages[0].module, nullptr);
|
||||
return false;
|
||||
@@ -696,7 +727,7 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreated() {
|
||||
dfn.vkDestroyShaderModule(device, stages[1].module, nullptr);
|
||||
dfn.vkDestroyShaderModule(device, stages[0].module, nullptr);
|
||||
if (pipeline_line_create_result != VK_SUCCESS) {
|
||||
XELOGE("Failed to create the immediate drawer line list Vulkan pipeline");
|
||||
XELOGE("VulkanImmediateDrawer: Failed to create the line list pipeline");
|
||||
dfn.vkDestroyPipeline(device, pipeline_triangle_, nullptr);
|
||||
pipeline_triangle_ = VK_NULL_HANDLE;
|
||||
return false;
|
||||
@@ -719,9 +750,8 @@ uint32_t VulkanImmediateDrawer::AllocateTextureDescriptor() {
|
||||
return (pool->index << 6) | local_index;
|
||||
}
|
||||
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
VkDescriptorSetAllocateInfo allocate_info;
|
||||
allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
@@ -770,7 +800,7 @@ uint32_t VulkanImmediateDrawer::AllocateTextureDescriptor() {
|
||||
if (dfn.vkCreateDescriptorPool(device, &descriptor_pool_create_info, nullptr,
|
||||
&descriptor_pool) != VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to create an immediate drawer Vulkan combined image sampler "
|
||||
"VulkanImmediateDrawer: Failed to create a combined image sampler "
|
||||
"descriptor pool with {} descriptors",
|
||||
TextureDescriptorPool::kDescriptorCount);
|
||||
return UINT32_MAX;
|
||||
@@ -780,7 +810,7 @@ uint32_t VulkanImmediateDrawer::AllocateTextureDescriptor() {
|
||||
if (dfn.vkAllocateDescriptorSets(device, &allocate_info, &descriptor_set) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to allocate an immediate drawer Vulkan combined image sampler "
|
||||
"VulkanImmediateDrawer: Failed to allocate a combined image sampler "
|
||||
"descriptor");
|
||||
dfn.vkDestroyDescriptorPool(device, descriptor_pool, nullptr);
|
||||
return UINT32_MAX;
|
||||
@@ -831,9 +861,8 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
bool is_repeated, const uint8_t* data,
|
||||
VulkanImmediateTexture::Resource& resource_out,
|
||||
size_t& pending_upload_index_out) {
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
// Create the image and the descriptor.
|
||||
|
||||
@@ -857,55 +886,14 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
image_create_info.pQueueFamilyIndices = nullptr;
|
||||
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
VkImage image;
|
||||
if (dfn.vkCreateImage(device, &image_create_info, nullptr, &image) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to create a Vulkan image for a {}x{} immediate drawer texture",
|
||||
width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo image_memory_allocate_info;
|
||||
VkMemoryRequirements image_memory_requirements;
|
||||
dfn.vkGetImageMemoryRequirements(device, image, &image_memory_requirements);
|
||||
if (!xe::bit_scan_forward(image_memory_requirements.memoryTypeBits &
|
||||
provider.memory_types_device_local(),
|
||||
&image_memory_allocate_info.memoryTypeIndex)) {
|
||||
XELOGE(
|
||||
"Failed to get a device-local memory type for a {}x{} immediate "
|
||||
"drawer Vulkan image",
|
||||
width, height);
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
return false;
|
||||
}
|
||||
image_memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
VkMemoryDedicatedAllocateInfoKHR image_memory_dedicated_allocate_info;
|
||||
if (provider.device_extensions().khr_dedicated_allocation) {
|
||||
image_memory_dedicated_allocate_info.sType =
|
||||
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
|
||||
image_memory_dedicated_allocate_info.pNext = nullptr;
|
||||
image_memory_dedicated_allocate_info.image = image;
|
||||
image_memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE;
|
||||
image_memory_allocate_info.pNext = &image_memory_dedicated_allocate_info;
|
||||
} else {
|
||||
image_memory_allocate_info.pNext = nullptr;
|
||||
}
|
||||
image_memory_allocate_info.allocationSize = image_memory_requirements.size;
|
||||
VkDeviceMemory image_memory;
|
||||
if (dfn.vkAllocateMemory(device, &image_memory_allocate_info, nullptr,
|
||||
&image_memory) != VK_SUCCESS) {
|
||||
if (!util::CreateDedicatedAllocationImage(provider_, image_create_info,
|
||||
util::MemoryPurpose::kDeviceLocal,
|
||||
image, image_memory)) {
|
||||
XELOGE(
|
||||
"Failed to allocate memory for a {}x{} immediate drawer Vulkan "
|
||||
"image",
|
||||
"VulkanImmediateDrawer: Failed to create an image with dedicated "
|
||||
"memory for a {}x{} texture",
|
||||
width, height);
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
return false;
|
||||
}
|
||||
if (dfn.vkBindImageMemory(device, image, image_memory, 0) != VK_SUCCESS) {
|
||||
XELOGE("Failed to bind memory to a {}x{} immediate drawer Vulkan image",
|
||||
width, height);
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
dfn.vkFreeMemory(device, image_memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -928,7 +916,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
if (dfn.vkCreateImageView(device, &image_view_create_info, nullptr,
|
||||
&image_view) != VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to create an image view for a {}x{} immediate drawer Vulkan "
|
||||
"VulkanImmediateDrawer: Failed to create an image view for a {}x{} "
|
||||
"image",
|
||||
width, height);
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
@@ -939,9 +927,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
uint32_t descriptor_index = AllocateTextureDescriptor();
|
||||
if (descriptor_index == UINT32_MAX) {
|
||||
XELOGE(
|
||||
"Failed to allocate a Vulkan descriptor for a {}x{} immediate drawer "
|
||||
"texture",
|
||||
width, height);
|
||||
"VulkanImmediateDrawer: Failed to allocate a descriptor for an image");
|
||||
dfn.vkDestroyImageView(device, image_view, nullptr);
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
dfn.vkFreeMemory(device, image_memory, nullptr);
|
||||
@@ -956,7 +942,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
host_sampler = is_repeated ? VulkanProvider::HostSampler::kNearestRepeat
|
||||
: VulkanProvider::HostSampler::kNearestClamp;
|
||||
}
|
||||
descriptor_image_info.sampler = provider.GetHostSampler(host_sampler);
|
||||
descriptor_image_info.sampler = provider_.GetHostSampler(host_sampler);
|
||||
descriptor_image_info.imageView = image_view;
|
||||
descriptor_image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
VkWriteDescriptorSet descriptor_write;
|
||||
@@ -982,12 +968,12 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
size_t data_size = sizeof(uint32_t) * width * height;
|
||||
uint32_t upload_buffer_memory_type;
|
||||
if (!util::CreateDedicatedAllocationBuffer(
|
||||
provider, VkDeviceSize(data_size), VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
||||
util::MemoryPurpose::kUpload, upload_buffer, upload_buffer_memory,
|
||||
&upload_buffer_memory_type)) {
|
||||
provider_, VkDeviceSize(data_size),
|
||||
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, util::MemoryPurpose::kUpload,
|
||||
upload_buffer, upload_buffer_memory, &upload_buffer_memory_type)) {
|
||||
XELOGE(
|
||||
"Failed to create a Vulkan upload buffer for a {}x{} immediate "
|
||||
"drawer texture",
|
||||
"VulkanImmediateDrawer: Failed to create an upload buffer for a "
|
||||
"{}x{} image",
|
||||
width, height);
|
||||
FreeTextureDescriptor(descriptor_index);
|
||||
dfn.vkDestroyImageView(device, image_view, nullptr);
|
||||
@@ -999,8 +985,8 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
if (dfn.vkMapMemory(device, upload_buffer_memory, 0, VK_WHOLE_SIZE, 0,
|
||||
&upload_buffer_mapping) != VK_SUCCESS) {
|
||||
XELOGE(
|
||||
"Failed to map Vulkan upload buffer memory for a {}x{} immediate "
|
||||
"drawer texture",
|
||||
"VulkanImmediateDrawer: Failed to map upload buffer memory for a "
|
||||
"{}x{} image",
|
||||
width, height);
|
||||
dfn.vkDestroyBuffer(device, upload_buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, upload_buffer_memory, nullptr);
|
||||
@@ -1011,7 +997,7 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
return false;
|
||||
}
|
||||
std::memcpy(upload_buffer_mapping, data, data_size);
|
||||
util::FlushMappedMemoryRange(provider, upload_buffer_memory,
|
||||
util::FlushMappedMemoryRange(provider_, upload_buffer_memory,
|
||||
upload_buffer_memory_type);
|
||||
dfn.vkUnmapMemory(device, upload_buffer_memory);
|
||||
}
|
||||
@@ -1038,9 +1024,8 @@ bool VulkanImmediateDrawer::CreateTextureResource(
|
||||
void VulkanImmediateDrawer::DestroyTextureResource(
|
||||
VulkanImmediateTexture::Resource& resource) {
|
||||
FreeTextureDescriptor(resource.descriptor_index);
|
||||
const VulkanProvider& provider = context_.GetVulkanProvider();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
dfn.vkDestroyImageView(device, resource.image_view, nullptr);
|
||||
dfn.vkDestroyImage(device, resource.image, nullptr);
|
||||
dfn.vkFreeMemory(device, resource.memory, nullptr);
|
||||
@@ -1070,7 +1055,7 @@ void VulkanImmediateDrawer::OnImmediateTextureDestroyed(
|
||||
textures_.pop_back();
|
||||
|
||||
// Destroy immediately or queue for destruction if in use.
|
||||
if (texture.last_usage_submission_ > context_.swap_submission_completed()) {
|
||||
if (texture.last_usage_submission_ > last_completed_submission_index_) {
|
||||
textures_deleted_.emplace_back(
|
||||
std::make_pair(texture.resource_, texture.last_usage_submission_));
|
||||
} else {
|
||||
|
||||
@@ -23,15 +23,19 @@ namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
class VulkanContext;
|
||||
|
||||
class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||
public:
|
||||
VulkanImmediateDrawer(VulkanContext& graphics_context);
|
||||
~VulkanImmediateDrawer() override;
|
||||
static std::unique_ptr<VulkanImmediateDrawer> Create(
|
||||
const VulkanProvider& provider) {
|
||||
auto immediate_drawer = std::unique_ptr<VulkanImmediateDrawer>(
|
||||
new VulkanImmediateDrawer(provider));
|
||||
if (!immediate_drawer->Initialize()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::move(immediate_drawer);
|
||||
}
|
||||
|
||||
bool Initialize();
|
||||
void Shutdown();
|
||||
~VulkanImmediateDrawer();
|
||||
|
||||
std::unique_ptr<ImmediateTexture> CreateTexture(uint32_t width,
|
||||
uint32_t height,
|
||||
@@ -39,16 +43,20 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||
bool is_repeated,
|
||||
const uint8_t* data) override;
|
||||
|
||||
void Begin(int render_target_width, int render_target_height) override;
|
||||
void Begin(UIDrawContext& ui_draw_context, float coordinate_space_width,
|
||||
float coordinate_space_height) override;
|
||||
void BeginDrawBatch(const ImmediateDrawBatch& batch) override;
|
||||
void Draw(const ImmediateDraw& draw) override;
|
||||
void EndDrawBatch() override;
|
||||
void End() override;
|
||||
|
||||
protected:
|
||||
void OnLeavePresenter() override;
|
||||
|
||||
private:
|
||||
struct PushConstants {
|
||||
struct Vertex {
|
||||
float viewport_size_inv[2];
|
||||
float coordinate_space_size_inv[2];
|
||||
} vertex;
|
||||
};
|
||||
|
||||
@@ -88,7 +96,10 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||
TextureDescriptorPool* recycled_next;
|
||||
};
|
||||
|
||||
bool EnsurePipelinesCreated();
|
||||
VulkanImmediateDrawer(const VulkanProvider& provider) : provider_(provider) {}
|
||||
bool Initialize();
|
||||
|
||||
bool EnsurePipelinesCreatedForCurrentRenderPass();
|
||||
|
||||
// Allocates a combined image sampler in a pool and returns its index, or
|
||||
// UINT32_MAX in case of failure.
|
||||
@@ -106,7 +117,7 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||
void DestroyTextureResource(VulkanImmediateTexture::Resource& resource);
|
||||
void OnImmediateTextureDestroyed(VulkanImmediateTexture& texture);
|
||||
|
||||
VulkanContext& context_;
|
||||
const VulkanProvider& provider_;
|
||||
|
||||
// Combined image sampler pools for textures.
|
||||
VkDescriptorSetLayout texture_descriptor_set_layout_;
|
||||
@@ -137,14 +148,21 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||
std::vector<std::pair<VulkanImmediateTexture::Resource, uint64_t>>
|
||||
textures_deleted_;
|
||||
|
||||
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
|
||||
|
||||
std::unique_ptr<VulkanUploadBufferPool> vertex_buffer_pool_;
|
||||
|
||||
VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
|
||||
|
||||
VkFormat pipeline_framebuffer_format_ = VK_FORMAT_UNDEFINED;
|
||||
VkPipeline pipeline_triangle_ = VK_NULL_HANDLE;
|
||||
VkPipeline pipeline_line_ = VK_NULL_HANDLE;
|
||||
|
||||
// The submission index within the current Begin (or the last, if outside
|
||||
// one).
|
||||
uint64_t last_paint_submission_index_ = 0;
|
||||
// Completed submission index as of the latest Begin, to coarsely skip delayed
|
||||
// texture deletion.
|
||||
uint64_t last_completed_submission_index_ = 0;
|
||||
|
||||
VkCommandBuffer current_command_buffer_ = VK_NULL_HANDLE;
|
||||
VkExtent2D current_render_target_extent_;
|
||||
VkRect2D current_scissor_;
|
||||
|
||||
2548
src/xenia/ui/vulkan/vulkan_presenter.cc
Normal file
2548
src/xenia/ui/vulkan/vulkan_presenter.cc
Normal file
File diff suppressed because it is too large
Load Diff
518
src/xenia/ui/vulkan/vulkan_presenter.h
Normal file
518
src/xenia/ui/vulkan/vulkan_presenter.h
Normal file
@@ -0,0 +1,518 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_VULKAN_VULKAN_PRESENTER_H_
|
||||
#define XENIA_UI_VULKAN_VULKAN_PRESENTER_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/ui/presenter.h"
|
||||
#include "xenia/ui/surface.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
#include "xenia/ui/vulkan/vulkan_submission_tracker.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
class VulkanUIDrawContext final : public UIDrawContext {
|
||||
public:
|
||||
VulkanUIDrawContext(Presenter& presenter, uint32_t render_target_width,
|
||||
uint32_t render_target_height,
|
||||
VkCommandBuffer draw_command_buffer,
|
||||
uint64_t submission_index_current,
|
||||
uint64_t submission_index_completed,
|
||||
VkRenderPass render_pass, VkFormat render_pass_format)
|
||||
: UIDrawContext(presenter, render_target_width, render_target_height),
|
||||
draw_command_buffer_(draw_command_buffer),
|
||||
submission_index_current_(submission_index_current),
|
||||
submission_index_completed_(submission_index_completed),
|
||||
render_pass_(render_pass),
|
||||
render_pass_format_(render_pass_format) {}
|
||||
|
||||
VkCommandBuffer draw_command_buffer() const { return draw_command_buffer_; }
|
||||
uint64_t submission_index_current() const {
|
||||
return submission_index_current_;
|
||||
}
|
||||
uint64_t submission_index_completed() const {
|
||||
return submission_index_completed_;
|
||||
}
|
||||
VkRenderPass render_pass() const { return render_pass_; }
|
||||
VkFormat render_pass_format() const { return render_pass_format_; }
|
||||
|
||||
private:
|
||||
VkCommandBuffer draw_command_buffer_;
|
||||
uint64_t submission_index_current_;
|
||||
uint64_t submission_index_completed_;
|
||||
// Has 1 subpass with a single render_pass_format_ attachment.
|
||||
VkRenderPass render_pass_;
|
||||
VkFormat render_pass_format_;
|
||||
};
|
||||
|
||||
class VulkanPresenter final : public Presenter {
|
||||
public:
|
||||
// Maximum number of different guest output image versions still potentially
|
||||
// considered alive that may be given to the refresher - this many instances
|
||||
// of dependent objects (such as framebuffers) may need to be kept by the
|
||||
// refresher across invocations (due to multiple-buffering of guest output
|
||||
// images inside the presenter, different versions may be given even every
|
||||
// invocation), to avoid recreation of dependent objects every frame.
|
||||
static constexpr size_t kMaxActiveGuestOutputImageVersions =
|
||||
kGuestOutputMailboxSize;
|
||||
|
||||
static constexpr VkFormat kGuestOutputFormat =
|
||||
VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||
// The guest output is expected to be acquired and released in this state by
|
||||
// the refresher. The exception is the first write to the current guest output
|
||||
// image - in this case, a barrier is only needed from
|
||||
// VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT without access. Also if the image is
|
||||
// being refreshed for the first time, it's in VK_IMAGE_LAYOUT_UNDEFINED (but
|
||||
// it's safe, and preferred, to transition it from VK_IMAGE_LAYOUT_UNDEFINED
|
||||
// when writing to it in general).
|
||||
static constexpr VkPipelineStageFlagBits kGuestOutputInternalStageMask =
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
static constexpr VkAccessFlags kGuestOutputInternalAccessMask =
|
||||
VK_ACCESS_SHADER_READ_BIT;
|
||||
static constexpr VkImageLayout kGuestOutputInternalLayout =
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
// The callback must use the graphics and compute queue 0 of the provider.
|
||||
class VulkanGuestOutputRefreshContext final
|
||||
: public GuestOutputRefreshContext {
|
||||
public:
|
||||
VulkanGuestOutputRefreshContext(bool& is_8bpc_out_ref, VkImage image,
|
||||
VkImageView image_view,
|
||||
uint64_t image_version,
|
||||
bool image_ever_written_previously)
|
||||
: GuestOutputRefreshContext(is_8bpc_out_ref),
|
||||
image_(image),
|
||||
image_view_(image_view),
|
||||
image_version_(image_version),
|
||||
image_ever_written_previously_(image_ever_written_previously) {}
|
||||
|
||||
// The format is kGuestOutputFormat.
|
||||
// Supports usage as a color attachment and as a sampled image, as well as
|
||||
// transfer source (but the reason of that is guest output capturing).
|
||||
VkImage image() const { return image_; }
|
||||
VkImageView image_view() const { return image_view_; }
|
||||
uint64_t image_version() const { return image_version_; }
|
||||
// Whether a proper barrier must be done to acquire the image.
|
||||
bool image_ever_written_previously() const {
|
||||
return image_ever_written_previously_;
|
||||
}
|
||||
|
||||
private:
|
||||
VkImage image_;
|
||||
VkImageView image_view_;
|
||||
uint64_t image_version_;
|
||||
bool image_ever_written_previously_;
|
||||
};
|
||||
|
||||
static std::unique_ptr<VulkanPresenter> Create(
|
||||
HostGpuLossCallback host_gpu_loss_callback, VulkanProvider& provider) {
|
||||
auto presenter = std::unique_ptr<VulkanPresenter>(
|
||||
new VulkanPresenter(host_gpu_loss_callback, provider));
|
||||
if (!presenter->InitializeSurfaceIndependent()) {
|
||||
return nullptr;
|
||||
}
|
||||
return presenter;
|
||||
}
|
||||
|
||||
~VulkanPresenter();
|
||||
|
||||
VulkanProvider& provider() const { return provider_; }
|
||||
|
||||
static Surface::TypeFlags GetSurfaceTypesSupportedByInstance(
|
||||
const VulkanProvider::InstanceExtensions& instance_extensions) {
|
||||
if (!instance_extensions.khr_surface) {
|
||||
return 0;
|
||||
}
|
||||
Surface::TypeFlags type_flags = 0;
|
||||
#if XE_PLATFORM_ANDROID
|
||||
if (instance_extensions.khr_android_surface) {
|
||||
type_flags |= Surface::kTypeFlag_AndroidNativeWindow;
|
||||
}
|
||||
#elif XE_PLATFORM_GNU_LINUX
|
||||
if (instance_extensions.khr_xcb_surface) {
|
||||
type_flags |= Surface::kTypeFlag_XcbWindow;
|
||||
}
|
||||
#elif XE_PLATFORM_WIN32
|
||||
if (instance_extensions.khr_win32_surface) {
|
||||
type_flags |= Surface::kTypeFlag_Win32Hwnd;
|
||||
}
|
||||
#endif
|
||||
return type_flags;
|
||||
}
|
||||
Surface::TypeFlags GetSupportedSurfaceTypes() const override;
|
||||
|
||||
bool CaptureGuestOutput(RawImage& image_out) override;
|
||||
|
||||
void AwaitUISubmissionCompletionFromUIThread(uint64_t submission_index) {
|
||||
ui_submission_tracker_.AwaitSubmissionCompletion(submission_index);
|
||||
}
|
||||
VkCommandBuffer AcquireUISetupCommandBufferFromUIThread();
|
||||
|
||||
protected:
|
||||
SurfacePaintConnectResult ConnectOrReconnectPaintingToSurfaceFromUIThread(
|
||||
Surface& new_surface, uint32_t new_surface_width,
|
||||
uint32_t new_surface_height, bool was_paintable,
|
||||
bool& is_vsync_implicit_out) override;
|
||||
void DisconnectPaintingFromSurfaceFromUIThreadImpl() override;
|
||||
|
||||
bool RefreshGuestOutputImpl(
|
||||
uint32_t mailbox_index, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height,
|
||||
std::function<bool(GuestOutputRefreshContext& context)> refresher,
|
||||
bool& is_8bpc_out_ref) override;
|
||||
|
||||
PaintResult PaintAndPresentImpl(bool execute_ui_drawers) override;
|
||||
|
||||
private:
|
||||
// Usable for both the guest output image itself and for intermediate images.
|
||||
class GuestOutputImage {
|
||||
public:
|
||||
static std::unique_ptr<GuestOutputImage> Create(
|
||||
const VulkanProvider& provider, uint32_t width, uint32_t height) {
|
||||
assert_not_zero(width);
|
||||
assert_not_zero(height);
|
||||
auto image = std::unique_ptr<GuestOutputImage>(
|
||||
new GuestOutputImage(provider, width, height));
|
||||
if (!image->Initialize()) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::move(image);
|
||||
}
|
||||
|
||||
GuestOutputImage(const GuestOutputImage& image) = delete;
|
||||
GuestOutputImage& operator=(const GuestOutputImage& image) = delete;
|
||||
~GuestOutputImage();
|
||||
|
||||
const VkExtent2D& extent() const { return extent_; }
|
||||
|
||||
VkImage image() const { return image_; }
|
||||
VkDeviceMemory memory() const { return memory_; }
|
||||
VkImageView view() const { return view_; }
|
||||
|
||||
private:
|
||||
GuestOutputImage(const VulkanProvider& provider, uint32_t width,
|
||||
uint32_t height)
|
||||
: provider_(provider) {
|
||||
extent_.width = width;
|
||||
extent_.height = height;
|
||||
}
|
||||
|
||||
bool Initialize();
|
||||
|
||||
const VulkanProvider& provider_;
|
||||
|
||||
VkExtent2D extent_;
|
||||
VkImage image_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory_ = VK_NULL_HANDLE;
|
||||
VkImageView view_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct GuestOutputImageInstance {
|
||||
// Refresher-side reference (painting has its own references for the purpose
|
||||
// of destruction only after painting is done on the GPU).
|
||||
std::shared_ptr<GuestOutputImage> image;
|
||||
uint64_t version = UINT64_MAX;
|
||||
uint64_t last_refresher_submission = 0;
|
||||
// For choosing the barrier stage and access mask and layout depending on
|
||||
// whether the image has previously been written. If an image is active
|
||||
// after a refresh, it can be assumed that this is true.
|
||||
bool ever_successfully_refreshed = false;
|
||||
|
||||
void SetToNewImage(const std::shared_ptr<GuestOutputImage>& new_image,
|
||||
uint64_t new_version) {
|
||||
image = new_image;
|
||||
version = new_version;
|
||||
last_refresher_submission = 0;
|
||||
ever_successfully_refreshed = false;
|
||||
}
|
||||
};
|
||||
|
||||
struct GuestOutputPaintRectangleConstants {
|
||||
union {
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
};
|
||||
float offset[2];
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
float size[2];
|
||||
};
|
||||
};
|
||||
|
||||
enum GuestOutputPaintPipelineLayoutIndex : size_t {
|
||||
kGuestOutputPaintPipelineLayoutIndexBilinear,
|
||||
kGuestOutputPaintPipelineLayoutIndexCasSharpen,
|
||||
kGuestOutputPaintPipelineLayoutIndexCasResample,
|
||||
kGuestOutputPaintPipelineLayoutIndexFsrEasu,
|
||||
kGuestOutputPaintPipelineLayoutIndexFsrRcas,
|
||||
|
||||
kGuestOutputPaintPipelineLayoutCount,
|
||||
};
|
||||
|
||||
static constexpr GuestOutputPaintPipelineLayoutIndex
|
||||
GetGuestOutputPaintPipelineLayoutIndex(GuestOutputPaintEffect effect) {
|
||||
switch (effect) {
|
||||
case GuestOutputPaintEffect::kBilinear:
|
||||
case GuestOutputPaintEffect::kBilinearDither:
|
||||
return kGuestOutputPaintPipelineLayoutIndexBilinear;
|
||||
case GuestOutputPaintEffect::kCasSharpen:
|
||||
case GuestOutputPaintEffect::kCasSharpenDither:
|
||||
return kGuestOutputPaintPipelineLayoutIndexCasSharpen;
|
||||
case GuestOutputPaintEffect::kCasResample:
|
||||
case GuestOutputPaintEffect::kCasResampleDither:
|
||||
return kGuestOutputPaintPipelineLayoutIndexCasResample;
|
||||
case GuestOutputPaintEffect::kFsrEasu:
|
||||
return kGuestOutputPaintPipelineLayoutIndexFsrEasu;
|
||||
case GuestOutputPaintEffect::kFsrRcas:
|
||||
case GuestOutputPaintEffect::kFsrRcasDither:
|
||||
return kGuestOutputPaintPipelineLayoutIndexFsrRcas;
|
||||
default:
|
||||
assert_unhandled_case(effect);
|
||||
return kGuestOutputPaintPipelineLayoutCount;
|
||||
}
|
||||
}
|
||||
|
||||
struct PaintContext {
|
||||
class Submission {
|
||||
public:
|
||||
static std::unique_ptr<Submission> Create(
|
||||
const VulkanProvider& provider) {
|
||||
auto submission = std::unique_ptr<Submission>(new Submission(provider));
|
||||
if (!submission->Initialize()) {
|
||||
return nullptr;
|
||||
}
|
||||
return submission;
|
||||
}
|
||||
|
||||
Submission(const Submission& submission) = delete;
|
||||
Submission& operator=(const Submission& submission) = delete;
|
||||
~Submission();
|
||||
|
||||
VkSemaphore acquire_semaphore() const { return acquire_semaphore_; }
|
||||
VkSemaphore present_semaphore() const { return present_semaphore_; }
|
||||
VkCommandPool draw_command_pool() const { return draw_command_pool_; }
|
||||
VkCommandBuffer draw_command_buffer() const {
|
||||
return draw_command_buffer_;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Submission(const VulkanProvider& provider)
|
||||
: provider_(provider) {}
|
||||
bool Initialize();
|
||||
|
||||
const VulkanProvider& provider_;
|
||||
VkSemaphore acquire_semaphore_ = VK_NULL_HANDLE;
|
||||
VkSemaphore present_semaphore_ = VK_NULL_HANDLE;
|
||||
VkCommandPool draw_command_pool_ = VK_NULL_HANDLE;
|
||||
VkCommandBuffer draw_command_buffer_ = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
static constexpr uint32_t kSubmissionCount = 3;
|
||||
|
||||
struct GuestOutputPaintPipeline {
|
||||
// Created on initialization.
|
||||
VkPipeline intermediate_pipeline = VK_NULL_HANDLE;
|
||||
// Created during guest output painting (after awaiting the last guest
|
||||
// output paint if outdated and needs to be recreated), when needed, for
|
||||
// the up-to-date render pass that draws to the swapchain with the actual
|
||||
// image format.
|
||||
VkPipeline swapchain_pipeline = VK_NULL_HANDLE;
|
||||
VkFormat swapchain_format = VK_FORMAT_UNDEFINED;
|
||||
};
|
||||
|
||||
enum GuestOutputDescriptorSet : uint32_t {
|
||||
kGuestOutputDescriptorSetGuestOutput0Sampled,
|
||||
|
||||
kGuestOutputDescriptorSetIntermediate0Sampled =
|
||||
kGuestOutputDescriptorSetGuestOutput0Sampled +
|
||||
kGuestOutputMailboxSize,
|
||||
|
||||
kGuestOutputDescriptorSetCount =
|
||||
kGuestOutputDescriptorSetIntermediate0Sampled +
|
||||
kMaxGuestOutputPaintEffects - 1,
|
||||
};
|
||||
|
||||
struct UISetupCommandBuffer {
|
||||
UISetupCommandBuffer(VkCommandPool command_pool,
|
||||
VkCommandBuffer command_buffer,
|
||||
uint64_t last_usage_submission_index = 0)
|
||||
: command_pool(command_pool),
|
||||
command_buffer(command_buffer),
|
||||
last_usage_submission_index(last_usage_submission_index) {}
|
||||
|
||||
VkCommandPool command_pool;
|
||||
VkCommandBuffer command_buffer;
|
||||
uint64_t last_usage_submission_index;
|
||||
};
|
||||
|
||||
struct SwapchainFramebuffer {
|
||||
SwapchainFramebuffer(VkImageView image_view, VkFramebuffer framebuffer)
|
||||
: image_view(image_view), framebuffer(framebuffer) {}
|
||||
|
||||
VkImageView image_view;
|
||||
VkFramebuffer framebuffer;
|
||||
};
|
||||
|
||||
explicit PaintContext(VulkanProvider& provider)
|
||||
: provider(provider), submission_tracker(provider) {}
|
||||
PaintContext(const PaintContext& paint_context) = delete;
|
||||
PaintContext& operator=(const PaintContext& paint_context) = delete;
|
||||
|
||||
// The old swapchain, if passed, should be assumed to be retired after this
|
||||
// call (though it may fail before the vkCreateSwapchainKHR that will
|
||||
// technically retire it, so it will be in an undefined state), and needs to
|
||||
// be destroyed externally no matter what the result is.
|
||||
static VkSwapchainKHR CreateSwapchainForVulkanSurface(
|
||||
const VulkanProvider& provider, VkSurfaceKHR surface, uint32_t width,
|
||||
uint32_t height, VkSwapchainKHR old_swapchain,
|
||||
uint32_t& present_queue_family_out, VkFormat& image_format_out,
|
||||
VkExtent2D& image_extent_out, bool& is_fifo_out,
|
||||
bool& ui_surface_unusable_out);
|
||||
|
||||
// Destroys the swapchain and its derivatives, nulls `swapchain` and returns
|
||||
// the original swapchain object, if it existed, for use as oldSwapchain if
|
||||
// needed and for destruction.
|
||||
VkSwapchainKHR PrepareForSwapchainRetirement();
|
||||
// May be called from the destructor of the presenter.
|
||||
void DestroySwapchainAndVulkanSurface();
|
||||
|
||||
// Connection-indepedent.
|
||||
|
||||
const VulkanProvider& provider;
|
||||
|
||||
std::array<std::unique_ptr<PaintContext::Submission>, kSubmissionCount>
|
||||
submissions;
|
||||
VulkanSubmissionTracker submission_tracker;
|
||||
|
||||
std::array<GuestOutputPaintPipeline, size_t(GuestOutputPaintEffect::kCount)>
|
||||
guest_output_paint_pipelines;
|
||||
|
||||
VkDescriptorPool guest_output_descriptor_pool = VK_NULL_HANDLE;
|
||||
// Descriptors are updated while painting if they're out of date.
|
||||
VkDescriptorSet
|
||||
guest_output_descriptor_sets[kGuestOutputDescriptorSetCount];
|
||||
|
||||
// Refreshed and cleaned up during guest output painting. The first is the
|
||||
// paint submission index in which the guest output image (and its
|
||||
// descriptors) was last used, the second is the reference to the image,
|
||||
// which may be null. The indices are not mailbox indices here, rather, if
|
||||
// the reference is not in this array yet, the most outdated reference, if
|
||||
// needed, is replaced with the new one, awaiting the usage completion of
|
||||
// the last paint usage.
|
||||
std::array<std::pair<uint64_t, std::shared_ptr<GuestOutputImage>>,
|
||||
kGuestOutputMailboxSize>
|
||||
guest_output_image_paint_refs;
|
||||
// The latest submission index at which any guest output image was drawn.
|
||||
uint64_t guest_output_image_paint_last_submission = 0;
|
||||
|
||||
// Current intermediate images for guest output painting, refreshed when
|
||||
// painting guest output.
|
||||
std::array<std::unique_ptr<GuestOutputImage>,
|
||||
kMaxGuestOutputPaintEffects - 1>
|
||||
guest_output_intermediate_images;
|
||||
// Created and destroyed alongside the images. UNORM only.
|
||||
std::array<VkFramebuffer, kMaxGuestOutputPaintEffects - 1>
|
||||
guest_output_intermediate_framebuffers = {};
|
||||
uint64_t guest_output_intermediate_image_last_submission = 0;
|
||||
|
||||
// Command buffers optionally executed before the draw command buffer,
|
||||
// outside the painting render pass.
|
||||
std::vector<UISetupCommandBuffer> ui_setup_command_buffers;
|
||||
size_t ui_setup_command_buffer_current_index = SIZE_MAX;
|
||||
|
||||
// Connection-specific.
|
||||
|
||||
// May be reused between connections if the format stays the same.
|
||||
VkRenderPass swapchain_render_pass = VK_NULL_HANDLE;
|
||||
VkFormat swapchain_render_pass_format = VK_FORMAT_UNDEFINED;
|
||||
bool swapchain_render_pass_clear_load_op = false;
|
||||
|
||||
VkSurfaceKHR vulkan_surface = VK_NULL_HANDLE;
|
||||
uint32_t present_queue_family = UINT32_MAX;
|
||||
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||
VkExtent2D swapchain_extent = {};
|
||||
bool swapchain_is_fifo = false;
|
||||
std::vector<VkImage> swapchain_images;
|
||||
std::vector<SwapchainFramebuffer> swapchain_framebuffers;
|
||||
};
|
||||
|
||||
explicit VulkanPresenter(HostGpuLossCallback host_gpu_loss_callback,
|
||||
VulkanProvider& provider)
|
||||
: Presenter(host_gpu_loss_callback),
|
||||
provider_(provider),
|
||||
guest_output_image_refresher_submission_tracker_(provider),
|
||||
ui_submission_tracker_(provider),
|
||||
paint_context_(provider) {}
|
||||
|
||||
bool InitializeSurfaceIndependent();
|
||||
|
||||
[[nodiscard]] VkPipeline CreateGuestOutputPaintPipeline(
|
||||
GuestOutputPaintEffect effect, VkRenderPass render_pass);
|
||||
|
||||
VulkanProvider& provider_;
|
||||
|
||||
// Static objects for guest output presentation, used only when painting the
|
||||
// main target (can be destroyed only after awaiting main target usage
|
||||
// completion).
|
||||
VkDescriptorSetLayout guest_output_paint_image_descriptor_set_layout_ =
|
||||
VK_NULL_HANDLE;
|
||||
std::array<VkPipelineLayout, kGuestOutputPaintPipelineLayoutCount>
|
||||
guest_output_paint_pipeline_layouts_ = {};
|
||||
VkShaderModule guest_output_paint_vs_ = VK_NULL_HANDLE;
|
||||
std::array<VkShaderModule, size_t(GuestOutputPaintEffect::kCount)>
|
||||
guest_output_paint_fs_ = {};
|
||||
// Not compatible with the swapchain render pass even if the format is the
|
||||
// same due to different dependencies (this is shader read > color
|
||||
// attachment > shader read).
|
||||
VkRenderPass guest_output_intermediate_render_pass_ = VK_NULL_HANDLE;
|
||||
|
||||
// Value monotonically increased every time a new guest output image is
|
||||
// initialized, for recreation of dependent objects such as framebuffers in
|
||||
// the refreshers - saving and comparing the handle in the refresher is not
|
||||
// enough as Create > Destroy > Create may result in the same handle for
|
||||
// actually different objects without the refresher being aware of the
|
||||
// destruction.
|
||||
uint64_t guest_output_image_next_version_ = 0;
|
||||
std::array<GuestOutputImageInstance, kGuestOutputMailboxSize>
|
||||
guest_output_images_;
|
||||
VulkanSubmissionTracker guest_output_image_refresher_submission_tracker_;
|
||||
|
||||
// UI submission tracker with the submission index that can be given to UI
|
||||
// drawers (accessible from the UI thread only, at any time).
|
||||
VulkanSubmissionTracker ui_submission_tracker_;
|
||||
|
||||
// Accessible only by painting and by surface connection lifetime management
|
||||
// (ConnectOrReconnectPaintingToSurfaceFromUIThread,
|
||||
// DisconnectPaintingFromSurfaceFromUIThreadImpl) by the thread doing it, as
|
||||
// well as by presenter initialization and shutdown.
|
||||
PaintContext paint_context_;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_D3D12_D3D12_PRESENTER_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -10,19 +10,26 @@
|
||||
#ifndef XENIA_UI_VULKAN_VULKAN_PROVIDER_H_
|
||||
#define XENIA_UI_VULKAN_VULKAN_PROVIDER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
#include "xenia/ui/renderdoc_api.h"
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
#ifndef VK_USE_PLATFORM_ANDROID_KHR
|
||||
#define VK_USE_PLATFORM_ANDROID_KHR 1
|
||||
#endif
|
||||
#elif XE_PLATFORM_GNU_LINUX
|
||||
#ifndef VK_USE_PLATFORM_XCB_KHR
|
||||
#define VK_USE_PLATFORM_XCB_KHR 1
|
||||
#endif
|
||||
#elif XE_PLATFORM_WIN32
|
||||
// Must be included before vulkan.h with VK_USE_PLATFORM_WIN32_KHR because it
|
||||
// includes Windows.h too.
|
||||
@@ -47,13 +54,17 @@ namespace vulkan {
|
||||
|
||||
class VulkanProvider : public GraphicsProvider {
|
||||
public:
|
||||
~VulkanProvider() override;
|
||||
~VulkanProvider();
|
||||
|
||||
static std::unique_ptr<VulkanProvider> Create();
|
||||
static std::unique_ptr<VulkanProvider> Create(bool is_surface_required);
|
||||
|
||||
std::unique_ptr<GraphicsContext> CreateHostContext(
|
||||
Window* target_window) override;
|
||||
std::unique_ptr<GraphicsContext> CreateEmulationContext() override;
|
||||
std::unique_ptr<Presenter> CreatePresenter(
|
||||
Presenter::HostGpuLossCallback host_gpu_loss_callback =
|
||||
Presenter::FatalErrorHostGpuLossCallback) override;
|
||||
|
||||
std::unique_ptr<ImmediateDrawer> CreateImmediateDrawer() override;
|
||||
|
||||
const RenderdocApi& renderdoc_api() const { return renderdoc_api_; }
|
||||
|
||||
struct LibraryFunctions {
|
||||
// From the module.
|
||||
@@ -63,6 +74,7 @@ class VulkanProvider : public GraphicsProvider {
|
||||
PFN_vkCreateInstance vkCreateInstance;
|
||||
PFN_vkEnumerateInstanceExtensionProperties
|
||||
vkEnumerateInstanceExtensionProperties;
|
||||
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties;
|
||||
struct {
|
||||
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
|
||||
} v_1_1;
|
||||
@@ -70,41 +82,41 @@ class VulkanProvider : public GraphicsProvider {
|
||||
const LibraryFunctions& lfn() const { return lfn_; }
|
||||
|
||||
struct InstanceExtensions {
|
||||
bool ext_debug_utils;
|
||||
// Core since 1.1.0.
|
||||
bool khr_get_physical_device_properties2;
|
||||
|
||||
// Surface extensions.
|
||||
bool khr_surface;
|
||||
#if XE_PLATFORM_ANDROID
|
||||
bool khr_android_surface;
|
||||
#elif XE_PLATFORM_GNU_LINUX
|
||||
bool khr_xcb_surface;
|
||||
#elif XE_PLATFORM_WIN32
|
||||
bool khr_win32_surface;
|
||||
#endif
|
||||
};
|
||||
const InstanceExtensions& instance_extensions() const {
|
||||
return instance_extensions_;
|
||||
}
|
||||
VkInstance instance() const { return instance_; }
|
||||
struct InstanceFunctions {
|
||||
PFN_vkCreateDevice vkCreateDevice;
|
||||
PFN_vkDestroyDevice vkDestroyDevice;
|
||||
PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
|
||||
PFN_vkEnumerateDeviceExtensionProperties
|
||||
vkEnumerateDeviceExtensionProperties;
|
||||
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
|
||||
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
|
||||
PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
|
||||
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
|
||||
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
|
||||
// VK_KHR_get_physical_device_properties2 or 1.1.0.
|
||||
PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR;
|
||||
PFN_vkGetPhysicalDeviceQueueFamilyProperties
|
||||
vkGetPhysicalDeviceQueueFamilyProperties;
|
||||
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR;
|
||||
PFN_vkGetPhysicalDeviceSurfaceSupportKHR
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR;
|
||||
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
|
||||
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
|
||||
PFN_##extension_name extension_name;
|
||||
#include "xenia/ui/vulkan/functions/instance_1_0.inc"
|
||||
#include "xenia/ui/vulkan/functions/instance_ext_debug_utils.inc"
|
||||
#include "xenia/ui/vulkan/functions/instance_khr_get_physical_device_properties2.inc"
|
||||
#include "xenia/ui/vulkan/functions/instance_khr_surface.inc"
|
||||
#if XE_PLATFORM_ANDROID
|
||||
PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
|
||||
#include "xenia/ui/vulkan/functions/instance_khr_android_surface.inc"
|
||||
#elif XE_PLATFORM_GNU_LINUX
|
||||
#include "xenia/ui/vulkan/functions/instance_khr_xcb_surface.inc"
|
||||
#elif XE_PLATFORM_WIN32
|
||||
PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
|
||||
#include "xenia/ui/vulkan/functions/instance_khr_win32_surface.inc"
|
||||
#endif
|
||||
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
|
||||
#undef XE_UI_VULKAN_FUNCTION
|
||||
};
|
||||
const InstanceFunctions& ifn() const { return ifn_; }
|
||||
|
||||
@@ -120,9 +132,12 @@ class VulkanProvider : public GraphicsProvider {
|
||||
// Core since 1.1.0.
|
||||
bool khr_dedicated_allocation;
|
||||
// Core since 1.2.0.
|
||||
bool khr_image_format_list;
|
||||
// Core since 1.2.0.
|
||||
bool khr_shader_float_controls;
|
||||
// Core since 1.2.0.
|
||||
bool khr_spirv_1_4;
|
||||
bool khr_swapchain;
|
||||
};
|
||||
const DeviceExtensions& device_extensions() const {
|
||||
return device_extensions_;
|
||||
@@ -139,117 +154,64 @@ class VulkanProvider : public GraphicsProvider {
|
||||
uint32_t memory_types_host_cached() const {
|
||||
return memory_types_host_cached_;
|
||||
}
|
||||
// FIXME(Triang3l): Allow a separate queue for present - see
|
||||
// vulkan_provider.cc for details.
|
||||
struct QueueFamily {
|
||||
uint32_t queue_first_index = 0;
|
||||
uint32_t queue_count = 0;
|
||||
bool potentially_supports_present = false;
|
||||
};
|
||||
const std::vector<QueueFamily>& queue_families() const {
|
||||
return queue_families_;
|
||||
}
|
||||
// Required.
|
||||
uint32_t queue_family_graphics_compute() const {
|
||||
return queue_family_graphics_compute_;
|
||||
}
|
||||
// Optional, if sparse binding is supported (UINT32_MAX otherwise). May be the
|
||||
// same as queue_family_graphics_compute_.
|
||||
uint32_t queue_family_sparse_binding() const {
|
||||
return queue_family_sparse_binding_;
|
||||
}
|
||||
const VkPhysicalDeviceFloatControlsPropertiesKHR&
|
||||
device_float_controls_properties() const {
|
||||
return device_float_controls_properties_;
|
||||
}
|
||||
|
||||
struct Queue {
|
||||
VkQueue queue = VK_NULL_HANDLE;
|
||||
std::recursive_mutex mutex;
|
||||
};
|
||||
struct QueueAcquisition {
|
||||
QueueAcquisition(std::unique_lock<std::recursive_mutex>&& lock,
|
||||
VkQueue queue)
|
||||
: lock(std::move(lock)), queue(queue) {}
|
||||
std::unique_lock<std::recursive_mutex> lock;
|
||||
VkQueue queue;
|
||||
};
|
||||
QueueAcquisition AcquireQueue(uint32_t index) {
|
||||
Queue& queue = queues_[index];
|
||||
return QueueAcquisition(std::unique_lock<std::recursive_mutex>(queue.mutex),
|
||||
queue.queue);
|
||||
}
|
||||
QueueAcquisition AcquireQueue(uint32_t family_index, uint32_t index) {
|
||||
assert_true(family_index != UINT32_MAX);
|
||||
return AcquireQueue(queue_families_[family_index].queue_first_index +
|
||||
index);
|
||||
}
|
||||
|
||||
VkDevice device() const { return device_; }
|
||||
struct DeviceFunctions {
|
||||
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
|
||||
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
|
||||
PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
|
||||
PFN_vkAllocateMemory vkAllocateMemory;
|
||||
PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
|
||||
PFN_vkBindBufferMemory vkBindBufferMemory;
|
||||
PFN_vkBindImageMemory vkBindImageMemory;
|
||||
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
|
||||
PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
|
||||
PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer;
|
||||
PFN_vkCmdBindPipeline vkCmdBindPipeline;
|
||||
PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
|
||||
PFN_vkCmdClearColorImage vkCmdClearColorImage;
|
||||
PFN_vkCmdCopyBuffer vkCmdCopyBuffer;
|
||||
PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
|
||||
PFN_vkCmdDraw vkCmdDraw;
|
||||
PFN_vkCmdDrawIndexed vkCmdDrawIndexed;
|
||||
PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
|
||||
PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier;
|
||||
PFN_vkCmdPushConstants vkCmdPushConstants;
|
||||
PFN_vkCmdSetScissor vkCmdSetScissor;
|
||||
PFN_vkCmdSetViewport vkCmdSetViewport;
|
||||
PFN_vkCreateBuffer vkCreateBuffer;
|
||||
PFN_vkCreateCommandPool vkCreateCommandPool;
|
||||
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
|
||||
PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
|
||||
PFN_vkCreateFence vkCreateFence;
|
||||
PFN_vkCreateFramebuffer vkCreateFramebuffer;
|
||||
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
|
||||
PFN_vkCreateImage vkCreateImage;
|
||||
PFN_vkCreateImageView vkCreateImageView;
|
||||
PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
|
||||
PFN_vkCreateRenderPass vkCreateRenderPass;
|
||||
PFN_vkCreateSampler vkCreateSampler;
|
||||
PFN_vkCreateSemaphore vkCreateSemaphore;
|
||||
PFN_vkCreateShaderModule vkCreateShaderModule;
|
||||
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
|
||||
PFN_vkDestroyBuffer vkDestroyBuffer;
|
||||
PFN_vkDestroyCommandPool vkDestroyCommandPool;
|
||||
PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool;
|
||||
PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout;
|
||||
PFN_vkDestroyFence vkDestroyFence;
|
||||
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
|
||||
PFN_vkDestroyImage vkDestroyImage;
|
||||
PFN_vkDestroyImageView vkDestroyImageView;
|
||||
PFN_vkDestroyPipeline vkDestroyPipeline;
|
||||
PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout;
|
||||
PFN_vkDestroyRenderPass vkDestroyRenderPass;
|
||||
PFN_vkDestroySampler vkDestroySampler;
|
||||
PFN_vkDestroySemaphore vkDestroySemaphore;
|
||||
PFN_vkDestroyShaderModule vkDestroyShaderModule;
|
||||
PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
|
||||
PFN_vkEndCommandBuffer vkEndCommandBuffer;
|
||||
PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
|
||||
PFN_vkFreeMemory vkFreeMemory;
|
||||
PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
|
||||
PFN_vkGetDeviceQueue vkGetDeviceQueue;
|
||||
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
|
||||
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
|
||||
PFN_vkMapMemory vkMapMemory;
|
||||
PFN_vkResetCommandPool vkResetCommandPool;
|
||||
PFN_vkResetDescriptorPool vkResetDescriptorPool;
|
||||
PFN_vkResetFences vkResetFences;
|
||||
PFN_vkQueueBindSparse vkQueueBindSparse;
|
||||
PFN_vkQueuePresentKHR vkQueuePresentKHR;
|
||||
PFN_vkQueueSubmit vkQueueSubmit;
|
||||
PFN_vkUnmapMemory vkUnmapMemory;
|
||||
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
|
||||
PFN_vkWaitForFences vkWaitForFences;
|
||||
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
|
||||
#include "xenia/ui/vulkan/functions/device_1_0.inc"
|
||||
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
|
||||
#undef XE_UI_VULKAN_FUNCTION
|
||||
};
|
||||
const DeviceFunctions& dfn() const { return dfn_; }
|
||||
|
||||
VkResult SubmitToGraphicsComputeQueue(uint32_t submit_count,
|
||||
const VkSubmitInfo* submits,
|
||||
VkFence fence) {
|
||||
std::lock_guard<std::mutex> lock(queue_graphics_compute_mutex_);
|
||||
return dfn_.vkQueueSubmit(queue_graphics_compute_, submit_count, submits,
|
||||
fence);
|
||||
}
|
||||
// Safer in Xenia context - in case a sparse binding queue was not obtained
|
||||
// for some reason.
|
||||
void SetDeviceObjectName(VkObjectType type, uint64_t handle,
|
||||
const char* name) const;
|
||||
|
||||
bool IsSparseBindingSupported() const {
|
||||
return queue_sparse_binding_ != VK_NULL_HANDLE;
|
||||
}
|
||||
VkResult BindSparse(uint32_t bind_info_count,
|
||||
const VkBindSparseInfo* bind_info, VkFence fence) {
|
||||
assert_true(IsSparseBindingSupported());
|
||||
std::mutex& mutex = queue_sparse_binding_ == queue_graphics_compute_
|
||||
? queue_graphics_compute_mutex_
|
||||
: queue_sparse_binding_separate_mutex_;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return dfn_.vkQueueBindSparse(queue_sparse_binding_, bind_info_count,
|
||||
bind_info, fence);
|
||||
}
|
||||
VkResult Present(const VkPresentInfoKHR* present_info) {
|
||||
// FIXME(Triang3l): Allow a separate queue for present - see
|
||||
// vulkan_provider.cc for details.
|
||||
std::lock_guard<std::mutex> lock(queue_graphics_compute_mutex_);
|
||||
return dfn_.vkQueuePresentKHR(queue_graphics_compute_, present_info);
|
||||
return queue_family_sparse_binding_ != UINT32_MAX;
|
||||
}
|
||||
|
||||
// Samplers that may be useful for host needs. Only these samplers should be
|
||||
@@ -269,10 +231,26 @@ class VulkanProvider : public GraphicsProvider {
|
||||
}
|
||||
|
||||
private:
|
||||
VulkanProvider() = default;
|
||||
explicit VulkanProvider(bool is_surface_required)
|
||||
: is_surface_required_(is_surface_required) {}
|
||||
|
||||
bool Initialize();
|
||||
|
||||
static void AccumulateInstanceExtensions(
|
||||
size_t properties_count, const VkExtensionProperties* properties,
|
||||
bool request_debug_utils, InstanceExtensions& instance_extensions,
|
||||
std::vector<const char*>& instance_extensions_enabled);
|
||||
|
||||
static VkBool32 VKAPI_CALL DebugMessengerCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT message_types,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
|
||||
void* user_data);
|
||||
|
||||
bool is_surface_required_;
|
||||
|
||||
RenderdocApi renderdoc_api_;
|
||||
|
||||
#if XE_PLATFORM_LINUX
|
||||
void* library_ = nullptr;
|
||||
#elif XE_PLATFORM_WIN32
|
||||
@@ -284,6 +262,8 @@ class VulkanProvider : public GraphicsProvider {
|
||||
InstanceExtensions instance_extensions_;
|
||||
VkInstance instance_ = VK_NULL_HANDLE;
|
||||
InstanceFunctions ifn_;
|
||||
VkDebugUtilsMessengerEXT debug_messenger_ = VK_NULL_HANDLE;
|
||||
bool debug_names_used_ = false;
|
||||
|
||||
VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
|
||||
VkPhysicalDeviceProperties device_properties_;
|
||||
@@ -293,20 +273,15 @@ class VulkanProvider : public GraphicsProvider {
|
||||
uint32_t memory_types_host_visible_;
|
||||
uint32_t memory_types_host_coherent_;
|
||||
uint32_t memory_types_host_cached_;
|
||||
std::vector<QueueFamily> queue_families_;
|
||||
uint32_t queue_family_graphics_compute_;
|
||||
uint32_t queue_family_sparse_binding_;
|
||||
VkPhysicalDeviceFloatControlsPropertiesKHR device_float_controls_properties_;
|
||||
|
||||
VkDevice device_ = VK_NULL_HANDLE;
|
||||
DeviceFunctions dfn_ = {};
|
||||
VkQueue queue_graphics_compute_;
|
||||
// VkQueue access must be externally synchronized - must be locked when
|
||||
// submitting anything.
|
||||
std::mutex queue_graphics_compute_mutex_;
|
||||
// May be VK_NULL_HANDLE if not available.
|
||||
VkQueue queue_sparse_binding_;
|
||||
// If queue_sparse_binding_ == queue_graphics_compute_, lock
|
||||
// queue_graphics_compute_mutex_ instead when submitting sparse bindings.
|
||||
std::mutex queue_sparse_binding_separate_mutex_;
|
||||
// Queues contain a mutex, can't use std::vector.
|
||||
std::unique_ptr<Queue[]> queues_;
|
||||
|
||||
VkSampler host_samplers_[size_t(HostSampler::kCount)] = {};
|
||||
};
|
||||
|
||||
174
src/xenia/ui/vulkan/vulkan_submission_tracker.cc
Normal file
174
src/xenia/ui/vulkan/vulkan_submission_tracker.cc
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/ui/vulkan/vulkan_submission_tracker.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/ui/vulkan/vulkan_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
VulkanSubmissionTracker::FenceAcquisition::~FenceAcquisition() {
|
||||
if (!submission_tracker_) {
|
||||
// Dropped submission or left after std::move.
|
||||
return;
|
||||
}
|
||||
assert_true(submission_tracker_->fence_acquired_ == fence_);
|
||||
if (fence_ != VK_NULL_HANDLE) {
|
||||
if (signal_failed_) {
|
||||
// Left in the unsignaled state.
|
||||
submission_tracker_->fences_reclaimed_.push_back(fence_);
|
||||
} else {
|
||||
// Left in the pending state.
|
||||
submission_tracker_->fences_pending_.emplace_back(
|
||||
submission_tracker_->submission_current_, fence_);
|
||||
}
|
||||
submission_tracker_->fence_acquired_ = VK_NULL_HANDLE;
|
||||
}
|
||||
++submission_tracker_->submission_current_;
|
||||
}
|
||||
|
||||
void VulkanSubmissionTracker::Shutdown() {
|
||||
AwaitAllSubmissionsCompletion();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
for (VkFence fence : fences_reclaimed_) {
|
||||
dfn.vkDestroyFence(device, fence, nullptr);
|
||||
}
|
||||
fences_reclaimed_.clear();
|
||||
for (const std::pair<uint64_t, VkFence>& fence_pair : fences_pending_) {
|
||||
dfn.vkDestroyFence(device, fence_pair.second, nullptr);
|
||||
}
|
||||
fences_pending_.clear();
|
||||
assert_true(fence_acquired_ == VK_NULL_HANDLE);
|
||||
util::DestroyAndNullHandle(dfn.vkDestroyFence, device, fence_acquired_);
|
||||
}
|
||||
|
||||
void VulkanSubmissionTracker::FenceAcquisition::SubmissionFailedOrDropped() {
|
||||
if (!submission_tracker_) {
|
||||
return;
|
||||
}
|
||||
assert_true(submission_tracker_->fence_acquired_ == fence_);
|
||||
if (fence_ != VK_NULL_HANDLE) {
|
||||
submission_tracker_->fences_reclaimed_.push_back(fence_);
|
||||
}
|
||||
submission_tracker_->fence_acquired_ = VK_NULL_HANDLE;
|
||||
fence_ = VK_NULL_HANDLE;
|
||||
// No submission acquisition from now on, don't increment the current
|
||||
// submission index as well.
|
||||
submission_tracker_ = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
uint64_t VulkanSubmissionTracker::UpdateAndGetCompletedSubmission() {
|
||||
if (!fences_pending_.empty()) {
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
while (!fences_pending_.empty()) {
|
||||
const std::pair<uint64_t, VkFence>& pending_pair =
|
||||
fences_pending_.front();
|
||||
assert_true(pending_pair.first > submission_completed_on_gpu_);
|
||||
if (dfn.vkGetFenceStatus(device, pending_pair.second) != VK_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
fences_reclaimed_.push_back(pending_pair.second);
|
||||
submission_completed_on_gpu_ = pending_pair.first;
|
||||
fences_pending_.pop_front();
|
||||
}
|
||||
}
|
||||
return submission_completed_on_gpu_;
|
||||
}
|
||||
|
||||
bool VulkanSubmissionTracker::AwaitSubmissionCompletion(
|
||||
uint64_t submission_index) {
|
||||
// The tracker itself can't give a submission index for a submission that
|
||||
// hasn't even started being recorded yet, the client has provided a
|
||||
// completely invalid value or has done overly optimistic math if such an
|
||||
// index has been obtained somehow.
|
||||
assert_true(submission_index <= submission_current_);
|
||||
// Waiting for the current submission is fine if there was a failure or a
|
||||
// refusal to submit, and the submission index wasn't incremented, but still
|
||||
// need to release objects referenced in the dropped submission (while
|
||||
// shutting down, for instance - in this case, waiting for the last successful
|
||||
// submission, which could have also referenced the objects from the new
|
||||
// submission - we can't know since the client has already overwritten its
|
||||
// last usage index, would correctly ensure that GPU usage of the objects is
|
||||
// not pending). Waiting for successful submissions, but failed signals, will
|
||||
// result in a true race condition, however, but waiting for the closest
|
||||
// successful signal is the best approximation - also retrying to signal in
|
||||
// this case.
|
||||
// Go from the most recent to wait only for one fence, which includes all the
|
||||
// preceding ones.
|
||||
// "Fence signal operations that are defined by vkQueueSubmit additionally
|
||||
// include in the first synchronization scope all commands that occur earlier
|
||||
// in submission order."
|
||||
size_t reclaim_end = fences_pending_.size();
|
||||
if (reclaim_end) {
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
while (reclaim_end) {
|
||||
const std::pair<uint64_t, VkFence>& pending_pair =
|
||||
fences_pending_[reclaim_end - 1];
|
||||
assert_true(pending_pair.first > submission_completed_on_gpu_);
|
||||
if (pending_pair.first <= submission_index) {
|
||||
// Wait if requested.
|
||||
if (dfn.vkWaitForFences(device, 1, &pending_pair.second, VK_TRUE,
|
||||
UINT64_MAX) == VK_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Just refresh the completed submission.
|
||||
if (dfn.vkGetFenceStatus(device, pending_pair.second) == VK_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
--reclaim_end;
|
||||
}
|
||||
if (reclaim_end) {
|
||||
submission_completed_on_gpu_ = fences_pending_[reclaim_end - 1].first;
|
||||
for (; reclaim_end; --reclaim_end) {
|
||||
fences_reclaimed_.push_back(fences_pending_.front().second);
|
||||
fences_pending_.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
return submission_completed_on_gpu_ == submission_index;
|
||||
}
|
||||
|
||||
VulkanSubmissionTracker::FenceAcquisition
|
||||
VulkanSubmissionTracker::AcquireFenceToAdvanceSubmission() {
|
||||
assert_true(fence_acquired_ == VK_NULL_HANDLE);
|
||||
// Reclaim fences if the client only gets the completed submission index or
|
||||
// awaits in special cases such as shutdown.
|
||||
UpdateAndGetCompletedSubmission();
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
if (!fences_reclaimed_.empty()) {
|
||||
VkFence reclaimed_fence = fences_reclaimed_.back();
|
||||
if (dfn.vkResetFences(device, 1, &reclaimed_fence) == VK_SUCCESS) {
|
||||
fence_acquired_ = fences_reclaimed_.back();
|
||||
fences_reclaimed_.pop_back();
|
||||
}
|
||||
}
|
||||
if (fence_acquired_ == VK_NULL_HANDLE) {
|
||||
VkFenceCreateInfo fence_create_info;
|
||||
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_create_info.pNext = nullptr;
|
||||
fence_create_info.flags = 0;
|
||||
// May fail, a null fence is handled in FenceAcquisition.
|
||||
dfn.vkCreateFence(device, &fence_create_info, nullptr, &fence_acquired_);
|
||||
}
|
||||
return FenceAcquisition(*this, fence_acquired_);
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
135
src/xenia/ui/vulkan/vulkan_submission_tracker.h
Normal file
135
src/xenia/ui/vulkan/vulkan_submission_tracker.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_UI_VULKAN_VULKAN_SUBMISSION_TRACKER_H_
|
||||
#define XENIA_UI_VULKAN_VULKAN_SUBMISSION_TRACKER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
// Fence wrapper, safely handling cases when the fence has not been initialized
|
||||
// yet or has already been shut down, and failed or dropped submissions.
|
||||
//
|
||||
// The current submission index can be associated with the usage of objects to
|
||||
// release them when the GPU isn't potentially referencing them anymore, and
|
||||
// should be incremented only
|
||||
//
|
||||
// 0 can be used as a "never referenced" submission index.
|
||||
//
|
||||
// The submission index timeline survives Shutdown, so submission indices can be
|
||||
// given to clients that are not aware of the lifetime of the tracker.
|
||||
//
|
||||
// To transfer the tracker to another queue (to make sure the first signal on
|
||||
// the new queue does not happen before the last signal on the old one), call
|
||||
// AwaitAllSubmissionsCompletion before doing the first submission on the new
|
||||
// one.
|
||||
class VulkanSubmissionTracker {
|
||||
public:
|
||||
class FenceAcquisition {
|
||||
public:
|
||||
FenceAcquisition() : submission_tracker_(nullptr), fence_(VK_NULL_HANDLE) {}
|
||||
FenceAcquisition(VulkanSubmissionTracker& submission_tracker, VkFence fence)
|
||||
: submission_tracker_(&submission_tracker), fence_(fence) {}
|
||||
FenceAcquisition(const FenceAcquisition& fence_acquisition) = delete;
|
||||
FenceAcquisition& operator=(const FenceAcquisition& fence_acquisition) =
|
||||
delete;
|
||||
FenceAcquisition(FenceAcquisition&& fence_acquisition) {
|
||||
*this = std::move(fence_acquisition);
|
||||
}
|
||||
FenceAcquisition& operator=(FenceAcquisition&& fence_acquisition) {
|
||||
if (this == &fence_acquisition) {
|
||||
return *this;
|
||||
}
|
||||
submission_tracker_ = fence_acquisition.submission_tracker_;
|
||||
fence_acquisition.submission_tracker_ = nullptr;
|
||||
fence_ = fence_acquisition.fence_;
|
||||
fence_acquisition.fence_ = VK_NULL_HANDLE;
|
||||
return *this;
|
||||
}
|
||||
~FenceAcquisition();
|
||||
|
||||
// In unsignaled state. May be null if failed to create or to reset a fence.
|
||||
VkFence fence() { return fence_; }
|
||||
|
||||
// Call if vkQueueSubmit has failed (or it was decided not to commit the
|
||||
// submission), and the submission index shouldn't be incremented by
|
||||
// releasing this submission (for instance, to retry commands with long-term
|
||||
// effects like copying or image layout changes later if in the attempt the
|
||||
// submission index stays the same).
|
||||
void SubmissionFailedOrDropped();
|
||||
// Call if for some reason (like signaling multiple fences) the fence
|
||||
// signaling was done in a separate submission than the command buffer, and
|
||||
// the command buffer vkQueueSubmit succeeded (so commands with long-term
|
||||
// effects will be executed), but the fence-only vkQueueSubmit has failed,
|
||||
// thus the tracker shouldn't attempt to wait for that fence (it will be in
|
||||
// the unsignaled state).
|
||||
void SubmissionSucceededSignalFailed() { signal_failed_ = true; }
|
||||
|
||||
private:
|
||||
// If nullptr, has been moved to another FenceAcquisition - not holding a
|
||||
// fence from now on.
|
||||
VulkanSubmissionTracker* submission_tracker_;
|
||||
VkFence fence_;
|
||||
bool signal_failed_ = false;
|
||||
};
|
||||
|
||||
VulkanSubmissionTracker(VulkanProvider& provider) : provider_(provider) {}
|
||||
VulkanSubmissionTracker(const VulkanSubmissionTracker& submission_tracker) =
|
||||
delete;
|
||||
VulkanSubmissionTracker& operator=(
|
||||
const VulkanSubmissionTracker& submission_tracker) = delete;
|
||||
~VulkanSubmissionTracker() { Shutdown(); }
|
||||
|
||||
void Shutdown();
|
||||
|
||||
uint64_t GetCurrentSubmission() const { return submission_current_; }
|
||||
uint64_t UpdateAndGetCompletedSubmission();
|
||||
|
||||
// Returns whether the expected GPU signal has actually been reached (rather
|
||||
// than some fallback condition) for cases when stronger completeness
|
||||
// guarantees as needed (when downloading, as opposed to just destroying).
|
||||
// If false is returned, it's also not guaranteed that GetCompletedSubmission
|
||||
// will return a value >= submission_index.
|
||||
bool AwaitSubmissionCompletion(uint64_t submission_index);
|
||||
bool AwaitAllSubmissionsCompletion() {
|
||||
return AwaitSubmissionCompletion(submission_current_ - 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] FenceAcquisition AcquireFenceToAdvanceSubmission();
|
||||
|
||||
private:
|
||||
VulkanProvider& provider_;
|
||||
uint64_t submission_current_ = 1;
|
||||
// Last submission with a successful fence signal as well as a successful
|
||||
// fence wait / query.
|
||||
uint64_t submission_completed_on_gpu_ = 0;
|
||||
// The flow is:
|
||||
// Reclaimed (or create if empty) > acquired > pending > reclaimed.
|
||||
// Or, if dropped the submission while acquired:
|
||||
// Reclaimed (or create if empty) > acquired > reclaimed.
|
||||
VkFence fence_acquired_ = VK_NULL_HANDLE;
|
||||
// Ordered by the submission index (the first pair member).
|
||||
std::deque<std::pair<uint64_t, VkFence>> fences_pending_;
|
||||
// Fences are reclaimed when awaiting or when refreshing the completed value.
|
||||
std::vector<VkFence> fences_reclaimed_;
|
||||
};
|
||||
|
||||
} // namespace vulkan
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_UI_VULKAN_VULKAN_SUBMISSION_TRACKER_H_
|
||||
@@ -19,8 +19,11 @@ namespace xe {
|
||||
namespace ui {
|
||||
namespace vulkan {
|
||||
|
||||
// Memory mappings are always aligned to nonCoherentAtomSize, so for simplicity,
|
||||
// round the page size to it now via GetMappableMemorySize.
|
||||
// Host-visible memory sizes are likely to be internally rounded to
|
||||
// nonCoherentAtomSize (it's the memory mapping granularity, though as the map
|
||||
// or flush range must be clamped to the actual allocation size as a special
|
||||
// case, but it's still unlikely that the allocation won't be aligned to it), so
|
||||
// try not to waste that padding.
|
||||
VulkanUploadBufferPool::VulkanUploadBufferPool(const VulkanProvider& provider,
|
||||
VkBufferUsageFlags usage,
|
||||
size_t page_size)
|
||||
@@ -72,8 +75,6 @@ VulkanUploadBufferPool::CreatePageImplementation() {
|
||||
const VulkanProvider::DeviceFunctions& dfn = provider_.dfn();
|
||||
VkDevice device = provider_.device();
|
||||
|
||||
// For the first call, the page size is already aligned to nonCoherentAtomSize
|
||||
// for mapping.
|
||||
VkBufferCreateInfo buffer_create_info;
|
||||
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
buffer_create_info.pNext = nullptr;
|
||||
@@ -105,16 +106,10 @@ VulkanUploadBufferPool::CreatePageImplementation() {
|
||||
return nullptr;
|
||||
}
|
||||
allocation_size_ = memory_requirements.size;
|
||||
// On some Android implementations, nonCoherentAtomSize is 0, not 1.
|
||||
VkDeviceSize non_coherent_atom_size =
|
||||
std::max(provider_.device_properties().limits.nonCoherentAtomSize,
|
||||
VkDeviceSize(1));
|
||||
VkDeviceSize allocation_size_aligned =
|
||||
allocation_size_ / non_coherent_atom_size * non_coherent_atom_size;
|
||||
if (allocation_size_aligned > page_size_) {
|
||||
// Try to occupy all the allocation padding. If that's going to require
|
||||
// even more memory for some reason, don't.
|
||||
buffer_create_info.size = allocation_size_aligned;
|
||||
if (allocation_size_ > page_size_) {
|
||||
// Try to occupy the allocation padding. If that's going to require even
|
||||
// more memory for some reason, don't.
|
||||
buffer_create_info.size = allocation_size_;
|
||||
VkBuffer buffer_expanded;
|
||||
if (dfn.vkCreateBuffer(device, &buffer_create_info, nullptr,
|
||||
&buffer_expanded) == VK_SUCCESS) {
|
||||
@@ -125,8 +120,7 @@ VulkanUploadBufferPool::CreatePageImplementation() {
|
||||
provider_, memory_requirements.memoryTypeBits, false);
|
||||
if (memory_requirements_expanded.size <= allocation_size_ &&
|
||||
memory_type_expanded != UINT32_MAX) {
|
||||
// page_size_ must be aligned to nonCoherentAtomSize.
|
||||
page_size_ = size_t(allocation_size_aligned);
|
||||
page_size_ = size_t(allocation_size_);
|
||||
allocation_size_ = memory_requirements_expanded.size;
|
||||
memory_type_ = memory_type_expanded;
|
||||
dfn.vkDestroyBuffer(device, buffer, nullptr);
|
||||
@@ -139,20 +133,22 @@ VulkanUploadBufferPool::CreatePageImplementation() {
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info;
|
||||
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
|
||||
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memory_allocate_info.pNext = nullptr;
|
||||
memory_allocate_info.allocationSize = allocation_size_;
|
||||
memory_allocate_info.memoryTypeIndex = memory_type_;
|
||||
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
|
||||
if (provider_.device_extensions().khr_dedicated_allocation) {
|
||||
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
|
||||
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
|
||||
&memory_dedicated_allocate_info);
|
||||
memory_dedicated_allocate_info.sType =
|
||||
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
|
||||
memory_dedicated_allocate_info.pNext = nullptr;
|
||||
memory_dedicated_allocate_info.image = VK_NULL_HANDLE;
|
||||
memory_dedicated_allocate_info.buffer = buffer;
|
||||
memory_allocate_info.pNext = &memory_dedicated_allocate_info;
|
||||
} else {
|
||||
memory_allocate_info.pNext = nullptr;
|
||||
}
|
||||
memory_allocate_info.allocationSize = allocation_size_;
|
||||
memory_allocate_info.memoryTypeIndex = memory_type_;
|
||||
VkDeviceMemory memory;
|
||||
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
|
||||
VK_SUCCESS) {
|
||||
@@ -171,10 +167,10 @@ VulkanUploadBufferPool::CreatePageImplementation() {
|
||||
}
|
||||
|
||||
void* mapping;
|
||||
// page_size_ is aligned to nonCoherentAtomSize.
|
||||
if (dfn.vkMapMemory(device, memory, 0, page_size_, 0, &mapping) !=
|
||||
if (dfn.vkMapMemory(device, memory, 0, VK_WHOLE_SIZE, 0, &mapping) !=
|
||||
VK_SUCCESS) {
|
||||
XELOGE("Failed to map {} bytes of Vulkan upload buffer memory", page_size_);
|
||||
XELOGE("Failed to map {} bytes of Vulkan upload buffer memory",
|
||||
allocation_size_);
|
||||
dfn.vkDestroyBuffer(device, buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, memory, nullptr);
|
||||
return nullptr;
|
||||
@@ -187,7 +183,7 @@ void VulkanUploadBufferPool::FlushPageWrites(Page* page, size_t offset,
|
||||
size_t size) {
|
||||
util::FlushMappedMemoryRange(
|
||||
provider_, static_cast<const VulkanPage*>(page)->memory_, memory_type_,
|
||||
VkDeviceSize(offset), VkDeviceSize(size));
|
||||
VkDeviceSize(offset), allocation_size_, VkDeviceSize(size));
|
||||
}
|
||||
|
||||
VulkanUploadBufferPool::VulkanPage::~VulkanPage() {
|
||||
|
||||
@@ -22,7 +22,11 @@ namespace util {
|
||||
|
||||
void FlushMappedMemoryRange(const VulkanProvider& provider,
|
||||
VkDeviceMemory memory, uint32_t memory_type,
|
||||
VkDeviceSize offset, VkDeviceSize size) {
|
||||
VkDeviceSize offset, VkDeviceSize memory_size,
|
||||
VkDeviceSize size) {
|
||||
assert_false(size != VK_WHOLE_SIZE && memory_size == VK_WHOLE_SIZE);
|
||||
assert_true(memory_size == VK_WHOLE_SIZE || offset <= memory_size);
|
||||
assert_true(memory_size == VK_WHOLE_SIZE || size <= memory_size - offset);
|
||||
if (!size ||
|
||||
(provider.memory_types_host_coherent() & (uint32_t(1) << memory_type))) {
|
||||
return;
|
||||
@@ -39,8 +43,9 @@ void FlushMappedMemoryRange(const VulkanProvider& provider,
|
||||
if (non_coherent_atom_size > 1) {
|
||||
range.offset = offset / non_coherent_atom_size * non_coherent_atom_size;
|
||||
if (size != VK_WHOLE_SIZE) {
|
||||
range.size =
|
||||
xe::round_up(offset + size, non_coherent_atom_size) - range.offset;
|
||||
range.size = std::min(xe::round_up(offset + size, non_coherent_atom_size),
|
||||
memory_size) -
|
||||
range.offset;
|
||||
}
|
||||
}
|
||||
provider.dfn().vkFlushMappedMemoryRanges(provider.device(), 1, &range);
|
||||
@@ -49,7 +54,8 @@ void FlushMappedMemoryRange(const VulkanProvider& provider,
|
||||
bool CreateDedicatedAllocationBuffer(
|
||||
const VulkanProvider& provider, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
MemoryPurpose memory_purpose, VkBuffer& buffer_out,
|
||||
VkDeviceMemory& memory_out, uint32_t* memory_type_out) {
|
||||
VkDeviceMemory& memory_out, uint32_t* memory_type_out,
|
||||
VkDeviceSize* memory_size_out) {
|
||||
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
|
||||
@@ -70,55 +76,37 @@ bool CreateDedicatedAllocationBuffer(
|
||||
|
||||
VkMemoryRequirements memory_requirements;
|
||||
dfn.vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
|
||||
uint32_t memory_type = UINT32_MAX;
|
||||
switch (memory_purpose) {
|
||||
case MemoryPurpose::kDeviceLocal:
|
||||
if (!xe::bit_scan_forward(memory_requirements.memoryTypeBits &
|
||||
provider.memory_types_device_local(),
|
||||
&memory_type)) {
|
||||
memory_type = UINT32_MAX;
|
||||
}
|
||||
break;
|
||||
case MemoryPurpose::kUpload:
|
||||
case MemoryPurpose::kReadback:
|
||||
memory_type =
|
||||
ChooseHostMemoryType(provider, memory_requirements.memoryTypeBits,
|
||||
memory_purpose == MemoryPurpose::kReadback);
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(memory_purpose);
|
||||
}
|
||||
uint32_t memory_type = ChooseMemoryType(
|
||||
provider, memory_requirements.memoryTypeBits, memory_purpose);
|
||||
if (memory_type == UINT32_MAX) {
|
||||
dfn.vkDestroyBuffer(device, buffer, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info;
|
||||
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
|
||||
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memory_allocate_info.pNext = nullptr;
|
||||
memory_allocate_info.allocationSize = memory_requirements.size;
|
||||
memory_allocate_info.memoryTypeIndex = memory_type;
|
||||
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
|
||||
if (provider.device_extensions().khr_dedicated_allocation) {
|
||||
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
|
||||
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
|
||||
&memory_dedicated_allocate_info);
|
||||
memory_dedicated_allocate_info.sType =
|
||||
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
|
||||
memory_dedicated_allocate_info.pNext = nullptr;
|
||||
memory_dedicated_allocate_info.image = VK_NULL_HANDLE;
|
||||
memory_dedicated_allocate_info.buffer = buffer;
|
||||
memory_allocate_info.pNext = &memory_dedicated_allocate_info;
|
||||
} else {
|
||||
memory_allocate_info.pNext = nullptr;
|
||||
}
|
||||
memory_allocate_info.allocationSize = memory_requirements.size;
|
||||
if (memory_purpose == MemoryPurpose::kUpload ||
|
||||
memory_purpose == MemoryPurpose::kReadback) {
|
||||
memory_allocate_info.allocationSize =
|
||||
GetMappableMemorySize(provider, memory_allocate_info.allocationSize);
|
||||
}
|
||||
memory_allocate_info.memoryTypeIndex = memory_type;
|
||||
VkDeviceMemory memory;
|
||||
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
|
||||
VK_SUCCESS) {
|
||||
dfn.vkDestroyBuffer(device, buffer, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dfn.vkBindBufferMemory(device, buffer, memory, 0) != VK_SUCCESS) {
|
||||
dfn.vkDestroyBuffer(device, buffer, nullptr);
|
||||
dfn.vkFreeMemory(device, memory, nullptr);
|
||||
@@ -130,6 +118,74 @@ bool CreateDedicatedAllocationBuffer(
|
||||
if (memory_type_out) {
|
||||
*memory_type_out = memory_type;
|
||||
}
|
||||
if (memory_size_out) {
|
||||
*memory_size_out = memory_allocate_info.allocationSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
|
||||
const VkImageCreateInfo& create_info,
|
||||
MemoryPurpose memory_purpose,
|
||||
VkImage& image_out,
|
||||
VkDeviceMemory& memory_out,
|
||||
uint32_t* memory_type_out,
|
||||
VkDeviceSize* memory_size_out) {
|
||||
const ui::vulkan::VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||
VkDevice device = provider.device();
|
||||
|
||||
VkImage image;
|
||||
if (dfn.vkCreateImage(device, &create_info, nullptr, &image) != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMemoryRequirements memory_requirements;
|
||||
dfn.vkGetImageMemoryRequirements(device, image, &memory_requirements);
|
||||
uint32_t memory_type = ChooseMemoryType(
|
||||
provider, memory_requirements.memoryTypeBits, memory_purpose);
|
||||
if (memory_type == UINT32_MAX) {
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
VkMemoryAllocateInfo memory_allocate_info;
|
||||
VkMemoryAllocateInfo* memory_allocate_info_last = &memory_allocate_info;
|
||||
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memory_allocate_info.pNext = nullptr;
|
||||
memory_allocate_info.allocationSize = memory_requirements.size;
|
||||
memory_allocate_info.memoryTypeIndex = memory_type;
|
||||
VkMemoryDedicatedAllocateInfoKHR memory_dedicated_allocate_info;
|
||||
if (provider.device_extensions().khr_dedicated_allocation) {
|
||||
memory_allocate_info_last->pNext = &memory_dedicated_allocate_info;
|
||||
memory_allocate_info_last = reinterpret_cast<VkMemoryAllocateInfo*>(
|
||||
&memory_dedicated_allocate_info);
|
||||
memory_dedicated_allocate_info.sType =
|
||||
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR;
|
||||
memory_dedicated_allocate_info.pNext = nullptr;
|
||||
memory_dedicated_allocate_info.image = image;
|
||||
memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE;
|
||||
}
|
||||
VkDeviceMemory memory;
|
||||
if (dfn.vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory) !=
|
||||
VK_SUCCESS) {
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dfn.vkBindImageMemory(device, image, memory, 0) != VK_SUCCESS) {
|
||||
dfn.vkDestroyImage(device, image, nullptr);
|
||||
dfn.vkFreeMemory(device, memory, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
image_out = image;
|
||||
memory_out = memory;
|
||||
if (memory_type_out) {
|
||||
*memory_type_out = memory_type;
|
||||
}
|
||||
if (memory_size_out) {
|
||||
*memory_size_out = memory_allocate_info.allocationSize;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
@@ -10,6 +10,7 @@
|
||||
#ifndef XENIA_UI_VULKAN_VULKAN_UTIL_H_
|
||||
#define XENIA_UI_VULKAN_VULKAN_UTIL_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "xenia/base/math.h"
|
||||
@@ -75,11 +76,46 @@ inline uint32_t ChooseHostMemoryType(const VulkanProvider& provider,
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
inline uint32_t ChooseMemoryType(const VulkanProvider& provider,
|
||||
uint32_t supported_types,
|
||||
MemoryPurpose purpose) {
|
||||
switch (purpose) {
|
||||
case MemoryPurpose::kDeviceLocal: {
|
||||
uint32_t memory_type;
|
||||
return xe::bit_scan_forward(supported_types, &memory_type) ? memory_type
|
||||
: UINT32_MAX;
|
||||
} break;
|
||||
case MemoryPurpose::kUpload:
|
||||
case MemoryPurpose::kReadback:
|
||||
return ChooseHostMemoryType(provider, supported_types,
|
||||
purpose == MemoryPurpose::kReadback);
|
||||
default:
|
||||
assert_unhandled_case(purpose);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
// Actual memory size is required if explicit size is specified for clamping to
|
||||
// the actual memory allocation size while rounding to the non-coherent atom
|
||||
// size (offset + size passed to vkFlushMappedMemoryRanges inside this function
|
||||
// must be either a multiple of nonCoherentAtomSize (but not exceeding the
|
||||
// memory size) or equal to the memory size).
|
||||
void FlushMappedMemoryRange(const VulkanProvider& provider,
|
||||
VkDeviceMemory memory, uint32_t memory_type,
|
||||
VkDeviceSize offset = 0,
|
||||
VkDeviceSize memory_size = VK_WHOLE_SIZE,
|
||||
VkDeviceSize size = VK_WHOLE_SIZE);
|
||||
|
||||
inline VkExtent2D GetMax2DFramebufferExtent(const VulkanProvider& provider) {
|
||||
const VkPhysicalDeviceLimits& limits = provider.device_properties().limits;
|
||||
VkExtent2D max_extent;
|
||||
max_extent.width =
|
||||
std::min(limits.maxFramebufferWidth, limits.maxImageDimension2D);
|
||||
max_extent.height =
|
||||
std::min(limits.maxFramebufferHeight, limits.maxImageDimension2D);
|
||||
return max_extent;
|
||||
}
|
||||
|
||||
inline void InitializeSubresourceRange(
|
||||
VkImageSubresourceRange& range,
|
||||
VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
@@ -93,13 +129,22 @@ inline void InitializeSubresourceRange(
|
||||
range.layerCount = layer_count;
|
||||
}
|
||||
|
||||
// Creates a buffer backed by a dedicated allocation. If using a mappable memory
|
||||
// purpose (upload/readback), the allocation size will be aligned to
|
||||
// nonCoherentAtomSize.
|
||||
// Creates a buffer backed by a dedicated allocation. The allocation size will
|
||||
// NOT be aligned to nonCoherentAtomSize - if mapping or flushing not the whole
|
||||
// size, memory_size_out must be used for clamping the range.
|
||||
bool CreateDedicatedAllocationBuffer(
|
||||
const VulkanProvider& provider, VkDeviceSize size, VkBufferUsageFlags usage,
|
||||
MemoryPurpose memory_purpose, VkBuffer& buffer_out,
|
||||
VkDeviceMemory& memory_out, uint32_t* memory_type_out = nullptr);
|
||||
VkDeviceMemory& memory_out, uint32_t* memory_type_out = nullptr,
|
||||
VkDeviceSize* memory_size_out = nullptr);
|
||||
|
||||
bool CreateDedicatedAllocationImage(const VulkanProvider& provider,
|
||||
const VkImageCreateInfo& create_info,
|
||||
MemoryPurpose memory_purpose,
|
||||
VkImage& image_out,
|
||||
VkDeviceMemory& memory_out,
|
||||
uint32_t* memory_type_out = nullptr,
|
||||
VkDeviceSize* memory_size_out = nullptr);
|
||||
|
||||
inline VkShaderModule CreateShaderModule(const VulkanProvider& provider,
|
||||
const void* code, size_t code_size) {
|
||||
@@ -114,7 +159,7 @@ inline VkShaderModule CreateShaderModule(const VulkanProvider& provider,
|
||||
provider.device(), &shader_module_create_info, nullptr,
|
||||
&shader_module) == VK_SUCCESS
|
||||
? shader_module
|
||||
: nullptr;
|
||||
: VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
@@ -34,7 +34,7 @@ class VulkanWindowDemoApp final : public WindowDemoApp {
|
||||
|
||||
std::unique_ptr<GraphicsProvider> VulkanWindowDemoApp::CreateGraphicsProvider()
|
||||
const {
|
||||
return VulkanProvider::Create();
|
||||
return VulkanProvider::Create(true);
|
||||
}
|
||||
|
||||
} // namespace vulkan
|
||||
|
||||
Reference in New Issue
Block a user