Renderer: Handle resize events on-demand instead of polling

We now differentiate between a resize event and surface change/destroyed
event, reducing the overhead for resizes in the Vulkan backend. It is
also now now safe to change the surface multiple times if the video thread
is lagging behind.
This commit is contained in:
Stenzek 2018-01-26 16:23:24 +10:00
parent 5baf3bbe2e
commit de632fc9c8
19 changed files with 364 additions and 350 deletions

View File

@ -321,12 +321,8 @@ class PlatformX11 : public Platform
{
last_window_width = event.xconfigure.width;
last_window_height = event.xconfigure.height;
// We call Renderer::ChangeSurface here to indicate the size has changed,
// but pass the same window handle. This is needed for the Vulkan backend,
// otherwise it cannot tell that the window has been resized on some drivers.
if (g_renderer)
g_renderer->ChangeSurface(s_window_handle);
g_renderer->ResizeSurface(last_window_width, last_window_height);
}
}
break;

View File

@ -51,10 +51,10 @@ void Host::SetRenderFullscreen(bool fullscreen)
m_render_fullscreen = fullscreen;
}
void Host::UpdateSurface()
void Host::ResizeSurface(int new_width, int new_height)
{
if (g_renderer)
g_renderer->ChangeSurface(GetRenderHandle());
g_renderer->ResizeSurface(new_width, new_height);
}
void Host_Message(int id)

View File

@ -26,7 +26,7 @@ public:
void SetRenderHandle(void* handle);
void SetRenderFocus(bool focus);
void SetRenderFullscreen(bool fullscreen);
void UpdateSurface();
void ResizeSurface(int new_width, int new_height);
signals:
void RequestTitle(const QString& title);

View File

@ -22,7 +22,7 @@ RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent)
Qt::DirectConnection);
connect(this, &RenderWidget::HandleChanged, Host::GetInstance(), &Host::SetRenderHandle,
Qt::DirectConnection);
connect(this, &RenderWidget::SizeChanged, Host::GetInstance(), &Host::UpdateSurface,
connect(this, &RenderWidget::SizeChanged, Host::GetInstance(), &Host::ResizeSurface,
Qt::DirectConnection);
emit HandleChanged((void*)winId());
@ -84,8 +84,12 @@ bool RenderWidget::event(QEvent* event)
Host::GetInstance()->SetRenderFocus(false);
break;
case QEvent::Resize:
emit SizeChanged();
{
const QResizeEvent* se = static_cast<QResizeEvent*>(event);
QSize new_size = se->size();
emit SizeChanged(new_size.width(), new_size.height());
break;
}
case QEvent::WindowStateChange:
emit StateChanged(isFullScreen());
break;

View File

@ -23,7 +23,7 @@ signals:
void Closed();
void HandleChanged(void* handle);
void StateChanged(bool fullscreen);
void SizeChanged();
void SizeChanged(int new_width, int new_height);
private:
void HandleCursorTimer();

View File

@ -603,21 +603,18 @@ void CFrame::OnRenderParentResize(wxSizeEvent& event)
if (Core::GetState() != Core::State::Uninitialized)
{
int width, height;
m_render_frame->GetClientSize(&width, &height);
if (!SConfig::GetInstance().bRenderToMain && !RendererIsFullscreen() &&
!m_render_frame->IsMaximized() && !m_render_frame->IsIconized())
{
m_render_frame->GetClientSize(&width, &height);
SConfig::GetInstance().iRenderWindowWidth = width;
SConfig::GetInstance().iRenderWindowHeight = height;
}
m_log_window->Refresh();
m_log_window->Update();
// We call Renderer::ChangeSurface here to indicate the size has changed,
// but pass the same window handle. This is needed for the Vulkan backend,
// otherwise it cannot tell that the window has been resized on some drivers.
if (g_renderer)
g_renderer->ChangeSurface(GetRenderHandle());
g_renderer->ResizeSurface(width, height);
}
event.Skip();
}

View File

@ -37,9 +37,9 @@ namespace D3D
ID3D11Device* device = nullptr;
ID3D11Device1* device1 = nullptr;
ID3D11DeviceContext* context = nullptr;
HWND hWnd;
IDXGISwapChain1* swapchain = nullptr;
static IDXGISwapChain1* s_swapchain;
static IDXGIFactory2* s_dxgi_factory;
static ID3D11Debug* s_debug;
static D3D_FEATURE_LEVEL s_featlevel;
static D3DTexture2D* s_backbuf;
@ -49,9 +49,6 @@ static std::vector<DXGI_SAMPLE_DESC> s_aa_modes; // supported AA modes of the c
static bool s_bgra_textures_supported;
static bool s_allow_tearing_supported;
static unsigned int s_xres;
static unsigned int s_yres;
constexpr UINT NUM_SUPPORTED_FEATURE_LEVELS = 3;
constexpr D3D_FEATURE_LEVEL supported_feature_levels[NUM_SUPPORTED_FEATURE_LEVELS] = {
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0};
@ -247,17 +244,74 @@ static bool SupportsBPTCTextures(ID3D11Device* dev)
return (bc7_support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
}
static bool CreateSwapChainTextures()
{
ID3D11Texture2D* buf;
HRESULT hr = swapchain->GetBuffer(0, IID_ID3D11Texture2D, (void**)&buf);
CHECK(SUCCEEDED(hr), "GetBuffer for swap chain failed with HRESULT %08X", hr);
if (FAILED(hr))
return false;
s_backbuf = new D3DTexture2D(buf, D3D11_BIND_RENDER_TARGET);
SAFE_RELEASE(buf);
SetDebugObjectName(s_backbuf->GetTex(), "backbuffer texture");
SetDebugObjectName(s_backbuf->GetRTV(), "backbuffer render target view");
return true;
}
static bool CreateSwapChain(HWND hWnd)
{
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.BufferCount = 2;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.SampleDesc.Quality = 0;
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.Scaling = DXGI_SCALING_STRETCH;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.Stereo = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer;
// This flag is necessary if we want to use a flip-model swapchain without locking the framerate
swap_chain_desc.Flags = s_allow_tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
HRESULT hr = s_dxgi_factory->CreateSwapChainForHwnd(device, hWnd, &swap_chain_desc, nullptr,
nullptr, &swapchain);
if (FAILED(hr))
{
// Flip-model discard swapchains aren't supported on Windows 8, so here we fall back to
// a sequential swapchain
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
hr = s_dxgi_factory->CreateSwapChainForHwnd(device, hWnd, &swap_chain_desc, nullptr, nullptr,
&swapchain);
}
if (FAILED(hr))
{
// Flip-model swapchains aren't supported on Windows 7, so here we fall back to a legacy
// BitBlt-model swapchain
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
hr = s_dxgi_factory->CreateSwapChainForHwnd(device, hWnd, &swap_chain_desc, nullptr, nullptr,
&swapchain);
}
if (FAILED(hr))
{
ERROR_LOG(VIDEO, "Failed to create swap chain with HRESULT %08X", hr);
return false;
}
if (!CreateSwapChainTextures())
{
SAFE_RELEASE(swapchain);
return false;
}
return true;
}
HRESULT Create(HWND wnd)
{
hWnd = wnd;
HRESULT hr;
RECT client;
GetClientRect(hWnd, &client);
s_xres = client.right - client.left;
s_yres = client.bottom - client.top;
hr = LoadDXGI();
HRESULT hr = LoadDXGI();
if (SUCCEEDED(hr))
hr = LoadD3D();
if (SUCCEEDED(hr))
@ -270,18 +324,17 @@ HRESULT Create(HWND wnd)
return hr;
}
IDXGIFactory2* factory;
hr = PCreateDXGIFactory(__uuidof(IDXGIFactory2), (void**)&factory);
hr = PCreateDXGIFactory(__uuidof(IDXGIFactory2), (void**)&s_dxgi_factory);
if (FAILED(hr))
MessageBox(wnd, _T("Failed to create IDXGIFactory object"), _T("Dolphin Direct3D 11 backend"),
MB_OK | MB_ICONERROR);
IDXGIAdapter* adapter;
hr = factory->EnumAdapters(g_ActiveConfig.iAdapter, &adapter);
hr = s_dxgi_factory->EnumAdapters(g_ActiveConfig.iAdapter, &adapter);
if (FAILED(hr))
{
// try using the first one
hr = factory->EnumAdapters(0, &adapter);
hr = s_dxgi_factory->EnumAdapters(0, &adapter);
if (FAILED(hr))
MessageBox(wnd, _T("Failed to enumerate adapters"), _T("Dolphin Direct3D 11 backend"),
MB_OK | MB_ICONERROR);
@ -301,7 +354,7 @@ HRESULT Create(HWND wnd)
// Check support for allow tearing, we query the interface for backwards compatibility
UINT allow_tearing = FALSE;
IDXGIFactory5* factory5;
hr = factory->QueryInterface(&factory5);
hr = s_dxgi_factory->QueryInterface(&factory5);
if (SUCCEEDED(hr))
{
hr = factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing,
@ -310,21 +363,6 @@ HRESULT Create(HWND wnd)
}
s_allow_tearing_supported = SUCCEEDED(hr) && allow_tearing;
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.BufferCount = 2;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.SampleDesc.Count = 1;
swap_chain_desc.SampleDesc.Quality = 0;
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.Scaling = DXGI_SCALING_STRETCH;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.Width = s_xres;
swap_chain_desc.Height = s_yres;
swap_chain_desc.Stereo = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer;
// This flag is necessary if we want to use a flip-model swapchain without locking the framerate
swap_chain_desc.Flags = s_allow_tearing_supported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
// Creating debug devices can sometimes fail if the user doesn't have the correct
// version of the DirectX SDK. If it does, simply fallback to a non-debug device.
if (g_Config.bEnableValidationLayer)
@ -360,30 +398,9 @@ HRESULT Create(HWND wnd)
D3D11_SDK_VERSION, &device, &s_featlevel, &context);
}
if (SUCCEEDED(hr))
{
hr = factory->CreateSwapChainForHwnd(device, hWnd, &swap_chain_desc, nullptr, nullptr,
&s_swapchain);
if (FAILED(hr))
{
// Flip-model discard swapchains aren't supported on Windows 8, so here we fall back to
// a sequential swapchain
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
hr = factory->CreateSwapChainForHwnd(device, hWnd, &swap_chain_desc, nullptr, nullptr,
&s_swapchain);
}
SAFE_RELEASE(adapter);
if (FAILED(hr))
{
// Flip-model swapchains aren't supported on Windows 7, so here we fall back to a legacy
// BitBlt-model swapchain
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
hr = factory->CreateSwapChainForHwnd(device, hWnd, &swap_chain_desc, nullptr, nullptr,
&s_swapchain);
}
}
if (FAILED(hr))
if (FAILED(hr) || (wnd && !CreateSwapChain(wnd)))
{
MessageBox(
wnd,
@ -391,7 +408,7 @@ HRESULT Create(HWND wnd)
_T("Dolphin Direct3D 11 backend"), MB_OK | MB_ICONERROR);
SAFE_RELEASE(device);
SAFE_RELEASE(context);
SAFE_RELEASE(s_swapchain);
SAFE_RELEASE(s_dxgi_factory);
return E_FAIL;
}
@ -399,52 +416,23 @@ HRESULT Create(HWND wnd)
if (FAILED(hr))
WARN_LOG(VIDEO, "Missing Direct3D 11.1 support. Logical operations will not be supported.");
// prevent DXGI from responding to Alt+Enter, unfortunately DXGI_MWA_NO_ALT_ENTER
// does not work so we disable all monitoring of window messages. However this
// may make it more difficult for DXGI to handle display mode changes.
hr = factory->MakeWindowAssociation(wnd, DXGI_MWA_NO_WINDOW_CHANGES);
if (FAILED(hr))
MessageBox(wnd, _T("Failed to associate the window"), _T("Dolphin Direct3D 11 backend"),
MB_OK | MB_ICONERROR);
SetDebugObjectName(context, "device context");
SAFE_RELEASE(factory);
SAFE_RELEASE(adapter);
if (SConfig::GetInstance().bFullscreen && !g_ActiveConfig.bBorderlessFullscreen)
{
s_swapchain->SetFullscreenState(true, nullptr);
s_swapchain->ResizeBuffers(0, s_xres, s_yres, DXGI_FORMAT_R8G8B8A8_UNORM,
swap_chain_desc.Flags);
}
ID3D11Texture2D* buf;
hr = s_swapchain->GetBuffer(0, IID_ID3D11Texture2D, (void**)&buf);
if (FAILED(hr))
{
MessageBox(wnd, _T("Failed to get swapchain buffer"), _T("Dolphin Direct3D 11 backend"),
MB_OK | MB_ICONERROR);
SAFE_RELEASE(device);
SAFE_RELEASE(context);
SAFE_RELEASE(s_swapchain);
return E_FAIL;
}
s_backbuf = new D3DTexture2D(buf, D3D11_BIND_RENDER_TARGET);
SAFE_RELEASE(buf);
CHECK(s_backbuf != nullptr, "Create back buffer texture");
SetDebugObjectName(s_backbuf->GetTex(), "backbuffer texture");
SetDebugObjectName(s_backbuf->GetRTV(), "backbuffer render target view");
context->OMSetRenderTargets(1, &s_backbuf->GetRTV(), nullptr);
// BGRA textures are easier to deal with in TextureCache, but might not be supported by the
// hardware
// BGRA textures are easier to deal with in TextureCache, but might not be supported
UINT format_support;
device->CheckFormatSupport(DXGI_FORMAT_B8G8R8A8_UNORM, &format_support);
s_bgra_textures_supported = (format_support & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
g_Config.backend_info.bSupportsST3CTextures = SupportsS3TCTextures(device);
g_Config.backend_info.bSupportsBPTCTextures = SupportsBPTCTextures(device);
// prevent DXGI from responding to Alt+Enter, unfortunately DXGI_MWA_NO_ALT_ENTER
// does not work so we disable all monitoring of window messages. However this
// may make it more difficult for DXGI to handle display mode changes.
hr = s_dxgi_factory->MakeWindowAssociation(wnd, DXGI_MWA_NO_WINDOW_CHANGES);
if (FAILED(hr))
MessageBox(wnd, _T("Failed to associate the window"), _T("Dolphin Direct3D 11 backend"),
MB_OK | MB_ICONERROR);
SetDebugObjectName(context, "device context");
stateman = new StateManager;
return S_OK;
}
@ -452,12 +440,13 @@ HRESULT Create(HWND wnd)
void Close()
{
// we can't release the swapchain while in fullscreen.
s_swapchain->SetFullscreenState(false, nullptr);
if (swapchain)
swapchain->SetFullscreenState(false, nullptr);
// release all bound resources
context->ClearState();
SAFE_RELEASE(s_backbuf);
SAFE_RELEASE(s_swapchain);
SAFE_RELEASE(swapchain);
SAFE_DELETE(stateman);
context->Flush(); // immediately destroy device objects
@ -465,7 +454,6 @@ void Close()
SAFE_RELEASE(device1);
ULONG references = device->Release();
#if defined(_DEBUG) || defined(DEBUGFAST)
if (s_debug)
{
--references; // the debug interface increases the refcount of the device, subtract that.
@ -477,7 +465,6 @@ void Close()
}
SAFE_RELEASE(s_debug)
}
#endif
if (references)
{
@ -524,19 +511,10 @@ const char* PixelShaderVersionString()
return "ps_4_0";
}
D3DTexture2D*& GetBackBuffer()
D3DTexture2D* GetBackBuffer()
{
return s_backbuf;
}
unsigned int GetBackBufferWidth()
{
return s_xres;
}
unsigned int GetBackBufferHeight()
{
return s_yres;
}
bool BGRATexturesSupported()
{
return s_bgra_textures_supported;
@ -572,37 +550,31 @@ u32 GetMaxTextureSize(D3D_FEATURE_LEVEL feature_level)
}
}
void Reset()
void Reset(HWND new_wnd)
{
// release all back buffer references
SAFE_RELEASE(s_backbuf);
UINT swap_chain_flags = AllowTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
// resize swapchain buffers
RECT client;
GetClientRect(hWnd, &client);
s_xres = client.right - client.left;
s_yres = client.bottom - client.top;
s_swapchain->ResizeBuffers(0, s_xres, s_yres, DXGI_FORMAT_R8G8B8A8_UNORM, swap_chain_flags);
// recreate back buffer texture
ID3D11Texture2D* buf;
HRESULT hr = s_swapchain->GetBuffer(0, IID_ID3D11Texture2D, (void**)&buf);
if (FAILED(hr))
if (swapchain)
{
MessageBox(hWnd, _T("Failed to get swapchain buffer"), _T("Dolphin Direct3D 11 backend"),
MB_OK | MB_ICONERROR);
SAFE_RELEASE(device);
SAFE_RELEASE(context);
SAFE_RELEASE(s_swapchain);
return;
if (GetFullscreenState())
swapchain->SetFullscreenState(FALSE, nullptr);
SAFE_RELEASE(swapchain);
}
if (new_wnd)
CreateSwapChain(new_wnd);
}
void ResizeSwapChain()
{
SAFE_RELEASE(s_backbuf);
const UINT swap_chain_flags = AllowTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
swapchain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_R8G8B8A8_UNORM, swap_chain_flags);
if (!CreateSwapChainTextures())
{
PanicAlert("Failed to get swap chain textures");
SAFE_RELEASE(swapchain);
}
s_backbuf = new D3DTexture2D(buf, D3D11_BIND_RENDER_TARGET);
SAFE_RELEASE(buf);
CHECK(s_backbuf != nullptr, "Create back buffer texture");
SetDebugObjectName(s_backbuf->GetTex(), "backbuffer texture");
SetDebugObjectName(s_backbuf->GetRTV(), "backbuffer render target view");
}
void Present()
@ -616,25 +588,24 @@ void Present()
if (AllowTearingSupported() && !g_ActiveConfig.IsVSync() && !GetFullscreenState())
present_flags |= DXGI_PRESENT_ALLOW_TEARING;
if (s_swapchain->IsTemporaryMonoSupported() &&
g_ActiveConfig.stereo_mode != StereoMode::QuadBuffer)
if (swapchain->IsTemporaryMonoSupported() && g_ActiveConfig.stereo_mode != StereoMode::QuadBuffer)
{
present_flags |= DXGI_PRESENT_STEREO_TEMPORARY_MONO;
}
// TODO: Is 1 the correct value for vsyncing?
s_swapchain->Present((UINT)g_ActiveConfig.IsVSync(), present_flags);
swapchain->Present(static_cast<UINT>(g_ActiveConfig.IsVSync()), present_flags);
}
HRESULT SetFullscreenState(bool enable_fullscreen)
{
return s_swapchain->SetFullscreenState(enable_fullscreen, nullptr);
return swapchain->SetFullscreenState(enable_fullscreen, nullptr);
}
bool GetFullscreenState()
{
BOOL state = FALSE;
s_swapchain->GetFullscreenState(&state, nullptr);
swapchain->GetFullscreenState(&state, nullptr);
return !!state;
}

View File

@ -59,14 +59,13 @@ void Close();
extern ID3D11Device* device;
extern ID3D11Device1* device1;
extern ID3D11DeviceContext* context;
extern HWND hWnd;
extern IDXGISwapChain1* swapchain;
void Reset();
void Reset(HWND new_wnd);
void ResizeSwapChain();
void Present();
unsigned int GetBackBufferWidth();
unsigned int GetBackBufferHeight();
D3DTexture2D*& GetBackBuffer();
D3DTexture2D* GetBackBuffer();
const char* PixelShaderVersionString();
const char* GeometryShaderVersionString();
const char* VertexShaderVersionString();

View File

@ -387,9 +387,9 @@ int CD3DFont::DrawTextScaled(float x, float y, float size, float spacing, u32 dw
UINT stride = sizeof(FONT2DVERTEX);
UINT bufoffset = 0;
float scalex = 1 / (float)D3D::GetBackBufferWidth() * 2.f;
float scaley = 1 / (float)D3D::GetBackBufferHeight() * 2.f;
float sizeratio = size / (float)m_LineHeight;
float scalex = 1.0f / g_renderer->GetBackbufferWidth() * 2.f;
float scaley = 1.0f / g_renderer->GetBackbufferHeight() * 2.f;
float sizeratio = size / m_LineHeight;
// translate starting positions
float sx = x * scalex - 1.f;

View File

@ -53,12 +53,12 @@ typedef struct _Nv_Stereo_Image_Header
#define NVSTEREO_IMAGE_SIGNATURE 0x4433564e
Renderer::Renderer() : ::Renderer(D3D::GetBackBufferWidth(), D3D::GetBackBufferHeight())
Renderer::Renderer(int backbuffer_width, int backbuffer_height)
: ::Renderer(backbuffer_width, backbuffer_height)
{
m_last_multisamples = g_ActiveConfig.iMultisamples;
m_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
m_last_fullscreen_mode = D3D::GetFullscreenState();
m_last_fullscreen_state = D3D::GetFullscreenState();
g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
SetupDeviceObjects();
@ -239,25 +239,6 @@ TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
return result;
}
// With D3D, we have to resize the backbuffer if the window changed
// size.
bool Renderer::CheckForResize()
{
RECT rcWindow;
GetClientRect(D3D::hWnd, &rcWindow);
int client_width = rcWindow.right - rcWindow.left;
int client_height = rcWindow.bottom - rcWindow.top;
// Sanity check
if ((client_width != GetBackbufferWidth() || client_height != GetBackbufferHeight()) &&
client_width >= 4 && client_height >= 4)
{
return true;
}
return false;
}
void Renderer::SetScissorRect(const MathUtil::Rectangle<int>& rc)
{
const RECT rect = {rc.left, rc.top, rc.right, rc.bottom};
@ -549,12 +530,13 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
ResetAPIState();
// Prepare to copy the XFBs to our backbuffer
CheckForSurfaceChange();
CheckForSurfaceResize();
UpdateDrawRectangle();
TargetRectangle targetRc = GetTargetRectangle();
static constexpr std::array<float, 4> clear_color{{0.f, 0.f, 0.f, 1.f}};
D3D::context->OMSetRenderTargets(1, &D3D::GetBackBuffer()->GetRTV(), nullptr);
constexpr std::array<float, 4> clear_color{{0.f, 0.f, 0.f, 1.f}};
D3D::context->ClearRenderTargetView(D3D::GetBackBuffer()->GetRTV(), clear_color.data());
// activate linear filtering for the buffer copies
@ -565,8 +547,8 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height, Gamma);
// Reset viewport for drawing text
D3D11_VIEWPORT vp =
CD3D11_VIEWPORT(0.0f, 0.0f, (float)GetBackbufferWidth(), (float)GetBackbufferHeight());
D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.0f, 0.0f, static_cast<float>(m_backbuffer_width),
static_cast<float>(m_backbuffer_height));
D3D::context->RSSetViewports(1, &vp);
Renderer::DrawDebugText();
@ -580,41 +562,21 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
g_texture_cache->OnConfigChanged(g_ActiveConfig);
VertexShaderCache::RetreiveAsyncShaders();
const bool window_resized = CheckForResize();
const bool fullscreen = D3D::GetFullscreenState();
const bool fs_changed = m_last_fullscreen_mode != fullscreen;
// Flip/present backbuffer to frontbuffer here
D3D::Present();
if (D3D::swapchain)
D3D::Present();
// Resize the back buffers NOW to avoid flickering
if (CalculateTargetSize() || window_resized || fs_changed ||
m_last_multisamples != g_ActiveConfig.iMultisamples ||
if (CalculateTargetSize() || m_last_multisamples != g_ActiveConfig.iMultisamples ||
m_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off))
{
m_last_multisamples = g_ActiveConfig.iMultisamples;
m_last_fullscreen_mode = fullscreen;
PixelShaderCache::InvalidateMSAAShaders();
if (window_resized || fs_changed)
{
// TODO: Aren't we still holding a reference to the back buffer right now?
D3D::Reset();
SAFE_RELEASE(m_screenshot_texture);
SAFE_RELEASE(m_3d_vision_texture);
m_backbuffer_width = D3D::GetBackBufferWidth();
m_backbuffer_height = D3D::GetBackBufferHeight();
}
UpdateDrawRectangle();
m_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
D3D::context->OMSetRenderTargets(1, &D3D::GetBackBuffer()->GetRTV(), nullptr);
PixelShaderCache::InvalidateMSAAShaders();
UpdateDrawRectangle();
g_framebuffer_manager.reset();
g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
D3D::context->ClearRenderTargetView(FramebufferManager::GetEFBColorTexture()->GetRTV(),
clear_color.data());
D3D::context->ClearDepthStencilView(FramebufferManager::GetEFBDepthTexture()->GetDSV(),
@ -633,6 +595,54 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
FramebufferManager::BindEFBRenderTarget();
}
void Renderer::CheckForSurfaceChange()
{
if (!m_surface_changed.TestAndClear())
return;
m_surface_handle = m_new_surface_handle;
m_new_surface_handle = nullptr;
SAFE_RELEASE(m_screenshot_texture);
SAFE_RELEASE(m_3d_vision_texture);
D3D::Reset(reinterpret_cast<HWND>(m_new_surface_handle));
UpdateBackbufferSize();
}
void Renderer::CheckForSurfaceResize()
{
const bool fullscreen_state = D3D::GetFullscreenState();
const bool exclusive_fullscreen_changed = fullscreen_state != m_last_fullscreen_state;
if (!m_surface_resized.TestAndClear() && !exclusive_fullscreen_changed)
return;
m_backbuffer_width = m_new_backbuffer_width;
m_backbuffer_height = m_new_backbuffer_height;
SAFE_RELEASE(m_screenshot_texture);
SAFE_RELEASE(m_3d_vision_texture);
m_last_fullscreen_state = fullscreen_state;
if (D3D::swapchain)
D3D::ResizeSwapChain();
UpdateBackbufferSize();
}
void Renderer::UpdateBackbufferSize()
{
if (D3D::swapchain)
{
DXGI_SWAP_CHAIN_DESC1 desc = {};
D3D::swapchain->GetDesc1(&desc);
m_backbuffer_width = std::max(desc.Width, 1u);
m_backbuffer_height = std::max(desc.Height, 1u);
}
else
{
m_backbuffer_width = 1;
m_backbuffer_height = 1;
}
}
// ALWAYS call RestoreAPIState for each ResetAPIState call you're doing
void Renderer::ResetAPIState()
{

View File

@ -18,7 +18,7 @@ class D3DTexture2D;
class Renderer : public ::Renderer
{
public:
Renderer();
Renderer(int backbuffer_width, int backbuffer_height);
~Renderer() override;
StateCache& GetStateCache() { return m_state_cache; }
@ -63,8 +63,6 @@ public:
void ReinterpretPixelData(unsigned int convtype) override;
bool CheckForResize();
private:
struct GXPipelineState
{
@ -77,6 +75,9 @@ private:
void SetupDeviceObjects();
void TeardownDeviceObjects();
void Create3DVisionTexture(int width, int height);
void CheckForSurfaceChange();
void CheckForSurfaceResize();
void UpdateBackbufferSize();
void BlitScreen(TargetRectangle src, TargetRectangle dst, D3DTexture2D* src_texture,
u32 src_width, u32 src_height, float Gamma);
@ -95,6 +96,6 @@ private:
u32 m_last_multisamples = 1;
bool m_last_stereo_mode = false;
bool m_last_fullscreen_mode = false;
bool m_last_fullscreen_state = false;
};
}

View File

@ -137,7 +137,17 @@ bool VideoBackend::Initialize(void* window_handle)
return false;
}
g_renderer = std::make_unique<Renderer>();
int backbuffer_width = 1, backbuffer_height = 1;
if (D3D::swapchain)
{
DXGI_SWAP_CHAIN_DESC1 desc = {};
D3D::swapchain->GetDesc1(&desc);
backbuffer_width = std::max(desc.Width, 1u);
backbuffer_height = std::max(desc.Height, 1u);
}
// internal interfaces
g_renderer = std::make_unique<Renderer>(backbuffer_width, backbuffer_height);
g_texture_cache = std::make_unique<TextureCache>();
g_vertex_manager = std::make_unique<VertexManager>();
g_perf_query = std::make_unique<PerfQuery>();

View File

@ -845,12 +845,10 @@ std::unique_ptr<AbstractStagingTexture> Renderer::CreateStagingTexture(StagingTe
void Renderer::RenderText(const std::string& text, int left, int top, u32 color)
{
u32 backbuffer_width = std::max(GLInterface->GetBackBufferWidth(), 1u);
u32 backbuffer_height = std::max(GLInterface->GetBackBufferHeight(), 1u);
s_raster_font->printMultilineText(text, left * 2.0f / static_cast<float>(backbuffer_width) - 1.0f,
1.0f - top * 2.0f / static_cast<float>(backbuffer_height), 0,
backbuffer_width, backbuffer_height, color);
s_raster_font->printMultilineText(text,
left * 2.0f / static_cast<float>(m_backbuffer_width) - 1.0f,
1.0f - top * 2.0f / static_cast<float>(m_backbuffer_height), 0,
m_backbuffer_width, m_backbuffer_height, color);
}
TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
@ -1319,15 +1317,16 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
ResetAPIState();
UpdateDrawRectangle();
TargetRectangle flipped_trc = GetTargetRectangle();
// Flip top and bottom for some reason; TODO: Fix the code to suck less?
std::swap(flipped_trc.top, flipped_trc.bottom);
// Do our OSD callbacks
OSD::DoCallbacks(OSD::CallbackType::OnFrame);
// Check if we need to render to a new surface.
CheckForSurfaceChange();
CheckForSurfaceResize();
UpdateDrawRectangle();
TargetRectangle flipped_trc = GetTargetRectangle();
std::swap(flipped_trc.top, flipped_trc.bottom);
// Skip screen rendering when running in headless mode.
if (!IsHeadless())
{
@ -1357,32 +1356,7 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
glFlush();
}
#ifdef ANDROID
// Handle surface changes on Android.
if (m_surface_needs_change.IsSet())
{
GLInterface->UpdateHandle(m_new_surface_handle);
GLInterface->UpdateSurface();
m_surface_handle = m_new_surface_handle;
m_new_surface_handle = nullptr;
m_surface_needs_change.Clear();
m_surface_changed.Set();
}
#endif
GLInterface->Update();
// Was the size changed since the last frame?
bool window_resized = false;
int window_width = static_cast<int>(std::max(GLInterface->GetBackBufferWidth(), 1u));
int window_height = static_cast<int>(std::max(GLInterface->GetBackBufferHeight(), 1u));
if (window_width != m_backbuffer_width || window_height != m_backbuffer_height)
{
window_resized = true;
m_backbuffer_width = window_width;
m_backbuffer_height = window_height;
}
bool target_size_changed = CalculateTargetSize();
bool stencil_buffer_enabled =
static_cast<FramebufferManager*>(g_framebuffer_manager.get())->HasStencilBuffer();
@ -1392,10 +1366,6 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
stencil_buffer_enabled != BoundingBox::NeedsStencilBuffer() ||
s_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off);
if (window_resized || fb_needs_update)
{
UpdateDrawRectangle();
}
if (fb_needs_update)
{
s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
@ -1415,6 +1385,7 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
g_framebuffer_manager = std::make_unique<FramebufferManager>(
m_target_width, m_target_height, s_MSAASamples, BoundingBox::NeedsStencilBuffer());
BoundingBox::SetTargetSizeChanged(m_target_width, m_target_height);
UpdateDrawRectangle();
}
if (s_vsync != g_ActiveConfig.IsVSync())
@ -1455,6 +1426,30 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
ClearEFBCache();
}
void Renderer::CheckForSurfaceChange()
{
if (!m_surface_changed.TestAndClear())
return;
m_surface_handle = m_new_surface_handle;
m_new_surface_handle = nullptr;
GLInterface->UpdateHandle(m_surface_handle);
GLInterface->UpdateSurface();
// With a surface change, the window likely has new dimensions.
m_backbuffer_width = GLInterface->GetBackBufferWidth();
m_backbuffer_height = GLInterface->GetBackBufferHeight();
}
void Renderer::CheckForSurfaceResize()
{
if (!m_surface_resized.TestAndClear())
return;
m_backbuffer_width = m_new_backbuffer_width;
m_backbuffer_height = m_new_backbuffer_height;
}
void Renderer::DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc,
const TargetRectangle& source_rc)
{
@ -1572,16 +1567,4 @@ void Renderer::SetInterlacingMode()
{
// TODO
}
void Renderer::ChangeSurface(void* new_surface_handle)
{
// Win32 polls the window size when redrawing, X11 runs an event loop in another thread.
// This is only necessary for Android at this point, although handling resizes here
// would be more efficient than polling.
#ifdef ANDROID
m_new_surface_handle = new_surface_handle;
m_surface_needs_change.Set();
m_surface_changed.Wait();
#endif
}
}

View File

@ -121,8 +121,6 @@ public:
void ReinterpretPixelData(unsigned int convtype) override;
void ChangeSurface(void* new_surface_handle) override;
private:
void UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc,
const TargetRectangle& targetPixelRc, const void* data);
@ -133,6 +131,9 @@ private:
void BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture, int src_width,
int src_height);
void CheckForSurfaceChange();
void CheckForSurfaceResize();
std::array<const AbstractTexture*, 8> m_bound_textures{};
};
}

View File

@ -500,6 +500,10 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
StateTracker::GetInstance()->EndRenderPass();
StateTracker::GetInstance()->OnEndFrame();
// Handle host window resizes.
CheckForSurfaceChange();
CheckForSurfaceResize();
// There are a few variables which can alter the final window draw rectangle, and some of them
// are determined by guest state. Currently, the only way to catch these is to update every frame.
UpdateDrawRectangle();
@ -543,9 +547,6 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
// Determine what (if anything) has changed in the config.
CheckForConfigChanges();
// Handle host window resizes.
CheckForSurfaceChange();
// Clean up stale textures.
TextureCache::GetInstance()->Cleanup(frameCount);
@ -650,71 +651,83 @@ void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_r
void Renderer::CheckForSurfaceChange()
{
if (!m_surface_needs_change.IsSet())
if (!m_surface_changed.TestAndClear())
return;
// Wait for the GPU to catch up since we're going to destroy the swap chain.
m_surface_handle = m_new_surface_handle;
m_new_surface_handle = nullptr;
// Submit the current draws up until rendering the XFB.
g_command_buffer_mgr->ExecuteCommandBuffer(false, false);
g_command_buffer_mgr->WaitForGPUIdle();
// Clear the present failed flag, since we don't want to resize after recreating.
g_command_buffer_mgr->CheckLastPresentFail();
// Fast path, if the surface handle is the same, the window has just been resized.
if (m_swap_chain && m_new_surface_handle == m_swap_chain->GetNativeHandle())
// Did we previously have a swap chain?
if (m_swap_chain)
{
INFO_LOG(VIDEO, "Detected window resize.");
m_swap_chain->RecreateSwapChain();
// Notify the main thread we are done.
m_surface_needs_change.Clear();
m_new_surface_handle = nullptr;
m_surface_changed.Set();
}
else
{
// Did we previously have a swap chain?
if (m_swap_chain)
if (!m_surface_handle)
{
if (!m_new_surface_handle)
{
// If there is no surface now, destroy the swap chain.
m_swap_chain.reset();
}
else
{
// Recreate the surface. If this fails we're in trouble.
if (!m_swap_chain->RecreateSurface(m_new_surface_handle))
PanicAlert("Failed to recreate Vulkan surface. Cannot continue.");
}
// If there is no surface now, destroy the swap chain.
m_swap_chain.reset();
}
else
{
// Previously had no swap chain. So create one.
VkSurfaceKHR surface = SwapChain::CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(),
m_new_surface_handle);
if (surface != VK_NULL_HANDLE)
{
m_swap_chain = SwapChain::Create(m_new_surface_handle, surface, g_ActiveConfig.IsVSync());
if (!m_swap_chain)
PanicAlert("Failed to create swap chain.");
}
else
{
PanicAlert("Failed to create surface.");
}
// Recreate the surface. If this fails we're in trouble.
if (!m_swap_chain->RecreateSurface(m_surface_handle))
PanicAlert("Failed to recreate Vulkan surface. Cannot continue.");
}
}
else
{
// Previously had no swap chain. So create one.
VkSurfaceKHR surface =
SwapChain::CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_surface_handle);
if (surface != VK_NULL_HANDLE)
{
m_swap_chain = SwapChain::Create(m_surface_handle, surface, g_ActiveConfig.IsVSync());
if (!m_swap_chain)
PanicAlert("Failed to create swap chain.");
}
else
{
PanicAlert("Failed to create surface.");
}
// Notify calling thread.
m_surface_needs_change.Clear();
m_surface_handle = m_new_surface_handle;
m_new_surface_handle = nullptr;
m_surface_changed.Set();
}
// Handle case where the dimensions are now different.
OnSwapChainResized();
}
void Renderer::CheckForSurfaceResize()
{
if (!m_surface_resized.TestAndClear())
return;
m_backbuffer_width = m_new_backbuffer_width;
m_backbuffer_height = m_new_backbuffer_height;
// If we don't have a surface, how can we resize the swap chain?
// CheckForSurfaceChange should handle this case.
if (!m_swap_chain)
{
WARN_LOG(VIDEO, "Surface resize event received without active surface, ignoring");
return;
}
// Wait for the GPU to catch up since we're going to destroy the swap chain.
g_command_buffer_mgr->ExecuteCommandBuffer(false, false);
g_command_buffer_mgr->WaitForGPUIdle();
// Clear the present failed flag, since we don't want to resize after recreating.
g_command_buffer_mgr->CheckLastPresentFail();
// Resize the swap chain.
m_swap_chain->RecreateSwapChain();
OnSwapChainResized();
}
void Renderer::CheckForConfigChanges()
{
// Save the video config so we can compare against to determine which settings have changed.
@ -782,9 +795,6 @@ void Renderer::OnSwapChainResized()
{
m_backbuffer_width = m_swap_chain->GetWidth();
m_backbuffer_height = m_swap_chain->GetHeight();
UpdateDrawRectangle();
if (CalculateTargetSize())
RecreateEFBFramebuffer();
}
void Renderer::BindEFBToStateTracker()
@ -914,14 +924,6 @@ void Renderer::SetViewport(float x, float y, float width, float height, float ne
StateTracker::GetInstance()->SetViewport(viewport);
}
void Renderer::ChangeSurface(void* new_surface_handle)
{
// Called by the main thread when the window is resized.
m_new_surface_handle = new_surface_handle;
m_surface_needs_change.Set();
m_surface_changed.Set();
}
void Renderer::RecompileShaders()
{
DestroyShaders();

View File

@ -70,8 +70,6 @@ public:
void SetViewport(float x, float y, float width, float height, float near_depth,
float far_depth) override;
void ChangeSurface(void* new_surface_handle) override;
private:
bool CreateSemaphores();
void DestroySemaphores();
@ -79,6 +77,7 @@ private:
void BeginFrame();
void CheckForSurfaceChange();
void CheckForSurfaceResize();
void CheckForConfigChanges();
void ResetSamplerStates();

View File

@ -14,6 +14,7 @@
#include "VideoBackends/Vulkan/CommandBufferManager.h"
#include "VideoBackends/Vulkan/VulkanContext.h"
#include "VideoCommon/RenderBase.h"
#if defined(VK_USE_PLATFORM_XLIB_KHR)
#include <X11/Xlib.h>
@ -261,11 +262,13 @@ bool SwapChain::CreateSwapChain()
VkExtent2D size = surface_capabilities.currentExtent;
if (size.width == UINT32_MAX)
{
size.width = std::min(std::max(surface_capabilities.minImageExtent.width, 640u),
surface_capabilities.maxImageExtent.width);
size.height = std::min(std::max(surface_capabilities.minImageExtent.height, 480u),
surface_capabilities.maxImageExtent.height);
size.width = std::max(g_renderer->GetBackbufferWidth(), 1);
size.height = std::max(g_renderer->GetBackbufferHeight(), 1);
}
size.width = MathUtil::Clamp(size.width, surface_capabilities.minImageExtent.width,
surface_capabilities.maxImageExtent.width);
size.height = MathUtil::Clamp(size.height, surface_capabilities.minImageExtent.height,
surface_capabilities.maxImageExtent.height);
// Prefer identity transform if possible
VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
@ -468,6 +471,22 @@ bool SwapChain::RecreateSurface(void* native_handle)
if (m_surface == VK_NULL_HANDLE)
return false;
// The validation layers get angry at us if we don't call this before creating the swapchain.
VkBool32 present_supported = VK_TRUE;
VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(
g_vulkan_context->GetPhysicalDevice(), g_vulkan_context->GetPresentQueueFamilyIndex(),
m_surface, &present_supported);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
return false;
}
if (!present_supported)
{
PanicAlert("Recreated surface does not support presenting.");
return false;
}
// Finally re-create the swap chain
if (!CreateSwapChain() || !SetupSwapChainImages() || !CreateRenderPass())
return false;

View File

@ -400,6 +400,21 @@ bool Renderer::IsHeadless() const
return !m_surface_handle;
}
void Renderer::ChangeSurface(void* new_surface_handle)
{
std::lock_guard<std::mutex> lock(m_swap_mutex);
m_new_surface_handle = new_surface_handle;
m_surface_changed.Set();
}
void Renderer::ResizeSurface(int new_width, int new_height)
{
std::lock_guard<std::mutex> lock(m_swap_mutex);
m_new_backbuffer_width = new_width;
m_new_backbuffer_height = new_height;
m_surface_resized.Set();
}
std::tuple<float, float> Renderer::ScaleToDisplayAspectRatio(const int width,
const int height) const
{
@ -651,7 +666,10 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
m_last_xfb_region = xfb_rect;
// TODO: merge more generic parts into VideoCommon
g_renderer->SwapImpl(xfb_entry->texture.get(), xfb_rect, ticks, xfb_entry->gamma);
{
std::lock_guard<std::mutex> guard(m_swap_mutex);
g_renderer->SwapImpl(xfb_entry->texture.get(), xfb_rect, ticks, xfb_entry->gamma);
}
// Update the window size based on the frame that was just rendered.
// Due to depending on guest state, we need to call this every frame.

View File

@ -154,7 +154,8 @@ public:
PostProcessingShaderImplementation* GetPostProcessor() const { return m_post_processor.get(); }
// Final surface changing
// This is called when the surface is resized (WX) or the window changes (Android).
virtual void ChangeSurface(void* new_surface_handle) {}
void ChangeSurface(void* new_surface_handle);
void ResizeSurface(int new_width, int new_height);
bool UseVertexDepthRange() const;
virtual void Shutdown();
@ -178,9 +179,11 @@ protected:
int m_target_width = 0;
int m_target_height = 0;
// TODO: Add functionality to reinit all the render targets when the window is resized.
// Backbuffer (window) size and render area
int m_backbuffer_width = 0;
int m_backbuffer_height = 0;
int m_new_backbuffer_width = 0;
int m_new_backbuffer_height = 0;
TargetRectangle m_target_rectangle = {};
FPSCounter m_fps_counter;
@ -189,8 +192,9 @@ protected:
void* m_surface_handle = nullptr;
void* m_new_surface_handle = nullptr;
Common::Flag m_surface_needs_change;
Common::Event m_surface_changed;
Common::Flag m_surface_changed;
Common::Flag m_surface_resized;
std::mutex m_swap_mutex;
u32 m_last_host_config_bits = 0;