[Vulkan] Context, remove Volk
This commit is contained in:
parent
dfa181a529
commit
203bf64d88
|
@ -226,7 +226,6 @@ solution("xenia")
|
||||||
include("third_party/SDL2.lua")
|
include("third_party/SDL2.lua")
|
||||||
include("third_party/snappy.lua")
|
include("third_party/snappy.lua")
|
||||||
include("third_party/spirv-tools.lua")
|
include("third_party/spirv-tools.lua")
|
||||||
include("third_party/volk.lua")
|
|
||||||
include("third_party/xxhash.lua")
|
include("third_party/xxhash.lua")
|
||||||
|
|
||||||
include("src/xenia")
|
include("src/xenia")
|
||||||
|
|
|
@ -20,7 +20,6 @@ project("xenia-app")
|
||||||
"mspack",
|
"mspack",
|
||||||
"snappy",
|
"snappy",
|
||||||
"spirv-tools",
|
"spirv-tools",
|
||||||
"volk",
|
|
||||||
"xenia-app-discord",
|
"xenia-app-discord",
|
||||||
"xenia-apu",
|
"xenia-apu",
|
||||||
"xenia-apu-nop",
|
"xenia-apu-nop",
|
||||||
|
|
|
@ -22,7 +22,6 @@ project("xenia-hid-demo")
|
||||||
links({
|
links({
|
||||||
"fmt",
|
"fmt",
|
||||||
"imgui",
|
"imgui",
|
||||||
"volk",
|
|
||||||
"xenia-base",
|
"xenia-base",
|
||||||
"xenia-helper-sdl",
|
"xenia-helper-sdl",
|
||||||
"xenia-hid",
|
"xenia-hid",
|
||||||
|
|
|
@ -9,9 +9,6 @@
|
||||||
|
|
||||||
#include "xenia/ui/d3d12/d3d12_context.h"
|
#include "xenia/ui/d3d12/d3d12_context.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include "xenia/base/cvar.h"
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/ui/d3d12/d3d12_immediate_drawer.h"
|
#include "xenia/ui/d3d12/d3d12_immediate_drawer.h"
|
||||||
|
@ -19,9 +16,6 @@
|
||||||
#include "xenia/ui/d3d12/d3d12_util.h"
|
#include "xenia/ui/d3d12/d3d12_util.h"
|
||||||
#include "xenia/ui/window.h"
|
#include "xenia/ui/window.h"
|
||||||
|
|
||||||
DEFINE_bool(d3d12_random_clear_color, false,
|
|
||||||
"Randomize presentation back buffer clear color.", "D3D12");
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
namespace d3d12 {
|
namespace d3d12 {
|
||||||
|
@ -32,14 +26,17 @@ D3D12Context::D3D12Context(D3D12Provider* provider, Window* target_window)
|
||||||
D3D12Context::~D3D12Context() { Shutdown(); }
|
D3D12Context::~D3D12Context() { Shutdown(); }
|
||||||
|
|
||||||
bool D3D12Context::Initialize() {
|
bool D3D12Context::Initialize() {
|
||||||
|
context_lost_ = false;
|
||||||
|
|
||||||
|
if (!target_window_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto& provider = GetD3D12Provider();
|
auto& provider = GetD3D12Provider();
|
||||||
auto dxgi_factory = provider.GetDXGIFactory();
|
auto dxgi_factory = provider.GetDXGIFactory();
|
||||||
auto device = provider.GetDevice();
|
auto device = provider.GetDevice();
|
||||||
auto direct_queue = provider.GetDirectQueue();
|
auto direct_queue = provider.GetDirectQueue();
|
||||||
|
|
||||||
context_lost_ = false;
|
|
||||||
|
|
||||||
if (target_window_) {
|
|
||||||
swap_fence_current_value_ = 1;
|
swap_fence_current_value_ = 1;
|
||||||
swap_fence_completed_value_ = 0;
|
swap_fence_completed_value_ = 0;
|
||||||
swap_fence_completion_event_ = CreateEvent(nullptr, false, false, nullptr);
|
swap_fence_completion_event_ = CreateEvent(nullptr, false, false, nullptr);
|
||||||
|
@ -75,7 +72,7 @@ bool D3D12Context::Initialize() {
|
||||||
IDXGISwapChain1* swap_chain_1;
|
IDXGISwapChain1* swap_chain_1;
|
||||||
if (FAILED(dxgi_factory->CreateSwapChainForHwnd(
|
if (FAILED(dxgi_factory->CreateSwapChainForHwnd(
|
||||||
provider.GetDirectQueue(),
|
provider.GetDirectQueue(),
|
||||||
static_cast<HWND>(target_window_->native_handle()),
|
reinterpret_cast<HWND>(target_window_->native_handle()),
|
||||||
&swap_chain_desc, nullptr, nullptr, &swap_chain_1))) {
|
&swap_chain_desc, nullptr, nullptr, &swap_chain_1))) {
|
||||||
XELOGE("Failed to create a DXGI swap chain");
|
XELOGE("Failed to create a DXGI swap chain");
|
||||||
Shutdown();
|
Shutdown();
|
||||||
|
@ -136,7 +133,6 @@ bool D3D12Context::Initialize() {
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -223,9 +219,11 @@ ImmediateDrawer* D3D12Context::immediate_drawer() {
|
||||||
return immediate_drawer_.get();
|
return immediate_drawer_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D12Context::BeginSwap() {
|
bool D3D12Context::WasLost() { return context_lost_; }
|
||||||
|
|
||||||
|
bool D3D12Context::BeginSwap() {
|
||||||
if (!target_window_ || context_lost_) {
|
if (!target_window_ || context_lost_) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize the swap chain if the window is resized.
|
// Resize the swap chain if the window is resized.
|
||||||
|
@ -252,13 +250,13 @@ void D3D12Context::BeginSwap() {
|
||||||
kSwapChainBufferCount, target_window_width, target_window_height,
|
kSwapChainBufferCount, target_window_width, target_window_height,
|
||||||
kSwapChainFormat, 0))) {
|
kSwapChainFormat, 0))) {
|
||||||
context_lost_ = true;
|
context_lost_ = true;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
swap_chain_width_ = target_window_width;
|
swap_chain_width_ = target_window_width;
|
||||||
swap_chain_height_ = target_window_height;
|
swap_chain_height_ = target_window_height;
|
||||||
if (!InitializeSwapChainBuffers()) {
|
if (!InitializeSwapChainBuffers()) {
|
||||||
context_lost_ = true;
|
context_lost_ = true;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,18 +293,11 @@ void D3D12Context::BeginSwap() {
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE back_buffer_rtv = GetSwapChainBackBufferRTV();
|
D3D12_CPU_DESCRIPTOR_HANDLE back_buffer_rtv = GetSwapChainBackBufferRTV();
|
||||||
swap_command_list_->OMSetRenderTargets(1, &back_buffer_rtv, TRUE, nullptr);
|
swap_command_list_->OMSetRenderTargets(1, &back_buffer_rtv, TRUE, nullptr);
|
||||||
float clear_color[4];
|
float clear_color[4];
|
||||||
if (cvars::d3d12_random_clear_color) {
|
GetClearColor(clear_color);
|
||||||
clear_color[0] = rand() / float(RAND_MAX); // NOLINT(runtime/threadsafe_fn)
|
|
||||||
clear_color[1] = 1.0f;
|
|
||||||
clear_color[2] = 0.0f;
|
|
||||||
} else {
|
|
||||||
clear_color[0] = 0.0f;
|
|
||||||
clear_color[1] = 0.0f;
|
|
||||||
clear_color[2] = 0.0f;
|
|
||||||
}
|
|
||||||
clear_color[3] = 1.0f;
|
|
||||||
swap_command_list_->ClearRenderTargetView(back_buffer_rtv, clear_color, 0,
|
swap_command_list_->ClearRenderTargetView(back_buffer_rtv, clear_color, 0,
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D12Context::EndSwap() {
|
void D3D12Context::EndSwap() {
|
||||||
|
|
|
@ -28,9 +28,9 @@ class D3D12Context : public GraphicsContext {
|
||||||
|
|
||||||
ImmediateDrawer* immediate_drawer() override;
|
ImmediateDrawer* immediate_drawer() override;
|
||||||
|
|
||||||
bool WasLost() override { return context_lost_; }
|
bool WasLost() override;
|
||||||
|
|
||||||
void BeginSwap() override;
|
bool BeginSwap() override;
|
||||||
void EndSwap() override;
|
void EndSwap() override;
|
||||||
|
|
||||||
std::unique_ptr<RawImage> Capture() override;
|
std::unique_ptr<RawImage> Capture() override;
|
||||||
|
|
|
@ -9,8 +9,13 @@
|
||||||
|
|
||||||
#include "xenia/ui/graphics_context.h"
|
#include "xenia/ui/graphics_context.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "xenia/base/cvar.h"
|
||||||
#include "xenia/ui/graphics_provider.h"
|
#include "xenia/ui/graphics_provider.h"
|
||||||
|
|
||||||
|
DEFINE_bool(random_clear_color, false, "Randomize window clear color.", "UI");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
@ -26,5 +31,18 @@ bool GraphicsContext::MakeCurrent() { return true; }
|
||||||
|
|
||||||
void GraphicsContext::ClearCurrent() {}
|
void GraphicsContext::ClearCurrent() {}
|
||||||
|
|
||||||
|
void GraphicsContext::GetClearColor(float* rgba) {
|
||||||
|
if (cvars::random_clear_color) {
|
||||||
|
rgba[0] = rand() / float(RAND_MAX); // NOLINT(runtime/threadsafe_fn)
|
||||||
|
rgba[1] = 1.0f;
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
} else {
|
||||||
|
rgba[0] = 0.0f;
|
||||||
|
rgba[1] = 0.0f;
|
||||||
|
rgba[2] = 0.0f;
|
||||||
|
}
|
||||||
|
rgba[3] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -51,7 +51,8 @@ class GraphicsContext {
|
||||||
// This context must be made current in order for this call to work properly.
|
// This context must be made current in order for this call to work properly.
|
||||||
virtual bool WasLost() = 0;
|
virtual bool WasLost() = 0;
|
||||||
|
|
||||||
virtual void BeginSwap() = 0;
|
// Returns true if able to draw now (the target surface is available).
|
||||||
|
virtual bool BeginSwap() = 0;
|
||||||
virtual void EndSwap() = 0;
|
virtual void EndSwap() = 0;
|
||||||
|
|
||||||
virtual std::unique_ptr<RawImage> Capture() = 0;
|
virtual std::unique_ptr<RawImage> Capture() = 0;
|
||||||
|
@ -59,6 +60,8 @@ class GraphicsContext {
|
||||||
protected:
|
protected:
|
||||||
explicit GraphicsContext(GraphicsProvider* provider, Window* target_window);
|
explicit GraphicsContext(GraphicsProvider* provider, Window* target_window);
|
||||||
|
|
||||||
|
static void GetClearColor(float* rgba);
|
||||||
|
|
||||||
GraphicsProvider* provider_ = nullptr;
|
GraphicsProvider* provider_ = nullptr;
|
||||||
Window* target_window_ = nullptr;
|
Window* target_window_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,24 @@
|
||||||
|
|
||||||
#include "xenia/ui/vulkan/vulkan_context.h"
|
#include "xenia/ui/vulkan/vulkan_context.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
||||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||||
|
#include "xenia/ui/vulkan/vulkan_util.h"
|
||||||
|
#include "xenia/ui/window.h"
|
||||||
|
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
#include <android/native_window.h>
|
||||||
|
#elif XE_PLATFORM_WIN32
|
||||||
|
#include "xenia/base/platform_win.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
@ -19,21 +35,842 @@ namespace vulkan {
|
||||||
VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window)
|
VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window)
|
||||||
: GraphicsContext(provider, target_window) {}
|
: GraphicsContext(provider, target_window) {}
|
||||||
|
|
||||||
bool VulkanContext::Initialize() { return false; }
|
bool VulkanContext::Initialize() {
|
||||||
|
context_lost_ = false;
|
||||||
|
|
||||||
|
if (!target_window_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
|
||||||
|
VkFenceCreateInfo fence_create_info;
|
||||||
|
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||||
|
fence_create_info.pNext = nullptr;
|
||||||
|
fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
|
||||||
|
VkCommandPoolCreateInfo command_pool_create_info;
|
||||||
|
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||||
|
command_pool_create_info.pNext = nullptr;
|
||||||
|
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
|
||||||
|
command_pool_create_info.queueFamilyIndex =
|
||||||
|
provider.queue_family_graphics_compute();
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo command_buffer_allocate_info;
|
||||||
|
command_buffer_allocate_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
|
command_buffer_allocate_info.pNext = nullptr;
|
||||||
|
command_buffer_allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
|
command_buffer_allocate_info.commandBufferCount = 1;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < kSwapchainMaxImageCount; ++i) {
|
||||||
|
SwapSubmission& submission = swap_submissions_[i];
|
||||||
|
if (dfn.vkCreateFence(device, &fence_create_info, nullptr,
|
||||||
|
&submission.fence) != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create the Vulkan composition fences");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dfn.vkCreateCommandPool(device, &command_pool_create_info, nullptr,
|
||||||
|
&submission.command_pool) != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create the Vulkan composition command pools");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
command_buffer_allocate_info.commandPool = submission.command_pool;
|
||||||
|
if (dfn.vkAllocateCommandBuffers(device, &command_buffer_allocate_info,
|
||||||
|
&submission.command_buffer) !=
|
||||||
|
VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to allocate the Vulkan composition command buffers");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSemaphoreCreateInfo semaphore_create_info;
|
||||||
|
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
|
semaphore_create_info.pNext = nullptr;
|
||||||
|
semaphore_create_info.flags = 0;
|
||||||
|
if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr,
|
||||||
|
&swap_image_acquisition_semaphore_) != VK_SUCCESS) {
|
||||||
|
XELOGE(
|
||||||
|
"Failed to create the Vulkan swap chain image acquisition semaphore");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr,
|
||||||
|
&swap_render_completion_semaphore_) != VK_SUCCESS) {
|
||||||
|
XELOGE(
|
||||||
|
"Failed to create the Vulkan swap chain rendering completion "
|
||||||
|
"semaphore");
|
||||||
|
Shutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
immediate_drawer_ = std::make_unique<VulkanImmediateDrawer>(*this);
|
||||||
|
// TODO(Triang3l): Initialize the immediate drawer.
|
||||||
|
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanContext::Shutdown() {
|
||||||
|
if (!target_window_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AwaitAllSwapSubmissionsCompletion();
|
||||||
|
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
|
||||||
|
VkInstance instance = provider.instance();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
|
||||||
|
swap_swapchain_image_current_ = UINT32_MAX;
|
||||||
|
DestroySwapchainFramebuffers();
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device,
|
||||||
|
swap_swapchain_);
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device,
|
||||||
|
swap_render_pass_);
|
||||||
|
util::DestroyAndNullHandle(ifn.vkDestroySurfaceKHR, instance, swap_surface_);
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = false;
|
||||||
|
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySemaphore, device,
|
||||||
|
swap_render_completion_semaphore_);
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySemaphore, device,
|
||||||
|
swap_image_acquisition_semaphore_);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < kSwapchainMaxImageCount; ++i) {
|
||||||
|
SwapSubmission& submission = swap_submissions_[i];
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroyCommandPool, device,
|
||||||
|
submission.command_pool);
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroyFence, device, submission.fence);
|
||||||
|
}
|
||||||
|
swap_submission_current_ = 1;
|
||||||
|
swap_submission_completed_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
ImmediateDrawer* VulkanContext::immediate_drawer() {
|
ImmediateDrawer* VulkanContext::immediate_drawer() {
|
||||||
return immediate_drawer_.get();
|
return immediate_drawer_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanContext::BeginSwap() {}
|
bool VulkanContext::WasLost() { return context_lost_; }
|
||||||
|
|
||||||
void VulkanContext::EndSwap() {}
|
bool VulkanContext::BeginSwap() {
|
||||||
|
if (!target_window_ || context_lost_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
|
||||||
|
VkPhysicalDevice physical_device = provider.physical_device();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
|
||||||
|
uint32_t window_width = uint32_t(target_window_->scaled_width());
|
||||||
|
uint32_t window_height = uint32_t(target_window_->scaled_height());
|
||||||
|
if (swap_swapchain_ != VK_NULL_HANDLE) {
|
||||||
|
// Check if need to resize.
|
||||||
|
assert_true(swap_surface_ != VK_NULL_HANDLE);
|
||||||
|
// Win32 has minImageExtent == maxImageExtent == currentExtent, so the
|
||||||
|
// capabilities need to be requested every time they are needed.
|
||||||
|
VkSurfaceCapabilitiesKHR surface_capabilities;
|
||||||
|
if (ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
|
||||||
|
physical_device, swap_surface_, &surface_capabilities) ==
|
||||||
|
VK_SUCCESS) {
|
||||||
|
if (swap_swapchain_extent_.width !=
|
||||||
|
xe::clamp(window_width, surface_capabilities.minImageExtent.width,
|
||||||
|
surface_capabilities.maxImageExtent.width) ||
|
||||||
|
swap_swapchain_extent_.height !=
|
||||||
|
xe::clamp(window_height,
|
||||||
|
surface_capabilities.minImageExtent.height,
|
||||||
|
surface_capabilities.maxImageExtent.height)) {
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the swap chain turns out to be out of date, try to recreate it on the
|
||||||
|
// second attempt (to avoid skipping the frame entirely in this case).
|
||||||
|
for (uint32_t attempt = 0; attempt < 2; ++attempt) {
|
||||||
|
if (swap_swapchain_or_surface_recreation_needed_) {
|
||||||
|
// If recreation fails, don't retry until some change happens.
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = false;
|
||||||
|
|
||||||
|
AwaitAllSwapSubmissionsCompletion();
|
||||||
|
|
||||||
|
uint32_t queue_family_graphics_compute =
|
||||||
|
provider.queue_family_graphics_compute();
|
||||||
|
|
||||||
|
if (swap_surface_ == VK_NULL_HANDLE) {
|
||||||
|
assert_true(swap_swapchain_ == VK_NULL_HANDLE);
|
||||||
|
assert_true(swap_swapchain_image_views_.empty());
|
||||||
|
assert_true(swap_swapchain_framebuffers_.empty());
|
||||||
|
|
||||||
|
VkInstance instance = provider.instance();
|
||||||
|
|
||||||
|
VkResult surface_create_result;
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
VkAndroidSurfaceCreateInfoKHR surface_create_info;
|
||||||
|
surface_create_info.window =
|
||||||
|
reinterpret_cast<ANativeWindow*>(target_window_->native_handle());
|
||||||
|
if (!surface_create_info.window) {
|
||||||
|
// The activity is in background - try again when the window is
|
||||||
|
// created.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
surface_create_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
|
||||||
|
surface_create_info.pNext = nullptr;
|
||||||
|
surface_create_info.flags = 0;
|
||||||
|
surface_create_result = ifn.vkCreateAndroidSurfaceKHR(
|
||||||
|
instance, &surface_create_info, nullptr, &swap_surface_);
|
||||||
|
#elif XE_PLATFORM_WIN32
|
||||||
|
VkWin32SurfaceCreateInfoKHR surface_create_info;
|
||||||
|
surface_create_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
|
||||||
|
surface_create_info.pNext = nullptr;
|
||||||
|
surface_create_info.flags = 0;
|
||||||
|
surface_create_info.hinstance = reinterpret_cast<HINSTANCE>(
|
||||||
|
target_window_->native_platform_handle());
|
||||||
|
surface_create_info.hwnd =
|
||||||
|
reinterpret_cast<HWND>(target_window_->native_handle());
|
||||||
|
surface_create_result = ifn.vkCreateWin32SurfaceKHR(
|
||||||
|
instance, &surface_create_info, nullptr, &swap_surface_);
|
||||||
|
#else
|
||||||
|
#error No Vulkan surface creation for the target platform.
|
||||||
|
#endif
|
||||||
|
if (surface_create_result != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create a Vulkan surface");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME(Triang3l): Allow a separate queue for present - see
|
||||||
|
// vulkan_provider.cc for details.
|
||||||
|
VkBool32 surface_supported;
|
||||||
|
if (ifn.vkGetPhysicalDeviceSurfaceSupportKHR(
|
||||||
|
physical_device, queue_family_graphics_compute, swap_surface_,
|
||||||
|
&surface_supported) != VK_SUCCESS ||
|
||||||
|
!surface_supported) {
|
||||||
|
XELOGE(
|
||||||
|
"The Vulkan graphics and compute queue doesn't support "
|
||||||
|
"presentation");
|
||||||
|
ifn.vkDestroySurfaceKHR(instance, swap_surface_, nullptr);
|
||||||
|
swap_surface_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose an SDR format, 8.8.8.8 preferred, or if not available, any
|
||||||
|
// supported. Windows and GNU/Linux use B8G8R8A8, Android uses R8G8B8A8.
|
||||||
|
std::vector<VkSurfaceFormatKHR> surface_formats;
|
||||||
|
VkResult surface_formats_get_result;
|
||||||
|
for (;;) {
|
||||||
|
uint32_t surface_format_count = uint32_t(surface_formats.size());
|
||||||
|
bool surface_formats_was_empty = !surface_format_count;
|
||||||
|
surface_formats_get_result = ifn.vkGetPhysicalDeviceSurfaceFormatsKHR(
|
||||||
|
physical_device, swap_surface_, &surface_format_count,
|
||||||
|
surface_formats_was_empty ? nullptr : surface_formats.data());
|
||||||
|
// If the original surface format count was 0 (first call), SUCCESS is
|
||||||
|
// returned, not INCOMPLETE.
|
||||||
|
if (surface_formats_get_result == VK_SUCCESS ||
|
||||||
|
surface_formats_get_result == VK_INCOMPLETE) {
|
||||||
|
surface_formats.resize(surface_format_count);
|
||||||
|
if (surface_formats_get_result == VK_SUCCESS &&
|
||||||
|
(!surface_formats_was_empty || !surface_format_count)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (surface_formats_get_result != VK_SUCCESS ||
|
||||||
|
surface_formats.empty()) {
|
||||||
|
XELOGE("Failed to get Vulkan surface formats");
|
||||||
|
ifn.vkDestroySurfaceKHR(instance, swap_surface_, nullptr);
|
||||||
|
swap_surface_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VkSurfaceFormatKHR surface_format;
|
||||||
|
if (surface_formats.size() == 1 &&
|
||||||
|
surface_formats[0].format == VK_FORMAT_UNDEFINED) {
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
surface_format.format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
#else
|
||||||
|
surface_format.format = VK_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
#endif
|
||||||
|
surface_format.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||||||
|
} else {
|
||||||
|
surface_format = surface_formats.front();
|
||||||
|
for (const VkSurfaceFormatKHR& surface_format_current :
|
||||||
|
surface_formats) {
|
||||||
|
if (surface_format_current.format == VK_FORMAT_B8G8R8A8_UNORM ||
|
||||||
|
surface_format_current.format == VK_FORMAT_R8G8B8A8_UNORM ||
|
||||||
|
surface_format_current.format ==
|
||||||
|
VK_FORMAT_A8B8G8R8_UNORM_PACK32) {
|
||||||
|
surface_format = surface_format_current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (swap_surface_format_.format != surface_format.format) {
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroyRenderPass, device,
|
||||||
|
swap_render_pass_);
|
||||||
|
}
|
||||||
|
swap_surface_format_ = surface_format;
|
||||||
|
|
||||||
|
// Prefer a low-latency present mode because emulation is done on the
|
||||||
|
// same queue, ordered by the decreasing amount of tearing, fall back to
|
||||||
|
// FIFO if no other options.
|
||||||
|
swap_surface_present_mode_ = VK_PRESENT_MODE_FIFO_KHR;
|
||||||
|
std::vector<VkPresentModeKHR> surface_present_modes;
|
||||||
|
VkResult surface_present_modes_get_result;
|
||||||
|
for (;;) {
|
||||||
|
uint32_t surface_present_mode_count =
|
||||||
|
uint32_t(surface_present_modes.size());
|
||||||
|
bool surface_present_modes_was_empty = !surface_present_mode_count;
|
||||||
|
surface_present_modes_get_result =
|
||||||
|
ifn.vkGetPhysicalDeviceSurfacePresentModesKHR(
|
||||||
|
physical_device, swap_surface_, &surface_present_mode_count,
|
||||||
|
surface_present_modes_was_empty
|
||||||
|
? nullptr
|
||||||
|
: surface_present_modes.data());
|
||||||
|
// If the original surface present mode count was 0 (first call),
|
||||||
|
// SUCCESS is returned, not INCOMPLETE.
|
||||||
|
if (surface_present_modes_get_result == VK_SUCCESS ||
|
||||||
|
surface_present_modes_get_result == VK_INCOMPLETE) {
|
||||||
|
surface_present_modes.resize(surface_present_mode_count);
|
||||||
|
if (surface_present_modes_get_result == VK_SUCCESS &&
|
||||||
|
(!surface_present_modes_was_empty ||
|
||||||
|
!surface_present_mode_count)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (surface_present_modes_get_result == VK_SUCCESS) {
|
||||||
|
static const VkPresentModeKHR present_modes_preferred[] = {
|
||||||
|
VK_PRESENT_MODE_MAILBOX_KHR,
|
||||||
|
VK_PRESENT_MODE_FIFO_RELAXED_KHR,
|
||||||
|
VK_PRESENT_MODE_IMMEDIATE_KHR,
|
||||||
|
};
|
||||||
|
for (size_t i = 0; i < xe::countof(present_modes_preferred); ++i) {
|
||||||
|
VkPresentModeKHR present_mode_preferred =
|
||||||
|
present_modes_preferred[i];
|
||||||
|
if (std::find(surface_present_modes.cbegin(),
|
||||||
|
surface_present_modes.cend(),
|
||||||
|
present_mode_preferred) !=
|
||||||
|
surface_present_modes.cend()) {
|
||||||
|
swap_surface_present_mode_ = present_mode_preferred;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate the swap chain unconditionally because a request was made.
|
||||||
|
// The old swapchain will be retired even if vkCreateSwapchainKHR fails,
|
||||||
|
// so destroy the framebuffers and the image views unconditionally.
|
||||||
|
// If anything fails before the vkCreateSwapchainKHR call, also destroy
|
||||||
|
// the swapchain to fulfill the request.
|
||||||
|
// It was safe to handle errors while creating the surface without caring
|
||||||
|
// about destroying the swapchain, because there can't be swapchain when
|
||||||
|
// there is no surface.
|
||||||
|
DestroySwapchainFramebuffers();
|
||||||
|
// Win32 has minImageExtent == maxImageExtent == currentExtent, so the
|
||||||
|
// capabilities need to be requested every time they are needed.
|
||||||
|
VkSurfaceCapabilitiesKHR surface_capabilities;
|
||||||
|
if (ifn.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
|
||||||
|
physical_device, swap_surface_, &surface_capabilities) !=
|
||||||
|
VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to get Vulkan surface capabilities");
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device,
|
||||||
|
swap_swapchain_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// TODO(Triang3l): Support rotated transforms.
|
||||||
|
if (!(surface_capabilities.supportedTransforms &
|
||||||
|
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)) {
|
||||||
|
XELOGE("The Vulkan surface doesn't support identity transform");
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device,
|
||||||
|
swap_swapchain_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VkSwapchainCreateInfoKHR swapchain_create_info;
|
||||||
|
swapchain_create_info.imageExtent.width =
|
||||||
|
xe::clamp(window_width, surface_capabilities.minImageExtent.width,
|
||||||
|
surface_capabilities.maxImageExtent.width);
|
||||||
|
swapchain_create_info.imageExtent.height =
|
||||||
|
xe::clamp(window_height, surface_capabilities.minImageExtent.height,
|
||||||
|
surface_capabilities.maxImageExtent.height);
|
||||||
|
if (!swapchain_create_info.imageExtent.width ||
|
||||||
|
!swapchain_create_info.imageExtent.height) {
|
||||||
|
// Everything else is fine with the surface, but the window is too
|
||||||
|
// small, try again when the window may be resized (won't try to do some
|
||||||
|
// vkCreate* every BeginSwap, will reach this part again, so okay to set
|
||||||
|
// swap_swapchain_or_surface_recreation_needed_ back to true).
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = true;
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device,
|
||||||
|
swap_swapchain_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||||
|
swapchain_create_info.pNext = nullptr;
|
||||||
|
swapchain_create_info.flags = 0;
|
||||||
|
swapchain_create_info.surface = swap_surface_;
|
||||||
|
swapchain_create_info.minImageCount = kSwapchainMaxImageCount;
|
||||||
|
if (surface_capabilities.maxImageCount) {
|
||||||
|
swapchain_create_info.minImageCount =
|
||||||
|
std::min(swapchain_create_info.minImageCount,
|
||||||
|
surface_capabilities.maxImageCount);
|
||||||
|
}
|
||||||
|
swapchain_create_info.imageFormat = swap_surface_format_.format;
|
||||||
|
swapchain_create_info.imageColorSpace = swap_surface_format_.colorSpace;
|
||||||
|
swapchain_create_info.imageArrayLayers = 1;
|
||||||
|
swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||||
|
// FIXME(Triang3l): Allow a separate queue for present - see
|
||||||
|
// vulkan_provider.cc for details.
|
||||||
|
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
swapchain_create_info.queueFamilyIndexCount = 1;
|
||||||
|
swapchain_create_info.pQueueFamilyIndices =
|
||||||
|
&queue_family_graphics_compute;
|
||||||
|
// TODO(Triang3l): Support rotated transforms.
|
||||||
|
swapchain_create_info.preTransform =
|
||||||
|
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||||
|
swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||||
|
if (!(surface_capabilities.supportedCompositeAlpha &
|
||||||
|
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) {
|
||||||
|
if (surface_capabilities.supportedCompositeAlpha &
|
||||||
|
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) {
|
||||||
|
swapchain_create_info.compositeAlpha =
|
||||||
|
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
|
||||||
|
} else {
|
||||||
|
// Whatever. supportedCompositeAlpha must have at least one bit set,
|
||||||
|
// but if it somehow doesn't (impossible situation according to the
|
||||||
|
// specification, but who knows), just assume opaque.
|
||||||
|
uint32_t composite_alpha_bit_index;
|
||||||
|
if (xe::bit_scan_forward(
|
||||||
|
uint32_t(surface_capabilities.supportedCompositeAlpha),
|
||||||
|
&composite_alpha_bit_index)) {
|
||||||
|
swapchain_create_info.compositeAlpha = VkCompositeAlphaFlagBitsKHR(
|
||||||
|
uint32_t(1) << composite_alpha_bit_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
swapchain_create_info.presentMode = swap_surface_present_mode_;
|
||||||
|
swapchain_create_info.clipped = VK_TRUE;
|
||||||
|
swapchain_create_info.oldSwapchain = swap_swapchain_;
|
||||||
|
VkResult swapchain_create_result = dfn.vkCreateSwapchainKHR(
|
||||||
|
device, &swapchain_create_info, nullptr, &swap_swapchain_);
|
||||||
|
// The old swapchain is retired even if vkCreateSwapchainKHR has failed.
|
||||||
|
if (swapchain_create_info.oldSwapchain != VK_NULL_HANDLE) {
|
||||||
|
dfn.vkDestroySwapchainKHR(device, swapchain_create_info.oldSwapchain,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
if (swapchain_create_result != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create a Vulkan swapchain");
|
||||||
|
swap_swapchain_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
swap_swapchain_extent_ = swapchain_create_info.imageExtent;
|
||||||
|
|
||||||
|
// The render pass is needed to create framebuffers for swapchain images.
|
||||||
|
// It depends on the surface format, and thus can be reused with different
|
||||||
|
// surfaces by different swapchains, so it has separate lifetime tracking.
|
||||||
|
// It's safe to fail now (though destroying the new swapchain), because
|
||||||
|
// the request to destroy the old VkSwapchain somehow (after retiring, or
|
||||||
|
// directly) has been fulfilled.
|
||||||
|
if (swap_render_pass_ == VK_NULL_HANDLE) {
|
||||||
|
VkAttachmentDescription render_pass_color_attachment;
|
||||||
|
render_pass_color_attachment.flags = 0;
|
||||||
|
render_pass_color_attachment.format = swap_surface_format_.format;
|
||||||
|
render_pass_color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
render_pass_color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
render_pass_color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
|
render_pass_color_attachment.stencilLoadOp =
|
||||||
|
VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||||
|
render_pass_color_attachment.stencilStoreOp =
|
||||||
|
VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
render_pass_color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||||
|
render_pass_color_attachment.finalLayout =
|
||||||
|
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||||
|
VkAttachmentReference render_pass_color_attachment_reference;
|
||||||
|
render_pass_color_attachment_reference.attachment = 0;
|
||||||
|
render_pass_color_attachment_reference.layout =
|
||||||
|
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||||
|
VkSubpassDescription render_pass_subpass;
|
||||||
|
render_pass_subpass.flags = 0;
|
||||||
|
render_pass_subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||||
|
render_pass_subpass.inputAttachmentCount = 0;
|
||||||
|
render_pass_subpass.pInputAttachments = nullptr;
|
||||||
|
render_pass_subpass.colorAttachmentCount = 1;
|
||||||
|
render_pass_subpass.pColorAttachments =
|
||||||
|
&render_pass_color_attachment_reference;
|
||||||
|
render_pass_subpass.pResolveAttachments = nullptr;
|
||||||
|
render_pass_subpass.pDepthStencilAttachment = nullptr;
|
||||||
|
render_pass_subpass.preserveAttachmentCount = 0;
|
||||||
|
render_pass_subpass.pPreserveAttachments = nullptr;
|
||||||
|
VkRenderPassCreateInfo render_pass_create_info;
|
||||||
|
render_pass_create_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||||
|
render_pass_create_info.pNext = nullptr;
|
||||||
|
render_pass_create_info.flags = 0;
|
||||||
|
render_pass_create_info.attachmentCount = 1;
|
||||||
|
render_pass_create_info.pAttachments = &render_pass_color_attachment;
|
||||||
|
render_pass_create_info.subpassCount = 1;
|
||||||
|
render_pass_create_info.pSubpasses = &render_pass_subpass;
|
||||||
|
render_pass_create_info.dependencyCount = 0;
|
||||||
|
render_pass_create_info.pDependencies = nullptr;
|
||||||
|
if (dfn.vkCreateRenderPass(device, &render_pass_create_info, nullptr,
|
||||||
|
&swap_render_pass_) != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create the Vulkan presentation render pass.");
|
||||||
|
dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr);
|
||||||
|
swap_swapchain_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<VkImage> swapchain_images;
|
||||||
|
uint32_t swapchain_image_count;
|
||||||
|
VkResult swapchain_images_get_result;
|
||||||
|
for (;;) {
|
||||||
|
swapchain_image_count = uint32_t(swapchain_images.size());
|
||||||
|
bool swapchain_images_was_empty = !swapchain_image_count;
|
||||||
|
swapchain_images_get_result = dfn.vkGetSwapchainImagesKHR(
|
||||||
|
device, swap_swapchain_, &swapchain_image_count,
|
||||||
|
swapchain_images_was_empty ? nullptr : swapchain_images.data());
|
||||||
|
// If the original swapchain image count was 0 (first call), SUCCESS is
|
||||||
|
// returned, not INCOMPLETE.
|
||||||
|
if (swapchain_images_get_result == VK_SUCCESS ||
|
||||||
|
swapchain_images_get_result == VK_INCOMPLETE) {
|
||||||
|
swapchain_images.resize(swapchain_image_count);
|
||||||
|
if (swapchain_images_get_result == VK_SUCCESS &&
|
||||||
|
(!swapchain_images_was_empty || !swapchain_image_count)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (swapchain_images_get_result != VK_SUCCESS ||
|
||||||
|
swapchain_images.empty()) {
|
||||||
|
XELOGE("Failed to get Vulkan swapchain images");
|
||||||
|
dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr);
|
||||||
|
swap_swapchain_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assert_true(swap_swapchain_image_views_.empty());
|
||||||
|
swap_swapchain_image_views_.reserve(swapchain_image_count);
|
||||||
|
assert_true(swap_swapchain_framebuffers_.empty());
|
||||||
|
swap_swapchain_framebuffers_.reserve(swapchain_image_count);
|
||||||
|
VkImageViewCreateInfo swapchain_image_view_create_info;
|
||||||
|
swapchain_image_view_create_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
|
swapchain_image_view_create_info.pNext = nullptr;
|
||||||
|
swapchain_image_view_create_info.flags = 0;
|
||||||
|
swapchain_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
swapchain_image_view_create_info.format = swap_surface_format_.format;
|
||||||
|
swapchain_image_view_create_info.components.r =
|
||||||
|
VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
swapchain_image_view_create_info.components.g =
|
||||||
|
VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
swapchain_image_view_create_info.components.b =
|
||||||
|
VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
swapchain_image_view_create_info.components.a =
|
||||||
|
VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||||
|
swapchain_image_view_create_info.subresourceRange.aspectMask =
|
||||||
|
VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
swapchain_image_view_create_info.subresourceRange.baseMipLevel = 0;
|
||||||
|
swapchain_image_view_create_info.subresourceRange.levelCount = 1;
|
||||||
|
swapchain_image_view_create_info.subresourceRange.baseArrayLayer = 0;
|
||||||
|
swapchain_image_view_create_info.subresourceRange.layerCount = 1;
|
||||||
|
VkFramebufferCreateInfo swapchain_framebuffer_create_info;
|
||||||
|
swapchain_framebuffer_create_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||||
|
swapchain_framebuffer_create_info.pNext = nullptr;
|
||||||
|
swapchain_framebuffer_create_info.flags = 0;
|
||||||
|
swapchain_framebuffer_create_info.renderPass = swap_render_pass_;
|
||||||
|
swapchain_framebuffer_create_info.attachmentCount = 1;
|
||||||
|
swapchain_framebuffer_create_info.width = swap_swapchain_extent_.width;
|
||||||
|
swapchain_framebuffer_create_info.height = swap_swapchain_extent_.height;
|
||||||
|
swapchain_framebuffer_create_info.layers = 1;
|
||||||
|
for (uint32_t i = 0; i < swapchain_image_count; ++i) {
|
||||||
|
VkImage swapchain_image = swapchain_images[i];
|
||||||
|
swapchain_image_view_create_info.image = swapchain_image;
|
||||||
|
VkImageView swapchain_image_view;
|
||||||
|
if (dfn.vkCreateImageView(device, &swapchain_image_view_create_info,
|
||||||
|
nullptr,
|
||||||
|
&swapchain_image_view) != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create Vulkan swapchain image views");
|
||||||
|
DestroySwapchainFramebuffers();
|
||||||
|
dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr);
|
||||||
|
swap_swapchain_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
swap_swapchain_image_views_.push_back(swapchain_image_view);
|
||||||
|
swapchain_framebuffer_create_info.pAttachments = &swapchain_image_view;
|
||||||
|
VkFramebuffer swapchain_framebuffer;
|
||||||
|
if (dfn.vkCreateFramebuffer(device, &swapchain_framebuffer_create_info,
|
||||||
|
nullptr,
|
||||||
|
&swapchain_framebuffer) != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to create Vulkan swapchain framebuffers");
|
||||||
|
DestroySwapchainFramebuffers();
|
||||||
|
dfn.vkDestroySwapchainKHR(device, swap_swapchain_, nullptr);
|
||||||
|
swap_swapchain_ = VK_NULL_HANDLE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
swap_swapchain_framebuffers_.push_back(swapchain_framebuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (swap_swapchain_ == VK_NULL_HANDLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
assert_true(swap_surface_ != VK_NULL_HANDLE);
|
||||||
|
assert_true(swap_render_pass_ != VK_NULL_HANDLE);
|
||||||
|
assert_false(swap_swapchain_image_views_.empty());
|
||||||
|
assert_false(swap_swapchain_framebuffers_.empty());
|
||||||
|
|
||||||
|
// Await the frame data to be available before doing anything else.
|
||||||
|
if (swap_submission_completed_ + kSwapchainMaxImageCount <
|
||||||
|
swap_submission_current_) {
|
||||||
|
uint64_t submission_awaited =
|
||||||
|
swap_submission_current_ - kSwapchainMaxImageCount;
|
||||||
|
VkFence submission_fences[kSwapchainMaxImageCount];
|
||||||
|
uint32_t submission_fence_count = 0;
|
||||||
|
while (swap_submission_completed_ + 1 + submission_fence_count <=
|
||||||
|
submission_awaited) {
|
||||||
|
assert_true(submission_fence_count < kSwapchainMaxImageCount);
|
||||||
|
uint32_t submission_index =
|
||||||
|
(swap_submission_completed_ + 1 + submission_fence_count) %
|
||||||
|
kSwapchainMaxImageCount;
|
||||||
|
submission_fences[submission_fence_count++] =
|
||||||
|
swap_submissions_[submission_index].fence;
|
||||||
|
}
|
||||||
|
if (submission_fence_count) {
|
||||||
|
if (dfn.vkWaitForFences(device, submission_fence_count,
|
||||||
|
submission_fences, VK_TRUE,
|
||||||
|
UINT64_MAX) != VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to await the Vulkan presentation submission fences");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
swap_submission_completed_ += submission_fence_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SwapSubmission& submission =
|
||||||
|
swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount];
|
||||||
|
if (dfn.vkResetCommandPool(device, submission.command_pool, 0) !=
|
||||||
|
VK_SUCCESS) {
|
||||||
|
XELOGE("Failed to reset the Vulkan presentation command pool");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// After the image is acquired, this function must not fail before the
|
||||||
|
// semaphore has been signaled, and the image also must be returned to the
|
||||||
|
// swapchain.
|
||||||
|
uint32_t acquired_image_index;
|
||||||
|
switch (dfn.vkAcquireNextImageKHR(device, swap_swapchain_, UINT64_MAX,
|
||||||
|
swap_image_acquisition_semaphore_,
|
||||||
|
nullptr, &acquired_image_index)) {
|
||||||
|
case VK_SUCCESS:
|
||||||
|
case VK_SUBOPTIMAL_KHR:
|
||||||
|
// Not recreating in case of suboptimal, just to prevent a recreation
|
||||||
|
// loop in case the newly created swapchain is suboptimal too.
|
||||||
|
break;
|
||||||
|
case VK_ERROR_DEVICE_LOST:
|
||||||
|
context_lost_ = true;
|
||||||
|
return false;
|
||||||
|
case VK_ERROR_OUT_OF_DATE_KHR:
|
||||||
|
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = true;
|
||||||
|
continue;
|
||||||
|
case VK_ERROR_SURFACE_LOST_KHR:
|
||||||
|
RequestSurfaceRecreation();
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
swap_swapchain_image_current_ = acquired_image_index;
|
||||||
|
|
||||||
|
VkCommandBufferBeginInfo command_buffer_begin_info;
|
||||||
|
command_buffer_begin_info.sType =
|
||||||
|
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
|
command_buffer_begin_info.pNext = nullptr;
|
||||||
|
command_buffer_begin_info.flags =
|
||||||
|
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||||
|
command_buffer_begin_info.pInheritanceInfo = nullptr;
|
||||||
|
dfn.vkBeginCommandBuffer(submission.command_buffer,
|
||||||
|
&command_buffer_begin_info);
|
||||||
|
VkClearValue clear_value;
|
||||||
|
GetClearColor(clear_value.color.float32);
|
||||||
|
VkRenderPassBeginInfo render_pass_begin_info;
|
||||||
|
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||||
|
render_pass_begin_info.pNext = nullptr;
|
||||||
|
render_pass_begin_info.renderPass = swap_render_pass_;
|
||||||
|
render_pass_begin_info.framebuffer =
|
||||||
|
swap_swapchain_framebuffers_[acquired_image_index];
|
||||||
|
render_pass_begin_info.renderArea.offset.x = 0;
|
||||||
|
render_pass_begin_info.renderArea.offset.y = 0;
|
||||||
|
render_pass_begin_info.renderArea.extent = swap_swapchain_extent_;
|
||||||
|
render_pass_begin_info.clearValueCount = 1;
|
||||||
|
render_pass_begin_info.pClearValues = &clear_value;
|
||||||
|
dfn.vkCmdBeginRenderPass(submission.command_buffer, &render_pass_begin_info,
|
||||||
|
VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR even after
|
||||||
|
// recreation.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanContext::EndSwap() {
|
||||||
|
if (!target_window_ || context_lost_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert_true(swap_swapchain_image_current_ != UINT32_MAX);
|
||||||
|
if (swap_swapchain_image_current_ == UINT32_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
VkQueue queue_graphics_compute = provider.queue_graphics_compute();
|
||||||
|
|
||||||
|
const SwapSubmission& submission =
|
||||||
|
swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount];
|
||||||
|
dfn.vkCmdEndRenderPass(submission.command_buffer);
|
||||||
|
dfn.vkEndCommandBuffer(submission.command_buffer);
|
||||||
|
dfn.vkResetFences(device, 1, &submission.fence);
|
||||||
|
VkSubmitInfo submit_info;
|
||||||
|
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
|
submit_info.pNext = nullptr;
|
||||||
|
submit_info.waitSemaphoreCount = 1;
|
||||||
|
submit_info.pWaitSemaphores = &swap_image_acquisition_semaphore_;
|
||||||
|
VkPipelineStageFlags image_acquisition_semaphore_wait_stage =
|
||||||
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
|
submit_info.pWaitDstStageMask = &image_acquisition_semaphore_wait_stage;
|
||||||
|
submit_info.commandBufferCount = 1;
|
||||||
|
submit_info.pCommandBuffers = &submission.command_buffer;
|
||||||
|
submit_info.signalSemaphoreCount = 1;
|
||||||
|
submit_info.pSignalSemaphores = &swap_render_completion_semaphore_;
|
||||||
|
VkResult submit_result = dfn.vkQueueSubmit(queue_graphics_compute, 1,
|
||||||
|
&submit_info, submission.fence);
|
||||||
|
if (submit_result != VK_SUCCESS) {
|
||||||
|
// If failed, can't even return the swapchain image - so treat all errors as
|
||||||
|
// context loss.
|
||||||
|
context_lost_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++swap_submission_current_;
|
||||||
|
|
||||||
|
VkPresentInfoKHR present_info;
|
||||||
|
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||||
|
present_info.pNext = nullptr;
|
||||||
|
present_info.waitSemaphoreCount = 1;
|
||||||
|
present_info.pWaitSemaphores = &swap_render_completion_semaphore_;
|
||||||
|
present_info.swapchainCount = 1;
|
||||||
|
present_info.pSwapchains = &swap_swapchain_;
|
||||||
|
present_info.pImageIndices = &swap_swapchain_image_current_;
|
||||||
|
present_info.pResults = nullptr;
|
||||||
|
// FIXME(Triang3l): Allow a separate queue for present - see
|
||||||
|
// vulkan_provider.cc for details.
|
||||||
|
VkResult present_result =
|
||||||
|
dfn.vkQueuePresentKHR(queue_graphics_compute, &present_info);
|
||||||
|
swap_swapchain_image_current_ = UINT32_MAX;
|
||||||
|
switch (present_result) {
|
||||||
|
case VK_SUCCESS:
|
||||||
|
case VK_SUBOPTIMAL_KHR:
|
||||||
|
// Not recreating in case of suboptimal, just to prevent a recreation
|
||||||
|
// loop in case the newly created swapchain is suboptimal too.
|
||||||
|
break;
|
||||||
|
case VK_ERROR_OUT_OF_DATE_KHR:
|
||||||
|
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = true;
|
||||||
|
return;
|
||||||
|
case VK_ERROR_SURFACE_LOST_KHR:
|
||||||
|
// Safe to await submission completion now - swap_submission_current_ has
|
||||||
|
// already been incremented to the next frame.
|
||||||
|
RequestSurfaceRecreation();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
// Treat any error as device loss since it would leave the semaphore
|
||||||
|
// forever signaled anyway, and the image won't be returned to the
|
||||||
|
// swapchain.
|
||||||
|
context_lost_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<RawImage> VulkanContext::Capture() {
|
std::unique_ptr<RawImage> VulkanContext::Capture() {
|
||||||
// TODO(Triang3l): Read back swap chain front buffer.
|
// TODO(Triang3l): Read back swap chain front buffer.
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VulkanContext::RequestSurfaceRecreation() {
|
||||||
|
#if XE_PLATFORM_ANDROID
|
||||||
|
// The surface doesn't exist when the activity is in background.
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ =
|
||||||
|
target_window_->native_handle() != nullptr;
|
||||||
|
#else
|
||||||
|
swap_swapchain_or_surface_recreation_needed_ = true;
|
||||||
|
#endif
|
||||||
|
if (swap_surface_ == VK_NULL_HANDLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AwaitAllSwapSubmissionsCompletion();
|
||||||
|
DestroySwapchainFramebuffers();
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
|
||||||
|
VkInstance instance = provider.instance();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
util::DestroyAndNullHandle(dfn.vkDestroySwapchainKHR, device,
|
||||||
|
swap_swapchain_);
|
||||||
|
ifn.vkDestroySurfaceKHR(instance, swap_surface_, nullptr);
|
||||||
|
swap_surface_ = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanContext::AwaitAllSwapSubmissionsCompletion() {
|
||||||
|
assert_not_null(target_window_);
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
VkFence fences[kSwapchainMaxImageCount];
|
||||||
|
uint32_t fence_count = 0;
|
||||||
|
while (swap_submission_completed_ + 1 < swap_submission_current_) {
|
||||||
|
assert_true(fence_count < kSwapchainMaxImageCount);
|
||||||
|
uint32_t submission_index =
|
||||||
|
++swap_submission_completed_ % kSwapchainMaxImageCount;
|
||||||
|
fences[fence_count++] = swap_submissions_[submission_index].fence;
|
||||||
|
}
|
||||||
|
if (fence_count && !context_lost_) {
|
||||||
|
dfn.vkWaitForFences(device, fence_count, fences, VK_TRUE, UINT64_MAX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanContext::DestroySwapchainFramebuffers() {
|
||||||
|
assert_not_null(target_window_);
|
||||||
|
const VulkanProvider& provider = GetVulkanProvider();
|
||||||
|
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
|
||||||
|
VkDevice device = provider.device();
|
||||||
|
for (VkFramebuffer framebuffer : swap_swapchain_framebuffers_) {
|
||||||
|
dfn.vkDestroyFramebuffer(device, framebuffer, nullptr);
|
||||||
|
}
|
||||||
|
swap_swapchain_framebuffers_.clear();
|
||||||
|
for (VkImageView image_view : swap_swapchain_image_views_) {
|
||||||
|
dfn.vkDestroyImageView(device, image_view, nullptr);
|
||||||
|
}
|
||||||
|
swap_swapchain_image_views_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace vulkan
|
} // namespace vulkan
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
#ifndef XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
#ifndef XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
||||||
#define XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
#define XENIA_UI_VULKAN_VULKAN_CONTEXT_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/ui/graphics_context.h"
|
#include "xenia/ui/graphics_context.h"
|
||||||
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
||||||
|
@ -24,19 +26,22 @@ class VulkanContext : public GraphicsContext {
|
||||||
public:
|
public:
|
||||||
ImmediateDrawer* immediate_drawer() override;
|
ImmediateDrawer* immediate_drawer() override;
|
||||||
|
|
||||||
// Returns true if the OS took away our context because we caused a TDR or
|
bool WasLost() override;
|
||||||
// some other outstanding error. When this happens, this context, as well as
|
|
||||||
// any other shared contexts are junk.
|
|
||||||
// This context must be made current in order for this call to work properly.
|
|
||||||
bool WasLost() override { return false; }
|
|
||||||
|
|
||||||
void BeginSwap() override;
|
bool BeginSwap() override;
|
||||||
void EndSwap() override;
|
void EndSwap() override;
|
||||||
|
|
||||||
std::unique_ptr<RawImage> Capture() override;
|
std::unique_ptr<RawImage> Capture() override;
|
||||||
|
|
||||||
VulkanProvider* GetVulkanProvider() const {
|
VulkanProvider& GetVulkanProvider() const {
|
||||||
return static_cast<VulkanProvider*>(provider_);
|
return static_cast<VulkanProvider&>(*provider_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RequestSurfaceRecreation();
|
||||||
|
|
||||||
|
VkCommandBuffer GetSwapCommandBuffer() const {
|
||||||
|
return swap_submissions_[swap_submission_current_ % kSwapchainMaxImageCount]
|
||||||
|
.command_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -45,6 +50,62 @@ class VulkanContext : public GraphicsContext {
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
void AwaitAllSwapSubmissionsCompletion();
|
||||||
|
|
||||||
|
// 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).
|
||||||
|
|
||||||
|
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;
|
||||||
|
VkCommandPool command_pool = VK_NULL_HANDLE;
|
||||||
|
VkCommandBuffer command_buffer;
|
||||||
|
};
|
||||||
|
SwapSubmission swap_submissions_[kSwapchainMaxImageCount];
|
||||||
|
uint64_t swap_submission_current_ = 1;
|
||||||
|
uint64_t swap_submission_completed_ = 0;
|
||||||
|
|
||||||
|
VkSemaphore swap_image_acquisition_semaphore_ = VK_NULL_HANDLE;
|
||||||
|
VkSemaphore swap_render_completion_semaphore_ = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
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;
|
std::unique_ptr<VulkanImmediateDrawer> immediate_drawer_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,26 @@
|
||||||
|
|
||||||
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
#include "xenia/ui/vulkan/vulkan_immediate_drawer.h"
|
||||||
|
|
||||||
|
#include "xenia/ui/vulkan/vulkan_context.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
namespace vulkan {
|
namespace vulkan {
|
||||||
|
|
||||||
|
class VulkanImmediateTexture : public ImmediateTexture {
|
||||||
|
public:
|
||||||
|
VulkanImmediateTexture(uint32_t width, uint32_t height)
|
||||||
|
: ImmediateTexture(width, height) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
VulkanImmediateDrawer::VulkanImmediateDrawer(VulkanContext& graphics_context)
|
||||||
|
: ImmediateDrawer(&graphics_context), context_(graphics_context) {}
|
||||||
|
|
||||||
std::unique_ptr<ImmediateTexture> VulkanImmediateDrawer::CreateTexture(
|
std::unique_ptr<ImmediateTexture> VulkanImmediateDrawer::CreateTexture(
|
||||||
uint32_t width, uint32_t height, ImmediateTextureFilter filter, bool repeat,
|
uint32_t width, uint32_t height, ImmediateTextureFilter filter, bool repeat,
|
||||||
const uint8_t* data) {
|
const uint8_t* data) {
|
||||||
return nullptr;
|
auto texture = std::make_unique<VulkanImmediateTexture>(width, height);
|
||||||
|
return std::unique_ptr<ImmediateTexture>(texture.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanImmediateDrawer::UpdateTexture(ImmediateTexture* texture,
|
void VulkanImmediateDrawer::UpdateTexture(ImmediateTexture* texture,
|
||||||
|
|
|
@ -16,8 +16,12 @@ namespace xe {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
namespace vulkan {
|
namespace vulkan {
|
||||||
|
|
||||||
|
class VulkanContext;
|
||||||
|
|
||||||
class VulkanImmediateDrawer : public ImmediateDrawer {
|
class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||||
public:
|
public:
|
||||||
|
VulkanImmediateDrawer(VulkanContext& graphics_context);
|
||||||
|
|
||||||
std::unique_ptr<ImmediateTexture> CreateTexture(uint32_t width,
|
std::unique_ptr<ImmediateTexture> CreateTexture(uint32_t width,
|
||||||
uint32_t height,
|
uint32_t height,
|
||||||
ImmediateTextureFilter filter,
|
ImmediateTextureFilter filter,
|
||||||
|
@ -30,6 +34,9 @@ class VulkanImmediateDrawer : public ImmediateDrawer {
|
||||||
void Draw(const ImmediateDraw& draw) override;
|
void Draw(const ImmediateDraw& draw) override;
|
||||||
void EndDrawBatch() override;
|
void EndDrawBatch() override;
|
||||||
void End() override;
|
void End() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VulkanContext& context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace vulkan
|
} // namespace vulkan
|
||||||
|
|
|
@ -24,6 +24,12 @@
|
||||||
#include "xenia/base/platform_win.h"
|
#include "xenia/base/platform_win.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// TODO(Triang3l): Disable Vulkan validation before releasing a stable version.
|
||||||
|
DEFINE_bool(
|
||||||
|
vulkan_validation, true,
|
||||||
|
"Enable Vulkan validation (VK_LAYER_KHRONOS_validation). Messages will be "
|
||||||
|
"written to the OS debug log.",
|
||||||
|
"GPU");
|
||||||
DEFINE_int32(
|
DEFINE_int32(
|
||||||
vulkan_device, -1,
|
vulkan_device, -1,
|
||||||
"Index of the physical device to use, or -1 for any compatible device.",
|
"Index of the physical device to use, or -1 for any compatible device.",
|
||||||
|
@ -55,10 +61,10 @@ VulkanProvider::VulkanProvider(Window* main_window)
|
||||||
|
|
||||||
VulkanProvider::~VulkanProvider() {
|
VulkanProvider::~VulkanProvider() {
|
||||||
if (device_ != VK_NULL_HANDLE) {
|
if (device_ != VK_NULL_HANDLE) {
|
||||||
ifn_.destroyDevice(device_, nullptr);
|
ifn_.vkDestroyDevice(device_, nullptr);
|
||||||
}
|
}
|
||||||
if (instance_ != VK_NULL_HANDLE) {
|
if (instance_ != VK_NULL_HANDLE) {
|
||||||
destroyInstance_(instance_, nullptr);
|
lfn_.vkDestroyInstance(instance_, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if XE_PLATFORM_LINUX
|
#if XE_PLATFORM_LINUX
|
||||||
|
@ -74,6 +80,7 @@ VulkanProvider::~VulkanProvider() {
|
||||||
|
|
||||||
bool VulkanProvider::Initialize() {
|
bool VulkanProvider::Initialize() {
|
||||||
// Load the library.
|
// Load the library.
|
||||||
|
bool library_functions_loaded = true;
|
||||||
#if XE_PLATFORM_LINUX
|
#if XE_PLATFORM_LINUX
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
const char* libvulkan_name = "libvulkan.so";
|
const char* libvulkan_name = "libvulkan.so";
|
||||||
|
@ -86,61 +93,46 @@ bool VulkanProvider::Initialize() {
|
||||||
XELOGE("Failed to load {}", libvulkan_name);
|
XELOGE("Failed to load {}", libvulkan_name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
getInstanceProcAddr_ =
|
#define XE_VULKAN_LOAD_MODULE_LFN(name) \
|
||||||
PFN_vkGetInstanceProcAddr(dlsym(library_, "vkGetInstanceProcAddr"));
|
library_functions_loaded &= \
|
||||||
destroyInstance_ =
|
(lfn_.name = PFN_##name(dlsym(library_, #name))) != nullptr;
|
||||||
PFN_vkDestroyInstance(dlsym(library_, "vkDestroyInstance"));
|
|
||||||
if (!getInstanceProcAddr_ || !destroyInstance_) {
|
|
||||||
XELOGE("Failed to get vkGetInstanceProcAddr and vkDestroyInstance from {}",
|
|
||||||
libvulkan_name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#elif XE_PLATFORM_WIN32
|
#elif XE_PLATFORM_WIN32
|
||||||
library_ = LoadLibraryA("vulkan-1.dll");
|
library_ = LoadLibraryA("vulkan-1.dll");
|
||||||
if (!library_) {
|
if (!library_) {
|
||||||
XELOGE("Failed to load vulkan-1.dll");
|
XELOGE("Failed to load vulkan-1.dll");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
getInstanceProcAddr_ = PFN_vkGetInstanceProcAddr(
|
#define XE_VULKAN_LOAD_MODULE_LFN(name) \
|
||||||
GetProcAddress(library_, "vkGetInstanceProcAddr"));
|
library_functions_loaded &= \
|
||||||
destroyInstance_ =
|
(lfn_.name = PFN_##name(GetProcAddress(library_, #name))) != nullptr;
|
||||||
PFN_vkDestroyInstance(GetProcAddress(library_, "vkDestroyInstance"));
|
|
||||||
if (!getInstanceProcAddr_ || !destroyInstance_) {
|
|
||||||
XELOGE(
|
|
||||||
"Failed to get vkGetInstanceProcAddr and vkDestroyInstance from "
|
|
||||||
"vulkan-1.dll");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
#error No Vulkan library loading provided for the target platform.
|
#error No Vulkan library loading provided for the target platform.
|
||||||
#endif
|
#endif
|
||||||
assert_not_null(getInstanceProcAddr_);
|
XE_VULKAN_LOAD_MODULE_LFN(vkGetInstanceProcAddr);
|
||||||
assert_not_null(destroyInstance_);
|
XE_VULKAN_LOAD_MODULE_LFN(vkDestroyInstance);
|
||||||
bool library_functions_loaded = true;
|
#undef XE_VULKAN_LOAD_MODULE_LFN
|
||||||
library_functions_loaded &=
|
|
||||||
(library_functions_.createInstance = PFN_vkCreateInstance(
|
|
||||||
getInstanceProcAddr_(VK_NULL_HANDLE, "vkCreateInstance"))) !=
|
|
||||||
nullptr;
|
|
||||||
library_functions_loaded &=
|
|
||||||
(library_functions_.enumerateInstanceExtensionProperties =
|
|
||||||
PFN_vkEnumerateInstanceExtensionProperties(getInstanceProcAddr_(
|
|
||||||
VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"))) !=
|
|
||||||
nullptr;
|
|
||||||
if (!library_functions_loaded) {
|
if (!library_functions_loaded) {
|
||||||
XELOGE("Failed to get Vulkan library function pointers");
|
XELOGE("Failed to get Vulkan library function pointers");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
library_functions_.enumerateInstanceVersion_1_1 =
|
library_functions_loaded &=
|
||||||
PFN_vkEnumerateInstanceVersion(
|
(lfn_.vkCreateInstance = PFN_vkCreateInstance(lfn_.vkGetInstanceProcAddr(
|
||||||
getInstanceProcAddr_(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"));
|
VK_NULL_HANDLE, "vkCreateInstance"))) != nullptr;
|
||||||
|
if (!library_functions_loaded) {
|
||||||
|
XELOGE(
|
||||||
|
"Failed to get Vulkan library function pointers via "
|
||||||
|
"vkGetInstanceProcAddr");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lfn_.v_1_1.vkEnumerateInstanceVersion = PFN_vkEnumerateInstanceVersion(
|
||||||
|
lfn_.vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"));
|
||||||
|
|
||||||
// Get the API version.
|
// Get the API version.
|
||||||
const uint32_t api_version_target = VK_MAKE_VERSION(1, 2, 148);
|
const uint32_t api_version_target = VK_MAKE_VERSION(1, 2, 148);
|
||||||
static_assert(VK_HEADER_VERSION_COMPLETE >= api_version_target,
|
static_assert(VK_HEADER_VERSION_COMPLETE >= api_version_target,
|
||||||
"Vulkan header files must be up to date");
|
"Vulkan header files must be up to date");
|
||||||
if (!library_functions_.enumerateInstanceVersion_1_1 ||
|
if (!lfn_.v_1_1.vkEnumerateInstanceVersion ||
|
||||||
library_functions_.enumerateInstanceVersion_1_1(&api_version_) !=
|
lfn_.v_1_1.vkEnumerateInstanceVersion(&api_version_) != VK_SUCCESS) {
|
||||||
VK_SUCCESS) {
|
|
||||||
api_version_ = VK_API_VERSION_1_0;
|
api_version_ = VK_API_VERSION_1_0;
|
||||||
}
|
}
|
||||||
XELOGVK("Vulkan instance version {}.{}.{}", VK_VERSION_MAJOR(api_version_),
|
XELOGVK("Vulkan instance version {}.{}.{}", VK_VERSION_MAJOR(api_version_),
|
||||||
|
@ -173,66 +165,59 @@ bool VulkanProvider::Initialize() {
|
||||||
instance_create_info.pNext = nullptr;
|
instance_create_info.pNext = nullptr;
|
||||||
instance_create_info.flags = 0;
|
instance_create_info.flags = 0;
|
||||||
instance_create_info.pApplicationInfo = &application_info;
|
instance_create_info.pApplicationInfo = &application_info;
|
||||||
// TODO(Triang3l): Enable the validation layer.
|
static const char* validation_layer = "VK_LAYER_KHRONOS_validation";
|
||||||
|
if (cvars::vulkan_validation) {
|
||||||
|
instance_create_info.enabledLayerCount = 1;
|
||||||
|
instance_create_info.ppEnabledLayerNames = &validation_layer;
|
||||||
|
} else {
|
||||||
instance_create_info.enabledLayerCount = 0;
|
instance_create_info.enabledLayerCount = 0;
|
||||||
instance_create_info.ppEnabledLayerNames = nullptr;
|
instance_create_info.ppEnabledLayerNames = nullptr;
|
||||||
|
}
|
||||||
instance_create_info.enabledExtensionCount =
|
instance_create_info.enabledExtensionCount =
|
||||||
uint32_t(instance_extensions_enabled.size());
|
uint32_t(instance_extensions_enabled.size());
|
||||||
instance_create_info.ppEnabledExtensionNames =
|
instance_create_info.ppEnabledExtensionNames =
|
||||||
instance_extensions_enabled.data();
|
instance_extensions_enabled.data();
|
||||||
if (library_functions_.createInstance(&instance_create_info, nullptr,
|
VkResult instance_create_result =
|
||||||
&instance_) != VK_SUCCESS) {
|
lfn_.vkCreateInstance(&instance_create_info, nullptr, &instance_);
|
||||||
|
if (instance_create_result != VK_SUCCESS) {
|
||||||
|
if (instance_create_result == VK_ERROR_LAYER_NOT_PRESENT) {
|
||||||
|
XELOGE("Failed to enable the Vulkan validation layer");
|
||||||
|
instance_create_info.enabledLayerCount = 0;
|
||||||
|
instance_create_info.ppEnabledLayerNames = nullptr;
|
||||||
|
instance_create_result =
|
||||||
|
lfn_.vkCreateInstance(&instance_create_info, nullptr, &instance_);
|
||||||
|
}
|
||||||
|
if (instance_create_result != VK_SUCCESS) {
|
||||||
XELOGE("Failed to create a Vulkan instance with surface support");
|
XELOGE("Failed to create a Vulkan instance with surface support");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get instance functions.
|
// Get instance functions.
|
||||||
bool instance_functions_loaded = true;
|
bool instance_functions_loaded = true;
|
||||||
instance_functions_loaded &=
|
#define XE_VULKAN_LOAD_IFN(name) \
|
||||||
(ifn_.createDevice = PFN_vkCreateDevice(
|
instance_functions_loaded &= \
|
||||||
getInstanceProcAddr_(instance_, "vkCreateDevice"))) != nullptr;
|
(ifn_.name = PFN_##name( \
|
||||||
instance_functions_loaded &=
|
lfn_.vkGetInstanceProcAddr(instance_, #name))) != nullptr;
|
||||||
(ifn_.destroyDevice = PFN_vkDestroyDevice(
|
XE_VULKAN_LOAD_IFN(vkCreateDevice);
|
||||||
getInstanceProcAddr_(instance_, "vkDestroyDevice"))) != nullptr;
|
XE_VULKAN_LOAD_IFN(vkDestroyDevice);
|
||||||
instance_functions_loaded &=
|
XE_VULKAN_LOAD_IFN(vkDestroySurfaceKHR);
|
||||||
(ifn_.enumerateDeviceExtensionProperties =
|
XE_VULKAN_LOAD_IFN(vkEnumerateDeviceExtensionProperties);
|
||||||
PFN_vkEnumerateDeviceExtensionProperties(getInstanceProcAddr_(
|
XE_VULKAN_LOAD_IFN(vkEnumeratePhysicalDevices);
|
||||||
instance_, "vkEnumerateDeviceExtensionProperties"))) != nullptr;
|
XE_VULKAN_LOAD_IFN(vkGetDeviceProcAddr);
|
||||||
instance_functions_loaded &=
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceFeatures);
|
||||||
(ifn_.enumeratePhysicalDevices = PFN_vkEnumeratePhysicalDevices(
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceProperties);
|
||||||
getInstanceProcAddr_(instance_, "vkEnumeratePhysicalDevices"))) !=
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceQueueFamilyProperties);
|
||||||
nullptr;
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
|
||||||
instance_functions_loaded &=
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfaceFormatsKHR);
|
||||||
(ifn_.getDeviceProcAddr = PFN_vkGetDeviceProcAddr(
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfacePresentModesKHR);
|
||||||
getInstanceProcAddr_(instance_, "vkGetDeviceProcAddr"))) != nullptr;
|
XE_VULKAN_LOAD_IFN(vkGetPhysicalDeviceSurfaceSupportKHR);
|
||||||
instance_functions_loaded &=
|
|
||||||
(ifn_.getPhysicalDeviceFeatures = PFN_vkGetPhysicalDeviceFeatures(
|
|
||||||
getInstanceProcAddr_(instance_, "vkGetPhysicalDeviceFeatures"))) !=
|
|
||||||
nullptr;
|
|
||||||
instance_functions_loaded &=
|
|
||||||
(ifn_.getPhysicalDeviceProperties = PFN_vkGetPhysicalDeviceProperties(
|
|
||||||
getInstanceProcAddr_(instance_, "vkGetPhysicalDeviceProperties"))) !=
|
|
||||||
nullptr;
|
|
||||||
instance_functions_loaded &=
|
|
||||||
(ifn_.getPhysicalDeviceQueueFamilyProperties =
|
|
||||||
PFN_vkGetPhysicalDeviceQueueFamilyProperties(getInstanceProcAddr_(
|
|
||||||
instance_, "vkGetPhysicalDeviceQueueFamilyProperties"))) !=
|
|
||||||
nullptr;
|
|
||||||
instance_functions_loaded &=
|
|
||||||
(ifn_.getPhysicalDeviceSurfaceSupportKHR =
|
|
||||||
PFN_vkGetPhysicalDeviceSurfaceSupportKHR(getInstanceProcAddr_(
|
|
||||||
instance_, "vkGetPhysicalDeviceSurfaceSupportKHR"))) != nullptr;
|
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
instance_functions_loaded &=
|
XE_VULKAN_LOAD_IFN(vkCreateAndroidSurfaceKHR);
|
||||||
(ifn_.createAndroidSurfaceKHR = PFN_vkCreateAndroidSurfaceKHR(
|
|
||||||
getInstanceProcAddr_(instance_, "vkCreateAndroidSurfaceKHR"))) !=
|
|
||||||
nullptr;
|
|
||||||
#elif XE_PLATFORM_WIN32
|
#elif XE_PLATFORM_WIN32
|
||||||
instance_functions_loaded &=
|
XE_VULKAN_LOAD_IFN(vkCreateWin32SurfaceKHR);
|
||||||
(ifn_.createWin32SurfaceKHR = PFN_vkCreateWin32SurfaceKHR(
|
|
||||||
getInstanceProcAddr_(instance_, "vkCreateWin32SurfaceKHR"))) !=
|
|
||||||
nullptr;
|
|
||||||
#endif
|
#endif
|
||||||
|
#undef XE_VULKAN_LOAD_IFN
|
||||||
if (!instance_functions_loaded) {
|
if (!instance_functions_loaded) {
|
||||||
XELOGE("Failed to get Vulkan instance function pointers");
|
XELOGE("Failed to get Vulkan instance function pointers");
|
||||||
return false;
|
return false;
|
||||||
|
@ -242,8 +227,8 @@ bool VulkanProvider::Initialize() {
|
||||||
std::vector<VkPhysicalDevice> physical_devices;
|
std::vector<VkPhysicalDevice> physical_devices;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint32_t physical_device_count = uint32_t(physical_devices.size());
|
uint32_t physical_device_count = uint32_t(physical_devices.size());
|
||||||
bool physical_devices_was_empty = physical_devices.empty();
|
bool physical_devices_was_empty = !physical_device_count;
|
||||||
VkResult physical_device_enumerate_result = ifn_.enumeratePhysicalDevices(
|
VkResult physical_device_enumerate_result = ifn_.vkEnumeratePhysicalDevices(
|
||||||
instance_, &physical_device_count,
|
instance_, &physical_device_count,
|
||||||
physical_devices_was_empty ? nullptr : physical_devices.data());
|
physical_devices_was_empty ? nullptr : physical_devices.data());
|
||||||
// If the original device count was 0 (first call), SUCCESS is returned, not
|
// If the original device count was 0 (first call), SUCCESS is returned, not
|
||||||
|
@ -288,7 +273,8 @@ bool VulkanProvider::Initialize() {
|
||||||
VkPhysicalDevice physical_device_current = physical_devices[i];
|
VkPhysicalDevice physical_device_current = physical_devices[i];
|
||||||
|
|
||||||
// Get physical device features and check if the needed ones are supported.
|
// Get physical device features and check if the needed ones are supported.
|
||||||
ifn_.getPhysicalDeviceFeatures(physical_device_current, &device_features_);
|
ifn_.vkGetPhysicalDeviceFeatures(physical_device_current,
|
||||||
|
&device_features_);
|
||||||
// TODO(Triang3l): Make geometry shaders optional by providing compute
|
// TODO(Triang3l): Make geometry shaders optional by providing compute
|
||||||
// shader fallback (though that would require vertex shader stores).
|
// shader fallback (though that would require vertex shader stores).
|
||||||
if (!device_features_.geometryShader) {
|
if (!device_features_.geometryShader) {
|
||||||
|
@ -299,10 +285,10 @@ bool VulkanProvider::Initialize() {
|
||||||
// (preferably the same for the least latency between the two, as Xenia
|
// (preferably the same for the least latency between the two, as Xenia
|
||||||
// submits sparse binding commands right before graphics commands anyway).
|
// submits sparse binding commands right before graphics commands anyway).
|
||||||
uint32_t queue_family_count = 0;
|
uint32_t queue_family_count = 0;
|
||||||
ifn_.getPhysicalDeviceQueueFamilyProperties(physical_device_current,
|
ifn_.vkGetPhysicalDeviceQueueFamilyProperties(physical_device_current,
|
||||||
&queue_family_count, nullptr);
|
&queue_family_count, nullptr);
|
||||||
queue_families.resize(queue_family_count);
|
queue_families.resize(queue_family_count);
|
||||||
ifn_.getPhysicalDeviceQueueFamilyProperties(
|
ifn_.vkGetPhysicalDeviceQueueFamilyProperties(
|
||||||
physical_device_current, &queue_family_count, queue_families.data());
|
physical_device_current, &queue_family_count, queue_families.data());
|
||||||
assert_true(queue_family_count == queue_families.size());
|
assert_true(queue_family_count == queue_families.size());
|
||||||
queue_family_graphics_compute_ = UINT32_MAX;
|
queue_family_graphics_compute_ = UINT32_MAX;
|
||||||
|
@ -364,9 +350,9 @@ bool VulkanProvider::Initialize() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
uint32_t device_extension_count =
|
uint32_t device_extension_count =
|
||||||
uint32_t(device_extension_properties.size());
|
uint32_t(device_extension_properties.size());
|
||||||
bool device_extensions_was_empty = device_extension_properties.empty();
|
bool device_extensions_was_empty = !device_extension_count;
|
||||||
device_extensions_enumerate_result =
|
device_extensions_enumerate_result =
|
||||||
ifn_.enumerateDeviceExtensionProperties(
|
ifn_.vkEnumerateDeviceExtensionProperties(
|
||||||
physical_device_current, nullptr, &device_extension_count,
|
physical_device_current, nullptr, &device_extension_count,
|
||||||
device_extensions_was_empty ? nullptr
|
device_extensions_was_empty ? nullptr
|
||||||
: device_extension_properties.data());
|
: device_extension_properties.data());
|
||||||
|
@ -411,7 +397,7 @@ bool VulkanProvider::Initialize() {
|
||||||
"support");
|
"support");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ifn_.getPhysicalDeviceProperties(physical_device_, &device_properties_);
|
ifn_.vkGetPhysicalDeviceProperties(physical_device_, &device_properties_);
|
||||||
XELOGVK(
|
XELOGVK(
|
||||||
"Vulkan device: {} (vendor {:04X}, device {:04X}, driver {:08X}, API "
|
"Vulkan device: {} (vendor {:04X}, device {:04X}, driver {:08X}, API "
|
||||||
"{}.{}.{})",
|
"{}.{}.{})",
|
||||||
|
@ -454,7 +440,7 @@ bool VulkanProvider::Initialize() {
|
||||||
device_create_info.queueCreateInfoCount =
|
device_create_info.queueCreateInfoCount =
|
||||||
separate_sparse_binding_queue ? 2 : 1;
|
separate_sparse_binding_queue ? 2 : 1;
|
||||||
device_create_info.pQueueCreateInfos = queue_create_infos;
|
device_create_info.pQueueCreateInfos = queue_create_infos;
|
||||||
// TODO(Triang3l): Enable the validation layer.
|
// Device layers are deprecated - using validation layer on the instance.
|
||||||
device_create_info.enabledLayerCount = 0;
|
device_create_info.enabledLayerCount = 0;
|
||||||
device_create_info.ppEnabledLayerNames = nullptr;
|
device_create_info.ppEnabledLayerNames = nullptr;
|
||||||
device_create_info.enabledExtensionCount =
|
device_create_info.enabledExtensionCount =
|
||||||
|
@ -462,7 +448,7 @@ bool VulkanProvider::Initialize() {
|
||||||
device_create_info.ppEnabledExtensionNames = device_extensions_enabled.data();
|
device_create_info.ppEnabledExtensionNames = device_extensions_enabled.data();
|
||||||
// TODO(Triang3l): Enable only needed features.
|
// TODO(Triang3l): Enable only needed features.
|
||||||
device_create_info.pEnabledFeatures = &device_features_;
|
device_create_info.pEnabledFeatures = &device_features_;
|
||||||
if (ifn_.createDevice(physical_device_, &device_create_info, nullptr,
|
if (ifn_.vkCreateDevice(physical_device_, &device_create_info, nullptr,
|
||||||
&device_) != VK_SUCCESS) {
|
&device_) != VK_SUCCESS) {
|
||||||
XELOGE("Failed to create a Vulkan device");
|
XELOGE("Failed to create a Vulkan device");
|
||||||
return false;
|
return false;
|
||||||
|
@ -470,20 +456,49 @@ bool VulkanProvider::Initialize() {
|
||||||
|
|
||||||
// Get device functions.
|
// Get device functions.
|
||||||
bool device_functions_loaded = true;
|
bool device_functions_loaded = true;
|
||||||
device_functions_loaded &=
|
#define XE_VULKAN_LOAD_DFN(name) \
|
||||||
(dfn_.getDeviceQueue = PFN_vkGetDeviceQueue(
|
device_functions_loaded &= \
|
||||||
ifn_.getDeviceProcAddr(device_, "vkGetDeviceQueue"))) != nullptr;
|
(dfn_.name = PFN_##name(ifn_.vkGetDeviceProcAddr(device_, #name))) != \
|
||||||
|
nullptr;
|
||||||
|
XE_VULKAN_LOAD_DFN(vkAcquireNextImageKHR);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkAllocateCommandBuffers);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkBeginCommandBuffer);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCmdBeginRenderPass);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCmdEndRenderPass);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateCommandPool);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateFence);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateFramebuffer);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateImageView);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateRenderPass);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateSemaphore);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkCreateSwapchainKHR);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroyCommandPool);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroyFence);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroyFramebuffer);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroyImageView);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroyRenderPass);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroySemaphore);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkDestroySwapchainKHR);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkEndCommandBuffer);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkGetDeviceQueue);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkGetSwapchainImagesKHR);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkResetCommandPool);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkResetFences);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkQueuePresentKHR);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkQueueSubmit);
|
||||||
|
XE_VULKAN_LOAD_DFN(vkWaitForFences);
|
||||||
|
#undef XE_VULKAN_LOAD_DFN
|
||||||
if (!device_functions_loaded) {
|
if (!device_functions_loaded) {
|
||||||
XELOGE("Failed to get Vulkan device function pointers");
|
XELOGE("Failed to get Vulkan device function pointers");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the queues.
|
// Get the queues.
|
||||||
dfn_.getDeviceQueue(device_, queue_family_graphics_compute_, 0,
|
dfn_.vkGetDeviceQueue(device_, queue_family_graphics_compute_, 0,
|
||||||
&queue_graphics_compute_);
|
&queue_graphics_compute_);
|
||||||
if (queue_family_sparse_binding != UINT32_MAX) {
|
if (queue_family_sparse_binding != UINT32_MAX) {
|
||||||
if (separate_sparse_binding_queue) {
|
if (separate_sparse_binding_queue) {
|
||||||
dfn_.getDeviceQueue(device_, queue_family_sparse_binding, 0,
|
dfn_.vkGetDeviceQueue(device_, queue_family_sparse_binding, 0,
|
||||||
&queue_sparse_binding_);
|
&queue_sparse_binding_);
|
||||||
} else {
|
} else {
|
||||||
queue_sparse_binding_ = queue_graphics_compute_;
|
queue_sparse_binding_ = queue_graphics_compute_;
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
#ifndef VK_USE_PLATFORM_WIN32_KHR
|
#ifndef VK_USE_PLATFORM_WIN32_KHR
|
||||||
#define VK_USE_PLATFORM_WIN32_KHR 1
|
#define VK_USE_PLATFORM_WIN32_KHR 1
|
||||||
#endif
|
#endif
|
||||||
#endif // XE_PLATFORM_WIN32
|
#endif
|
||||||
|
|
||||||
#ifndef VK_NO_PROTOTYPES
|
#ifndef VK_NO_PROTOTYPES
|
||||||
#define VK_NO_PROTOTYPES 1
|
#define VK_NO_PROTOTYPES 1
|
||||||
|
@ -47,37 +47,45 @@ class VulkanProvider : public GraphicsProvider {
|
||||||
Window* target_window) override;
|
Window* target_window) override;
|
||||||
std::unique_ptr<GraphicsContext> CreateOffscreenContext() override;
|
std::unique_ptr<GraphicsContext> CreateOffscreenContext() override;
|
||||||
|
|
||||||
// Functions with a version suffix (like _1_1) are null when api_version() is
|
|
||||||
// below this version.
|
|
||||||
|
|
||||||
struct LibraryFunctions {
|
struct LibraryFunctions {
|
||||||
PFN_vkCreateInstance createInstance;
|
// From the module.
|
||||||
PFN_vkEnumerateInstanceExtensionProperties
|
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
|
||||||
enumerateInstanceExtensionProperties;
|
PFN_vkDestroyInstance vkDestroyInstance;
|
||||||
PFN_vkEnumerateInstanceVersion enumerateInstanceVersion_1_1;
|
// From vkGetInstanceProcAddr.
|
||||||
|
PFN_vkCreateInstance vkCreateInstance;
|
||||||
|
struct {
|
||||||
|
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
|
||||||
|
} v_1_1;
|
||||||
};
|
};
|
||||||
const LibraryFunctions& library_functions() const {
|
const LibraryFunctions& lfn() const { return lfn_; }
|
||||||
return library_functions_;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t api_version() const { return api_version_; }
|
uint32_t api_version() const { return api_version_; }
|
||||||
|
|
||||||
VkInstance instance() const { return instance_; }
|
VkInstance instance() const { return instance_; }
|
||||||
struct InstanceFunctions {
|
struct InstanceFunctions {
|
||||||
PFN_vkCreateDevice createDevice;
|
PFN_vkCreateDevice vkCreateDevice;
|
||||||
PFN_vkDestroyDevice destroyDevice;
|
PFN_vkDestroyDevice vkDestroyDevice;
|
||||||
PFN_vkEnumerateDeviceExtensionProperties enumerateDeviceExtensionProperties;
|
PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
|
||||||
PFN_vkEnumeratePhysicalDevices enumeratePhysicalDevices;
|
PFN_vkEnumerateDeviceExtensionProperties
|
||||||
PFN_vkGetDeviceProcAddr getDeviceProcAddr;
|
vkEnumerateDeviceExtensionProperties;
|
||||||
PFN_vkGetPhysicalDeviceFeatures getPhysicalDeviceFeatures;
|
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
|
||||||
PFN_vkGetPhysicalDeviceProperties getPhysicalDeviceProperties;
|
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr;
|
||||||
|
PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures;
|
||||||
|
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties;
|
||||||
PFN_vkGetPhysicalDeviceQueueFamilyProperties
|
PFN_vkGetPhysicalDeviceQueueFamilyProperties
|
||||||
getPhysicalDeviceQueueFamilyProperties;
|
vkGetPhysicalDeviceQueueFamilyProperties;
|
||||||
PFN_vkGetPhysicalDeviceSurfaceSupportKHR getPhysicalDeviceSurfaceSupportKHR;
|
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR
|
||||||
|
vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
|
||||||
|
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
|
||||||
|
vkGetPhysicalDeviceSurfaceFormatsKHR;
|
||||||
|
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
|
||||||
|
vkGetPhysicalDeviceSurfacePresentModesKHR;
|
||||||
|
PFN_vkGetPhysicalDeviceSurfaceSupportKHR
|
||||||
|
vkGetPhysicalDeviceSurfaceSupportKHR;
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
PFN_vkCreateAndroidSurfaceKHR createAndroidSurfaceKHR;
|
PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR;
|
||||||
#elif XE_PLATFORM_WIN32
|
#elif XE_PLATFORM_WIN32
|
||||||
PFN_vkCreateWin32SurfaceKHR createWin32SurfaceKHR;
|
PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
const InstanceFunctions& ifn() const { return ifn_; }
|
const InstanceFunctions& ifn() const { return ifn_; }
|
||||||
|
@ -103,7 +111,33 @@ class VulkanProvider : public GraphicsProvider {
|
||||||
|
|
||||||
VkDevice device() const { return device_; }
|
VkDevice device() const { return device_; }
|
||||||
struct DeviceFunctions {
|
struct DeviceFunctions {
|
||||||
PFN_vkGetDeviceQueue getDeviceQueue;
|
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
|
||||||
|
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
|
||||||
|
PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
|
||||||
|
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
|
||||||
|
PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
|
||||||
|
PFN_vkCreateCommandPool vkCreateCommandPool;
|
||||||
|
PFN_vkCreateFence vkCreateFence;
|
||||||
|
PFN_vkCreateFramebuffer vkCreateFramebuffer;
|
||||||
|
PFN_vkCreateImageView vkCreateImageView;
|
||||||
|
PFN_vkCreateRenderPass vkCreateRenderPass;
|
||||||
|
PFN_vkCreateSemaphore vkCreateSemaphore;
|
||||||
|
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
|
||||||
|
PFN_vkDestroyCommandPool vkDestroyCommandPool;
|
||||||
|
PFN_vkDestroyFence vkDestroyFence;
|
||||||
|
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
|
||||||
|
PFN_vkDestroyImageView vkDestroyImageView;
|
||||||
|
PFN_vkDestroyRenderPass vkDestroyRenderPass;
|
||||||
|
PFN_vkDestroySemaphore vkDestroySemaphore;
|
||||||
|
PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR;
|
||||||
|
PFN_vkEndCommandBuffer vkEndCommandBuffer;
|
||||||
|
PFN_vkGetDeviceQueue vkGetDeviceQueue;
|
||||||
|
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
|
||||||
|
PFN_vkResetCommandPool vkResetCommandPool;
|
||||||
|
PFN_vkResetFences vkResetFences;
|
||||||
|
PFN_vkQueuePresentKHR vkQueuePresentKHR;
|
||||||
|
PFN_vkQueueSubmit vkQueueSubmit;
|
||||||
|
PFN_vkWaitForFences vkWaitForFences;
|
||||||
};
|
};
|
||||||
const DeviceFunctions& dfn() const { return dfn_; }
|
const DeviceFunctions& dfn() const { return dfn_; }
|
||||||
|
|
||||||
|
@ -122,9 +156,7 @@ class VulkanProvider : public GraphicsProvider {
|
||||||
HMODULE library_ = nullptr;
|
HMODULE library_ = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PFN_vkGetInstanceProcAddr getInstanceProcAddr_ = nullptr;
|
LibraryFunctions lfn_ = {};
|
||||||
PFN_vkDestroyInstance destroyInstance_ = nullptr;
|
|
||||||
LibraryFunctions library_functions_ = {};
|
|
||||||
|
|
||||||
uint32_t api_version_ = VK_API_VERSION_1_0;
|
uint32_t api_version_ = VK_API_VERSION_1_0;
|
||||||
|
|
||||||
|
|
|
@ -200,11 +200,15 @@ void Window::OnPaint(UIEvent* e) {
|
||||||
io.DisplaySize = ImVec2(static_cast<float>(scaled_width()),
|
io.DisplaySize = ImVec2(static_cast<float>(scaled_width()),
|
||||||
static_cast<float>(scaled_height()));
|
static_cast<float>(scaled_height()));
|
||||||
|
|
||||||
context_->BeginSwap();
|
bool can_swap = context_->BeginSwap();
|
||||||
if (context_->WasLost()) {
|
if (context_->WasLost()) {
|
||||||
on_context_lost(e);
|
on_context_lost(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!can_swap) {
|
||||||
|
// Surface not available.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 30a851b67e129a3d91f191b2e9dcdad65ba98438
|
|
|
@ -1,30 +0,0 @@
|
||||||
group("third_party")
|
|
||||||
project("volk")
|
|
||||||
uuid("C9781C93-2DF5-47A2-94EE-2C5EBED61239")
|
|
||||||
kind("StaticLib")
|
|
||||||
language("C")
|
|
||||||
|
|
||||||
defines({
|
|
||||||
"_LIB",
|
|
||||||
"API_NAME=\"vulkan\"",
|
|
||||||
})
|
|
||||||
removedefines({
|
|
||||||
"_UNICODE",
|
|
||||||
"UNICODE",
|
|
||||||
})
|
|
||||||
includedirs({
|
|
||||||
"volk",
|
|
||||||
})
|
|
||||||
files({
|
|
||||||
"volk/volk.c",
|
|
||||||
"volk/volk.h",
|
|
||||||
})
|
|
||||||
|
|
||||||
filter("platforms:Windows")
|
|
||||||
defines({
|
|
||||||
"VK_USE_PLATFORM_WIN32_KHR",
|
|
||||||
})
|
|
||||||
filter("platforms:Linux")
|
|
||||||
defines({
|
|
||||||
"VK_USE_PLATFORM_XCB_KHR",
|
|
||||||
})
|
|
Loading…
Reference in New Issue