Merge branch 'master' into vulkan

This commit is contained in:
Triang3l
2022-02-03 21:12:10 +03:00
501 changed files with 56183 additions and 10196 deletions

View 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)

View 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)

View 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)

View File

@@ -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)

View File

@@ -0,0 +1,2 @@
// VK_KHR_android_surface functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkCreateAndroidSurfaceKHR)

View File

@@ -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)

View 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)

View File

@@ -0,0 +1,3 @@
// VK_KHR_win32_surface functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkCreateWin32SurfaceKHR)
XE_UI_VULKAN_FUNCTION(vkGetPhysicalDeviceWin32PresentationSupportKHR)

View File

@@ -0,0 +1,2 @@
// VK_KHR_xcb_surface functions used in Xenia.
XE_UI_VULKAN_FUNCTION(vkCreateXcbSurfaceKHR)

View File

@@ -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

View File

@@ -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_

View File

@@ -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 {

View File

@@ -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_;

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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)] = {};
};

View 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

View 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_

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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