Support rendering windowless (tested on the Vulkan backend)

This commit is contained in:
DrChat 2017-12-19 16:02:09 -06:00
parent 8fc71f6f7c
commit 09b3a07e3c
14 changed files with 156 additions and 146 deletions

View File

@ -191,11 +191,13 @@ X_STATUS Emulator::Setup(
// Initialize emulator fallback exception handling last.
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
// Finish initializing the display.
display_window_->loop()->PostSynchronous([this]() {
xe::ui::GraphicsContextLock context_lock(display_window_->context());
Profiler::set_window(display_window_);
});
if (display_window_) {
// Finish initializing the display.
display_window_->loop()->PostSynchronous([this]() {
xe::ui::GraphicsContextLock context_lock(display_window_->context());
Profiler::set_window(display_window_);
});
}
return result;
}

View File

@ -51,16 +51,21 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
// This must happen on the UI thread.
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
if (provider_) {
target_window_->loop()->PostSynchronous([&]() {
// Create the context used for presentation.
assert_null(target_window->context());
target_window_->set_context(provider_->CreateContext(target_window_));
if (target_window_) {
target_window_->loop()->PostSynchronous([&]() {
// Create the context used for presentation.
assert_null(target_window->context());
target_window_->set_context(provider_->CreateContext(target_window_));
// Setup the context the command processor will do all its drawing in.
// It's shared with the display context so that we can resolve
// framebuffers from it.
// Setup the context the command processor will do all its drawing in.
// It's shared with the display context so that we can resolve
// framebuffers from it.
processor_context = provider()->CreateOffscreenContext();
});
} else {
processor_context = provider()->CreateOffscreenContext();
});
}
if (!processor_context) {
xe::FatalError(
"Unable to initialize graphics context. Xenia requires OpenGL 4.5 or "
@ -78,16 +83,21 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
XELOGE("Unable to initialize command processor");
return X_STATUS_UNSUCCESSFUL;
}
command_processor_->set_swap_request_handler(
[this]() { target_window_->Invalidate(); });
// Watch for paint requests to do our swap.
target_window->on_painting.AddListener(
[this](xe::ui::UIEvent* e) { Swap(e); });
if (target_window) {
command_processor_->set_swap_request_handler(
[this]() { target_window_->Invalidate(); });
// Watch for context lost events.
target_window->on_context_lost.AddListener(
[this](xe::ui::UIEvent* e) { Reset(); });
// Watch for paint requests to do our swap.
target_window->on_painting.AddListener(
[this](xe::ui::UIEvent* e) { Swap(e); });
// Watch for context lost events.
target_window->on_context_lost.AddListener(
[this](xe::ui::UIEvent* e) { Reset(); });
} else {
command_processor_->set_swap_request_handler([]() {});
}
// Let the processor know we want register access callbacks.
memory_->AddVirtualMappedRange(

View File

@ -212,13 +212,13 @@ VkResult TextureCache::Initialize() {
invalidated_textures_sets_[1].reserve(64);
invalidated_textures_ = &invalidated_textures_sets_[0];
device_queue_ = device_->AcquireQueue();
device_queue_ = device_->AcquireQueue(device_->queue_family_index());
return VK_SUCCESS;
}
void TextureCache::Shutdown() {
if (device_queue_) {
device_->ReleaseQueue(device_queue_);
device_->ReleaseQueue(device_queue_, device_->queue_family_index());
}
// Free all textures allocated.

View File

@ -62,7 +62,7 @@ bool VulkanCommandProcessor::SetupContext() {
// Acquire our device and queue.
auto context = static_cast<xe::ui::vulkan::VulkanContext*>(context_.get());
device_ = context->device();
queue_ = device_->AcquireQueue();
queue_ = device_->AcquireQueue(device_->queue_family_index());
if (!queue_) {
// Need to reuse primary queue (with locks).
queue_ = device_->primary_queue();
@ -159,7 +159,7 @@ void VulkanCommandProcessor::ShutdownContext() {
// Release queue, if we were using an acquired one.
if (!queue_mutex_) {
device_->ReleaseQueue(queue_);
device_->ReleaseQueue(queue_, device_->queue_family_index());
queue_ = nullptr;
}

View File

@ -37,16 +37,19 @@ X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state,
ui::Window* target_window) {
// Must create the provider so we can create contexts.
provider_ = xe::ui::vulkan::VulkanProvider::Create(target_window);
auto provider = xe::ui::vulkan::VulkanProvider::Create(target_window);
device_ = provider->device();
provider_ = std::move(provider);
auto result = GraphicsSystem::Setup(processor, kernel_state, target_window);
if (result) {
return result;
}
display_context_ = reinterpret_cast<xe::ui::vulkan::VulkanContext*>(
target_window->context());
device_ = display_context_->device();
if (target_window) {
display_context_ = reinterpret_cast<xe::ui::vulkan::VulkanContext*>(
target_window->context());
}
// Create our own command pool we can use for captures.
VkCommandPoolCreateInfo create_info = {

View File

@ -124,12 +124,9 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) {
uint32_t queue_count = 1;
for (size_t i = 0; i < device_info.queue_family_properties.size(); ++i) {
auto queue_flags = device_info.queue_family_properties[i].queueFlags;
if (!device_info.queue_family_supports_present[i]) {
// Can't present from this queue, so ignore it.
continue;
}
if (queue_flags & VK_QUEUE_GRAPHICS_BIT) {
// Can do graphics and present - good!
if (queue_flags & VK_QUEUE_GRAPHICS_BIT &&
queue_flags & VK_QUEUE_TRANSFER_BIT) {
// Can do graphics and transfer - good!
ideal_queue_family_index = static_cast<uint32_t>(i);
// Grab all the queues we can.
queue_count = device_info.queue_family_properties[i].queueCount;
@ -138,7 +135,7 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) {
}
if (ideal_queue_family_index == UINT_MAX) {
FatalVulkanError(
"No queue families available that can both do graphics and present");
"No queue families available that can both do graphics and transfer");
return false;
}
@ -147,23 +144,36 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) {
queue_count = 1;
}
VkDeviceQueueCreateInfo queue_info;
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = nullptr;
queue_info.flags = 0;
queue_info.queueFamilyIndex = ideal_queue_family_index;
queue_info.queueCount = queue_count;
std::vector<float> queue_priorities(queue_count);
// Prioritize the primary queue.
queue_priorities[0] = 1.0f;
queue_info.pQueuePriorities = queue_priorities.data();
std::vector<VkDeviceQueueCreateInfo> queue_infos;
queue_infos.resize(device_info.queue_family_properties.size());
for (int i = 0; i < queue_infos.size(); i++) {
VkDeviceQueueCreateInfo& queue_info = queue_infos[i];
VkQueueFamilyProperties& family_props =
device_info.queue_family_properties[i];
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = nullptr;
queue_info.flags = 0;
queue_info.queueFamilyIndex = i;
queue_info.queueCount = family_props.queueCount;
std::vector<float> queue_priorities(queue_count);
if (i == ideal_queue_family_index) {
// Prioritize the first queue on the primary queue family.
queue_priorities[0] = 1.0f;
} else {
queue_priorities[0] = 0.f;
}
queue_info.pQueuePriorities = queue_priorities.data();
}
VkDeviceCreateInfo create_info;
create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.queueCreateInfoCount = 1;
create_info.pQueueCreateInfos = &queue_info;
create_info.queueCreateInfoCount = static_cast<uint32_t>(queue_infos.size());
create_info.pQueueCreateInfos = queue_infos.data();
create_info.enabledLayerCount = static_cast<uint32_t>(enabled_layers.size());
create_info.ppEnabledLayerNames = enabled_layers.data();
create_info.enabledExtensionCount =
@ -205,31 +215,49 @@ bool VulkanDevice::Initialize(DeviceInfo device_info) {
// Get the primary queue used for most submissions/etc.
vkGetDeviceQueue(handle, queue_family_index_, 0, &primary_queue_);
if (!primary_queue_) {
XELOGE("vkGetDeviceQueue returned nullptr!");
return false;
}
// Get all additional queues, if we got any.
for (uint32_t i = 0; i < queue_count - 1; ++i) {
VkQueue queue;
vkGetDeviceQueue(handle, queue_family_index_, i, &queue);
free_queues_.push_back(queue);
free_queues_.resize(device_info_.queue_family_properties.size());
for (uint32_t i = 0; i < device_info_.queue_family_properties.size(); i++) {
VkQueueFamilyProperties& family_props =
device_info_.queue_family_properties[i];
for (uint32_t j = 0; j < family_props.queueCount; j++) {
VkQueue queue = nullptr;
if (i == queue_family_index_ && j == 0) {
// Already retrieved the primary queue index.
continue;
}
vkGetDeviceQueue(handle, i, j, &queue);
if (queue) {
free_queues_[i].push_back(queue);
}
}
}
XELOGVK("Device initialized successfully!");
return true;
}
VkQueue VulkanDevice::AcquireQueue() {
VkQueue VulkanDevice::AcquireQueue(uint32_t queue_family_index) {
std::lock_guard<std::mutex> lock(queue_mutex_);
if (free_queues_.empty()) {
if (free_queues_[queue_family_index].empty()) {
return nullptr;
}
auto queue = free_queues_.back();
free_queues_.pop_back();
auto queue = free_queues_[queue_family_index].back();
free_queues_[queue_family_index].pop_back();
return queue;
}
void VulkanDevice::ReleaseQueue(VkQueue queue) {
void VulkanDevice::ReleaseQueue(VkQueue queue, uint32_t queue_family_index) {
std::lock_guard<std::mutex> lock(queue_mutex_);
free_queues_.push_back(queue);
free_queues_[queue_family_index].push_back(queue);
}
void VulkanDevice::DbgSetObjectName(VkDevice device, uint64_t object,

View File

@ -70,10 +70,10 @@ class VulkanDevice {
// returns null the primary_queue should be used with the
// primary_queue_mutex.
// This method is thread safe.
VkQueue AcquireQueue();
VkQueue AcquireQueue(uint32_t queue_family_index);
// Releases a queue back to the device pool.
// This method is thread safe.
void ReleaseQueue(VkQueue queue);
void ReleaseQueue(VkQueue queue, uint32_t queue_family_index);
static void DbgSetObjectName(VkDevice device, uint64_t object,
VkDebugReportObjectTypeEXT object_type,
@ -108,7 +108,7 @@ class VulkanDevice {
uint32_t queue_family_index_ = 0;
std::mutex queue_mutex_;
VkQueue primary_queue_ = nullptr;
std::vector<VkQueue> free_queues_;
std::vector<std::vector<VkQueue>> free_queues_;
};
} // namespace vulkan

View File

@ -69,7 +69,7 @@ VulkanInstance::VulkanInstance() {
VulkanInstance::~VulkanInstance() { DestroyInstance(); }
bool VulkanInstance::Initialize(Window* any_target_window) {
bool VulkanInstance::Initialize() {
auto version = Version::Parse(VK_API_VERSION);
XELOGVK("Initializing Vulkan %s...", version.pretty_string.c_str());
@ -87,7 +87,7 @@ bool VulkanInstance::Initialize(Window* any_target_window) {
}
// Query available devices so that we can pick one.
if (!QueryDevices(any_target_window)) {
if (!QueryDevices()) {
XELOGE("Failed to query devices");
return false;
}
@ -347,7 +347,7 @@ void VulkanInstance::DisableDebugValidation() {
dbg_report_callback_ = nullptr;
}
bool VulkanInstance::QueryDevices(Window* any_target_window) {
bool VulkanInstance::QueryDevices() {
// Get handles to all devices.
uint32_t count = 0;
std::vector<VkPhysicalDevice> device_handles;
@ -376,56 +376,6 @@ bool VulkanInstance::QueryDevices(Window* any_target_window) {
vkGetPhysicalDeviceQueueFamilyProperties(
device_handle, &count, device_info.queue_family_properties.data());
// Gather queue family presentation support.
// TODO(benvanik): move to swap chain?
VkSurfaceKHR any_surface = nullptr;
#if XE_PLATFORM_WIN32
VkWin32SurfaceCreateInfoKHR create_info;
create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.hinstance =
static_cast<HINSTANCE>(any_target_window->native_platform_handle());
create_info.hwnd = static_cast<HWND>(any_target_window->native_handle());
err = vkCreateWin32SurfaceKHR(handle, &create_info, nullptr, &any_surface);
CheckResult(err, "vkCreateWin32SurfaceKHR");
#elif XE_PLATFORM_LINUX
#ifdef GDK_WINDOWING_X11
GtkWidget* window_handle =
static_cast<GtkWidget*>(any_target_window->native_handle());
GdkDisplay* gdk_display = gtk_widget_get_display(window_handle);
assert(GDK_IS_X11_DISPLAY(gdk_display));
xcb_connection_t* connection =
XGetXCBConnection(gdk_x11_display_get_xdisplay(gdk_display));
xcb_window_t window =
gdk_x11_window_get_xid(gtk_widget_get_window(window_handle));
VkXcbSurfaceCreateInfoKHR create_info;
create_info.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
create_info.pNext = nullptr;
create_info.flags = 0;
create_info.connection = static_cast<xcb_connection_t*>(
any_target_window->native_platform_handle());
create_info.window = static_cast<xcb_window_t>(window);
auto err =
vkCreateXcbSurfaceKHR(handle, &create_info, nullptr, &any_surface);
CheckResult(err, "vkCreateXcbSurfaceKHR");
#else
#error Unsupported GDK Backend on Linux.
#endif // GDK_WINDOWING_X11
#else
#error Platform not yet implemented.
#endif // XE_PLATFORM_WIN32
device_info.queue_family_supports_present.resize(
device_info.queue_family_properties.size());
for (size_t j = 0; j < device_info.queue_family_supports_present.size();
++j) {
err = vkGetPhysicalDeviceSurfaceSupportKHR(
device_handle, static_cast<uint32_t>(j), any_surface,
&device_info.queue_family_supports_present[j]);
CheckResult(err, "vkGetPhysicalDeviceSurfaceSupportKHR");
}
vkDestroySurfaceKHR(handle, any_surface, nullptr);
// Gather layers.
std::vector<VkLayerProperties> layer_properties;
err = vkEnumerateDeviceLayerProperties(device_handle, &count, nullptr);
@ -552,8 +502,6 @@ void VulkanInstance::DumpDeviceInfo(const DeviceInfo& device_info) {
: "");
XELOGVK(" queueCount = %u", queue_props.queueCount);
XELOGVK(" timestampValidBits = %u", queue_props.timestampValidBits);
XELOGVK(" supportsPresent = %s",
device_info.queue_family_supports_present[j] ? "true" : "false");
}
XELOGVK(" Layers:");

View File

@ -50,8 +50,7 @@ class VulkanInstance {
// preparing the instance for general use.
// If initialization succeeds it's likely that no more failures beyond runtime
// issues will occur.
// TODO(benvanik): remove need for any_target_window - it's just for queries.
bool Initialize(Window* any_target_window);
bool Initialize();
// Returns a list of all available devices as detected during initialization.
const std::vector<DeviceInfo>& available_devices() const {
@ -79,7 +78,7 @@ class VulkanInstance {
void DisableDebugValidation();
// Queries all available physical devices.
bool QueryDevices(Window* any_target_window);
bool QueryDevices();
void DumpLayers(const std::vector<LayerInfo>& layers, const char* indent);
void DumpExtensions(const std::vector<VkExtensionProperties>& extensions,

View File

@ -25,7 +25,7 @@ namespace xe {
namespace ui {
namespace vulkan {
std::unique_ptr<GraphicsProvider> VulkanProvider::Create(Window* main_window) {
std::unique_ptr<VulkanProvider> VulkanProvider::Create(Window* main_window) {
std::unique_ptr<VulkanProvider> provider(new VulkanProvider(main_window));
if (!provider->Initialize()) {
xe::FatalError(
@ -35,7 +35,7 @@ std::unique_ptr<GraphicsProvider> VulkanProvider::Create(Window* main_window) {
"list of supported GPUs.");
return nullptr;
}
return std::unique_ptr<GraphicsProvider>(provider.release());
return provider;
}
VulkanProvider::VulkanProvider(Window* main_window)
@ -56,7 +56,7 @@ bool VulkanProvider::Initialize() {
Version::Make(0, 0, 0), false);
// Attempt initialization and device query.
if (!instance_->Initialize(main_window_)) {
if (!instance_->Initialize()) {
XELOGE("Failed to initialize vulkan instance");
return false;
}

View File

@ -25,7 +25,7 @@ class VulkanProvider : public GraphicsProvider {
public:
~VulkanProvider() override;
static std::unique_ptr<GraphicsProvider> Create(Window* main_window);
static std::unique_ptr<VulkanProvider> Create(Window* main_window);
VulkanInstance* instance() const { return instance_.get(); }
VulkanDevice* device() const { return device_.get(); }

View File

@ -36,14 +36,40 @@ VulkanSwapChain::~VulkanSwapChain() { Shutdown(); }
VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) {
surface_ = surface;
VkResult status;
// Find a queue family that supports presentation.
VkBool32 surface_supported = false;
auto status = vkGetPhysicalDeviceSurfaceSupportKHR(
*device_, device_->queue_family_index(), surface, &surface_supported);
assert_true(surface_supported);
CheckResult(status, "vkGetPhysicalDeviceSurfaceSupportKHR");
if (status != VK_SUCCESS) {
return status;
uint32_t queue_family_index = -1;
for (uint32_t i = 0;
i < device_->device_info().queue_family_properties.size(); i++) {
const VkQueueFamilyProperties& family_props =
device_->device_info().queue_family_properties[i];
if (!(family_props.queueFlags & VK_QUEUE_GRAPHICS_BIT) ||
!(family_props.queueFlags & VK_QUEUE_TRANSFER_BIT)) {
continue;
}
status = vkGetPhysicalDeviceSurfaceSupportKHR(*device_, i, surface,
&surface_supported);
if (status == VK_SUCCESS && surface_supported == VK_TRUE) {
queue_family_index = i;
break;
}
}
if (!surface_supported) {
XELOGE(
"Physical device does not have a queue that supports "
"graphics/transfer/presentation!");
return VK_ERROR_INCOMPATIBLE_DRIVER;
}
presentation_queue_family_ = queue_family_index;
presentation_queue_ = device_->AcquireQueue(queue_family_index);
if (!presentation_queue_) {
XELOGE("Failed to acquire swap chain presentation queue!");
return VK_ERROR_INITIALIZATION_FAILED;
}
// Query supported target formats.
@ -190,7 +216,7 @@ VkResult VulkanSwapChain::Initialize(VkSurfaceKHR surface) {
cmd_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmd_pool_info.pNext = nullptr;
cmd_pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
cmd_pool_info.queueFamilyIndex = device_->queue_family_index();
cmd_pool_info.queueFamilyIndex = presentation_queue_family_;
status = vkCreateCommandPool(*device_, &cmd_pool_info, nullptr, &cmd_pool_);
CheckResult(status, "vkCreateCommandPool");
if (status != VK_SUCCESS) {
@ -432,6 +458,11 @@ void VulkanSwapChain::Shutdown() {
vkDestroyCommandPool(*device_, cmd_pool_, nullptr);
cmd_pool_ = nullptr;
}
if (presentation_queue_) {
device_->ReleaseQueue(presentation_queue_, presentation_queue_family_);
presentation_queue_ = nullptr;
presentation_queue_family_ = -1;
}
// images_ doesn't need to be cleaned up as the swapchain does it implicitly.
if (handle) {
vkDestroySwapchainKHR(*device_, handle, nullptr);
@ -471,11 +502,7 @@ VkResult VulkanSwapChain::Begin() {
wait_submit_info.pCommandBuffers = nullptr;
wait_submit_info.signalSemaphoreCount = 1;
wait_submit_info.pSignalSemaphores = &image_usage_semaphore_;
{
std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex());
status =
vkQueueSubmit(device_->primary_queue(), 1, &wait_submit_info, nullptr);
}
status = vkQueueSubmit(presentation_queue_, 1, &wait_submit_info, nullptr);
if (status != VK_SUCCESS) {
return status;
}
@ -687,12 +714,7 @@ VkResult VulkanSwapChain::End() {
render_submit_info.pCommandBuffers = &cmd_buffer_;
render_submit_info.signalSemaphoreCount = uint32_t(semaphores.size()) - 1;
render_submit_info.pSignalSemaphores = semaphores.data();
{
std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex());
status = vkQueueSubmit(device_->primary_queue(), 1, &render_submit_info,
nullptr);
}
status = vkQueueSubmit(presentation_queue_, 1, &render_submit_info, nullptr);
if (status != VK_SUCCESS) {
return status;
}
@ -709,10 +731,7 @@ VkResult VulkanSwapChain::End() {
present_info.pSwapchains = swap_chains;
present_info.pImageIndices = swap_chain_image_indices;
present_info.pResults = nullptr;
{
std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex());
status = vkQueuePresentKHR(device_->primary_queue(), &present_info);
}
status = vkQueuePresentKHR(presentation_queue_, &present_info);
switch (status) {
case VK_SUCCESS:
break;

View File

@ -78,6 +78,8 @@ class VulkanSwapChain {
VulkanInstance* instance_ = nullptr;
VulkanDevice* device_ = nullptr;
VkQueue presentation_queue_ = nullptr;
uint32_t presentation_queue_family_ = -1;
VkSurfaceKHR surface_ = nullptr;
uint32_t surface_width_ = 0;
uint32_t surface_height_ = 0;

View File

@ -95,7 +95,6 @@ struct DeviceInfo {
VkPhysicalDeviceFeatures features;
VkPhysicalDeviceMemoryProperties memory_properties;
std::vector<VkQueueFamilyProperties> queue_family_properties;
std::vector<VkBool32> queue_family_supports_present;
std::vector<LayerInfo> layers;
std::vector<VkExtensionProperties> extensions;
};