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:
Stenzek 2023-04-25 22:52:41 +10:00 committed by refractionpcsx2
parent 20d040d5d1
commit befbf57191
25 changed files with 710 additions and 625 deletions

View File

@ -70,8 +70,7 @@ namespace Vulkan
Context::~Context() = default;
VkInstance Context::CreateVulkanInstance(
const WindowInfo* wi, bool enable_debug_utils, bool enable_validation_layer)
VkInstance Context::CreateVulkanInstance(const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer)
{
ExtensionList enabled_extensions;
if (!SelectInstanceExtensions(&enabled_extensions, wi, enable_debug_utils))
@ -118,7 +117,7 @@ namespace Vulkan
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;
VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr);
@ -155,34 +154,32 @@ namespace Vulkan
};
// 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;
#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;
#endif
#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;
#endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
if (wi && wi->type == WindowInfo::Type::Wayland &&
!SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))
if (wi.type == WindowInfo::Type::Wayland && !SupportsExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true))
return false;
#endif
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
if (wi && wi->type == WindowInfo::Type::Android &&
!SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
if (wi.type == WindowInfo::Type::Android && !SupportsExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true))
return false;
#endif
#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;
#endif
#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;
#endif
@ -274,91 +271,11 @@ namespace Vulkan
return gpu_names;
}
bool Context::Create(std::string_view gpu_name, const WindowInfo* wi, std::unique_ptr<SwapChain>* out_swap_chain,
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
bool enable_validation_layer)
bool Context::Create(VkInstance instance, VkSurfaceKHR surface, VkPhysicalDevice physical_device,
bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer)
{
pxAssertMsg(!g_vulkan_context, "Has no current context");
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]));
g_vulkan_context.reset(new Context(instance, physical_device));
if (enable_debug_utils)
g_vulkan_context->EnableDebugUtils();
@ -367,8 +284,7 @@ namespace Vulkan
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->CreateCommandBuffers() || !g_vulkan_context->CreateTextureStreamBuffer() ||
!g_vulkan_context->InitSpinResources() ||
(enable_surface && (*out_swap_chain = SwapChain::Create(wi_copy, surface, preferred_present_mode)) == nullptr))
!g_vulkan_context->InitSpinResources())
{
// Since we are destroying the instance, we're also responsible for destroying the surface.
if (surface != VK_NULL_HANDLE)

View File

@ -63,7 +63,7 @@ namespace Vulkan
// Helper method to create a Vulkan instance.
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.
using GPUList = std::vector<VkPhysicalDevice>;
@ -72,9 +72,8 @@ namespace Vulkan
static GPUNameList EnumerateGPUNames(VkInstance instance);
// 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,
VkPresentModeKHR preferred_present_mode, bool threaded_presentation, bool enable_debug_utils,
bool enable_validation_layer);
static bool Create(VkInstance instance, VkSurfaceKHR surface, VkPhysicalDevice physical_device,
bool threaded_presentation, bool enable_debug_utils, bool enable_validation_layer);
// Destroys context.
static void Destroy();
@ -267,7 +266,7 @@ namespace Vulkan
using ExtensionList = std::vector<const char*>;
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 SelectDeviceFeatures(const VkPhysicalDeviceFeatures* required_features);
bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer, const char** required_device_extensions,

View File

@ -569,8 +569,8 @@ namespace Vulkan
}
// Select swap chain flags, we only need a colour attachment
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (!(surface_capabilities.supportedUsageFlags & 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 & image_usage) != image_usage)
{
Console.Error("Vulkan: Swap chain does not support usage as color attachment");
return false;

View File

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

View File

@ -405,10 +405,9 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
connect(m_ui.actionStartFullscreenUI, &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::onCreateDisplayRequested, this, &MainWindow::createDisplayWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplayWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested);
connect(thread, &EmuThread::onAcquireRenderWindowRequested, this, &MainWindow::acquireRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onReleaseRenderWindowRequested, this, &MainWindow::releaseRenderWindow, Qt::BlockingQueuedConnection);
connect(thread, &EmuThread::onResizeRenderWindowRequested, this, &MainWindow::displayResizeRequested);
connect(thread, &EmuThread::onRelativeMouseModeRequested, this, &MainWindow::relativeMouseModeRequested);
connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
@ -1748,57 +1747,27 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
#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(
"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",
DevCon.WriteLn("acquireRenderWindow() 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");
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_rendering_to_main = isRenderingToMain();
const bool changing_surfaceless = (!m_display_widget != surfaceless);
if (!recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless)
return m_display_widget->getWindowInfo();
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
!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.
// .. except on Wayland, where everything tends to break if you don't recreate.
const bool has_container = (m_display_container != nullptr);
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 &&
!changing_surfaceless)
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
!needs_container && !changing_surfaceless)
{
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);
m_display_created = true;
// if we're going to surfaceless, we're done here
if (surfaceless)
@ -1832,10 +1802,13 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
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 new window info from widget"));
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
destroyDisplayWidget(true);
return std::nullopt;
}
@ -1845,6 +1818,9 @@ std::optional<WindowInfo> MainWindow::updateDisplayWindow(bool recreate_window,
updateWindowTitle();
updateWindowState();
m_ui.actionStartFullscreenUI->setEnabled(false);
m_ui.actionStartFullscreenUI2->setEnabled(false);
updateDisplayWidgetCursor();
m_display_widget->setFocus();
@ -1954,7 +1930,7 @@ void MainWindow::relativeMouseModeRequested(bool enabled)
updateDisplayWidgetCursor();
}
void MainWindow::destroyDisplay()
void MainWindow::releaseRenderWindow()
{
// Now we can safely destroy the display window.
destroyDisplayWidget(true);
@ -2506,19 +2482,33 @@ void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path)
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;
// 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)
g_emu_thread->setVMPaused(true);
// We want to parent dialogs to the display widget, except if we were fullscreen,
// since it's going to get destroyed by the surfaceless call above.
QWidget* dialog_parent = was_fullscreen ? static_cast<QWidget*>(this) : getDisplayContainer();
// We need to switch out of exclusive fullscreen before we can display our popup.
// However, we do not want to switch back to render-to-main, the window might have generated this event.
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);
}
@ -2548,17 +2538,10 @@ MainWindow::VMLock::VMLock(VMLock&& lock)
MainWindow::VMLock::~VMLock()
{
if (m_was_fullscreen)
g_emu_thread->setSurfaceless(false);
g_emu_thread->setFullscreen(true, true);
if (!m_was_paused)
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()

View File

@ -69,7 +69,7 @@ public:
void cancelResume();
private:
VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen);
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen);
friend MainWindow;
QWidget* m_dialog_parent;
@ -122,11 +122,10 @@ public Q_SLOTS:
private Q_SLOTS:
void onUpdateCheckComplete();
std::optional<WindowInfo> createDisplayWindow(bool fullscreen, bool render_to_main);
std::optional<WindowInfo> updateDisplayWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void displayResizeRequested(qint32 width, qint32 height);
void relativeMouseModeRequested(bool enabled);
void destroyDisplay();
void releaseRenderWindow();
void focusDisplayWidget();
void onGameListRefreshComplete();

View File

@ -196,8 +196,8 @@ void EmuThread::startFullscreenUI(bool fullscreen)
// this should just set the flag so it gets automatically started
ImGuiManager::InitializeFullscreenUI();
m_run_fullscreen_ui = true;
if (fullscreen)
m_is_fullscreen = true;
m_is_rendering_to_main = shouldRenderToMain();
m_is_fullscreen = fullscreen;
if (!GetMTGS().WaitForOpen())
{
@ -242,6 +242,7 @@ void EmuThread::startVM(std::shared_ptr<VMBootParameters> boot_params)
pxAssertRel(!VMManager::HasValidVM(), "VM is shut down");
// Determine whether to start fullscreen or not.
m_is_rendering_to_main = shouldRenderToMain();
if (boot_params->fullscreen.has_value())
m_is_fullscreen = boot_params->fullscreen.value();
else
@ -492,14 +493,14 @@ void EmuThread::toggleFullscreen()
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())
{
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;
}
@ -508,6 +509,7 @@ void EmuThread::setFullscreen(bool fullscreen)
// This will call back to us on the MTGS thread.
m_is_fullscreen = fullscreen;
m_is_rendering_to_main = allow_render_to_main && shouldRenderToMain();
GetMTGS().UpdateDisplayWindow();
GetMTGS().WaitGS();
@ -893,32 +895,24 @@ void EmuThread::endCapture()
GetMTGS().RunOnGSThread(&GSEndCapture);
}
std::optional<WindowInfo> EmuThread::acquireRenderWindow()
std::optional<WindowInfo> EmuThread::acquireRenderWindow(bool recreate_window)
{
m_is_rendering_to_main = shouldRenderToMain();
m_is_surfaceless = false;
// 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_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);
}
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);
return emit onAcquireRenderWindowRequested(recreate_window, window_fullscreen, render_to_main, m_is_surfaceless);
}
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();
}
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
{
return g_emu_thread->updateRenderWindow(recreate_window);
return g_emu_thread->acquireRenderWindow(recreate_window);
}
void Host::ReleaseRenderWindow()
@ -932,7 +926,7 @@ void Host::BeginPresentFrame()
void Host::RequestResizeHostDisplay(s32 width, s32 height)
{
g_emu_thread->onResizeDisplayRequested(width, height);
g_emu_thread->onResizeRenderWindowRequested(width, height);
}
void Host::OnVMStarting()
@ -1194,7 +1188,7 @@ bool Host::IsFullscreen()
void Host::SetFullscreen(bool enabled)
{
g_emu_thread->setFullscreen(enabled);
g_emu_thread->setFullscreen(enabled, true);
}
alignas(16) static SysMtgsThread s_mtgs_thread;

View File

@ -61,6 +61,7 @@ public:
__fi QEventLoop* getEventLoop() const { return m_event_loop; }
__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 isSurfaceless() const { return m_is_surfaceless; }
__fi bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
@ -69,8 +70,7 @@ public:
bool shouldRenderToMain() const;
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
std::optional<WindowInfo> acquireRenderWindow();
std::optional<WindowInfo> updateRenderWindow(bool recreate_window);
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
void connectDisplaySignals(DisplayWidget* widget);
void releaseRenderWindow();
@ -93,7 +93,7 @@ public Q_SLOTS:
void saveState(const QString& filename);
void saveStateToSlot(qint32 slot);
void toggleFullscreen();
void setFullscreen(bool fullscreen);
void setFullscreen(bool fullscreen, bool allow_render_to_main);
void setSurfaceless(bool surfaceless);
void applySettings();
void reloadGameSettings();
@ -117,10 +117,9 @@ public Q_SLOTS:
Q_SIGNALS:
bool messageConfirmed(const QString& title, const QString& message);
std::optional<WindowInfo> onCreateDisplayRequested(bool fullscreen, bool render_to_main);
std::optional<WindowInfo> onUpdateDisplayRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeDisplayRequested(qint32 width, qint32 height);
void onDestroyDisplayRequested();
std::optional<WindowInfo> onAcquireRenderWindowRequested(bool recreate_window, bool fullscreen, bool render_to_main, bool surfaceless);
void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested();
void onRelativeMouseModeRequested(bool enabled);
/// 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_is_rendering_to_main = false;
bool m_is_fullscreen = false;
bool m_is_exclusive_fullscreen = false;
bool m_is_surfaceless = false;
bool m_save_state_on_shutdown = false;
bool m_pause_on_focus_loss = false;

View File

@ -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);
switch (new_api)
{
@ -212,8 +168,7 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
return false;
}
const VsyncMode vsync_mode = Host::GetEffectiveVSyncMode();
bool okay = g_gs_device->Create(wi.value(), vsync_mode);
bool okay = g_gs_device->Create();
if (okay)
{
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.Indent().WriteLn(g_gs_device->GetDriverInfo());
// Switch to exclusive fullscreen if enabled.
UpdateExclusiveFullscreen(false);
return true;
}
static void CloseGSDevice(bool clear_state, bool reopening)
static void CloseGSDevice(bool clear_state)
{
if (!g_gs_device)
return;
UpdateExclusiveFullscreen(true);
ImGuiManager::Shutdown(clear_state);
g_gs_device->Destroy();
g_gs_device.reset();
if (!reopening)
Host::ReleaseRenderWindow();
}
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.
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)))
{
Host::AddKeyedOSDMessage(
"GSReopenFailed", "Failed to reopen, restoring old configuration.", Host::OSD_CRITICAL_ERROR_DURATION);
CloseGSDevice(false, true);
CloseGSDevice(false);
GSConfig = old_config;
if (!OpenGSDevice(GSConfig.Renderer, false, true, recreate_window) ||
if (!OpenGSDevice(GSConfig.Renderer, false, recreate_window) ||
(recreate_renderer && !OpenGSRenderer(GSConfig.Renderer, basemem)))
{
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.Renderer = renderer;
bool res = OpenGSDevice(renderer, true, false, false);
bool res = OpenGSDevice(renderer, true, false);
if (res)
{
res = OpenGSRenderer(renderer, basemem);
if (!res)
CloseGSDevice(true, false);
CloseGSDevice(true);
}
if (!res)
@ -410,7 +358,7 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
void GSclose()
{
CloseGSRenderer();
CloseGSDevice(true, false);
CloseGSDevice(true);
}
void GSreset(bool hardware_reset)
@ -626,24 +574,12 @@ void GSResizeDisplayWindow(int width, int height, float scale)
void GSUpdateDisplayWindow()
{
UpdateExclusiveFullscreen(true);
g_gs_device->DestroySurface();
const std::optional<WindowInfo> wi = Host::UpdateRenderWindow(false);
if (!wi.has_value())
if (!g_gs_device->UpdateWindow())
{
pxFailRel("Failed to get window info after update.");
Host::ReportErrorAsync("Error", "Failed to change window after update. The log may contain more information.");
return;
}
if (!g_gs_device->ChangeWindow(wi.value()))
{
pxFailRel("Failed to change window after update.");
return;
}
UpdateExclusiveFullscreen(false);
ImGuiManager::WindowResized();
}
@ -652,6 +588,16 @@ void GSSetVSyncMode(VsyncMode 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)
{
if (!g_gs_device)

View File

@ -99,6 +99,7 @@ void GSResizeDisplayWindow(int width, int height, float scale);
void GSUpdateDisplayWindow();
void GSSetVSyncMode(VsyncMode mode);
bool GSWantsExclusiveFullscreen();
bool GSGetHostRefreshRate(float* refresh_rate);
void GSGetAdaptersAndFullscreenModes(
GSRendererType renderer, std::vector<std::string>* adapters, std::vector<std::string>* fullscreen_modes);
@ -128,11 +129,8 @@ struct GSRecoverableError : GSError
namespace Host
{
/// Called when the GS is creating a render device.
std::optional<WindowInfo> AcquireRenderWindow();
/// 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);
/// This could also be fullscreen transition.
std::optional<WindowInfo> AcquireRenderWindow(bool recreate_window);
/// Called before drawing the OSD and other display elements.
void BeginPresentFrame();

View File

@ -18,6 +18,7 @@
#include "GS/GSGL.h"
#include "GS/GS.h"
#include "Host.h"
#include "HostSettings.h"
#include "common/Align.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())
{
const std::string_view mode_view = mode;
std::string_view::size_type sep1 = mode.find('x');
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++;
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);
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++;
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())
{
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())
{
*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 = vsync;
m_vsync_mode = Host::GetEffectiveVSyncMode();
return true;
}
@ -200,6 +202,26 @@ void GSDevice::Destroy()
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)
{
if (m_window_info.surface_refresh_rate > 0.0f)

View File

@ -779,6 +779,9 @@ protected:
bool m_rbswapped = false;
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;
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.
static const char* RenderAPIToString(RenderAPI api);
/// Parses a fullscreen mode into its components (width * height @ refresh hz)
static bool ParseFullscreenMode(const std::string_view& mode, u32* width, u32* height, float* refresh_rate);
/// Parses the configured fullscreen mode into its components (width * height @ refresh hz)
static bool GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate);
/// Converts a fullscreen mode to a string.
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.
bool UpdateImGuiFontTexture();
virtual bool Create(const WindowInfo& wi, VsyncMode vsync);
virtual bool Create();
virtual void Destroy();
/// Returns the graphics API used by this device.
@ -843,7 +846,7 @@ public:
virtual void DestroySurface() = 0;
/// 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.
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.
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
/// displayed, but the GPU command queue will still be flushed.
virtual PresentResult BeginPresent(bool frame_skip) = 0;

View File

@ -120,6 +120,79 @@ std::vector<std::string> D3D::GetFullscreenModes(IDXGIFactory5* factory, const s
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)
{
if (name.empty())

View File

@ -36,6 +36,10 @@ namespace D3D
// returns a list of fullscreen modes for the specified adapter
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
wil::com_ptr_nothrow<IDXGIAdapter1> GetAdapterByName(IDXGIFactory5* factory, const std::string_view& name);

View File

@ -37,6 +37,8 @@
// #define REPORT_LEAKED_OBJECTS 1
static constexpr std::array<float, 4> s_present_clear_color = {};
static bool SupportsTextureFormat(ID3D11Device* dev, DXGI_FORMAT format)
{
UINT support;
@ -75,9 +77,9 @@ RenderAPI GSDevice11::GetRenderAPI() const
return RenderAPI::D3D11;
}
bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
bool GSDevice11::Create()
{
if (!GSDevice::Create(wi, vsync))
if (!GSDevice::Create())
return false;
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));
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;
D3D11_BUFFER_DESC bd;
@ -490,7 +492,8 @@ bool GSDevice11::Create(const WindowInfo& wi, VsyncMode vsync)
void GSDevice11::Destroy()
{
GSDevice::Destroy();
GSDevice11::DestroySurface();
DestroySwapChain();
ReleaseWindow();
DestroyTimestampQueries();
m_convert = {};
@ -561,7 +564,7 @@ bool GSDevice11::HasSurface() const
bool GSDevice11::GetHostRefreshRate(float* refresh_rate)
{
if (m_swap_chain && IsExclusiveFullscreen())
if (m_swap_chain && m_is_exclusive_fullscreen)
{
DXGI_SWAP_CHAIN_DESC desc;
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;
}
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)
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);
RECT 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 = {};
swap_chain_desc.Width = width;
swap_chain_desc.Height = height;
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = swap_chain_format;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferCount = 3;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SwapEffect =
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)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
if (fullscreen_mode)
HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen)
{
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
swap_chain_desc.Width = fullscreen_mode->Width;
swap_chain_desc.Height = fullscreen_mode->Height;
fs_desc.RefreshRate = fullscreen_mode->RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode->Scaling;
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width;
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;
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,
m_using_flip_model_swap_chain ? "flip-discard" : "discard", fullscreen_mode ? "full-screen" : "windowed");
if (!m_is_exclusive_fullscreen)
{
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)
{
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_allow_tearing = false;
hr = m_dxgi_factory->CreateSwapChainForHwnd(m_dev.get(), window_hwnd, &swap_chain_desc,
fullscreen_mode ? &fs_desc : nullptr, nullptr, m_swap_chain.put());
hr = m_dxgi_factory->CreateSwapChainForHwnd(
m_dev.get(), window_hwnd, &swap_chain_desc, nullptr, nullptr, m_swap_chain.put());
if (FAILED(hr))
{
Console.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
@ -648,7 +685,16 @@ bool GSDevice11::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
if (FAILED(hr))
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()
@ -670,6 +716,7 @@ bool GSDevice11::CreateSwapChainRTV()
if (FAILED(hr))
{
Console.Error("CreateRenderTargetView for swap chain failed: 0x%08X", hr);
m_swap_chain_rtv.reset();
return false;
}
@ -696,25 +743,42 @@ bool GSDevice11::CreateSwapChainRTV()
return true;
}
bool GSDevice11::ChangeWindow(const WindowInfo& new_wi)
void GSDevice11::DestroySwapChain()
{
DestroySurface();
if (!m_swap_chain)
return;
m_swap_chain_rtv.reset();
// 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;
}
m_window_info = new_wi;
if (new_wi.type == WindowInfo::Type::Surfaceless)
return true;
return CreateSwapChain(nullptr);
}
void GSDevice11::DestroySurface()
{
m_swap_chain_rtv.reset();
if (IsExclusiveFullscreen())
SetExclusiveFullscreen(false, 0, 0, 0.0f);
m_swap_chain.reset();
DestroySwapChain();
}
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)
{
if (!m_swap_chain)
if (!m_swap_chain || m_is_exclusive_fullscreen)
return;
m_window_info.surface_scale = new_window_scale;
@ -792,79 +856,21 @@ bool GSDevice11::SupportsExclusiveFullscreen() const
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(&current_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)
{
if (frame_skip || !m_swap_chain)
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.
// 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
@ -872,8 +878,7 @@ GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
if (m_vsync_mode != VsyncMode::Off && m_gpu_timing_enabled)
PopTimestampQuery();
static constexpr std::array<float, 4> clear_color = {};
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), clear_color.data());
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
if (m_state.rt_view)
m_state.rt_view->Release();

View File

@ -120,8 +120,9 @@ private:
void SetFeatures();
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChain();
bool CreateSwapChainRTV();
void DestroySwapChain();
bool CreateTimestampQueries();
void DestroyTimestampQueries();
@ -160,6 +161,7 @@ private:
bool m_allow_tearing_supported = false;
bool m_using_flip_model_swap_chain = true;
bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false;
struct
{
@ -281,16 +283,14 @@ public:
__fi ID3D11Device1* GetD3DDevice() const { return m_dev.get(); }
__fi ID3D11DeviceContext1* GetD3DContext() const { return m_ctx.get(); }
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
bool Create() override;
void Destroy() 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;
bool SupportsExclusiveFullscreen() const override;
bool IsExclusiveFullscreen() override;
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
bool HasSurface() const override;
void DestroySurface() override;
std::string GetDriverInfo() const override;

View File

@ -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 =
{{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)
{
if (!tex)
@ -116,9 +118,9 @@ bool GSDevice12::HasSurface() const
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;
m_dxgi_factory = D3D::CreateFactory(EmuConfig.GS.UseDebugDevice);
@ -144,7 +146,7 @@ bool GSDevice12::Create(const WindowInfo& wi, VsyncMode vsync)
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;
{
@ -215,16 +217,15 @@ void GSDevice12::Destroy()
EndRenderPass();
ExecuteCommandList(true);
DestroyResources();
g_d3d12_context->WaitForGPUIdle();
GSDevice12::DestroySurface();
DestroySwapChain();
ReleaseWindow();
g_d3d12_context->Destroy();
}
}
bool GSDevice12::GetHostRefreshRate(float* refresh_rate)
{
if (m_swap_chain && IsExclusiveFullscreen())
if (m_swap_chain && m_is_exclusive_fullscreen)
{
DXGI_SWAP_CHAIN_DESC desc;
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;
}
bool GSDevice12::CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode)
bool GSDevice12::CreateSwapChain()
{
constexpr DXGI_FORMAT swap_chain_format = DXGI_FORMAT_R8G8B8A8_UNORM;
if (m_window_info.type != WindowInfo::Type::Win32)
return false;
const HWND window_hwnd = reinterpret_cast<HWND>(m_window_info.window_handle);
RECT 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 = {};
swap_chain_desc.Width = width;
swap_chain_desc.Height = height;
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.Width = static_cast<u32>(client_rc.right - client_rc.left);
swap_chain_desc.Height = static_cast<u32>(client_rc.bottom - client_rc.top);
swap_chain_desc.Format = swap_chain_format;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.BufferCount = 3;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
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)
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
if (fullscreen_mode)
HRESULT hr = S_OK;
if (m_is_exclusive_fullscreen)
{
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
swap_chain_desc.Width = fullscreen_mode->Width;
swap_chain_desc.Height = fullscreen_mode->Height;
fs_desc.RefreshRate = fullscreen_mode->RefreshRate;
fs_desc.ScanlineOrdering = fullscreen_mode->ScanlineOrdering;
fs_desc.Scaling = fullscreen_mode->Scaling;
DXGI_SWAP_CHAIN_DESC1 fs_sd_desc = swap_chain_desc;
DXGI_SWAP_CHAIN_FULLSCREEN_DESC fs_desc = {};
fs_sd_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
fs_sd_desc.Width = fullscreen_mode.Width;
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;
}
DevCon.WriteLn("Creating a %dx%d %s swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
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());
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.Error("CreateSwapChainForHwnd failed: 0x%08X", hr);
return false;
Console.Warning("Failed to create fullscreen swap chain, trying windowed.");
m_is_exclusive_fullscreen = false;
m_using_allow_tearing = m_allow_tearing_supported;
}
}
if (!m_is_exclusive_fullscreen)
{
Console.WriteLn("Creating a %dx%d windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
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);
if (FAILED(hr))
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()
@ -315,6 +358,7 @@ bool GSDevice12::CreateSwapChainRTV()
if (FAILED(hr))
{
Console.Error("GetBuffer for RTV failed: 0x%08X", hr);
m_swap_chain_buffers.clear();
return false;
}
@ -322,6 +366,7 @@ bool GSDevice12::CreateSwapChainRTV()
if (!tex.Adopt(std::move(backbuffer), DXGI_FORMAT_UNKNOWN, swap_chain_desc.BufferDesc.Format,
DXGI_FORMAT_UNKNOWN, D3D12_RESOURCE_STATE_PRESENT))
{
m_swap_chain_buffers.clear();
return false;
}
@ -360,26 +405,44 @@ void GSDevice12::DestroySwapChainRTVs()
m_current_swap_chain_buffer = 0;
}
bool GSDevice12::ChangeWindow(const WindowInfo& new_wi)
void GSDevice12::DestroySwapChain()
{
DestroySurface();
if (!m_swap_chain)
return;
DestroySwapChainRTVs();
// 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;
}
m_window_info = new_wi;
if (new_wi.type == WindowInfo::Type::Surfaceless)
return true;
return CreateSwapChain(nullptr);
}
void GSDevice12::DestroySurface()
{
ExecuteCommandList(true);
if (IsExclusiveFullscreen())
SetExclusiveFullscreen(false, 0, 0, 0.0f);
DestroySwapChainRTVs();
m_swap_chain.reset();
DestroySwapChain();
}
std::string GSDevice12::GetDriverInfo() const
@ -452,75 +515,6 @@ bool GSDevice12::SupportsExclusiveFullscreen() const
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(&current_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)
{
EndRenderPass();
@ -531,12 +525,21 @@ GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
if (frame_skip || !m_swap_chain)
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];
ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList();
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);
g_perfmon.Put(GSPerfMon::RenderPasses, 1);

View File

@ -145,6 +145,7 @@ private:
bool m_allow_tearing_supported = false;
bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false;
bool m_device_lost = false;
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;
bool CreateSwapChain(const DXGI_MODE_DESC* fullscreen_mode);
bool CreateSwapChain();
bool CreateSwapChainRTV();
void DestroySwapChainRTVs();
void DestroySwapChain();
GSTexture* CreateSurface(GSTexture::Type type, int width, int height, int levels, GSTexture::Format format) override;
@ -243,14 +245,12 @@ public:
RenderAPI GetRenderAPI() const override;
bool HasSurface() const override;
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
bool Create() 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;
bool SupportsExclusiveFullscreen() const override;
bool IsExclusiveFullscreen() override;
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
void DestroySurface() override;
std::string GetDriverInfo() const override;

View File

@ -378,7 +378,7 @@ public:
MRCOwned<id<MTLFunction>> LoadShader(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);
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
bool Create() override;
void Destroy() override;
void AttachSurfaceOnMainThread();
@ -387,10 +387,8 @@ public:
RenderAPI GetRenderAPI() const override;
bool HasSurface() const override;
void DestroySurface() override;
bool ChangeWindow(const WindowInfo& wi) override;
bool UpdateWindow() 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;
void ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) override;

View File

@ -764,9 +764,9 @@ static MRCOwned<id<MTLBuffer>> CreatePrivateBufferWithContent(
return actual;
}
bool GSDeviceMTL::Create(const WindowInfo& wi, VsyncMode vsync)
bool GSDeviceMTL::Create()
{ @autoreleasepool {
if (!GSDevice::Create(wi, vsync))
if (!GSDevice::Create())
return false;
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)
{
// 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]
{
AttachSurfaceOnMainThread();
});
SetVSync(vsync);
[m_layer setDisplaySyncEnabled:m_vsync_mode != VsyncMode::Off];
}
else
{
@ -1165,6 +1170,7 @@ void GSDeviceMTL::Destroy()
GSDevice::Destroy();
GSDeviceMTL::DestroySurface();
ReleaseWindow();
m_queue = nullptr;
m_dev.Reset();
}}
@ -1177,20 +1183,21 @@ void GSDeviceMTL::DestroySurface()
m_layer = nullptr;
}
bool GSDeviceMTL::ChangeWindow(const WindowInfo& wi)
bool GSDeviceMTL::UpdateWindow()
{
OnMainThread([this, &wi]
{
DetachSurfaceOnMainThread();
m_window_info = wi;
AttachSurfaceOnMainThread();
});
DestroySurface();
if (!AcquireWindow(false))
return false;
if (m_window_info.type == WindowInfo::Type::Surfaceless)
return true;
OnMainThread([this] { AttachSurfaceOnMainThread(); });
return true;
}
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
{ @autoreleasepool {
@ -1339,6 +1346,9 @@ void GSDeviceMTL::EndPresent()
void GSDeviceMTL::SetVSync(VsyncMode mode)
{
if (m_vsync_mode == mode)
return;
[m_layer setDisplaySyncEnabled:mode != VsyncMode::Off];
m_vsync_mode = mode;
}

View File

@ -85,9 +85,13 @@ void GSDeviceOGL::SetVSync(VsyncMode 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;
// 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, 2}, {GL::Context::Profile::Core, 4, 1}, {GL::Context::Profile::Core, 4, 0},
{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)
{
Console.Error("Failed to create any GL context");
@ -109,6 +113,10 @@ bool GSDeviceOGL::Create(const WindowInfo& wi, VsyncMode vsync)
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())
return false;
@ -543,6 +551,8 @@ void GSDeviceOGL::Destroy()
m_gl_context->DoneCurrent();
m_gl_context.reset();
ReleaseWindow();
}
}
@ -651,23 +661,31 @@ void GSDeviceOGL::DestroyResources()
glDeleteFramebuffers(1, &m_fbo_write);
}
bool GSDeviceOGL::ChangeWindow(const WindowInfo& new_wi)
bool GSDeviceOGL::UpdateWindow()
{
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");
ReleaseWindow();
return false;
}
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
if (m_vsync_mode != VsyncMode::Adaptive || !m_gl_context->SetSwapInterval(-1))
m_gl_context->SetSwapInterval(static_cast<s32>(m_vsync_mode != VsyncMode::Off));
RenderBlankFrame();
}
return true;
@ -691,16 +709,6 @@ bool GSDeviceOGL::SupportsExclusiveFullscreen() const
return false;
}
bool GSDeviceOGL::IsExclusiveFullscreen()
{
return false;
}
bool GSDeviceOGL::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
{
return false;
}
void GSDeviceOGL::DestroySurface()
{
m_window_info = {};
@ -2039,6 +2047,17 @@ void GSDeviceOGL::RenderImGui()
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)
{
if (rt)

View File

@ -262,6 +262,7 @@ private:
bool CreateImGuiProgram();
void RenderImGui();
void RenderBlankFrame();
void OMAttachRt(GSTextureOGL* rt = nullptr);
void OMAttachDs(GSTextureOGL* ds = nullptr);
@ -286,14 +287,12 @@ public:
RenderAPI GetRenderAPI() const override;
bool HasSurface() const override;
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
bool Create() 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;
bool SupportsExclusiveFullscreen() const override;
bool IsExclusiveFullscreen() override;
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
void DestroySurface() override;
std::string GetDriverInfo() const override;

View File

@ -70,6 +70,8 @@ static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(VsyncMode mode)
return VK_PRESENT_MODE_IMMEDIATE_KHR;
}
static constexpr VkClearValue s_present_clear_color = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
GSDeviceVK::GSDeviceVK()
{
#ifdef ENABLE_OGL_DEBUG
@ -105,7 +107,7 @@ void GSDeviceVK::GetAdaptersAndFullscreenModes(
if (Vulkan::LoadVulkanLibrary())
{
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 (Vulkan::LoadVulkanInstanceFunctions(instance))
@ -171,26 +173,17 @@ bool GSDeviceVK::HasSurface() const
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;
WindowInfo local_wi(wi);
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");
if (!CreateDeviceAndSwapChain())
return false;
}
Vulkan::ShaderCache::Create(GSConfig.DisableShaderCache ? std::string_view() : std::string_view(EmuFolders::Cache),
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())
{
Console.Error("Your GPU does not support the required Vulkan features.");
@ -264,29 +257,31 @@ void GSDeviceVK::Destroy()
g_vulkan_context->WaitForGPUIdle();
m_swap_chain.reset();
ReleaseWindow();
Vulkan::ShaderCache::Destroy();
Vulkan::Context::Destroy();
}
}
bool GSDeviceVK::ChangeWindow(const WindowInfo& new_wi)
bool GSDeviceVK::UpdateWindow()
{
if (new_wi.type == WindowInfo::Type::Surfaceless)
{
ExecuteCommandBuffer(true);
m_swap_chain.reset();
m_window_info = new_wi;
DestroySurface();
if (!AcquireWindow(false))
return false;
if (m_window_info.type == WindowInfo::Type::Surfaceless)
return true;
}
// make sure previous frames are presented
ExecuteCommandBuffer(false);
g_vulkan_context->WaitForGPUIdle();
// recreate surface in existing swap chain if it already exists
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();
return true;
@ -295,24 +290,26 @@ bool GSDeviceVK::ChangeWindow(const WindowInfo& new_wi)
m_swap_chain.reset();
}
WindowInfo wi_copy(new_wi);
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)
{
Console.Error("Failed to create new surface for swap chain");
ReleaseWindow();
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)
{
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;
}
m_window_info = m_swap_chain->GetWindowInfo();
RenderBlankFrame();
return true;
}
@ -344,16 +341,6 @@ bool GSDeviceVK::SupportsExclusiveFullscreen() const
return false;
}
bool GSDeviceVK::IsExclusiveFullscreen()
{
return false;
}
bool GSDeviceVK::SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate)
{
return false;
}
void GSDeviceVK::DestroySurface()
{
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.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,
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);
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
}
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()
{
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)
{
EndRenderPass();

View File

@ -190,6 +190,7 @@ private:
VkShaderModule GetUtilityVertexShader(const std::string& source, const char* replace_main);
VkShaderModule GetUtilityFragmentShader(const std::string& source, const char* replace_main);
bool CreateDeviceAndSwapChain();
bool CheckFeatures();
bool CreateNullTexture();
bool CreateBuffers();
@ -205,6 +206,7 @@ private:
bool CompileImGuiPipeline();
void RenderImGui();
void RenderBlankFrame();
void DestroyResources();
@ -230,14 +232,12 @@ public:
RenderAPI GetRenderAPI() const override;
bool HasSurface() const override;
bool Create(const WindowInfo& wi, VsyncMode vsync) override;
bool Create() 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;
bool SupportsExclusiveFullscreen() const override;
bool IsExclusiveFullscreen() override;
bool SetExclusiveFullscreen(bool fullscreen, u32 width, u32 height, float refresh_rate) override;
void DestroySurface() override;
std::string GetDriverInfo() const override;

View File

@ -105,12 +105,7 @@ void Host::SetRelativeMouseMode(bool enabled)
{
}
std::optional<WindowInfo> Host::AcquireRenderWindow()
{
return std::nullopt;
}
std::optional<WindowInfo> Host::UpdateRenderWindow(bool recreate_window)
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
{
return std::nullopt;
}