rsx: Clean up window management code

- Removes a lot of wm_event code that was used to perform window management and is no longer needed.
- Significantly simplifies the vulkan code.
- Implements resource management when vulkan window is minimized to allow resources to be freed.
This commit is contained in:
kd-11 2019-06-10 12:29:46 +03:00 committed by kd-11
parent 57196f0504
commit d361eedbec
8 changed files with 77 additions and 446 deletions

View file

@ -1,4 +1,4 @@
#ifdef _MSC_VER
#ifdef _MSC_VER
#include "stdafx.h"
#include "stdafx_d3d12.h"
#include "D3D12GSRender.h"
@ -308,12 +308,6 @@ void D3D12GSRender::on_exit()
void D3D12GSRender::do_local_task(rsx::FIFO_state state)
{
if (state != rsx::FIFO_state::lock_wait)
{
//TODO
m_frame->clear_wm_events();
}
rsx::thread::do_local_task(state);
}

View file

@ -939,12 +939,8 @@ void GLGSRender::on_init_thread()
if (!supports_native_ui)
{
m_frame->disable_wm_event_queue();
m_frame->hide();
m_shaders_cache->load(nullptr);
m_frame->enable_wm_event_queue();
m_frame->show();
}
else
@ -1004,7 +1000,6 @@ void GLGSRender::on_init_thread()
}
helper(this);
m_frame->enable_wm_event_queue();
m_shaders_cache->load(&helper);
}
}
@ -1918,8 +1913,6 @@ void GLGSRender::do_local_task(rsx::FIFO_state state)
return;
}
m_frame->clear_wm_events();
if (m_overlay_manager)
{
if (!in_begin_end && async_flip_requested & flip_request::native_ui)

View file

@ -1,4 +1,4 @@
#include "stdafx.h"
#include "stdafx.h"
#include "Emu/Memory/vm.h"
#include "Emu/System.h"
@ -41,8 +41,6 @@ void GSRender::on_exit()
{
if (m_frame)
{
m_frame->disable_wm_event_queue();
m_frame->clear_wm_events();
m_frame->delete_context(m_context);
m_context = nullptr;
}

View file

@ -84,78 +84,6 @@ using draw_context_t = void*;
virtual int client_height() = 0;
virtual display_handle_t handle() const = 0;
protected:
// window manager event management
std::deque<wm_event> m_raised_events;
std::atomic_bool wm_event_queue_enabled = {};
std::atomic_bool wm_allow_fullscreen = { true };
public:
// synchronize native window access
shared_mutex wm_event_lock;
void wm_wait() const
{
while (!m_raised_events.empty() && !Emu.IsStopped()) _mm_pause();
}
bool has_wm_events() const
{
return !m_raised_events.empty();
}
void clear_wm_events()
{
if (!m_raised_events.empty())
{
std::lock_guard lock(wm_event_lock);
m_raised_events.clear();
}
}
void push_wm_event(wm_event&& _event)
{
std::lock_guard lock(wm_event_lock);
m_raised_events.push_back(_event);
}
wm_event get_wm_event()
{
if (m_raised_events.empty())
{
return wm_event::none;
}
else
{
std::lock_guard lock(wm_event_lock);
const auto _event = m_raised_events.front();
m_raised_events.pop_front();
return _event;
}
}
void disable_wm_event_queue()
{
wm_event_queue_enabled.store(false);
}
void enable_wm_event_queue()
{
wm_event_queue_enabled.store(true);
}
void disable_wm_fullscreen()
{
wm_allow_fullscreen.store(false);
}
void enable_wm_fullscreen()
{
wm_allow_fullscreen.store(true);
}
};
class GSRender : public rsx::thread

View file

@ -444,10 +444,13 @@ VKGSRender::VKGSRender() : GSRender()
vk::set_current_thread_ctx(m_thread_context);
vk::set_current_renderer(m_swapchain->get_device());
m_client_width = m_frame->client_width();
m_client_height = m_frame->client_height();
if (!m_swapchain->init(m_client_width, m_client_height))
present_surface_dirty_flag = true;
m_swapchain_dims.width = m_frame->client_width();
m_swapchain_dims.height = m_frame->client_height();
if (!m_swapchain->init(m_swapchain_dims.width, m_swapchain_dims.height))
{
swapchain_unavailable = true;
}
//create command buffer...
m_command_buffer_pool.create((*m_device));
@ -896,113 +899,6 @@ void VKGSRender::check_descriptors()
}
}
void VKGSRender::check_window_status()
{
if (m_swapchain->supports_automatic_wm_reports())
{
// This driver will report window events as VK_ERROR_OUT_OF_DATE_KHR
m_frame->clear_wm_events();
return;
}
#ifdef _WIN32
if (LIKELY(!m_frame->has_wm_events()))
{
return;
}
while (const auto _event = m_frame->get_wm_event())
{
switch (_event)
{
case wm_event::toggle_fullscreen:
{
renderer_unavailable = true;
m_frame->enable_wm_fullscreen();
m_frame->toggle_fullscreen();
m_frame->disable_wm_fullscreen();
break;
}
case wm_event::geometry_change_notice:
{
// Stall until finish notification is received. Also, raise surface dirty flag
u32 timeout = 1000;
bool handled = false;
while (timeout)
{
switch (m_frame->get_wm_event())
{
default:
break;
case wm_event::window_resized:
handled = true;
present_surface_dirty_flag = true;
break;
case wm_event::geometry_change_in_progress:
timeout += 10; // Extend timeout to wait for user to finish resizing
break;
case wm_event::window_restored:
case wm_event::window_visibility_changed:
case wm_event::window_minimized:
case wm_event::window_moved:
handled = true; // Ignore these events as they do not alter client area
break;
}
if (handled)
{
break;
}
else
{
// Wait for window manager event
std::this_thread::sleep_for(1ms);
timeout --;
}
}
if (!timeout)
{
LOG_ERROR(RSX, "wm event handler timed out");
}
// Reset renderer availability if something has changed about the window
renderer_unavailable = false;
break;
}
case wm_event::window_resized:
{
LOG_ERROR(RSX, "wm_event::window_resized received without corresponding wm_event::geometry_change_notice!");
std::this_thread::sleep_for(100ms);
renderer_unavailable = false;
break;
}
}
}
#else
// If the queue is in use, it should be properly consumed
verify(HERE), !m_frame->has_wm_events();
const auto frame_width = m_frame->client_width();
const auto frame_height = m_frame->client_height();
if (m_client_height != frame_height ||
m_client_width != frame_width)
{
if (!!frame_width && !!frame_height)
{
present_surface_dirty_flag = true;
renderer_unavailable = false;
}
}
#endif
}
VkDescriptorSet VKGSRender::allocate_descriptor_set()
{
verify(HERE), m_current_frame->used_descriptors < DESCRIPTOR_MAX_DRAW_CALLS;
@ -1024,7 +920,7 @@ void VKGSRender::begin()
{
rsx::thread::begin();
if (skip_frame || renderer_unavailable ||
if (skip_frame || swapchain_unavailable ||
(conditional_render_enabled && conditional_render_test_failed))
return;
@ -1300,7 +1196,7 @@ void VKGSRender::emit_geometry(u32 sub_index)
void VKGSRender::end()
{
if (skip_frame || !framebuffer_status_valid || renderer_unavailable ||
if (skip_frame || !framebuffer_status_valid || swapchain_unavailable ||
(conditional_render_enabled && conditional_render_test_failed))
{
execute_nop_draw();
@ -1876,10 +1772,8 @@ void VKGSRender::on_init_thread()
if (!supports_native_ui)
{
m_frame->disable_wm_event_queue();
m_frame->hide();
m_shaders_cache->load(nullptr, *m_device, pipeline_layout);
m_frame->enable_wm_event_queue();
m_frame->show();
}
else
@ -1939,18 +1833,8 @@ void VKGSRender::on_init_thread()
}
helper(this);
//TODO: Handle window resize messages during loading on GPUs without OUT_OF_DATE_KHR support
m_frame->disable_wm_event_queue();
// TODO: Handle window resize messages during loading on GPUs without OUT_OF_DATE_KHR support
m_shaders_cache->load(&helper, *m_device, pipeline_layout);
m_frame->enable_wm_event_queue();
#ifdef _WIN32
if (!m_swapchain->supports_automatic_wm_reports())
{
// If the renderer does not handle WM events itself, switching to fullscreen is done by the renderer, not the UI
m_frame->disable_wm_fullscreen();
}
#endif
}
}
@ -1962,7 +1846,7 @@ void VKGSRender::on_exit()
void VKGSRender::clear_surface(u32 mask)
{
if (skip_frame || renderer_unavailable) return;
if (skip_frame || swapchain_unavailable) return;
// If stencil write mask is disabled, remove clear_stencil bit
if (!rsx::method_registers.stencil_mask()) mask &= ~0x2u;
@ -2261,7 +2145,7 @@ void VKGSRender::present(frame_context_t *ctx)
{
verify(HERE), ctx->present_image != UINT32_MAX;
if (!present_surface_dirty_flag)
if (!swapchain_unavailable)
{
switch (VkResult error = m_swapchain->present(ctx->present_wait_semaphore, ctx->present_image))
{
@ -2270,7 +2154,7 @@ void VKGSRender::present(frame_context_t *ctx)
case VK_SUBOPTIMAL_KHR:
break;
case VK_ERROR_OUT_OF_DATE_KHR:
present_surface_dirty_flag = true;
swapchain_unavailable = true;
break;
default:
vk::die_with_error(HERE, error);
@ -2323,9 +2207,8 @@ void VKGSRender::frame_context_cleanup(frame_context_t *ctx, bool free_resources
// Perform hard swap here
if (ctx->swap_command_buffer->wait(FRAME_PRESENT_TIMEOUT) != VK_SUCCESS)
{
// Lost surface, release renderer
present_surface_dirty_flag = true;
renderer_unavailable = true;
// Lost surface/device, release swapchain
swapchain_unavailable = true;
}
free_resources = true;
@ -2447,8 +2330,6 @@ void VKGSRender::do_local_task(rsx::FIFO_state state)
break;
}
check_window_status();
if (m_overlay_manager)
{
if (!in_begin_end && async_flip_requested & flip_request::native_ui)
@ -3046,14 +2927,17 @@ void VKGSRender::prepare_rtts(rsx::framebuffer_creation_context context)
void VKGSRender::reinitialize_swapchain()
{
const auto new_width = m_frame->client_width();
const auto new_height = m_frame->client_height();
m_swapchain_dims.width = m_frame->client_width();
m_swapchain_dims.height = m_frame->client_height();
// Reject requests to acquire new swapchain if the window is minimized
// The NVIDIA driver will spam VK_ERROR_OUT_OF_DATE_KHR if you try to acquire an image from the swapchain and the window is minimized
// However, any attempt to actually renew the swapchain will crash the driver with VK_ERROR_DEVICE_LOST while the window is in this state
if (new_width == 0 || new_height == 0)
if (m_swapchain_dims.width == 0 || m_swapchain_dims.height == 0)
{
swapchain_unavailable = true;
return;
}
// NOTE: This operation will create a hard sync point
close_and_submit_command_buffer(m_current_command_buffer->submit_fence);
@ -3073,18 +2957,14 @@ void VKGSRender::reinitialize_swapchain()
vkDeviceWaitIdle(*m_device);
// Rebuild swapchain. Old swapchain destruction is handled by the init_swapchain call
if (!m_swapchain->init(new_width, new_height))
if (!m_swapchain->init(m_swapchain_dims.width, m_swapchain_dims.height))
{
LOG_WARNING(RSX, "Swapchain initialization failed. Request ignored [%dx%d]", new_width, new_height);
present_surface_dirty_flag = true;
renderer_unavailable = true;
LOG_WARNING(RSX, "Swapchain initialization failed. Request ignored [%dx%d]", m_swapchain_dims.width, m_swapchain_dims.height);
swapchain_unavailable = true;
open_command_buffer();
return;
}
m_client_width = new_width;
m_client_height = new_height;
// Prepare new swapchain images for use
open_command_buffer();
@ -3115,26 +2995,24 @@ void VKGSRender::reinitialize_swapchain()
m_current_command_buffer->reset();
open_command_buffer();
present_surface_dirty_flag = false;
renderer_unavailable = false;
swapchain_unavailable = false;
}
void VKGSRender::flip(int buffer, bool emu_flip)
{
if (skip_frame || renderer_unavailable)
// Check swapchain condition/status
if (!m_swapchain->supports_automatic_wm_reports())
{
m_frame->flip(m_context);
rsx::thread::flip(buffer, emu_flip);
if (!skip_frame)
if (m_swapchain_dims.width != m_frame->client_width() ||
m_swapchain_dims.height != m_frame->client_height())
{
m_draw_time = 0;
m_setup_time = 0;
m_vertex_upload_time = 0;
m_textures_upload_time = 0;
swapchain_unavailable = true;
}
}
return;
if (swapchain_unavailable)
{
reinitialize_swapchain();
}
std::chrono::time_point<steady_clock> flip_start = steady_clock::now();
@ -3156,7 +3034,7 @@ void VKGSRender::flip(int buffer, bool emu_flip)
{
if (m_draw_calls > 0)
{
// This can be 'legal' if the window was being resized and no polling happened because of renderer_unavailable flag
// This can be 'legal' if the window was being resized and no polling happened because of swapchain_unavailable flag
LOG_ERROR(RSX, "Possible data corruption on frame context storage detected");
}
@ -3164,14 +3042,29 @@ void VKGSRender::flip(int buffer, bool emu_flip)
frame_context_cleanup(m_current_frame, true);
}
if (present_surface_dirty_flag)
if (skip_frame || swapchain_unavailable)
{
//Recreate swapchain and continue as usual
reinitialize_swapchain();
}
m_frame->flip(m_context);
rsx::thread::flip(buffer, emu_flip);
if (!skip_frame)
{
verify(HERE), swapchain_unavailable;
// Perform a mini-flip here without invoking present code
m_current_frame->swap_command_buffer = m_current_command_buffer;
flush_command_queue(true);
vk::advance_frame_counter();
frame_context_cleanup(m_current_frame, true);
m_draw_time = 0;
m_setup_time = 0;
m_vertex_upload_time = 0;
m_textures_upload_time = 0;
}
if (renderer_unavailable)
return;
}
u32 buffer_width = display_buffers[buffer].width;
u32 buffer_height = display_buffers[buffer].height;
@ -3196,7 +3089,7 @@ void VKGSRender::flip(int buffer, bool emu_flip)
coordi aspect_ratio;
sizei csize = { (s32)m_client_width, (s32)m_client_height };
sizei csize = m_swapchain_dims;
sizei new_size = csize;
if (!g_cfg.video.stretch_to_display_area)
@ -3237,17 +3130,17 @@ void VKGSRender::flip(int buffer, bool emu_flip)
//This makes fullscreen performance slower than windowed performance as throughput is lowered due to losing one presentable image
//Found on AMD Crimson 17.7.2
//Whatever returned from status, this is now a spin
timeout = 0ull;
check_present_status();
continue;
}
case VK_ERROR_OUT_OF_DATE_KHR:
LOG_WARNING(RSX, "vkAcquireNextImageKHR failed with VK_ERROR_OUT_OF_DATE_KHR. Flip request ignored until surface is recreated.");
present_surface_dirty_flag = true;
swapchain_unavailable = true;
reinitialize_swapchain();
return;
continue;
default:
vk::die_with_error(HERE, status);
}
@ -3389,7 +3282,7 @@ void VKGSRender::flip(int buffer, bool emu_flip)
VkRenderPass single_target_pass = vk::get_renderpass(*m_device, key);
verify("Usupported renderpass configuration" HERE), single_target_pass != VK_NULL_HANDLE;
auto direct_fbo = vk::get_framebuffer(*m_device, m_client_width, m_client_height, single_target_pass, m_swapchain->get_surface_format(), target_image);
auto direct_fbo = vk::get_framebuffer(*m_device, m_swapchain_dims.width, m_swapchain_dims.height, single_target_pass, m_swapchain->get_surface_format(), target_image);
direct_fbo->add_ref();
if (has_overlay)
@ -3454,7 +3347,7 @@ void VKGSRender::flip(int buffer, bool emu_flip)
bool VKGSRender::scaled_image_from_memory(rsx::blit_src_info& src, rsx::blit_dst_info& dst, bool interpolate)
{
if (renderer_unavailable)
if (swapchain_unavailable)
return false;
// Verify enough memory exists before attempting to handle data transfer

View file

@ -414,8 +414,8 @@ private:
vk::framebuffer_holder* m_draw_fbo = nullptr;
bool present_surface_dirty_flag = false;
bool renderer_unavailable = false;
sizeu m_swapchain_dims{};
bool swapchain_unavailable = false;
u64 m_last_heap_sync_time = 0;
u32 m_texbuffer_view_size = 0;
@ -445,9 +445,6 @@ private:
frame_context_t* m_current_frame = nullptr;
std::deque<frame_context_t*> m_queued_frames;
u32 m_client_width = 0;
u32 m_client_height = 0;
VkViewport m_viewport{};
VkRect2D m_scissor{};
@ -506,6 +503,7 @@ private:
void update_draw_state();
void check_heap_status(u32 flags = VK_HEAP_CHECK_ALL);
void check_present_status();
void check_descriptors();
VkDescriptorSet allocate_descriptor_set();
@ -524,9 +522,6 @@ public:
void set_scissor();
void bind_viewport();
void check_window_status();
void check_present_status();
void sync_hint(rsx::FIFO_hint hint) override;
void begin_occlusion_query(rsx::reports::occlusion_query_info* query) override;

View file

@ -436,9 +436,6 @@ namespace vk
const auto gpu_name = get_name();
if (gpu_name.find("Radeon") != std::string::npos)
{
#ifndef _WIN32
LOG_ERROR(RSX, "Using non RADV drivers on linux currently incurs a ~40% performance loss due to a window resizing workaround. Using RADV is recommended.");
#endif
return driver_vendor::AMD;
}
@ -2036,13 +2033,9 @@ public:
switch (gpu.get_driver_vendor())
{
case driver_vendor::NVIDIA:
#ifndef _WIN32
m_wm_reports_flag = true;
#endif
break;
case driver_vendor::AMD:
break;
case driver_vendor::NVIDIA:
case driver_vendor::INTEL:
case driver_vendor::RADV:
m_wm_reports_flag = true;

View file

@ -27,155 +27,6 @@
constexpr auto qstr = QString::fromStdString;
#ifdef _WIN32
namespace win32
{
HHOOK _hook = NULL;
bool _in_sizing_event = false;
bool _interactive_resize = false;
bool _user_interaction_active = false;
bool _minimized = false;
void _ReleaseHook();
LRESULT CALLBACK __HookCallback(INT nCode, WPARAM wParam, LPARAM lParam)
{
std::shared_ptr<GSRender> renderer;
if (Emu.IsStopped())
{
// Stop receiving events
_ReleaseHook();
}
else
{
renderer = fxm::get<GSRender>();
}
if (nCode >= 0 && renderer)
{
auto frame_wnd = renderer->get_frame();
auto msg = (CWPSTRUCT*)lParam;
switch ((msg->hwnd == frame_wnd->handle()) ? msg->message : 0)
{
case WM_WINDOWPOSCHANGING:
{
const auto flags = reinterpret_cast<LPWINDOWPOS>(msg->lParam)->flags & SWP_NOSIZE;
if (_in_sizing_event || flags != 0)
break;
// About to resize
_in_sizing_event = true;
_interactive_resize = false;
frame_wnd->push_wm_event(wm_event::geometry_change_notice);
break;
}
case WM_WINDOWPOSCHANGED:
{
if (_user_interaction_active)
{
// Window dragged or resized by user causing position to change, but user is not yet done
frame_wnd->push_wm_event(wm_event::geometry_change_in_progress);
break;
}
const auto flags = reinterpret_cast<LPWINDOWPOS>(msg->lParam)->flags & (SWP_NOSIZE | SWP_NOMOVE);
if (!_in_sizing_event || flags == (SWP_NOSIZE | SWP_NOMOVE))
break;
_in_sizing_event = false;
if (flags & SWP_NOSIZE)
{
frame_wnd->push_wm_event(wm_event::window_moved);
}
else
{
LPWINDOWPOS wpos = reinterpret_cast<LPWINDOWPOS>(msg->lParam);
if (wpos->cx <= GetSystemMetrics(SM_CXMINIMIZED) || wpos->cy <= GetSystemMetrics(SM_CYMINIMIZED))
{
// Minimize event
_minimized = true;
frame_wnd->push_wm_event(wm_event::window_minimized);
}
else if (_minimized)
{
_minimized = false;
frame_wnd->push_wm_event(wm_event::window_restored);
}
else
{
// Handle the resize in WM_SIZE message
_in_sizing_event = true;
}
}
break;
}
case WM_ENTERSIZEMOVE:
_user_interaction_active = true;
break;
case WM_EXITSIZEMOVE:
_user_interaction_active = false;
if (_in_sizing_event && !_user_interaction_active)
{
// Just finished resizing using manual interaction. The corresponding WM_SIZE is consumed before this event fires
frame_wnd->push_wm_event(_interactive_resize ? wm_event::window_resized : wm_event::window_moved);
_in_sizing_event = false;
}
break;
case WM_SIZE:
{
if (_user_interaction_active)
{
// Interaction is a resize not a move
_interactive_resize = true;
frame_wnd->push_wm_event(wm_event::geometry_change_in_progress);
}
else if (_in_sizing_event)
{
// Any other unexpected resize mode will give an unconsumed WM_SIZE event
frame_wnd->push_wm_event(wm_event::window_resized);
_in_sizing_event = false;
}
break;
}
}
}
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
void _InstallHook()
{
if (_hook)
{
_ReleaseHook();
}
Emu.CallAfter([&]()
{
if (_hook = SetWindowsHookEx(WH_CALLWNDPROC, __HookCallback, NULL, GetCurrentThreadId()); !_hook)
{
LOG_ERROR(RSX, "Failed to install window hook!");
}
});
}
void _ReleaseHook()
{
if (_hook)
{
UnhookWindowsHookEx(_hook);
_hook = NULL;
}
}
}
#endif
gs_frame::gs_frame(const QString& title, const QRect& geometry, const QIcon& appIcon, const std::shared_ptr<gui_settings>& gui_settings)
: QWindow(), m_windowTitle(title), m_gui_settings(gui_settings)
{
@ -235,7 +86,6 @@ gs_frame::gs_frame(const QString& title, const QRect& geometry, const QIcon& app
m_tb_progress->setRange(0, m_gauge_max);
m_tb_progress->setVisible(false);
win32::_ReleaseHook();
#elif HAVE_QTDBUS
UpdateProgress(0);
m_progress_value = 0;
@ -309,28 +159,19 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent)
void gs_frame::toggle_fullscreen()
{
if (wm_allow_fullscreen)
auto l_setFullScreenVis = [&]()
{
auto l_setFullScreenVis = [&]()
if (visibility() == FullScreen)
{
if (visibility() == FullScreen)
{
setVisibility(Windowed);
}
else
{
setVisibility(FullScreen);
}
};
setVisibility(Windowed);
}
else
{
setVisibility(FullScreen);
}
};
Emu.CallAfter(l_setFullScreenVis);
}
else
{
// Forward the request to the backend
push_wm_event(wm_event::toggle_fullscreen);
std::this_thread::sleep_for(1s);
}
Emu.CallAfter(l_setFullScreenVis);
}
void gs_frame::close()
@ -405,10 +246,6 @@ draw_context_t gs_frame::make_context()
void gs_frame::set_current(draw_context_t ctx)
{
Q_UNUSED(ctx);
#ifdef _WIN32
win32::_InstallHook();
#endif
}
void gs_frame::delete_context(draw_context_t ctx)