Merge pull request #6330 from stenzek/resizing

Improve handling of surface change/resize events in graphics backends
This commit is contained in:
Stenzek 2018-02-20 01:19:45 +10:00 committed by GitHub
commit 93c502fef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 373 additions and 364 deletions

View File

@ -420,8 +420,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
else if (state == State.PAUSED)
{
Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.UnPauseEmulation();
NativeLibrary.SurfaceChanged(mSurface);
NativeLibrary.UnPauseEmulation();
}
else
{

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
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,33 +1317,36 @@ 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())
{
// Copy the framebuffer to screen.
// Clear the framebuffer before drawing anything.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Copy the framebuffer to screen.
BlitScreen(sourceRc, flipped_trc, xfb_texture->GetRawTexIdentifier(),
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);
// Finish up the current frame, print some stats
// Render OSD messages.
glViewport(0, 0, m_backbuffer_width, m_backbuffer_height);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Reset viewport for drawing text
glViewport(0, 0, GLInterface->GetBackBufferWidth(), GLInterface->GetBackBufferHeight());
DrawDebugText();
OSD::DrawMessages();
// Copy the rendered frame to the real window.
// Swap the back and front buffers, presenting the image.
GLInterface->Swap();
}
else
@ -1355,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();
@ -1390,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;
@ -1413,13 +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);
}
// Clear framebuffer
if (!IsHeadless())
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
UpdateDrawRectangle();
}
if (s_vsync != g_ActiveConfig.IsVSync())
@ -1460,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)
{
@ -1577,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,32 +651,23 @@ 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())
{
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_new_surface_handle)
if (!m_surface_handle)
{
// If there is no surface now, destroy the swap chain.
m_swap_chain.reset();
@ -683,18 +675,18 @@ void Renderer::CheckForSurfaceChange()
else
{
// Recreate the surface. If this fails we're in trouble.
if (!m_swap_chain->RecreateSurface(m_new_surface_handle))
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_new_surface_handle);
VkSurfaceKHR surface =
SwapChain::CreateVulkanSurface(g_vulkan_context->GetVulkanInstance(), m_surface_handle);
if (surface != VK_NULL_HANDLE)
{
m_swap_chain = SwapChain::Create(m_new_surface_handle, surface, g_ActiveConfig.IsVSync());
m_swap_chain = SwapChain::Create(m_surface_handle, surface, g_ActiveConfig.IsVSync());
if (!m_swap_chain)
PanicAlert("Failed to create swap chain.");
}
@ -704,14 +696,35 @@ void Renderer::CheckForSurfaceChange()
}
}
// 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();
}
// Handle case where the dimensions are now different.
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();
}
@ -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
{
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;