mirror of https://github.com/PCSX2/pcsx2.git
GS: Refactor exclusive fullscreen yet again
Also acquire render window as late as possible. Limits the duration that the main window isn't displaying anything.
This commit is contained in:
parent
20d040d5d1
commit
befbf57191
|
@ -70,8 +70,7 @@ namespace Vulkan
|
||||||
|
|
||||||
Context::~Context() = default;
|
Context::~Context() = default;
|
||||||
|
|
||||||
VkInstance Context::CreateVulkanInstance(
|
VkInstance Context::CreateVulkanInstance(const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer)
|
||||||
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer)
|
|
||||||
{
|
{
|
||||||
ExtensionList enabled_extensions;
|
ExtensionList enabled_extensions;
|
||||||
if (!SelectInstanceExtensions(&enabled_extensions, wi, enable_debug_utils))
|
if (!SelectInstanceExtensions(&enabled_extensions, wi, enable_debug_utils))
|
||||||
|
@ -118,7 +117,7 @@ namespace Vulkan
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Context::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo* wi, bool enable_debug_utils)
|
bool Context::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, bool enable_debug_utils)
|
||||||
{
|
{
|
||||||
u32 extension_count = 0;
|
u32 extension_count = 0;
|
||||||
VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
|
VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
|
||||||
|
@ -155,34 +154,32 @@ namespace Vulkan
|
||||||
};
|
};
|
||||||
|
|
||||||
// Common extensions
|
// Common extensions
|
||||||
if (wi && wi->type != WindowInfo::Type::Surfaceless && !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true))
|
if (wi.type != WindowInfo::Type::Surfaceless && !SupportsExtension(VK_KHR_SURFACE_EXTENSION_NAME, true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
#if defined(VK_USE_PLATFORM_WIN32_KHR)
|
||||||
if (wi && wi->type == WindowInfo::Type::Win32 && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
|
if (wi.type == WindowInfo::Type::Win32 && !SupportsExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true))
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
#if defined(VK_USE_PLATFORM_XLIB_KHR)
|
||||||
if (wi && wi->type == WindowInfo::Type::X11 && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true))
|
if (wi.type == WindowInfo::Type::X11 && !SupportsExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME, true))
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
|
||||||
if (wi && wi->type == WindowInfo::Type::Wayland &&
|
if (wi.type == WindowInfo::Type::Wayland && !SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))
|
||||||
!SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))
|
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
|
||||||
if (wi && wi->type == WindowInfo::Type::Android &&
|
if (wi.type == WindowInfo::Type::Android && !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
|
||||||
!SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
|
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
||||||
if (wi && wi->type == WindowInfo::Type::MacOS && !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))
|
if (wi.type == WindowInfo::Type::MacOS && !SupportsExtension(VK_EXT_METAL_SURFACE_EXTENSION_NAME, true))
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
if (wi && wi->type == WindowInfo::Type::Display && !SupportsExtension(VK_KHR_DISPLAY_EXTENSION_NAME, true))
|
if (wi.type == WindowInfo::Type::Display && !SupportsExtension(VK_KHR_DISPLAY_EXTENSION_NAME, true))
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -274,91 +271,11 @@ namespace Vulkan
|
||||||
return gpu_names;
|
return gpu_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
|
bool Context::Create(VkInstance instance, VkSurfaceKHR surface, VkPhysicalDevice physical_device,
|
||||||
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
|
bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer)
|
||||||
bool enable_validation_layer)
|
|
||||||
{
|
{
|
||||||
pxAssertMsg(!g_vulkan_context, "Has no current context");
|
pxAssertMsg(!g_vulkan_context, "Has no current context");
|
||||||
|
g_vulkan_context.reset(new Context(instance, physical_device));
|
||||||
if (!Vulkan::LoadVulkanLibrary())
|
|
||||||
{
|
|
||||||
Console.Error("Failed to load Vulkan library");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool enable_surface = (wi && wi->type != WindowInfo::Type::Surfaceless);
|
|
||||||
VkInstance instance = CreateVulkanInstance(wi, enable_debug_utils, enable_validation_layer);
|
|
||||||
if (instance == VK_NULL_HANDLE)
|
|
||||||
{
|
|
||||||
if (enable_debug_utils || enable_validation_layer)
|
|
||||||
{
|
|
||||||
// Try again without the validation layer.
|
|
||||||
enable_debug_utils = false;
|
|
||||||
enable_validation_layer = false;
|
|
||||||
instance = CreateVulkanInstance(wi, enable_debug_utils, enable_validation_layer);
|
|
||||||
if (instance == VK_NULL_HANDLE)
|
|
||||||
{
|
|
||||||
Vulkan::UnloadVulkanLibrary();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.Error("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Vulkan::LoadVulkanInstanceFunctions(instance))
|
|
||||||
{
|
|
||||||
Console.Error("Failed to load Vulkan instance functions");
|
|
||||||
vkDestroyInstance(instance, nullptr);
|
|
||||||
Vulkan::UnloadVulkanLibrary();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GPUList gpus = EnumerateGPUs(instance);
|
|
||||||
if (gpus.empty())
|
|
||||||
{
|
|
||||||
vkDestroyInstance(instance, nullptr);
|
|
||||||
Vulkan::UnloadVulkanLibrary();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 gpu_index = 0;
|
|
||||||
GPUNameList gpu_names = EnumerateGPUNames(instance);
|
|
||||||
if (!gpu_name.empty())
|
|
||||||
{
|
|
||||||
for (; gpu_index < static_cast<u32>(gpu_names.size()); gpu_index++)
|
|
||||||
{
|
|
||||||
Console.WriteLn("GPU %u: %s", static_cast<u32>(gpu_index), gpu_names[gpu_index].c_str());
|
|
||||||
if (gpu_names[gpu_index] == gpu_name)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gpu_index == static_cast<u32>(gpu_names.size()))
|
|
||||||
{
|
|
||||||
Console.Warning("Requested GPU '%s' not found, using first (%s)", std::string(gpu_name).c_str(),
|
|
||||||
gpu_names[0].c_str());
|
|
||||||
gpu_index = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLn("No GPU requested, using first (%s)", gpu_names[0].c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
|
||||||
WindowInfo wi_copy;
|
|
||||||
if (wi)
|
|
||||||
wi_copy = *wi;
|
|
||||||
|
|
||||||
if (enable_surface &&
|
|
||||||
(surface = SwapChain::CreateVulkanSurface(instance, gpus[gpu_index], &wi_copy)) == VK_NULL_HANDLE)
|
|
||||||
{
|
|
||||||
vkDestroyInstance(instance, nullptr);
|
|
||||||
Vulkan::UnloadVulkanLibrary();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_vulkan_context.reset(new Context(instance, gpus[gpu_index]));
|
|
||||||
|
|
||||||
if (enable_debug_utils)
|
if (enable_debug_utils)
|
||||||
g_vulkan_context->EnableDebugUtils();
|
g_vulkan_context->EnableDebugUtils();
|
||||||
|
@ -367,8 +284,7 @@ namespace Vulkan
|
||||||
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) ||
|
if (!g_vulkan_context->CreateDevice(surface, enable_validation_layer, nullptr, 0, nullptr, 0, nullptr) ||
|
||||||
!g_vulkan_context->CreateAllocator() || !g_vulkan_context->CreateGlobalDescriptorPool() ||
|
!g_vulkan_context->CreateAllocator() || !g_vulkan_context->CreateGlobalDescriptorPool() ||
|
||||||
!g_vulkan_context->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() ||
|
!g_vulkan_context->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() ||
|
||||||
!g_vulkan_context->InitSpinResources() ||
|
!g_vulkan_context->InitSpinResources())
|
||||||
(enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, preferred_present_mode)) == nullptr))
|
|
||||||
{
|
{
|
||||||
// Since we are destroying the instance, we're also responsible for destroying the surface.
|
// Since we are destroying the instance, we're also responsible for destroying the surface.
|
||||||
if (surface != VK_NULL_HANDLE)
|
if (surface != VK_NULL_HANDLE)
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace Vulkan
|
||||||
|
|
||||||
// Helper method to create a Vulkan instance.
|
// Helper method to create a Vulkan instance.
|
||||||
static VkInstance CreateVulkanInstance(
|
static VkInstance CreateVulkanInstance(
|
||||||
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer);
|
const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer);
|
||||||
|
|
||||||
// Returns a list of Vulkan-compatible GPUs.
|
// Returns a list of Vulkan-compatible GPUs.
|
||||||
using GPUList = std::vector<VkPhysicalDevice>;
|
using GPUList = std::vector<VkPhysicalDevice>;
|
||||||
|
@ -72,9 +72,8 @@ namespace Vulkan
|
||||||
static GPUNameList EnumerateGPUNames(VkInstance instance);
|
static GPUNameList EnumerateGPUNames(VkInstance instance);
|
||||||
|
|
||||||
// Creates a new context and sets it up as global.
|
// Creates a new context and sets it up as global.
|
||||||
static bool Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
|
static bool Create(VkInstance instance, VkSurfaceKHR surface, VkPhysicalDevice physical_device,
|
||||||
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
|
bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer);
|
||||||
bool enable_validation_layer);
|
|
||||||
|
|
||||||
// Destroys context.
|
// Destroys context.
|
||||||
static void Destroy();
|
static void Destroy();
|
||||||
|
@ -267,7 +266,7 @@ namespace Vulkan
|
||||||
|
|
||||||
using ExtensionList = std::vector<const char*>;
|
using ExtensionList = std::vector<const char*>;
|
||||||
static bool SelectInstanceExtensions(
|
static bool SelectInstanceExtensions(
|
||||||
ExtensionList* extension_list, const WindowInfo* wi, bool enable_debug_utils);
|
ExtensionList* extension_list, const WindowInfo& wi, bool enable_debug_utils);
|
||||||
bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface);
|
bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface);
|
||||||
bool SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features);
|
bool SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features);
|
||||||
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions,
|
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions,
|
||||||
|
|
|
@ -569,8 +569,8 @@ namespace Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select swap chain flags, we only need a colour attachment
|
// Select swap chain flags, we only need a colour attachment
|
||||||
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||||
if (!(surface_capabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT))
|
if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
|
||||||
{
|
{
|
||||||
Console.Error("Vulkan: Swap chain does not support usage as color attachment");
|
Console.Error("Vulkan: Swap chain does not support usage as color attachment");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -254,17 +254,11 @@ void Host::SetRelativeMouseMode(bool enabled)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WindowInfo> Host::AcquireRenderWindow()
|
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||||
{
|
{
|
||||||
return GSRunner::GetPlatformWindowInfo();
|
return GSRunner::GetPlatformWindowInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
|
|
||||||
{
|
|
||||||
// We shouldn't ever recreate with the runner, so this is okay..
|
|
||||||
return GSRunner::GetPlatformWindowInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Host::ReleaseRenderWindow()
|
void Host::ReleaseRenderWindow()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -405,10 +405,9 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
|
||||||
connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
|
connect(m_ui.actionStartFullscreenUI, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
|
||||||
connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
|
connect(m_ui.actionStartFullscreenUI2, &QAction::triggered, thread, &EmuThread::startFullscreenUI);
|
||||||
connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
|
connect(thread, &EmuThread::messageConfirmed, this, &MainWindow::confirmMessage, Qt::BlockingQueuedConnection);
|
||||||
connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplayWindow, Qt::BlockingQueuedConnection);
|
connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection);
|
||||||
connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplayWindow, Qt::BlockingQueuedConnection);
|
connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection);
|
||||||
connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay, Qt::BlockingQueuedConnection);
|
connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested);
|
||||||
connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested);
|
|
||||||
connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested);
|
connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested);
|
||||||
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
|
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
|
||||||
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
|
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
|
||||||
|
@ -1748,57 +1747,27 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::optional<WindowInfo> MainWindow::createDisplayWindow(bool fullscreen, bool render_to_main)
|
std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless)
|
||||||
{
|
{
|
||||||
DevCon.WriteLn(
|
DevCon.WriteLn("acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false",
|
||||||
"createDisplayWindow() fullscreen=%s render_to_main=%s", fullscreen ? "true" : "false", render_to_main ? "true" : "false");
|
|
||||||
|
|
||||||
createDisplayWidget(fullscreen, render_to_main);
|
|
||||||
|
|
||||||
// we need the surface visible.. this might be able to be replaced with something else
|
|
||||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
||||||
|
|
||||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
|
||||||
if (!wi.has_value())
|
|
||||||
{
|
|
||||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
|
||||||
destroyDisplayWidget(true);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_emu_thread->connectDisplaySignals(m_display_widget);
|
|
||||||
m_display_created = true;
|
|
||||||
|
|
||||||
updateWindowTitle();
|
|
||||||
updateWindowState();
|
|
||||||
|
|
||||||
m_ui.actionStartFullscreenUI->setEnabled(false);
|
|
||||||
m_ui.actionStartFullscreenUI2->setEnabled(false);
|
|
||||||
|
|
||||||
updateDisplayWidgetCursor();
|
|
||||||
m_display_widget->setFocus();
|
|
||||||
|
|
||||||
return wi;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless)
|
|
||||||
{
|
|
||||||
DevCon.WriteLn("updateDisplayWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false",
|
|
||||||
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false");
|
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false");
|
||||||
|
|
||||||
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||||
const bool is_fullscreen = isRenderingFullscreen();
|
const bool is_fullscreen = isRenderingFullscreen();
|
||||||
const bool is_rendering_to_main = isRenderingToMain();
|
const bool is_rendering_to_main = isRenderingToMain();
|
||||||
const bool changing_surfaceless = (!m_display_widget != surfaceless);
|
const bool changing_surfaceless = (!m_display_widget != surfaceless);
|
||||||
if (!recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless)
|
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
|
||||||
return m_display_widget->getWindowInfo();
|
!changing_surfaceless)
|
||||||
|
{
|
||||||
|
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
|
||||||
|
}
|
||||||
|
|
||||||
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
|
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
|
||||||
// .. except on Wayland, where everything tends to break if you don't recreate.
|
// .. except on Wayland, where everything tends to break if you don't recreate.
|
||||||
const bool has_container = (m_display_container != nullptr);
|
const bool has_container = (m_display_container != nullptr);
|
||||||
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
|
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
|
||||||
if (!recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container && !needs_container &&
|
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
|
||||||
!changing_surfaceless)
|
!needs_container && !changing_surfaceless)
|
||||||
{
|
{
|
||||||
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
||||||
|
|
||||||
|
@ -1825,6 +1794,7 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyDisplayWidget(surfaceless);
|
destroyDisplayWidget(surfaceless);
|
||||||
|
m_display_created = true;
|
||||||
|
|
||||||
// if we're going to surfaceless, we're done here
|
// if we're going to surfaceless, we're done here
|
||||||
if (surfaceless)
|
if (surfaceless)
|
||||||
|
@ -1832,10 +1802,13 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
|
||||||
|
|
||||||
createDisplayWidget(fullscreen, render_to_main);
|
createDisplayWidget(fullscreen, render_to_main);
|
||||||
|
|
||||||
|
// we need the surface visible.. this might be able to be replaced with something else
|
||||||
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
||||||
if (!wi.has_value())
|
if (!wi.has_value())
|
||||||
{
|
{
|
||||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget"));
|
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
||||||
destroyDisplayWidget(true);
|
destroyDisplayWidget(true);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -1845,6 +1818,9 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
updateWindowState();
|
updateWindowState();
|
||||||
|
|
||||||
|
m_ui.actionStartFullscreenUI->setEnabled(false);
|
||||||
|
m_ui.actionStartFullscreenUI2->setEnabled(false);
|
||||||
|
|
||||||
updateDisplayWidgetCursor();
|
updateDisplayWidgetCursor();
|
||||||
m_display_widget->setFocus();
|
m_display_widget->setFocus();
|
||||||
|
|
||||||
|
@ -1954,7 +1930,7 @@ void MainWindow::relativeMouseModeRequested(bool enabled)
|
||||||
updateDisplayWidgetCursor();
|
updateDisplayWidgetCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::destroyDisplay()
|
void MainWindow::releaseRenderWindow()
|
||||||
{
|
{
|
||||||
// Now we can safely destroy the display window.
|
// Now we can safely destroy the display window.
|
||||||
destroyDisplayWidget(true);
|
destroyDisplayWidget(true);
|
||||||
|
@ -2506,19 +2482,33 @@ void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path)
|
||||||
|
|
||||||
MainWindow::VMLock MainWindow::pauseAndLockVM()
|
MainWindow::VMLock MainWindow::pauseAndLockVM()
|
||||||
{
|
{
|
||||||
const bool was_fullscreen = isRenderingFullscreen();
|
// To switch out of fullscreen when displaying a popup, or not to?
|
||||||
|
// For Windows, with driver's direct scanout, what renders behind tends to be hit and miss.
|
||||||
|
// We can't draw anything over exclusive fullscreen, so get out of it in that case.
|
||||||
|
// Wayland's a pain as usual, we need to recreate the window, which means there'll be a brief
|
||||||
|
// period when there's no window, and Qt might shut us down. So avoid it there.
|
||||||
|
// On MacOS, it forces a workspace switch, which is kinda jarring.
|
||||||
|
#ifndef __APPLE__
|
||||||
|
const bool was_fullscreen = g_emu_thread->isFullscreen() && !s_use_central_widget;
|
||||||
|
#else
|
||||||
|
const bool was_fullscreen = false;
|
||||||
|
#endif
|
||||||
const bool was_paused = s_vm_paused;
|
const bool was_paused = s_vm_paused;
|
||||||
|
|
||||||
// We use surfaceless rather than switching out of fullscreen, because
|
|
||||||
// we're paused, so we're not going to be rendering anyway.
|
|
||||||
if (was_fullscreen)
|
|
||||||
g_emu_thread->setSurfaceless(true);
|
|
||||||
if (!was_paused)
|
if (!was_paused)
|
||||||
g_emu_thread->setVMPaused(true);
|
g_emu_thread->setVMPaused(true);
|
||||||
|
|
||||||
// We want to parent dialogs to the display widget, except if we were fullscreen,
|
// We need to switch out of exclusive fullscreen before we can display our popup.
|
||||||
// since it's going to get destroyed by the surfaceless call above.
|
// However, we do not want to switch back to render-to-main, the window might have generated this event.
|
||||||
QWidget* dialog_parent = was_fullscreen ? static_cast<QWidget*>(this) : getDisplayContainer();
|
if (was_fullscreen)
|
||||||
|
{
|
||||||
|
g_emu_thread->setFullscreen(false, false);
|
||||||
|
while (QtHost::IsVMValid() && g_emu_thread->isFullscreen())
|
||||||
|
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen).
|
||||||
|
QWidget* dialog_parent = getDisplayContainer();
|
||||||
|
|
||||||
return VMLock(dialog_parent, was_paused, was_fullscreen);
|
return VMLock(dialog_parent, was_paused, was_fullscreen);
|
||||||
}
|
}
|
||||||
|
@ -2548,17 +2538,10 @@ MainWindow::VMLock::VMLock(VMLock&& lock)
|
||||||
MainWindow::VMLock::~VMLock()
|
MainWindow::VMLock::~VMLock()
|
||||||
{
|
{
|
||||||
if (m_was_fullscreen)
|
if (m_was_fullscreen)
|
||||||
g_emu_thread->setSurfaceless(false);
|
g_emu_thread->setFullscreen(true, true);
|
||||||
|
|
||||||
if (!m_was_paused)
|
if (!m_was_paused)
|
||||||
g_emu_thread->setVMPaused(false);
|
g_emu_thread->setVMPaused(false);
|
||||||
|
|
||||||
if (m_was_fullscreen)
|
|
||||||
{
|
|
||||||
// Wait until we get the display widget back, so we're not the last window left and closing.
|
|
||||||
while (QtHost::IsVMValid() && !g_main_window->m_display_widget)
|
|
||||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::VMLock::cancelResume()
|
void MainWindow::VMLock::cancelResume()
|
||||||
|
|
|
@ -69,7 +69,7 @@ public:
|
||||||
void cancelResume();
|
void cancelResume();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen);
|
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen);
|
||||||
friend MainWindow;
|
friend MainWindow;
|
||||||
|
|
||||||
QWidget* m_dialog_parent;
|
QWidget* m_dialog_parent;
|
||||||
|
@ -122,11 +122,10 @@ public Q_SLOTS:
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onUpdateCheckComplete();
|
void onUpdateCheckComplete();
|
||||||
|
|
||||||
std::optional<WindowInfo> createDisplayWindow(bool fullscreen, bool render_to_main);
|
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
||||||
std::optional<WindowInfo> updateDisplayWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
|
||||||
void displayResizeRequested(qint32 width, qint32 height);
|
void displayResizeRequested(qint32 width, qint32 height);
|
||||||
void relativeMouseModeRequested(bool enabled);
|
void relativeMouseModeRequested(bool enabled);
|
||||||
void destroyDisplay();
|
void releaseRenderWindow();
|
||||||
void focusDisplayWidget();
|
void focusDisplayWidget();
|
||||||
|
|
||||||
void onGameListRefreshComplete();
|
void onGameListRefreshComplete();
|
||||||
|
|
|
@ -196,8 +196,8 @@ void EmuThread::startFullscreenUI(bool fullscreen)
|
||||||
// this should just set the flag so it gets automatically started
|
// this should just set the flag so it gets automatically started
|
||||||
ImGuiManager::InitializeFullscreenUI();
|
ImGuiManager::InitializeFullscreenUI();
|
||||||
m_run_fullscreen_ui = true;
|
m_run_fullscreen_ui = true;
|
||||||
if (fullscreen)
|
m_is_rendering_to_main = shouldRenderToMain();
|
||||||
m_is_fullscreen = true;
|
m_is_fullscreen = fullscreen;
|
||||||
|
|
||||||
if (!GetMTGS().WaitForOpen())
|
if (!GetMTGS().WaitForOpen())
|
||||||
{
|
{
|
||||||
|
@ -242,6 +242,7 @@ void EmuThread::startVM(std::shared_ptr<VMBootParameters> boot_params)
|
||||||
pxAssertRel(!VMManager::HasValidVM(), "VM is shut down");
|
pxAssertRel(!VMManager::HasValidVM(), "VM is shut down");
|
||||||
|
|
||||||
// Determine whether to start fullscreen or not.
|
// Determine whether to start fullscreen or not.
|
||||||
|
m_is_rendering_to_main = shouldRenderToMain();
|
||||||
if (boot_params->fullscreen.has_value())
|
if (boot_params->fullscreen.has_value())
|
||||||
m_is_fullscreen = boot_params->fullscreen.value();
|
m_is_fullscreen = boot_params->fullscreen.value();
|
||||||
else
|
else
|
||||||
|
@ -492,14 +493,14 @@ void EmuThread::toggleFullscreen()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFullscreen(!m_is_fullscreen);
|
setFullscreen(!m_is_fullscreen, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::setFullscreen(bool fullscreen)
|
void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
|
||||||
{
|
{
|
||||||
if (!isOnEmuThread())
|
if (!isOnEmuThread())
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen));
|
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen), Q_ARG(bool, allow_render_to_main));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,6 +509,7 @@ void EmuThread::setFullscreen(bool fullscreen)
|
||||||
|
|
||||||
// This will call back to us on the MTGS thread.
|
// This will call back to us on the MTGS thread.
|
||||||
m_is_fullscreen = fullscreen;
|
m_is_fullscreen = fullscreen;
|
||||||
|
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
|
||||||
GetMTGS().UpdateDisplayWindow();
|
GetMTGS().UpdateDisplayWindow();
|
||||||
GetMTGS().WaitGS();
|
GetMTGS().WaitGS();
|
||||||
|
|
||||||
|
@ -893,32 +895,24 @@ void EmuThread::endCapture()
|
||||||
GetMTGS().RunOnGSThread(&GSEndCapture);
|
GetMTGS().RunOnGSThread(&GSEndCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WindowInfo> EmuThread::acquireRenderWindow()
|
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
|
||||||
{
|
{
|
||||||
m_is_rendering_to_main = shouldRenderToMain();
|
// Check if we're wanting to get exclusive fullscreen. This should be safe to read, since we're going to be calling from the GS thread.
|
||||||
m_is_surfaceless = false;
|
m_is_exclusive_fullscreen = m_is_fullscreen && GSWantsExclusiveFullscreen();
|
||||||
|
const bool window_fullscreen = m_is_fullscreen && !m_is_exclusive_fullscreen;
|
||||||
|
const bool render_to_main = !m_is_exclusive_fullscreen && !window_fullscreen && m_is_rendering_to_main;
|
||||||
|
|
||||||
return emit onCreateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main);
|
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless);
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> EmuThread::updateRenderWindow(bool recreate_window)
|
|
||||||
{
|
|
||||||
return emit onUpdateDisplayRequested(recreate_window, m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main, m_is_surfaceless);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::releaseRenderWindow()
|
void EmuThread::releaseRenderWindow()
|
||||||
{
|
{
|
||||||
emit onDestroyDisplayRequested();
|
emit onReleaseRenderWindowRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WindowInfo> Host::AcquireRenderWindow()
|
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||||
{
|
{
|
||||||
return g_emu_thread->acquireRenderWindow();
|
return g_emu_thread->acquireRenderWindow(recreate_window);
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
|
|
||||||
{
|
|
||||||
return g_emu_thread->updateRenderWindow(recreate_window);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::ReleaseRenderWindow()
|
void Host::ReleaseRenderWindow()
|
||||||
|
@ -932,7 +926,7 @@ void Host::BeginPresentFrame()
|
||||||
|
|
||||||
void Host::RequestResizeHostDisplay(s32 width, s32 height)
|
void Host::RequestResizeHostDisplay(s32 width, s32 height)
|
||||||
{
|
{
|
||||||
g_emu_thread->onResizeDisplayRequested(width, height);
|
g_emu_thread->onResizeRenderWindowRequested(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Host::OnVMStarting()
|
void Host::OnVMStarting()
|
||||||
|
@ -1194,7 +1188,7 @@ bool Host::IsFullscreen()
|
||||||
|
|
||||||
void Host::SetFullscreen(bool enabled)
|
void Host::SetFullscreen(bool enabled)
|
||||||
{
|
{
|
||||||
g_emu_thread->setFullscreen(enabled);
|
g_emu_thread->setFullscreen(enabled, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
alignas(16) static SysMtgsThread s_mtgs_thread;
|
alignas(16) static SysMtgsThread s_mtgs_thread;
|
||||||
|
|
|
@ -61,6 +61,7 @@ public:
|
||||||
|
|
||||||
__fi QEventLoop* getEventLoop() const { return m_event_loop; }
|
__fi QEventLoop* getEventLoop() const { return m_event_loop; }
|
||||||
__fi bool isFullscreen() const { return m_is_fullscreen; }
|
__fi bool isFullscreen() const { return m_is_fullscreen; }
|
||||||
|
__fi bool isExclusiveFullscreen() const { return m_is_exclusive_fullscreen; }
|
||||||
__fi bool isRenderingToMain() const { return m_is_rendering_to_main; }
|
__fi bool isRenderingToMain() const { return m_is_rendering_to_main; }
|
||||||
__fi bool isSurfaceless() const { return m_is_surfaceless; }
|
__fi bool isSurfaceless() const { return m_is_surfaceless; }
|
||||||
__fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
|
__fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
|
||||||
|
@ -69,8 +70,7 @@ public:
|
||||||
bool shouldRenderToMain() const;
|
bool shouldRenderToMain() const;
|
||||||
|
|
||||||
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
|
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
|
||||||
std::optional<WindowInfo> acquireRenderWindow();
|
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
|
||||||
std::optional<WindowInfo> updateRenderWindow(bool recreate_window);
|
|
||||||
void connectDisplaySignals(DisplayWidget* widget);
|
void connectDisplaySignals(DisplayWidget* widget);
|
||||||
void releaseRenderWindow();
|
void releaseRenderWindow();
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ public Q_SLOTS:
|
||||||
void saveState(const QString& filename);
|
void saveState(const QString& filename);
|
||||||
void saveStateToSlot(qint32 slot);
|
void saveStateToSlot(qint32 slot);
|
||||||
void toggleFullscreen();
|
void toggleFullscreen();
|
||||||
void setFullscreen(bool fullscreen);
|
void setFullscreen(bool fullscreen, bool allow_render_to_main);
|
||||||
void setSurfaceless(bool surfaceless);
|
void setSurfaceless(bool surfaceless);
|
||||||
void applySettings();
|
void applySettings();
|
||||||
void reloadGameSettings();
|
void reloadGameSettings();
|
||||||
|
@ -117,10 +117,9 @@ public Q_SLOTS:
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
bool messageConfirmed(const QString& title, const QString& message);
|
bool messageConfirmed(const QString& title, const QString& message);
|
||||||
|
|
||||||
std::optional<WindowInfo> onCreateDisplayRequested(bool fullscreen, bool render_to_main);
|
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
||||||
std::optional<WindowInfo> onUpdateDisplayRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
|
void onResizeRenderWindowRequested(qint32 width, qint32 height);
|
||||||
void onResizeDisplayRequested(qint32 width, qint32 height);
|
void onReleaseRenderWindowRequested();
|
||||||
void onDestroyDisplayRequested();
|
|
||||||
void onRelativeMouseModeRequested(bool enabled);
|
void onRelativeMouseModeRequested(bool enabled);
|
||||||
|
|
||||||
/// Called when the VM is starting initialization, but has not been completed yet.
|
/// Called when the VM is starting initialization, but has not been completed yet.
|
||||||
|
@ -195,6 +194,7 @@ private:
|
||||||
bool m_run_fullscreen_ui = false;
|
bool m_run_fullscreen_ui = false;
|
||||||
bool m_is_rendering_to_main = false;
|
bool m_is_rendering_to_main = false;
|
||||||
bool m_is_fullscreen = false;
|
bool m_is_fullscreen = false;
|
||||||
|
bool m_is_exclusive_fullscreen = false;
|
||||||
bool m_is_surfaceless = false;
|
bool m_is_surfaceless = false;
|
||||||
bool m_save_state_on_shutdown = false;
|
bool m_save_state_on_shutdown = false;
|
||||||
bool m_pause_on_focus_loss = false;
|
bool m_pause_on_focus_loss = false;
|
||||||
|
|
|
@ -133,52 +133,8 @@ static RenderAPI GetAPIForRenderer(GSRendererType renderer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UpdateExclusiveFullscreen(bool force_off)
|
static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool recreate_window)
|
||||||
{
|
{
|
||||||
if (!g_gs_device->SupportsExclusiveFullscreen())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::string fullscreen_mode = Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "");
|
|
||||||
u32 width, height;
|
|
||||||
float refresh_rate;
|
|
||||||
|
|
||||||
const bool wants_fullscreen = !force_off && Host::IsFullscreen() && !fullscreen_mode.empty() &&
|
|
||||||
GSDevice::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate);
|
|
||||||
const bool is_fullscreen = g_gs_device->IsExclusiveFullscreen();
|
|
||||||
if (wants_fullscreen == is_fullscreen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (wants_fullscreen)
|
|
||||||
{
|
|
||||||
Console.WriteLn("Trying to acquire exclusive fullscreen...");
|
|
||||||
if (g_gs_device->SetExclusiveFullscreen(true, width, height, refresh_rate))
|
|
||||||
{
|
|
||||||
Host::AddKeyedOSDMessage(
|
|
||||||
"UpdateExclusiveFullscreen", "Acquired exclusive fullscreen.", Host::OSD_INFO_DURATION);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Host::AddKeyedOSDMessage(
|
|
||||||
"UpdateExclusiveFullscreen", "Failed to acquire exclusive fullscreen.", Host::OSD_WARNING_DURATION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLn("Leaving exclusive fullscreen...");
|
|
||||||
g_gs_device->SetExclusiveFullscreen(false, 0, 0, 0.0f);
|
|
||||||
Host::AddKeyedOSDMessage("UpdateExclusiveFullscreen", "Lost exclusive fullscreen.", Host::OSD_INFO_DURATION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool reopening, bool recreate_window)
|
|
||||||
{
|
|
||||||
std::optional<WindowInfo> wi = reopening ? Host::UpdateRenderWindow(recreate_window) : Host::AcquireRenderWindow();
|
|
||||||
if (!wi.has_value())
|
|
||||||
{
|
|
||||||
Console.Error("Failed to acquire render window.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RenderAPI new_api = GetAPIForRenderer(renderer);
|
const RenderAPI new_api = GetAPIForRenderer(renderer);
|
||||||
switch (new_api)
|
switch (new_api)
|
||||||
{
|
{
|
||||||
|
@ -212,8 +168,7 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VsyncMode vsync_mode = Host::GetEffectiveVSyncMode();
|
bool okay = g_gs_device->Create();
|
||||||
bool okay = g_gs_device->Create(wi.value(), vsync_mode);
|
|
||||||
if (okay)
|
if (okay)
|
||||||
{
|
{
|
||||||
okay = ImGuiManager::Initialize();
|
okay = ImGuiManager::Initialize();
|
||||||
|
@ -239,24 +194,17 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
|
||||||
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api));
|
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api));
|
||||||
Console.Indent().WriteLn(g_gs_device->GetDriverInfo());
|
Console.Indent().WriteLn(g_gs_device->GetDriverInfo());
|
||||||
|
|
||||||
// Switch to exclusive fullscreen if enabled.
|
|
||||||
UpdateExclusiveFullscreen(false);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CloseGSDevice(bool clear_state, bool reopening)
|
static void CloseGSDevice(bool clear_state)
|
||||||
{
|
{
|
||||||
if (!g_gs_device)
|
if (!g_gs_device)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UpdateExclusiveFullscreen(true);
|
|
||||||
ImGuiManager::Shutdown(clear_state);
|
ImGuiManager::Shutdown(clear_state);
|
||||||
g_gs_device->Destroy();
|
g_gs_device->Destroy();
|
||||||
g_gs_device.reset();
|
g_gs_device.reset();
|
||||||
|
|
||||||
if (!reopening)
|
|
||||||
Host::ReleaseRenderWindow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool OpenGSRenderer(GSRendererType renderer, u8* basemem)
|
static bool OpenGSRenderer(GSRendererType renderer, u8* basemem)
|
||||||
|
@ -336,18 +284,18 @@ bool GSreopen(bool recreate_device, bool recreate_renderer, const Pcsx2Config::G
|
||||||
{
|
{
|
||||||
// We need a new render window when changing APIs.
|
// We need a new render window when changing APIs.
|
||||||
const bool recreate_window = (g_gs_device->GetRenderAPI() != GetAPIForRenderer(GSConfig.Renderer));
|
const bool recreate_window = (g_gs_device->GetRenderAPI() != GetAPIForRenderer(GSConfig.Renderer));
|
||||||
CloseGSDevice(false, true);
|
CloseGSDevice(false);
|
||||||
|
|
||||||
if (!OpenGSDevice(GSConfig.Renderer, false, true, recreate_window) ||
|
if (!OpenGSDevice(GSConfig.Renderer, false, recreate_window) ||
|
||||||
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
|
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
|
||||||
{
|
{
|
||||||
Host::AddKeyedOSDMessage(
|
Host::AddKeyedOSDMessage(
|
||||||
"GSReopenFailed", "Failed to reopen, restoring old configuration.", Host::OSD_CRITICAL_ERROR_DURATION);
|
"GSReopenFailed", "Failed to reopen, restoring old configuration.", Host::OSD_CRITICAL_ERROR_DURATION);
|
||||||
|
|
||||||
CloseGSDevice(false, true);
|
CloseGSDevice(false);
|
||||||
|
|
||||||
GSConfig = old_config;
|
GSConfig = old_config;
|
||||||
if (!OpenGSDevice(GSConfig.Renderer, false, true, recreate_window) ||
|
if (!OpenGSDevice(GSConfig.Renderer, false, recreate_window) ||
|
||||||
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
|
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
|
||||||
{
|
{
|
||||||
pxFailRel("Failed to reopen GS on old config");
|
pxFailRel("Failed to reopen GS on old config");
|
||||||
|
@ -387,12 +335,12 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
|
||||||
GSConfig = config;
|
GSConfig = config;
|
||||||
GSConfig.Renderer = renderer;
|
GSConfig.Renderer = renderer;
|
||||||
|
|
||||||
bool res = OpenGSDevice(renderer, true, false, false);
|
bool res = OpenGSDevice(renderer, true, false);
|
||||||
if (res)
|
if (res)
|
||||||
{
|
{
|
||||||
res = OpenGSRenderer(renderer, basemem);
|
res = OpenGSRenderer(renderer, basemem);
|
||||||
if (!res)
|
if (!res)
|
||||||
CloseGSDevice(true, false);
|
CloseGSDevice(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res)
|
if (!res)
|
||||||
|
@ -410,7 +358,7 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
|
||||||
void GSclose()
|
void GSclose()
|
||||||
{
|
{
|
||||||
CloseGSRenderer();
|
CloseGSRenderer();
|
||||||
CloseGSDevice(true, false);
|
CloseGSDevice(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSreset(bool hardware_reset)
|
void GSreset(bool hardware_reset)
|
||||||
|
@ -626,24 +574,12 @@ void GSResizeDisplayWindow(int width, int height, float scale)
|
||||||
|
|
||||||
void GSUpdateDisplayWindow()
|
void GSUpdateDisplayWindow()
|
||||||
{
|
{
|
||||||
UpdateExclusiveFullscreen(true);
|
if (!g_gs_device->UpdateWindow())
|
||||||
g_gs_device->DestroySurface();
|
|
||||||
|
|
||||||
const std::optional<WindowInfo> wi = Host::UpdateRenderWindow(false);
|
|
||||||
if (!wi.has_value())
|
|
||||||
{
|
{
|
||||||
pxFailRel("Failed to get window info after update.");
|
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_gs_device->ChangeWindow(wi.value()))
|
|
||||||
{
|
|
||||||
pxFailRel("Failed to change window after update.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateExclusiveFullscreen(false);
|
|
||||||
|
|
||||||
ImGuiManager::WindowResized();
|
ImGuiManager::WindowResized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,6 +588,16 @@ void GSSetVSyncMode(VsyncMode mode)
|
||||||
g_gs_device->SetVSync(mode);
|
g_gs_device->SetVSync(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GSWantsExclusiveFullscreen()
|
||||||
|
{
|
||||||
|
if (!g_gs_device || !g_gs_device->SupportsExclusiveFullscreen())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
u32 width, height;
|
||||||
|
float refresh_rate;
|
||||||
|
return GSDevice::GetRequestedExclusiveFullscreenMode(&width, &height, &refresh_rate);
|
||||||
|
}
|
||||||
|
|
||||||
bool GSGetHostRefreshRate(float* refresh_rate)
|
bool GSGetHostRefreshRate(float* refresh_rate)
|
||||||
{
|
{
|
||||||
if (!g_gs_device)
|
if (!g_gs_device)
|
||||||
|
|
|
@ -99,6 +99,7 @@ void GSResizeDisplayWindow(int width, int height, float scale);
|
||||||
void GSUpdateDisplayWindow();
|
void GSUpdateDisplayWindow();
|
||||||
void GSSetVSyncMode(VsyncMode mode);
|
void GSSetVSyncMode(VsyncMode mode);
|
||||||
|
|
||||||
|
bool GSWantsExclusiveFullscreen();
|
||||||
bool GSGetHostRefreshRate(float* refresh_rate);
|
bool GSGetHostRefreshRate(float* refresh_rate);
|
||||||
void GSGetAdaptersAndFullscreenModes(
|
void GSGetAdaptersAndFullscreenModes(
|
||||||
GSRendererType renderer, std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes);
|
GSRendererType renderer, std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes);
|
||||||
|
@ -128,11 +129,8 @@ struct GSRecoverableError : GSError
|
||||||
namespace Host
|
namespace Host
|
||||||
{
|
{
|
||||||
/// Called when the GS is creating a render device.
|
/// Called when the GS is creating a render device.
|
||||||
std::optional<WindowInfo> AcquireRenderWindow();
|
/// This could also be fullscreen transition.
|
||||||
|
std::optional<WindowInfo> AcquireRenderWindow(bool recreate_window);
|
||||||
/// Called on the MTGS thread when a request to update the display is received.
|
|
||||||
/// This could be a fullscreen transition, for example.
|
|
||||||
std::optional<WindowInfo> UpdateRenderWindow(bool recreate_window);
|
|
||||||
|
|
||||||
/// Called before drawing the OSD and other display elements.
|
/// Called before drawing the OSD and other display elements.
|
||||||
void BeginPresentFrame();
|
void BeginPresentFrame();
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "GS/GSGL.h"
|
#include "GS/GSGL.h"
|
||||||
#include "GS/GS.h"
|
#include "GS/GS.h"
|
||||||
#include "Host.h"
|
#include "Host.h"
|
||||||
|
#include "HostSettings.h"
|
||||||
#include "common/Align.h"
|
#include "common/Align.h"
|
||||||
#include "common/StringUtil.h"
|
#include "common/StringUtil.h"
|
||||||
|
|
||||||
|
@ -113,14 +114,16 @@ const char* GSDevice::RenderAPIToString(RenderAPI api)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate)
|
bool GSDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate)
|
||||||
{
|
{
|
||||||
|
const std::string mode = Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "");
|
||||||
if (!mode.empty())
|
if (!mode.empty())
|
||||||
{
|
{
|
||||||
|
const std::string_view mode_view = mode;
|
||||||
std::string_view::size_type sep1 = mode.find('x');
|
std::string_view::size_type sep1 = mode.find('x');
|
||||||
if (sep1 != std::string_view::npos)
|
if (sep1 != std::string_view::npos)
|
||||||
{
|
{
|
||||||
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode.substr(0, sep1));
|
std::optional<u32> owidth = StringUtil::FromChars<u32>(mode_view.substr(0, sep1));
|
||||||
sep1++;
|
sep1++;
|
||||||
|
|
||||||
while (sep1 < mode.length() && std::isspace(mode[sep1]))
|
while (sep1 < mode.length() && std::isspace(mode[sep1]))
|
||||||
|
@ -131,7 +134,7 @@ bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32
|
||||||
std::string_view::size_type sep2 = mode.find('@', sep1);
|
std::string_view::size_type sep2 = mode.find('@', sep1);
|
||||||
if (sep2 != std::string_view::npos)
|
if (sep2 != std::string_view::npos)
|
||||||
{
|
{
|
||||||
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode.substr(sep1, sep2 - sep1));
|
std::optional<u32> oheight = StringUtil::FromChars<u32>(mode_view.substr(sep1, sep2 - sep1));
|
||||||
sep2++;
|
sep2++;
|
||||||
|
|
||||||
while (sep2 < mode.length() && std::isspace(mode[sep2]))
|
while (sep2 < mode.length() && std::isspace(mode[sep2]))
|
||||||
|
@ -139,7 +142,7 @@ bool GSDevice::ParseFullscreenMode(const std::string_view& mode, u32* width, u32
|
||||||
|
|
||||||
if (oheight.has_value() && sep2 < mode.length())
|
if (oheight.has_value() && sep2 < mode.length())
|
||||||
{
|
{
|
||||||
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode.substr(sep2));
|
std::optional<float> orefresh_rate = StringUtil::FromChars<float>(mode_view.substr(sep2));
|
||||||
if (orefresh_rate.has_value())
|
if (orefresh_rate.has_value())
|
||||||
{
|
{
|
||||||
*width = owidth.value();
|
*width = owidth.value();
|
||||||
|
@ -181,10 +184,9 @@ void GSDevice::GenerateExpansionIndexBuffer(void* buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice::Create(const WindowInfo& wi, VsyncMode vsync)
|
bool GSDevice::Create()
|
||||||
{
|
{
|
||||||
m_window_info = wi;
|
m_vsync_mode = Host::GetEffectiveVSyncMode();
|
||||||
m_vsync_mode = vsync;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +202,26 @@ void GSDevice::Destroy()
|
||||||
PurgePool();
|
PurgePool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GSDevice::AcquireWindow(bool recreate_window)
|
||||||
|
{
|
||||||
|
std::optional<WindowInfo> wi = Host::AcquireRenderWindow(recreate_window);
|
||||||
|
if (!wi.has_value())
|
||||||
|
{
|
||||||
|
Console.Error("Failed to acquire render window.");
|
||||||
|
Host::ReportErrorAsync("Error", "Failed to acquire render window. The log may have more information.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_window_info = std::move(wi.value());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GSDevice::ReleaseWindow()
|
||||||
|
{
|
||||||
|
Host::ReleaseRenderWindow();
|
||||||
|
m_window_info = WindowInfo();
|
||||||
|
}
|
||||||
|
|
||||||
bool GSDevice::GetHostRefreshRate(float* refresh_rate)
|
bool GSDevice::GetHostRefreshRate(float* refresh_rate)
|
||||||
{
|
{
|
||||||
if (m_window_info.surface_refresh_rate > 0.0f)
|
if (m_window_info.surface_refresh_rate > 0.0f)
|
||||||
|
|
|
@ -779,6 +779,9 @@ protected:
|
||||||
bool m_rbswapped = false;
|
bool m_rbswapped = false;
|
||||||
FeatureSupport m_features;
|
FeatureSupport m_features;
|
||||||
|
|
||||||
|
bool AcquireWindow(bool recreate_window);
|
||||||
|
void ReleaseWindow();
|
||||||
|
|
||||||
virtual GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) = 0;
|
virtual GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) = 0;
|
||||||
GSTexture* FetchSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_reuse);
|
GSTexture* FetchSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format, bool clear, bool prefer_reuse);
|
||||||
|
|
||||||
|
@ -800,8 +803,8 @@ public:
|
||||||
/// Returns a string representing the specified API.
|
/// Returns a string representing the specified API.
|
||||||
static const char* RenderAPIToString(RenderAPI api);
|
static const char* RenderAPIToString(RenderAPI api);
|
||||||
|
|
||||||
/// Parses a fullscreen mode into its components (width * height @ refresh hz)
|
/// Parses the configured fullscreen mode into its components (width * height @ refresh hz)
|
||||||
static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate);
|
static bool GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate);
|
||||||
|
|
||||||
/// Converts a fullscreen mode to a string.
|
/// Converts a fullscreen mode to a string.
|
||||||
static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate);
|
static std::string GetFullscreenModeString(u32 width, u32 height, float refresh_rate);
|
||||||
|
@ -830,7 +833,7 @@ public:
|
||||||
/// Recreates the font, call when the window scaling changes.
|
/// Recreates the font, call when the window scaling changes.
|
||||||
bool UpdateImGuiFontTexture();
|
bool UpdateImGuiFontTexture();
|
||||||
|
|
||||||
virtual bool Create(const WindowInfo& wi, VsyncMode vsync);
|
virtual bool Create();
|
||||||
virtual void Destroy();
|
virtual void Destroy();
|
||||||
|
|
||||||
/// Returns the graphics API used by this device.
|
/// Returns the graphics API used by this device.
|
||||||
|
@ -843,7 +846,7 @@ public:
|
||||||
virtual void DestroySurface() = 0;
|
virtual void DestroySurface() = 0;
|
||||||
|
|
||||||
/// Switches to a new window/surface.
|
/// Switches to a new window/surface.
|
||||||
virtual bool ChangeWindow(const WindowInfo& wi) = 0;
|
virtual bool UpdateWindow() = 0;
|
||||||
|
|
||||||
/// Call when the window size changes externally to recreate any resources.
|
/// Call when the window size changes externally to recreate any resources.
|
||||||
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0;
|
virtual void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) = 0;
|
||||||
|
@ -851,12 +854,6 @@ public:
|
||||||
/// Returns true if exclusive fullscreen is supported.
|
/// Returns true if exclusive fullscreen is supported.
|
||||||
virtual bool SupportsExclusiveFullscreen() const = 0;
|
virtual bool SupportsExclusiveFullscreen() const = 0;
|
||||||
|
|
||||||
/// Returns true if exclusive fullscreen is active.
|
|
||||||
virtual bool IsExclusiveFullscreen() = 0;
|
|
||||||
|
|
||||||
/// Attempts to switch to the specified mode in exclusive fullscreen.
|
|
||||||
virtual bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) = 0;
|
|
||||||
|
|
||||||
/// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be
|
/// Returns false if the window was completely occluded. If frame_skip is set, the frame won't be
|
||||||
/// displayed, but the GPU command queue will still be flushed.
|
/// displayed, but the GPU command queue will still be flushed.
|
||||||
virtual PresentResult BeginPresent(bool frame_skip) = 0;
|
virtual PresentResult BeginPresent(bool frame_skip) = 0;
|
||||||
|
|
|
@ -120,6 +120,79 @@ std::vector<std::string> D3D::GetFullscreenModes(IDXGIFactory5* factory, const s
|
||||||
return modes;
|
return modes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool D3D::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width,
|
||||||
|
u32 height, float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output)
|
||||||
|
{
|
||||||
|
// We need to find which monitor the window is located on.
|
||||||
|
const GSVector4i client_rc_vec = GSVector4i::load<false>(&window_rect);
|
||||||
|
|
||||||
|
// The window might be on a different adapter to which we are rendering.. so we have to enumerate them all.
|
||||||
|
HRESULT hr;
|
||||||
|
wil::com_ptr_nothrow<IDXGIOutput> first_output, intersecting_output;
|
||||||
|
|
||||||
|
for (u32 adapter_index = 0; !intersecting_output; adapter_index++)
|
||||||
|
{
|
||||||
|
wil::com_ptr_nothrow<IDXGIAdapter1> adapter;
|
||||||
|
hr = factory->EnumAdapters1(adapter_index, adapter.put());
|
||||||
|
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||||
|
break;
|
||||||
|
else if (FAILED(hr))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (u32 output_index = 0;; output_index++)
|
||||||
|
{
|
||||||
|
wil::com_ptr_nothrow<IDXGIOutput> this_output;
|
||||||
|
DXGI_OUTPUT_DESC output_desc;
|
||||||
|
hr = adapter->EnumOutputs(output_index, this_output.put());
|
||||||
|
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||||
|
break;
|
||||||
|
else if (FAILED(hr) || FAILED(this_output->GetDesc(&output_desc)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const GSVector4i output_rc = GSVector4i::load<false>(&output_desc.DesktopCoordinates);
|
||||||
|
if (!client_rc_vec.rintersect(output_rc).rempty())
|
||||||
|
{
|
||||||
|
intersecting_output = std::move(this_output);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to the first monitor.
|
||||||
|
if (!first_output)
|
||||||
|
first_output = std::move(this_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!intersecting_output)
|
||||||
|
{
|
||||||
|
if (!first_output)
|
||||||
|
{
|
||||||
|
Console.Error("No DXGI output found. Can't use exclusive fullscreen.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Warning("No DXGI output found for window, using first.");
|
||||||
|
intersecting_output = std::move(first_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
DXGI_MODE_DESC request_mode = {};
|
||||||
|
request_mode.Width = width;
|
||||||
|
request_mode.Height = height;
|
||||||
|
request_mode.Format = format;
|
||||||
|
request_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
|
||||||
|
request_mode.RefreshRate.Denominator = 1000u;
|
||||||
|
|
||||||
|
if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
|
||||||
|
request_mode.Format != format)
|
||||||
|
{
|
||||||
|
Console.Error("Failed to find closest matching mode, hr=%08X", hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*output = intersecting_output.get();
|
||||||
|
intersecting_output->AddRef();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
wil::com_ptr_nothrow<IDXGIAdapter1> D3D::GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name)
|
wil::com_ptr_nothrow<IDXGIAdapter1> D3D::GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name)
|
||||||
{
|
{
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
|
|
|
@ -36,6 +36,10 @@ namespace D3D
|
||||||
// returns a list of fullscreen modes for the specified adapter
|
// returns a list of fullscreen modes for the specified adapter
|
||||||
std::vector<std::string> GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name);
|
std::vector<std::string> GetFullscreenModes(IDXGIFactory5* factory, const std::string_view& adapter_name);
|
||||||
|
|
||||||
|
// returns the fullscreen mode to use for the specified dimensions
|
||||||
|
bool GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory, const RECT& window_rect, u32 width, u32 height,
|
||||||
|
float refresh_rate, DXGI_FORMAT format, DXGI_MODE_DESC* fullscreen_mode, IDXGIOutput** output);
|
||||||
|
|
||||||
// get an adapter based on name
|
// get an adapter based on name
|
||||||
wil::com_ptr_nothrow<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name);
|
wil::com_ptr_nothrow<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@
|
||||||
|
|
||||||
// #define REPORT_LEAKED_OBJECTS 1
|
// #define REPORT_LEAKED_OBJECTS 1
|
||||||
|
|
||||||
|
static constexpr std::array<float, 4> s_present_clear_color = {};
|
||||||
|
|
||||||
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
|
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
|
||||||
{
|
{
|
||||||
UINT support;
|
UINT support;
|
||||||
|
@ -75,9 +77,9 @@ RenderAPI GSDevice11::GetRenderAPI() const
|
||||||
return RenderAPI::D3D11;
|
return RenderAPI::D3D11;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
|
bool GSDevice11::Create()
|
||||||
{
|
{
|
||||||
if (!GSDevice::Create(wi, vsync))
|
if (!GSDevice::Create())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
UINT create_flags = 0;
|
UINT create_flags = 0;
|
||||||
|
@ -150,7 +152,7 @@ bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||||
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported));
|
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported, sizeof(allow_tearing_supported));
|
||||||
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
|
m_allow_tearing_supported = (SUCCEEDED(hr) && allow_tearing_supported == TRUE);
|
||||||
|
|
||||||
if (wi.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
|
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
D3D11_BUFFER_DESC bd;
|
D3D11_BUFFER_DESC bd;
|
||||||
|
@ -490,7 +492,8 @@ bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||||
void GSDevice11::Destroy()
|
void GSDevice11::Destroy()
|
||||||
{
|
{
|
||||||
GSDevice::Destroy();
|
GSDevice::Destroy();
|
||||||
GSDevice11::DestroySurface();
|
DestroySwapChain();
|
||||||
|
ReleaseWindow();
|
||||||
DestroyTimestampQueries();
|
DestroyTimestampQueries();
|
||||||
|
|
||||||
m_convert = {};
|
m_convert = {};
|
||||||
|
@ -561,7 +564,7 @@ bool GSDevice11::HasSurface() const
|
||||||
|
|
||||||
bool GSDevice11::GetHostRefreshRate(float* refresh_rate)
|
bool GSDevice11::GetHostRefreshRate(float* refresh_rate)
|
||||||
{
|
{
|
||||||
if (m_swap_chain && IsExclusiveFullscreen())
|
if (m_swap_chain && m_is_exclusive_fullscreen)
|
||||||
{
|
{
|
||||||
DXGI_SWAP_CHAIN_DESC desc;
|
DXGI_SWAP_CHAIN_DESC desc;
|
||||||
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
|
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
|
||||||
|
@ -583,50 +586,84 @@ void GSDevice11::SetVSync(VsyncMode mode)
|
||||||
m_vsync_mode = mode;
|
m_vsync_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
bool GSDevice11::CreateSwapChain()
|
||||||
{
|
{
|
||||||
|
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
|
||||||
if (m_window_info.type != WindowInfo::Type::Win32)
|
if (m_window_info.type != WindowInfo::Type::Win32)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
m_using_flip_model_swap_chain = !EmuConfig.GS.UseBlitSwapChain || fullscreen_mode;
|
|
||||||
|
|
||||||
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
||||||
RECT client_rc{};
|
RECT client_rc{};
|
||||||
GetClientRect(window_hwnd, &client_rc);
|
GetClientRect(window_hwnd, &client_rc);
|
||||||
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
|
|
||||||
const u32 height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
DXGI_MODE_DESC fullscreen_mode;
|
||||||
|
wil::com_ptr_nothrow<IDXGIOutput> fullscreen_output;
|
||||||
|
if (Host::IsFullscreen())
|
||||||
|
{
|
||||||
|
u32 fullscreen_width, fullscreen_height;
|
||||||
|
float fullscreen_refresh_rate;
|
||||||
|
m_is_exclusive_fullscreen =
|
||||||
|
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
|
||||||
|
D3D::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.get(), client_rc, fullscreen_width,
|
||||||
|
fullscreen_height, fullscreen_refresh_rate, swap_chain_format, &fullscreen_mode,
|
||||||
|
fullscreen_output.put());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_is_exclusive_fullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_using_flip_model_swap_chain = !EmuConfig.GS.UseBlitSwapChain || m_is_exclusive_fullscreen;
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||||
swap_chain_desc.Width = width;
|
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
|
||||||
swap_chain_desc.Height = height;
|
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||||
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
swap_chain_desc.Format = swap_chain_format;
|
||||||
swap_chain_desc.SampleDesc.Count = 1;
|
swap_chain_desc.SampleDesc.Count = 1;
|
||||||
swap_chain_desc.BufferCount = 3;
|
swap_chain_desc.BufferCount = 3;
|
||||||
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
swap_chain_desc.SwapEffect =
|
swap_chain_desc.SwapEffect =
|
||||||
m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
|
m_using_flip_model_swap_chain ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD;
|
||||||
|
|
||||||
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !fullscreen_mode);
|
m_using_allow_tearing = (m_allow_tearing_supported && m_using_flip_model_swap_chain && !m_is_exclusive_fullscreen);
|
||||||
if (m_using_allow_tearing)
|
if (m_using_allow_tearing)
|
||||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
HRESULT hr = S_OK;
|
||||||
if (fullscreen_mode)
|
|
||||||
|
if (m_is_exclusive_fullscreen)
|
||||||
{
|
{
|
||||||
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
|
||||||
swap_chain_desc.Width = fullscreen_mode->Width;
|
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||||
swap_chain_desc.Height = fullscreen_mode->Height;
|
|
||||||
fs_desc.RefreshRate = fullscreen_mode->RefreshRate;
|
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||||
fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering;
|
fs_sd_desc.Width = fullscreen_mode.Width;
|
||||||
fs_desc.Scaling = fullscreen_mode->Scaling;
|
fs_sd_desc.Height = fullscreen_mode.Height;
|
||||||
|
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
|
||||||
|
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
|
||||||
|
fs_desc.Scaling = fullscreen_mode.Scaling;
|
||||||
fs_desc.Windowed = FALSE;
|
fs_desc.Windowed = FALSE;
|
||||||
|
|
||||||
|
Console.WriteLn("Creating a %dx%d exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
|
||||||
|
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||||
|
m_dev.get(), window_hwnd, &fs_sd_desc, &fs_desc, fullscreen_output.get(), m_swap_chain.put());
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
Console.Warning("Failed to create fullscreen swap chain, trying windowed.");
|
||||||
|
m_is_exclusive_fullscreen = false;
|
||||||
|
m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLn("Creating a %dx%d %s %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
if (!m_is_exclusive_fullscreen)
|
||||||
m_using_flip_model_swap_chain ? "flip-discard" : "discard", fullscreen_mode ? "full-screen" : "windowed");
|
{
|
||||||
|
Console.WriteLn("Creating a %dx%d %s windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
||||||
|
m_using_flip_model_swap_chain ? "flip-discard" : "discard");
|
||||||
|
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||||
|
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
|
||||||
|
}
|
||||||
|
|
||||||
HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc,
|
|
||||||
fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
|
|
||||||
if (FAILED(hr) && m_using_flip_model_swap_chain)
|
if (FAILED(hr) && m_using_flip_model_swap_chain)
|
||||||
{
|
{
|
||||||
Console.Warning("Failed to create a flip-discard swap chain, trying discard.");
|
Console.Warning("Failed to create a flip-discard swap chain, trying discard.");
|
||||||
|
@ -635,8 +672,8 @@ bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
||||||
m_using_flip_model_swap_chain = false;
|
m_using_flip_model_swap_chain = false;
|
||||||
m_using_allow_tearing = false;
|
m_using_allow_tearing = false;
|
||||||
|
|
||||||
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc,
|
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||||
fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
|
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
|
Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
|
||||||
|
@ -648,7 +685,16 @@ bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
|
Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
|
||||||
|
|
||||||
return CreateSwapChainRTV();
|
if (!CreateSwapChainRTV())
|
||||||
|
{
|
||||||
|
DestroySwapChain();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||||
|
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
|
||||||
|
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice11::CreateSwapChainRTV()
|
bool GSDevice11::CreateSwapChainRTV()
|
||||||
|
@ -670,6 +716,7 @@ bool GSDevice11::CreateSwapChainRTV()
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr);
|
Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr);
|
||||||
|
m_swap_chain_rtv.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -696,25 +743,42 @@ bool GSDevice11::CreateSwapChainRTV()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice11::ChangeWindow(const WindowInfo& new_wi)
|
void GSDevice11::DestroySwapChain()
|
||||||
{
|
{
|
||||||
DestroySurface();
|
if (!m_swap_chain)
|
||||||
|
return;
|
||||||
|
|
||||||
m_window_info = new_wi;
|
m_swap_chain_rtv.reset();
|
||||||
if (new_wi.type == WindowInfo::Type::Surfaceless)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return CreateSwapChain(nullptr);
|
// switch out of fullscreen before destroying
|
||||||
|
BOOL is_fullscreen;
|
||||||
|
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||||
|
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||||
|
|
||||||
|
m_swap_chain.reset();
|
||||||
|
m_is_exclusive_fullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GSDevice11::UpdateWindow()
|
||||||
|
{
|
||||||
|
DestroySwapChain();
|
||||||
|
|
||||||
|
if (!AcquireWindow(false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
|
||||||
|
{
|
||||||
|
Console.WriteLn("Failed to create swap chain on updated window");
|
||||||
|
ReleaseWindow();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSDevice11::DestroySurface()
|
void GSDevice11::DestroySurface()
|
||||||
{
|
{
|
||||||
m_swap_chain_rtv.reset();
|
DestroySwapChain();
|
||||||
|
|
||||||
if (IsExclusiveFullscreen())
|
|
||||||
SetExclusiveFullscreen(false, 0, 0, 0.0f);
|
|
||||||
|
|
||||||
m_swap_chain.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GSDevice11::GetDriverInfo() const
|
std::string GSDevice11::GetDriverInfo() const
|
||||||
|
@ -768,7 +832,7 @@ std::string GSDevice11::GetDriverInfo() const
|
||||||
|
|
||||||
void GSDevice11::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
void GSDevice11::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
|
||||||
{
|
{
|
||||||
if (!m_swap_chain)
|
if (!m_swap_chain || m_is_exclusive_fullscreen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_window_info.surface_scale = new_window_scale;
|
m_window_info.surface_scale = new_window_scale;
|
||||||
|
@ -792,79 +856,21 @@ bool GSDevice11::SupportsExclusiveFullscreen() const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice11::IsExclusiveFullscreen()
|
|
||||||
{
|
|
||||||
BOOL is_fullscreen = FALSE;
|
|
||||||
return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GSDevice11::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
|
||||||
{
|
|
||||||
if (!m_swap_chain)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BOOL is_fullscreen = FALSE;
|
|
||||||
HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr);
|
|
||||||
if (!fullscreen)
|
|
||||||
{
|
|
||||||
// leaving fullscreen
|
|
||||||
if (is_fullscreen)
|
|
||||||
return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr));
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDXGIOutput* output;
|
|
||||||
if (FAILED(hr = m_swap_chain->GetContainingOutput(&output)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC current_desc;
|
|
||||||
hr = m_swap_chain->GetDesc(¤t_desc);
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DXGI_MODE_DESC new_mode = current_desc.BufferDesc;
|
|
||||||
new_mode.Width = width;
|
|
||||||
new_mode.Height = height;
|
|
||||||
new_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
|
|
||||||
new_mode.RefreshRate.Denominator = 1000u;
|
|
||||||
|
|
||||||
DXGI_MODE_DESC closest_mode;
|
|
||||||
if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) ||
|
|
||||||
new_mode.Format != current_desc.BufferDesc.Format)
|
|
||||||
{
|
|
||||||
Console.Error("Failed to find closest matching mode, hr=%08X", hr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Height &&
|
|
||||||
new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator &&
|
|
||||||
new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator)
|
|
||||||
{
|
|
||||||
DevCon.WriteLn("Fullscreen mode already set");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_swap_chain_rtv.reset();
|
|
||||||
m_swap_chain.reset();
|
|
||||||
|
|
||||||
if (!CreateSwapChain(&closest_mode))
|
|
||||||
{
|
|
||||||
Console.Error("Failed to create a fullscreen swap chain");
|
|
||||||
if (!CreateSwapChain(nullptr))
|
|
||||||
pxFailRel("Failed to recreate windowed swap chain");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
||||||
{
|
{
|
||||||
if (frame_skip || !m_swap_chain)
|
if (frame_skip || !m_swap_chain)
|
||||||
return PresentResult::FrameSkipped;
|
return PresentResult::FrameSkipped;
|
||||||
|
|
||||||
|
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
|
||||||
|
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
|
||||||
|
BOOL is_fullscreen;
|
||||||
|
if (m_is_exclusive_fullscreen &&
|
||||||
|
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||||
|
{
|
||||||
|
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
|
||||||
|
return PresentResult::FrameSkipped;
|
||||||
|
}
|
||||||
|
|
||||||
// When using vsync, the time here seems to include the time for the buffer to become available.
|
// When using vsync, the time here seems to include the time for the buffer to become available.
|
||||||
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
|
// This blows our our GPU usage number considerably, so read the timestamp before the final blit
|
||||||
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
|
// in this configuration. It does reduce accuracy a little, but better than seeing 100% all of
|
||||||
|
@ -872,8 +878,7 @@ GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
||||||
if (m_vsync_mode != VsyncMode::Off && m_gpu_timing_enabled)
|
if (m_vsync_mode != VsyncMode::Off && m_gpu_timing_enabled)
|
||||||
PopTimestampQuery();
|
PopTimestampQuery();
|
||||||
|
|
||||||
static constexpr std::array<float, 4> clear_color = {};
|
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
|
||||||
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), clear_color.data());
|
|
||||||
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
|
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
|
||||||
if (m_state.rt_view)
|
if (m_state.rt_view)
|
||||||
m_state.rt_view->Release();
|
m_state.rt_view->Release();
|
||||||
|
|
|
@ -120,8 +120,9 @@ private:
|
||||||
|
|
||||||
void SetFeatures();
|
void SetFeatures();
|
||||||
|
|
||||||
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
|
bool CreateSwapChain();
|
||||||
bool CreateSwapChainRTV();
|
bool CreateSwapChainRTV();
|
||||||
|
void DestroySwapChain();
|
||||||
|
|
||||||
bool CreateTimestampQueries();
|
bool CreateTimestampQueries();
|
||||||
void DestroyTimestampQueries();
|
void DestroyTimestampQueries();
|
||||||
|
@ -160,6 +161,7 @@ private:
|
||||||
bool m_allow_tearing_supported = false;
|
bool m_allow_tearing_supported = false;
|
||||||
bool m_using_flip_model_swap_chain = true;
|
bool m_using_flip_model_swap_chain = true;
|
||||||
bool m_using_allow_tearing = false;
|
bool m_using_allow_tearing = false;
|
||||||
|
bool m_is_exclusive_fullscreen = false;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
@ -281,16 +283,14 @@ public:
|
||||||
__fi ID3D11Device1* GetD3DDevice() const { return m_dev.get(); }
|
__fi ID3D11Device1* GetD3DDevice() const { return m_dev.get(); }
|
||||||
__fi ID3D11DeviceContext1* GetD3DContext() const { return m_ctx.get(); }
|
__fi ID3D11DeviceContext1* GetD3DContext() const { return m_ctx.get(); }
|
||||||
|
|
||||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
bool Create() override;
|
||||||
void Destroy() override;
|
void Destroy() override;
|
||||||
|
|
||||||
RenderAPI GetRenderAPI() const override;
|
RenderAPI GetRenderAPI() const override;
|
||||||
|
|
||||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
bool UpdateWindow() override;
|
||||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||||
bool SupportsExclusiveFullscreen() const override;
|
bool SupportsExclusiveFullscreen() const override;
|
||||||
bool IsExclusiveFullscreen() override;
|
|
||||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
|
||||||
bool HasSurface() const override;
|
bool HasSurface() const override;
|
||||||
void DestroySurface() override;
|
void DestroySurface() override;
|
||||||
std::string GetDriverInfo() const override;
|
std::string GetDriverInfo() const override;
|
||||||
|
|
|
@ -44,6 +44,8 @@ static bool IsDATEModePrimIDInit(u32 flag) { return flag == 1 || flag == 2; }
|
||||||
static constexpr std::array<D3D12_PRIMITIVE_TOPOLOGY, 3> s_primitive_topology_mapping =
|
static constexpr std::array<D3D12_PRIMITIVE_TOPOLOGY, 3> s_primitive_topology_mapping =
|
||||||
{{D3D_PRIMITIVE_TOPOLOGY_POINTLIST, D3D_PRIMITIVE_TOPOLOGY_LINELIST, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST}};
|
{{D3D_PRIMITIVE_TOPOLOGY_POINTLIST, D3D_PRIMITIVE_TOPOLOGY_LINELIST, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST}};
|
||||||
|
|
||||||
|
static constexpr std::array<float, 4> s_present_clear_color = {};
|
||||||
|
|
||||||
static D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE GetLoadOpForTexture(GSTexture12* tex)
|
static D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE GetLoadOpForTexture(GSTexture12* tex)
|
||||||
{
|
{
|
||||||
if (!tex)
|
if (!tex)
|
||||||
|
@ -116,9 +118,9 @@ bool GSDevice12::HasSurface() const
|
||||||
return static_cast<bool>(m_swap_chain);
|
return static_cast<bool>(m_swap_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice12::Create(const WindowInfo& wi, VsyncMode vsync)
|
bool GSDevice12::Create()
|
||||||
{
|
{
|
||||||
if (!GSDevice::Create(wi, vsync))
|
if (!GSDevice::Create())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice);
|
m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice);
|
||||||
|
@ -144,7 +146,7 @@ bool GSDevice12::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
|
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -215,16 +217,15 @@ void GSDevice12::Destroy()
|
||||||
EndRenderPass();
|
EndRenderPass();
|
||||||
ExecuteCommandList(true);
|
ExecuteCommandList(true);
|
||||||
DestroyResources();
|
DestroyResources();
|
||||||
|
DestroySwapChain();
|
||||||
g_d3d12_context->WaitForGPUIdle();
|
ReleaseWindow();
|
||||||
GSDevice12::DestroySurface();
|
|
||||||
g_d3d12_context->Destroy();
|
g_d3d12_context->Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice12::GetHostRefreshRate(float* refresh_rate)
|
bool GSDevice12::GetHostRefreshRate(float* refresh_rate)
|
||||||
{
|
{
|
||||||
if (m_swap_chain && IsExclusiveFullscreen())
|
if (m_swap_chain && m_is_exclusive_fullscreen)
|
||||||
{
|
{
|
||||||
DXGI_SWAP_CHAIN_DESC desc;
|
DXGI_SWAP_CHAIN_DESC desc;
|
||||||
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
|
if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
|
||||||
|
@ -246,59 +247,101 @@ void GSDevice12::SetVSync(VsyncMode mode)
|
||||||
m_vsync_mode = mode;
|
m_vsync_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GSDevice12::CreateSwapChain()
|
||||||
bool GSDevice12::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
|
|
||||||
{
|
{
|
||||||
|
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||||
|
|
||||||
if (m_window_info.type != WindowInfo::Type::Win32)
|
if (m_window_info.type != WindowInfo::Type::Win32)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
|
||||||
RECT client_rc{};
|
RECT client_rc{};
|
||||||
GetClientRect(window_hwnd, &client_rc);
|
GetClientRect(window_hwnd, &client_rc);
|
||||||
const u32 width = static_cast<u32>(client_rc.right - client_rc.left);
|
|
||||||
const u32 height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
DXGI_MODE_DESC fullscreen_mode;
|
||||||
|
wil::com_ptr_nothrow<IDXGIOutput> fullscreen_output;
|
||||||
|
if (Host::IsFullscreen())
|
||||||
|
{
|
||||||
|
u32 fullscreen_width, fullscreen_height;
|
||||||
|
float fullscreen_refresh_rate;
|
||||||
|
m_is_exclusive_fullscreen =
|
||||||
|
GetRequestedExclusiveFullscreenMode(&fullscreen_width, &fullscreen_height, &fullscreen_refresh_rate) &&
|
||||||
|
D3D::GetRequestedExclusiveFullscreenModeDesc(m_dxgi_factory.get(), client_rc, fullscreen_width,
|
||||||
|
fullscreen_height, fullscreen_refresh_rate, swap_chain_format, &fullscreen_mode,
|
||||||
|
fullscreen_output.put());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_is_exclusive_fullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
|
||||||
swap_chain_desc.Width = width;
|
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
|
||||||
swap_chain_desc.Height = height;
|
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
|
||||||
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
swap_chain_desc.Format = swap_chain_format;
|
||||||
swap_chain_desc.SampleDesc.Count = 1;
|
swap_chain_desc.SampleDesc.Count = 1;
|
||||||
swap_chain_desc.BufferCount = 3;
|
swap_chain_desc.BufferCount = 3;
|
||||||
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||||
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
||||||
|
|
||||||
m_using_allow_tearing = (m_allow_tearing_supported && !fullscreen_mode);
|
m_using_allow_tearing = (m_allow_tearing_supported && !m_is_exclusive_fullscreen);
|
||||||
if (m_using_allow_tearing)
|
if (m_using_allow_tearing)
|
||||||
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
HRESULT hr = S_OK;
|
||||||
if (fullscreen_mode)
|
|
||||||
|
if (m_is_exclusive_fullscreen)
|
||||||
{
|
{
|
||||||
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
|
||||||
swap_chain_desc.Width = fullscreen_mode->Width;
|
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
|
||||||
swap_chain_desc.Height = fullscreen_mode->Height;
|
|
||||||
fs_desc.RefreshRate = fullscreen_mode->RefreshRate;
|
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
|
||||||
fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering;
|
fs_sd_desc.Width = fullscreen_mode.Width;
|
||||||
fs_desc.Scaling = fullscreen_mode->Scaling;
|
fs_sd_desc.Height = fullscreen_mode.Height;
|
||||||
|
fs_desc.RefreshRate = fullscreen_mode.RefreshRate;
|
||||||
|
fs_desc.ScanlineOrdering = fullscreen_mode.ScanlineOrdering;
|
||||||
|
fs_desc.Scaling = fullscreen_mode.Scaling;
|
||||||
fs_desc.Windowed = FALSE;
|
fs_desc.Windowed = FALSE;
|
||||||
|
|
||||||
|
Console.WriteLn("Creating a %dx%d exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
|
||||||
|
hr = m_dxgi_factory->CreateSwapChainForHwnd(g_d3d12_context->GetCommandQueue(), window_hwnd, &fs_sd_desc,
|
||||||
|
&fs_desc, fullscreen_output.get(), m_swap_chain.put());
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
Console.Warning("Failed to create fullscreen swap chain, trying windowed.");
|
||||||
|
m_is_exclusive_fullscreen = false;
|
||||||
|
m_using_allow_tearing = m_allow_tearing_supported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DevCon.WriteLn("Creating a %dx%d %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
|
if (!m_is_exclusive_fullscreen)
|
||||||
fullscreen_mode ? "full-screen" : "windowed");
|
|
||||||
|
|
||||||
HRESULT hr = m_dxgi_factory->CreateSwapChainForHwnd(g_d3d12_context->GetCommandQueue(), window_hwnd,
|
|
||||||
&swap_chain_desc, fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
|
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
{
|
||||||
Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
|
Console.WriteLn("Creating a %dx%d windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
|
||||||
return false;
|
hr = m_dxgi_factory->CreateSwapChainForHwnd(
|
||||||
|
g_d3d12_context->GetCommandQueue(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
|
hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
|
Console.Warning("MakeWindowAssociation() to disable ALT+ENTER failed");
|
||||||
|
|
||||||
return CreateSwapChainRTV();
|
if (!CreateSwapChainRTV())
|
||||||
|
{
|
||||||
|
DestroySwapChain();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||||
|
EndRenderPass();
|
||||||
|
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||||
|
ID3D12GraphicsCommandList4* cmdlist = g_d3d12_context->GetCommandList();
|
||||||
|
m_current_swap_chain_buffer = ((m_current_swap_chain_buffer + 1) % static_cast<u32>(m_swap_chain_buffers.size()));
|
||||||
|
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||||
|
cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), s_present_clear_color.data(), 0, nullptr);
|
||||||
|
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PRESENT);
|
||||||
|
ExecuteCommandList(false);
|
||||||
|
m_swap_chain->Present(0, m_using_allow_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice12::CreateSwapChainRTV()
|
bool GSDevice12::CreateSwapChainRTV()
|
||||||
|
@ -315,6 +358,7 @@ bool GSDevice12::CreateSwapChainRTV()
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
Console.Error("GetBuffer for RTV failed: 0x%08X", hr);
|
Console.Error("GetBuffer for RTV failed: 0x%08X", hr);
|
||||||
|
m_swap_chain_buffers.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,6 +366,7 @@ bool GSDevice12::CreateSwapChainRTV()
|
||||||
if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format,
|
if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format,
|
||||||
DXGI_FORMAT_UNKNOWN, D3D12_RESOURCE_STATE_PRESENT))
|
DXGI_FORMAT_UNKNOWN, D3D12_RESOURCE_STATE_PRESENT))
|
||||||
{
|
{
|
||||||
|
m_swap_chain_buffers.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,26 +405,44 @@ void GSDevice12::DestroySwapChainRTVs()
|
||||||
m_current_swap_chain_buffer = 0;
|
m_current_swap_chain_buffer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice12::ChangeWindow(const WindowInfo& new_wi)
|
void GSDevice12::DestroySwapChain()
|
||||||
{
|
{
|
||||||
DestroySurface();
|
if (!m_swap_chain)
|
||||||
|
return;
|
||||||
|
|
||||||
m_window_info = new_wi;
|
DestroySwapChainRTVs();
|
||||||
if (new_wi.type == WindowInfo::Type::Surfaceless)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return CreateSwapChain(nullptr);
|
// switch out of fullscreen before destroying
|
||||||
|
BOOL is_fullscreen;
|
||||||
|
if (SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen)
|
||||||
|
m_swap_chain->SetFullscreenState(FALSE, nullptr);
|
||||||
|
|
||||||
|
m_swap_chain.reset();
|
||||||
|
m_is_exclusive_fullscreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GSDevice12::UpdateWindow()
|
||||||
|
{
|
||||||
|
ExecuteCommandList(true);
|
||||||
|
DestroySwapChain();
|
||||||
|
|
||||||
|
if (!AcquireWindow(false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
|
||||||
|
{
|
||||||
|
Console.WriteLn("Failed to create swap chain on updated window");
|
||||||
|
ReleaseWindow();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GSDevice12::DestroySurface()
|
void GSDevice12::DestroySurface()
|
||||||
{
|
{
|
||||||
ExecuteCommandList(true);
|
ExecuteCommandList(true);
|
||||||
|
DestroySwapChain();
|
||||||
if (IsExclusiveFullscreen())
|
|
||||||
SetExclusiveFullscreen(false, 0, 0, 0.0f);
|
|
||||||
|
|
||||||
DestroySwapChainRTVs();
|
|
||||||
m_swap_chain.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GSDevice12::GetDriverInfo() const
|
std::string GSDevice12::GetDriverInfo() const
|
||||||
|
@ -452,75 +515,6 @@ bool GSDevice12::SupportsExclusiveFullscreen() const
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDevice12::IsExclusiveFullscreen()
|
|
||||||
{
|
|
||||||
BOOL is_fullscreen = FALSE;
|
|
||||||
return (m_swap_chain && SUCCEEDED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) && is_fullscreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GSDevice12::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
|
||||||
{
|
|
||||||
if (!m_swap_chain)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BOOL is_fullscreen = FALSE;
|
|
||||||
HRESULT hr = m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr);
|
|
||||||
if (!fullscreen)
|
|
||||||
{
|
|
||||||
// leaving fullscreen
|
|
||||||
if (is_fullscreen)
|
|
||||||
return SUCCEEDED(m_swap_chain->SetFullscreenState(FALSE, nullptr));
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDXGIOutput* output;
|
|
||||||
if (FAILED(hr = m_swap_chain->GetContainingOutput(&output)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC current_desc;
|
|
||||||
hr = m_swap_chain->GetDesc(¤t_desc);
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DXGI_MODE_DESC new_mode = current_desc.BufferDesc;
|
|
||||||
new_mode.Width = width;
|
|
||||||
new_mode.Height = height;
|
|
||||||
new_mode.RefreshRate.Numerator = static_cast<UINT>(std::floor(refresh_rate * 1000.0f));
|
|
||||||
new_mode.RefreshRate.Denominator = 1000u;
|
|
||||||
|
|
||||||
DXGI_MODE_DESC closest_mode;
|
|
||||||
if (FAILED(hr = output->FindClosestMatchingMode(&new_mode, &closest_mode, nullptr)) ||
|
|
||||||
new_mode.Format != current_desc.BufferDesc.Format)
|
|
||||||
{
|
|
||||||
Console.Error("Failed to find closest matching mode, hr=%08X", hr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_mode.Width == current_desc.BufferDesc.Width && new_mode.Height == current_desc.BufferDesc.Width &&
|
|
||||||
new_mode.RefreshRate.Numerator == current_desc.BufferDesc.RefreshRate.Numerator &&
|
|
||||||
new_mode.RefreshRate.Denominator == current_desc.BufferDesc.RefreshRate.Denominator)
|
|
||||||
{
|
|
||||||
DevCon.WriteLn("Fullscreen mode already set");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_d3d12_context->ExecuteCommandList(D3D12::Context::WaitType::Sleep);
|
|
||||||
DestroySwapChainRTVs();
|
|
||||||
m_swap_chain.reset();
|
|
||||||
|
|
||||||
if (!CreateSwapChain(&closest_mode))
|
|
||||||
{
|
|
||||||
Console.Error("Failed to create a fullscreen swap chain");
|
|
||||||
if (!CreateSwapChain(nullptr))
|
|
||||||
pxFailRel("Failed to recreate windowed swap chain");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
|
GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
|
||||||
{
|
{
|
||||||
EndRenderPass();
|
EndRenderPass();
|
||||||
|
@ -531,12 +525,21 @@ GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
|
||||||
if (frame_skip || !m_swap_chain)
|
if (frame_skip || !m_swap_chain)
|
||||||
return PresentResult::FrameSkipped;
|
return PresentResult::FrameSkipped;
|
||||||
|
|
||||||
static constexpr std::array<float, 4> clear_color = {};
|
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
|
||||||
|
// This might get called repeatedly if it takes a while to switch back, that's the host's problem.
|
||||||
|
BOOL is_fullscreen;
|
||||||
|
if (m_is_exclusive_fullscreen &&
|
||||||
|
(FAILED(m_swap_chain->GetFullscreenState(&is_fullscreen, nullptr)) || !is_fullscreen))
|
||||||
|
{
|
||||||
|
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
|
||||||
|
return PresentResult::FrameSkipped;
|
||||||
|
}
|
||||||
|
|
||||||
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
D3D12::Texture& swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer];
|
||||||
|
|
||||||
ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
|
ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
|
||||||
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
swap_chain_buf.TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
||||||
cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), clear_color.data(), 0, nullptr);
|
cmdlist->ClearRenderTargetView(swap_chain_buf.GetWriteDescriptor(), s_present_clear_color.data(), 0, nullptr);
|
||||||
cmdlist->OMSetRenderTargets(1, &swap_chain_buf.GetWriteDescriptor().cpu_handle, FALSE, nullptr);
|
cmdlist->OMSetRenderTargets(1, &swap_chain_buf.GetWriteDescriptor().cpu_handle, FALSE, nullptr);
|
||||||
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
|
g_perfmon.Put(GSPerfMon::RenderPasses, 1);
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,7 @@ private:
|
||||||
|
|
||||||
bool m_allow_tearing_supported = false;
|
bool m_allow_tearing_supported = false;
|
||||||
bool m_using_allow_tearing = false;
|
bool m_using_allow_tearing = false;
|
||||||
|
bool m_is_exclusive_fullscreen = false;
|
||||||
bool m_device_lost = false;
|
bool m_device_lost = false;
|
||||||
|
|
||||||
ComPtr<ID3D12RootSignature> m_tfx_root_signature;
|
ComPtr<ID3D12RootSignature> m_tfx_root_signature;
|
||||||
|
@ -191,9 +192,10 @@ private:
|
||||||
|
|
||||||
void LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const;
|
void LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format, DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const;
|
||||||
|
|
||||||
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
|
bool CreateSwapChain();
|
||||||
bool CreateSwapChainRTV();
|
bool CreateSwapChainRTV();
|
||||||
void DestroySwapChainRTVs();
|
void DestroySwapChainRTVs();
|
||||||
|
void DestroySwapChain();
|
||||||
|
|
||||||
GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override;
|
GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override;
|
||||||
|
|
||||||
|
@ -243,14 +245,12 @@ public:
|
||||||
RenderAPI GetRenderAPI() const override;
|
RenderAPI GetRenderAPI() const override;
|
||||||
bool HasSurface() const override;
|
bool HasSurface() const override;
|
||||||
|
|
||||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
bool Create() override;
|
||||||
void Destroy() override;
|
void Destroy() override;
|
||||||
|
|
||||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
bool UpdateWindow() override;
|
||||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||||
bool SupportsExclusiveFullscreen() const override;
|
bool SupportsExclusiveFullscreen() const override;
|
||||||
bool IsExclusiveFullscreen() override;
|
|
||||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
|
||||||
void DestroySurface() override;
|
void DestroySurface() override;
|
||||||
std::string GetDriverInfo() const override;
|
std::string GetDriverInfo() const override;
|
||||||
|
|
||||||
|
|
|
@ -378,7 +378,7 @@ public:
|
||||||
MRCOwned<id<MTLFunction>> LoadShader(NSString* name);
|
MRCOwned<id<MTLFunction>> LoadShader(NSString* name);
|
||||||
MRCOwned<id<MTLRenderPipelineState>> MakePipeline(MTLRenderPipelineDescriptor* desc, id<MTLFunction> vertex, id<MTLFunction> fragment, NSString* name);
|
MRCOwned<id<MTLRenderPipelineState>> MakePipeline(MTLRenderPipelineDescriptor* desc, id<MTLFunction> vertex, id<MTLFunction> fragment, NSString* name);
|
||||||
MRCOwned<id<MTLComputePipelineState>> MakeComputePipeline(id<MTLFunction> compute, NSString* name);
|
MRCOwned<id<MTLComputePipelineState>> MakeComputePipeline(id<MTLFunction> compute, NSString* name);
|
||||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
bool Create() override;
|
||||||
void Destroy() override;
|
void Destroy() override;
|
||||||
|
|
||||||
void AttachSurfaceOnMainThread();
|
void AttachSurfaceOnMainThread();
|
||||||
|
@ -387,10 +387,8 @@ public:
|
||||||
RenderAPI GetRenderAPI() const override;
|
RenderAPI GetRenderAPI() const override;
|
||||||
bool HasSurface() const override;
|
bool HasSurface() const override;
|
||||||
void DestroySurface() override;
|
void DestroySurface() override;
|
||||||
bool ChangeWindow(const WindowInfo& wi) override;
|
bool UpdateWindow() override;
|
||||||
bool SupportsExclusiveFullscreen() const override;
|
bool SupportsExclusiveFullscreen() const override;
|
||||||
bool IsExclusiveFullscreen() override;
|
|
||||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
|
||||||
std::string GetDriverInfo() const override;
|
std::string GetDriverInfo() const override;
|
||||||
|
|
||||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||||
|
|
|
@ -764,9 +764,9 @@ static MRCOwned<id<MTLBuffer>> CreatePrivateBufferWithContent(
|
||||||
return actual;
|
return actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceMTL::Create(const WindowInfo& wi, VsyncMode vsync)
|
bool GSDeviceMTL::Create()
|
||||||
{ @autoreleasepool {
|
{ @autoreleasepool {
|
||||||
if (!GSDevice::Create(wi, vsync))
|
if (!GSDevice::Create())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
NSString* ns_adapter_name = [NSString stringWithUTF8String:EmuConfig.GS.Adapter.c_str()];
|
NSString* ns_adapter_name = [NSString stringWithUTF8String:EmuConfig.GS.Adapter.c_str()];
|
||||||
|
@ -810,11 +810,16 @@ bool GSDeviceMTL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||||
|
|
||||||
if (m_dev.IsOk() && m_queue)
|
if (m_dev.IsOk() && m_queue)
|
||||||
{
|
{
|
||||||
|
// This is a little less than ideal, pinging back and forward between threads, but we don't really
|
||||||
|
// have any other option, because Qt uses a blocking queued connection for window acquire.
|
||||||
|
if (!AcquireWindow(true))
|
||||||
|
return false;
|
||||||
|
|
||||||
OnMainThread([this]
|
OnMainThread([this]
|
||||||
{
|
{
|
||||||
AttachSurfaceOnMainThread();
|
AttachSurfaceOnMainThread();
|
||||||
});
|
});
|
||||||
SetVSync(vsync);
|
[m_layer setDisplaySyncEnabled:m_vsync_mode != VsyncMode::Off];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1165,6 +1170,7 @@ void GSDeviceMTL::Destroy()
|
||||||
|
|
||||||
GSDevice::Destroy();
|
GSDevice::Destroy();
|
||||||
GSDeviceMTL::DestroySurface();
|
GSDeviceMTL::DestroySurface();
|
||||||
|
ReleaseWindow();
|
||||||
m_queue = nullptr;
|
m_queue = nullptr;
|
||||||
m_dev.Reset();
|
m_dev.Reset();
|
||||||
}}
|
}}
|
||||||
|
@ -1177,20 +1183,21 @@ void GSDeviceMTL::DestroySurface()
|
||||||
m_layer = nullptr;
|
m_layer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceMTL::ChangeWindow(const WindowInfo& wi)
|
bool GSDeviceMTL::UpdateWindow()
|
||||||
{
|
{
|
||||||
OnMainThread([this, &wi]
|
DestroySurface();
|
||||||
{
|
|
||||||
DetachSurfaceOnMainThread();
|
if (!AcquireWindow(false))
|
||||||
m_window_info = wi;
|
return false;
|
||||||
AttachSurfaceOnMainThread();
|
|
||||||
});
|
if (m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
OnMainThread([this] { AttachSurfaceOnMainThread(); });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; }
|
bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; }
|
||||||
bool GSDeviceMTL::IsExclusiveFullscreen() { return false; }
|
|
||||||
bool GSDeviceMTL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) { return false; }
|
|
||||||
|
|
||||||
std::string GSDeviceMTL::GetDriverInfo() const
|
std::string GSDeviceMTL::GetDriverInfo() const
|
||||||
{ @autoreleasepool {
|
{ @autoreleasepool {
|
||||||
|
@ -1339,6 +1346,9 @@ void GSDeviceMTL::EndPresent()
|
||||||
|
|
||||||
void GSDeviceMTL::SetVSync(VsyncMode mode)
|
void GSDeviceMTL::SetVSync(VsyncMode mode)
|
||||||
{
|
{
|
||||||
|
if (m_vsync_mode == mode)
|
||||||
|
return;
|
||||||
|
|
||||||
[m_layer setDisplaySyncEnabled:mode != VsyncMode::Off];
|
[m_layer setDisplaySyncEnabled:mode != VsyncMode::Off];
|
||||||
m_vsync_mode = mode;
|
m_vsync_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,9 +85,13 @@ void GSDeviceOGL::SetVSync(VsyncMode mode)
|
||||||
m_vsync_mode = mode;
|
m_vsync_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
bool GSDeviceOGL::Create()
|
||||||
{
|
{
|
||||||
if (!GSDevice::Create(wi, vsync))
|
if (!GSDevice::Create())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// GL is a pain and needs the window super early to create the context.
|
||||||
|
if (!AcquireWindow(true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// We need at least GL3.3.
|
// We need at least GL3.3.
|
||||||
|
@ -95,7 +99,7 @@ bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||||
{GL::Context::Profile::Core, 4, 5}, {GL::Context::Profile::Core, 4, 4}, {GL::Context::Profile::Core, 4, 3},
|
{GL::Context::Profile::Core, 4, 5}, {GL::Context::Profile::Core, 4, 4}, {GL::Context::Profile::Core, 4, 3},
|
||||||
{GL::Context::Profile::Core, 4, 2}, {GL::Context::Profile::Core, 4, 1}, {GL::Context::Profile::Core, 4, 0},
|
{GL::Context::Profile::Core, 4, 2}, {GL::Context::Profile::Core, 4, 1}, {GL::Context::Profile::Core, 4, 0},
|
||||||
{GL::Context::Profile::Core, 3, 3}};
|
{GL::Context::Profile::Core, 3, 3}};
|
||||||
m_gl_context = GL::Context::Create(wi, version_list);
|
m_gl_context = GL::Context::Create(m_window_info, version_list);
|
||||||
if (!m_gl_context)
|
if (!m_gl_context)
|
||||||
{
|
{
|
||||||
Console.Error("Failed to create any GL context");
|
Console.Error("Failed to create any GL context");
|
||||||
|
@ -109,6 +113,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||||
|
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||||
|
RenderBlankFrame();
|
||||||
|
|
||||||
if (!GLLoader::check_gl_requirements())
|
if (!GLLoader::check_gl_requirements())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -543,6 +551,8 @@ void GSDeviceOGL::Destroy()
|
||||||
|
|
||||||
m_gl_context->DoneCurrent();
|
m_gl_context->DoneCurrent();
|
||||||
m_gl_context.reset();
|
m_gl_context.reset();
|
||||||
|
|
||||||
|
ReleaseWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,23 +661,31 @@ void GSDeviceOGL::DestroyResources()
|
||||||
glDeleteFramebuffers(1, &m_fbo_write);
|
glDeleteFramebuffers(1, &m_fbo_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceOGL::ChangeWindow(const WindowInfo& new_wi)
|
bool GSDeviceOGL::UpdateWindow()
|
||||||
{
|
{
|
||||||
pxAssert(m_gl_context);
|
pxAssert(m_gl_context);
|
||||||
|
|
||||||
if (!m_gl_context->ChangeSurface(new_wi))
|
DestroySurface();
|
||||||
|
|
||||||
|
if (!AcquireWindow(false))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!m_gl_context->ChangeSurface(m_window_info))
|
||||||
{
|
{
|
||||||
Console.Error("Failed to change surface");
|
Console.Error("Failed to change surface");
|
||||||
|
ReleaseWindow();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_window_info = m_gl_context->GetWindowInfo();
|
m_window_info = m_gl_context->GetWindowInfo();
|
||||||
|
|
||||||
if (new_wi.type != WindowInfo::Type::Surfaceless)
|
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||||
{
|
{
|
||||||
// reset vsync rate, since it (usually) gets lost
|
// reset vsync rate, since it (usually) gets lost
|
||||||
if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
|
if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
|
||||||
m_gl_context->SetSwapInterval(static_cast<s32>(m_vsync_mode != VsyncMode::Off));
|
m_gl_context->SetSwapInterval(static_cast<s32>(m_vsync_mode != VsyncMode::Off));
|
||||||
|
|
||||||
|
RenderBlankFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -691,16 +709,6 @@ bool GSDeviceOGL::SupportsExclusiveFullscreen() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceOGL::IsExclusiveFullscreen()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GSDeviceOGL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GSDeviceOGL::DestroySurface()
|
void GSDeviceOGL::DestroySurface()
|
||||||
{
|
{
|
||||||
m_window_info = {};
|
m_window_info = {};
|
||||||
|
@ -2039,6 +2047,17 @@ void GSDeviceOGL::RenderImGui()
|
||||||
glScissor(GLState::scissor.x, GLState::scissor.y, GLState::scissor.width(), GLState::scissor.height());
|
glScissor(GLState::scissor.x, GLState::scissor.y, GLState::scissor.width(), GLState::scissor.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GSDeviceOGL::RenderBlankFrame()
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
m_gl_context->SwapBuffers();
|
||||||
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLState::fbo);
|
||||||
|
glEnable(GL_SCISSOR_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
void GSDeviceOGL::OMAttachRt(GSTextureOGL* rt)
|
void GSDeviceOGL::OMAttachRt(GSTextureOGL* rt)
|
||||||
{
|
{
|
||||||
if (rt)
|
if (rt)
|
||||||
|
|
|
@ -262,6 +262,7 @@ private:
|
||||||
|
|
||||||
bool CreateImGuiProgram();
|
bool CreateImGuiProgram();
|
||||||
void RenderImGui();
|
void RenderImGui();
|
||||||
|
void RenderBlankFrame();
|
||||||
|
|
||||||
void OMAttachRt(GSTextureOGL* rt = nullptr);
|
void OMAttachRt(GSTextureOGL* rt = nullptr);
|
||||||
void OMAttachDs(GSTextureOGL* ds = nullptr);
|
void OMAttachDs(GSTextureOGL* ds = nullptr);
|
||||||
|
@ -286,14 +287,12 @@ public:
|
||||||
RenderAPI GetRenderAPI() const override;
|
RenderAPI GetRenderAPI() const override;
|
||||||
bool HasSurface() const override;
|
bool HasSurface() const override;
|
||||||
|
|
||||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
bool Create() override;
|
||||||
void Destroy() override;
|
void Destroy() override;
|
||||||
|
|
||||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
bool UpdateWindow() override;
|
||||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||||
bool SupportsExclusiveFullscreen() const override;
|
bool SupportsExclusiveFullscreen() const override;
|
||||||
bool IsExclusiveFullscreen() override;
|
|
||||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
|
||||||
void DestroySurface() override;
|
void DestroySurface() override;
|
||||||
std::string GetDriverInfo() const override;
|
std::string GetDriverInfo() const override;
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@ static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(VsyncMode mode)
|
||||||
return VK_PRESENT_MODE_IMMEDIATE_KHR;
|
return VK_PRESENT_MODE_IMMEDIATE_KHR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr VkClearValue s_present_clear_color = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||||
|
|
||||||
GSDeviceVK::GSDeviceVK()
|
GSDeviceVK::GSDeviceVK()
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_OGL_DEBUG
|
#ifdef ENABLE_OGL_DEBUG
|
||||||
|
@ -105,7 +107,7 @@ void GSDeviceVK::GetAdaptersAndFullscreenModes(
|
||||||
if (Vulkan::LoadVulkanLibrary())
|
if (Vulkan::LoadVulkanLibrary())
|
||||||
{
|
{
|
||||||
ScopedGuard lib_guard([]() { Vulkan::UnloadVulkanLibrary(); });
|
ScopedGuard lib_guard([]() { Vulkan::UnloadVulkanLibrary(); });
|
||||||
const VkInstance instance = Vulkan::Context::CreateVulkanInstance(nullptr, false, false);
|
const VkInstance instance = Vulkan::Context::CreateVulkanInstance(WindowInfo(), false, false);
|
||||||
if (instance != VK_NULL_HANDLE)
|
if (instance != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
if (Vulkan::LoadVulkanInstanceFunctions(instance))
|
if (Vulkan::LoadVulkanInstanceFunctions(instance))
|
||||||
|
@ -171,26 +173,17 @@ bool GSDeviceVK::HasSurface() const
|
||||||
return static_cast<bool>(m_swap_chain);
|
return static_cast<bool>(m_swap_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceVK::Create(const WindowInfo& wi, VsyncMode vsync)
|
bool GSDeviceVK::Create()
|
||||||
{
|
{
|
||||||
if (!GSDevice::Create(wi, vsync))
|
if (!GSDevice::Create())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
WindowInfo local_wi(wi);
|
if (!CreateDeviceAndSwapChain())
|
||||||
const bool debug_device = GSConfig.UseDebugDevice;
|
|
||||||
if (!Vulkan::Context::Create(GSConfig.Adapter, &local_wi, &m_swap_chain, GetPreferredPresentModeForVsyncMode(vsync),
|
|
||||||
!GSConfig.DisableThreadedPresentation, debug_device, debug_device))
|
|
||||||
{
|
|
||||||
Console.Error("Failed to create Vulkan context");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
Vulkan::ShaderCache::Create(GSConfig.DisableShaderCache ? std::string_view() : std::string_view(EmuFolders::Cache),
|
Vulkan::ShaderCache::Create(GSConfig.DisableShaderCache ? std::string_view() : std::string_view(EmuFolders::Cache),
|
||||||
SHADER_CACHE_VERSION, GSConfig.UseDebugDevice);
|
SHADER_CACHE_VERSION, GSConfig.UseDebugDevice);
|
||||||
|
|
||||||
// NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal).
|
|
||||||
m_window_info = m_swap_chain ? m_swap_chain->GetWindowInfo() : local_wi;
|
|
||||||
|
|
||||||
if (!CheckFeatures())
|
if (!CheckFeatures())
|
||||||
{
|
{
|
||||||
Console.Error("Your GPU does not support the required Vulkan features.");
|
Console.Error("Your GPU does not support the required Vulkan features.");
|
||||||
|
@ -264,29 +257,31 @@ void GSDeviceVK::Destroy()
|
||||||
|
|
||||||
g_vulkan_context->WaitForGPUIdle();
|
g_vulkan_context->WaitForGPUIdle();
|
||||||
m_swap_chain.reset();
|
m_swap_chain.reset();
|
||||||
|
ReleaseWindow();
|
||||||
|
|
||||||
Vulkan::ShaderCache::Destroy();
|
Vulkan::ShaderCache::Destroy();
|
||||||
Vulkan::Context::Destroy();
|
Vulkan::Context::Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceVK::ChangeWindow(const WindowInfo& new_wi)
|
bool GSDeviceVK::UpdateWindow()
|
||||||
{
|
{
|
||||||
if (new_wi.type == WindowInfo::Type::Surfaceless)
|
DestroySurface();
|
||||||
{
|
|
||||||
ExecuteCommandBuffer(true);
|
if (!AcquireWindow(false))
|
||||||
m_swap_chain.reset();
|
return false;
|
||||||
m_window_info = new_wi;
|
|
||||||
|
if (m_window_info.type == WindowInfo::Type::Surfaceless)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
// make sure previous frames are presented
|
// make sure previous frames are presented
|
||||||
|
ExecuteCommandBuffer(false);
|
||||||
g_vulkan_context->WaitForGPUIdle();
|
g_vulkan_context->WaitForGPUIdle();
|
||||||
|
|
||||||
// recreate surface in existing swap chain if it already exists
|
// recreate surface in existing swap chain if it already exists
|
||||||
if (m_swap_chain)
|
if (m_swap_chain)
|
||||||
{
|
{
|
||||||
if (m_swap_chain->RecreateSurface(new_wi))
|
if (m_swap_chain->RecreateSurface(m_window_info))
|
||||||
{
|
{
|
||||||
m_window_info = m_swap_chain->GetWindowInfo();
|
m_window_info = m_swap_chain->GetWindowInfo();
|
||||||
return true;
|
return true;
|
||||||
|
@ -295,24 +290,26 @@ bool GSDeviceVK::ChangeWindow(const WindowInfo& new_wi)
|
||||||
m_swap_chain.reset();
|
m_swap_chain.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowInfo wi_copy(new_wi);
|
|
||||||
VkSurfaceKHR surface = Vulkan::SwapChain::CreateVulkanSurface(
|
VkSurfaceKHR surface = Vulkan::SwapChain::CreateVulkanSurface(
|
||||||
g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), &wi_copy);
|
g_vulkan_context->GetVulkanInstance(), g_vulkan_context->GetPhysicalDevice(), &m_window_info);
|
||||||
if (surface == VK_NULL_HANDLE)
|
if (surface == VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
Console.Error("Failed to create new surface for swap chain");
|
Console.Error("Failed to create new surface for swap chain");
|
||||||
|
ReleaseWindow();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_swap_chain = Vulkan::SwapChain::Create(wi_copy, surface, GetPreferredPresentModeForVsyncMode(m_vsync_mode));
|
m_swap_chain = Vulkan::SwapChain::Create(m_window_info, surface, GetPreferredPresentModeForVsyncMode(m_vsync_mode));
|
||||||
if (!m_swap_chain)
|
if (!m_swap_chain)
|
||||||
{
|
{
|
||||||
Console.Error("Failed to create swap chain");
|
Console.Error("Failed to create swap chain");
|
||||||
Vulkan::SwapChain::DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), &wi_copy, surface);
|
Vulkan::SwapChain::DestroyVulkanSurface(g_vulkan_context->GetVulkanInstance(), &m_window_info, surface);
|
||||||
|
ReleaseWindow();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_window_info = m_swap_chain->GetWindowInfo();
|
m_window_info = m_swap_chain->GetWindowInfo();
|
||||||
|
RenderBlankFrame();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,16 +341,6 @@ bool GSDeviceVK::SupportsExclusiveFullscreen() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GSDeviceVK::IsExclusiveFullscreen()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GSDeviceVK::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GSDeviceVK::DestroySurface()
|
void GSDeviceVK::DestroySurface()
|
||||||
{
|
{
|
||||||
g_vulkan_context->WaitForGPUIdle();
|
g_vulkan_context->WaitForGPUIdle();
|
||||||
|
@ -453,10 +440,9 @@ GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip)
|
||||||
swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
|
swap_chain_texture.OverrideImageLayout(VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
swap_chain_texture.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
||||||
|
|
||||||
const VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
|
||||||
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr,
|
const VkRenderPassBeginInfo rp = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr,
|
||||||
m_swap_chain->GetClearRenderPass(), m_swap_chain->GetCurrentFramebuffer(),
|
m_swap_chain->GetClearRenderPass(), m_swap_chain->GetCurrentFramebuffer(),
|
||||||
{{0, 0}, {swap_chain_texture.GetWidth(), swap_chain_texture.GetHeight()}}, 1u, &clear_value};
|
{{0, 0}, {swap_chain_texture.GetWidth(), swap_chain_texture.GetHeight()}}, 1u, &s_present_clear_color};
|
||||||
vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
|
vkCmdBeginRenderPass(g_vulkan_context->GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
const VkViewport vp{0.0f, 0.0f, static_cast<float>(swap_chain_texture.GetWidth()),
|
const VkViewport vp{0.0f, 0.0f, static_cast<float>(swap_chain_texture.GetWidth()),
|
||||||
|
@ -570,6 +556,128 @@ void GSDeviceVK::InsertDebugMessage(DebugMessageCategory category, const char* f
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GSDeviceVK::CreateDeviceAndSwapChain()
|
||||||
|
{
|
||||||
|
bool enable_debug_utils = GSConfig.UseDebugDevice;
|
||||||
|
bool enable_validation_layer = GSConfig.UseDebugDevice;
|
||||||
|
|
||||||
|
if (!Vulkan::LoadVulkanLibrary())
|
||||||
|
{
|
||||||
|
Host::ReportErrorAsync("Error", "Failed to load Vulkan library. Does your GPU and/or driver support Vulkan?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGuard library_cleanup(&Vulkan::UnloadVulkanLibrary);
|
||||||
|
|
||||||
|
if (!AcquireWindow(true))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ScopedGuard window_cleanup = [this]() { ReleaseWindow(); };
|
||||||
|
|
||||||
|
VkInstance instance =
|
||||||
|
Vulkan::Context::CreateVulkanInstance(m_window_info, enable_debug_utils, enable_validation_layer);
|
||||||
|
if (instance == VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
if (enable_debug_utils || enable_validation_layer)
|
||||||
|
{
|
||||||
|
// Try again without the validation layer.
|
||||||
|
enable_debug_utils = false;
|
||||||
|
enable_validation_layer = false;
|
||||||
|
instance =
|
||||||
|
Vulkan::Context::CreateVulkanInstance(m_window_info, enable_debug_utils, enable_validation_layer);
|
||||||
|
if (instance == VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
Host::ReportErrorAsync(
|
||||||
|
"Error", "Failed to create Vulkan instance. Does your GPU and/or driver support Vulkan?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Error("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedGuard instance_cleanup = [&instance]() { vkDestroyInstance(instance, nullptr); };
|
||||||
|
if (!Vulkan::LoadVulkanInstanceFunctions(instance))
|
||||||
|
{
|
||||||
|
Console.Error("Failed to load Vulkan instance functions");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vulkan::Context::GPUList gpus = Vulkan::Context::EnumerateGPUs(instance);
|
||||||
|
if (gpus.empty())
|
||||||
|
{
|
||||||
|
Host::ReportErrorAsync("Error", "No physical devices found. Does your GPU and/or driver support Vulkan?");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 gpu_index = 0;
|
||||||
|
Vulkan::Context::GPUNameList gpu_names = Vulkan::Context::EnumerateGPUNames(instance);
|
||||||
|
if (!GSConfig.Adapter.empty())
|
||||||
|
{
|
||||||
|
for (; gpu_index < static_cast<u32>(gpu_names.size()); gpu_index++)
|
||||||
|
{
|
||||||
|
Console.WriteLn(fmt::format("GPU {}: {}", gpu_index, gpu_names[gpu_index]));
|
||||||
|
if (gpu_names[gpu_index] == GSConfig.Adapter)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpu_index == static_cast<u32>(gpu_names.size()))
|
||||||
|
{
|
||||||
|
Console.Warning(
|
||||||
|
fmt::format("Requested GPU '{}' not found, using first ({})", GSConfig.Adapter, gpu_names[0]));
|
||||||
|
gpu_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLn("No GPU requested, using first (%s)", gpu_names[0].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||||
|
ScopedGuard surface_cleanup = [&instance, &surface]() {
|
||||||
|
if (surface != VK_NULL_HANDLE)
|
||||||
|
vkDestroySurfaceKHR(instance, surface, nullptr);
|
||||||
|
};
|
||||||
|
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||||
|
{
|
||||||
|
surface = Vulkan::SwapChain::CreateVulkanSurface(instance, gpus[gpu_index], &m_window_info);
|
||||||
|
if (surface == VK_NULL_HANDLE)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Vulkan::Context::Create(instance, surface, gpus[gpu_index], enable_debug_utils, enable_validation_layer,
|
||||||
|
!GSConfig.DisableThreadedPresentation))
|
||||||
|
{
|
||||||
|
Console.Error("Failed to create Vulkan context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This is assigned afterwards, because some platforms can modify the window info (e.g. Metal).
|
||||||
|
if (surface != VK_NULL_HANDLE)
|
||||||
|
{
|
||||||
|
m_swap_chain =
|
||||||
|
Vulkan::SwapChain::Create(m_window_info, surface, GetPreferredPresentModeForVsyncMode(m_vsync_mode));
|
||||||
|
if (!m_swap_chain)
|
||||||
|
{
|
||||||
|
Console.Error("Failed to create swap chain");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_window_info = m_swap_chain->GetWindowInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
surface_cleanup.Cancel();
|
||||||
|
instance_cleanup.Cancel();
|
||||||
|
window_cleanup.Cancel();
|
||||||
|
library_cleanup.Cancel();
|
||||||
|
|
||||||
|
// Render a frame as soon as possible to clear out whatever was previously being displayed.
|
||||||
|
if (m_window_info.type != WindowInfo::Type::Surfaceless)
|
||||||
|
RenderBlankFrame();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool GSDeviceVK::CheckFeatures()
|
bool GSDeviceVK::CheckFeatures()
|
||||||
{
|
{
|
||||||
const VkPhysicalDeviceProperties& properties = g_vulkan_context->GetDeviceProperties();
|
const VkPhysicalDeviceProperties& properties = g_vulkan_context->GetDeviceProperties();
|
||||||
|
@ -2369,6 +2477,29 @@ void GSDeviceVK::RenderImGui()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GSDeviceVK::RenderBlankFrame()
|
||||||
|
{
|
||||||
|
VkResult res = m_swap_chain->AcquireNextImage();
|
||||||
|
if (res != VK_SUCCESS)
|
||||||
|
{
|
||||||
|
Console.Error("Failed to acquire image for blank frame present");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBuffer cmdbuffer = g_vulkan_context->GetCurrentCommandBuffer();
|
||||||
|
Vulkan::Texture& sctex = m_swap_chain->GetCurrentTexture();
|
||||||
|
sctex.TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
||||||
|
|
||||||
|
constexpr VkImageSubresourceRange srr = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||||
|
vkCmdClearColorImage(cmdbuffer, sctex.GetImage(), sctex.GetLayout(), &s_present_clear_color.color, 1, &srr);
|
||||||
|
|
||||||
|
m_swap_chain->GetCurrentTexture().TransitionToLayout(cmdbuffer, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
|
g_vulkan_context->SubmitCommandBuffer(m_swap_chain.get(), !m_swap_chain->IsPresentModeSynchronizing());
|
||||||
|
g_vulkan_context->MoveToNextCommandBuffer();
|
||||||
|
|
||||||
|
InvalidateCachedState();
|
||||||
|
}
|
||||||
|
|
||||||
bool GSDeviceVK::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants)
|
bool GSDeviceVK::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants)
|
||||||
{
|
{
|
||||||
EndRenderPass();
|
EndRenderPass();
|
||||||
|
|
|
@ -190,6 +190,7 @@ private:
|
||||||
VkShaderModule GetUtilityVertexShader(const std::string& source, const char* replace_main);
|
VkShaderModule GetUtilityVertexShader(const std::string& source, const char* replace_main);
|
||||||
VkShaderModule GetUtilityFragmentShader(const std::string& source, const char* replace_main);
|
VkShaderModule GetUtilityFragmentShader(const std::string& source, const char* replace_main);
|
||||||
|
|
||||||
|
bool CreateDeviceAndSwapChain();
|
||||||
bool CheckFeatures();
|
bool CheckFeatures();
|
||||||
bool CreateNullTexture();
|
bool CreateNullTexture();
|
||||||
bool CreateBuffers();
|
bool CreateBuffers();
|
||||||
|
@ -205,6 +206,7 @@ private:
|
||||||
|
|
||||||
bool CompileImGuiPipeline();
|
bool CompileImGuiPipeline();
|
||||||
void RenderImGui();
|
void RenderImGui();
|
||||||
|
void RenderBlankFrame();
|
||||||
|
|
||||||
void DestroyResources();
|
void DestroyResources();
|
||||||
|
|
||||||
|
@ -230,14 +232,12 @@ public:
|
||||||
RenderAPI GetRenderAPI() const override;
|
RenderAPI GetRenderAPI() const override;
|
||||||
bool HasSurface() const override;
|
bool HasSurface() const override;
|
||||||
|
|
||||||
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
|
bool Create() override;
|
||||||
void Destroy() override;
|
void Destroy() override;
|
||||||
|
|
||||||
bool ChangeWindow(const WindowInfo& new_wi) override;
|
bool UpdateWindow() override;
|
||||||
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;
|
||||||
bool SupportsExclusiveFullscreen() const override;
|
bool SupportsExclusiveFullscreen() const override;
|
||||||
bool IsExclusiveFullscreen() override;
|
|
||||||
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
|
|
||||||
void DestroySurface() override;
|
void DestroySurface() override;
|
||||||
std::string GetDriverInfo() const override;
|
std::string GetDriverInfo() const override;
|
||||||
|
|
||||||
|
|
|
@ -105,12 +105,7 @@ void Host::SetRelativeMouseMode(bool enabled)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<WindowInfo> Host::AcquireRenderWindow()
|
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
|
||||||
{
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
|
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue