Add VSync and fullscreen settings, fix swap chain resize and revert lighting changes

- Add VSync and Exclusive Fullscreen toggles to the graphics settings menu
- Rewrite D3D11 swap chain to use DXGI flip model with tearing support
- Fix black screen on resize by creating new swap chain instead of ResizeBuffers
- Revert conditional lighting optimization in Level::setTileAndData back to unconditional checkLight
- Revert deferred lightGaps flagging in LevelChunk::recalcHeight back to immediate lightGap calls
- Add SWF/ARC editing tools used to add new UI checkboxes
This commit is contained in:
Revela
2026-03-19 11:04:49 -05:00
parent 0a343d2c8d
commit 4fffcac6e7
24 changed files with 753 additions and 53 deletions

View File

@@ -467,6 +467,57 @@ D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0;
ID3D11Device* g_pd3dDevice = nullptr;
ID3D11DeviceContext* g_pImmediateContext = nullptr;
IDXGISwapChain* g_pSwapChain = nullptr;
bool g_bVSync = false;
static bool g_bTearingSupported = false;
static bool g_bPendingExclusiveFullscreen = false;
static bool g_bPendingExclusiveFullscreenValue = false;
// COM proxy for IDXGISwapChain — delegates all calls to the real swap chain,
// but overrides Present() to set SyncInterval=1 when VSync is enabled.
// Avoids vtable patching, which conflicts with the D3D11 debug layer.
static class SwapChainVSyncProxy : public IDXGISwapChain
{
public:
void SetTarget(IDXGISwapChain* pReal) { m_pReal = pReal; }
// IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { return m_pReal->QueryInterface(riid, ppvObject); }
ULONG STDMETHODCALLTYPE AddRef() override { return m_pReal->AddRef(); }
ULONG STDMETHODCALLTYPE Release() override { return m_pReal->Release(); }
// IDXGIObject
HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void* pData) override { return m_pReal->SetPrivateData(Name, DataSize, pData); }
HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown* pUnknown) override { return m_pReal->SetPrivateDataInterface(Name, pUnknown); }
HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT* pDataSize, void* pData) override { return m_pReal->GetPrivateData(Name, pDataSize, pData); }
HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void** ppParent) override { return m_pReal->GetParent(riid, ppParent); }
// IDXGIDeviceSubObject
HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void** ppDevice) override { return m_pReal->GetDevice(riid, ppDevice); }
// IDXGISwapChain
HRESULT STDMETHODCALLTYPE Present(UINT SyncInterval, UINT Flags) override
{
if (g_bVSync)
return m_pReal->Present(1, Flags);
// DXGI_PRESENT_ALLOW_TEARING is only valid in windowed mode
if (g_bTearingSupported)
Flags |= DXGI_PRESENT_ALLOW_TEARING;
return m_pReal->Present(0, Flags);
}
HRESULT STDMETHODCALLTYPE GetBuffer(UINT Buffer, REFIID riid, void** ppSurface) override { return m_pReal->GetBuffer(Buffer, riid, ppSurface); }
HRESULT STDMETHODCALLTYPE SetFullscreenState(BOOL Fullscreen, IDXGIOutput* pTarget) override { return m_pReal->SetFullscreenState(Fullscreen, pTarget); }
HRESULT STDMETHODCALLTYPE GetFullscreenState(BOOL* pFullscreen, IDXGIOutput** ppTarget) override { return m_pReal->GetFullscreenState(pFullscreen, ppTarget); }
HRESULT STDMETHODCALLTYPE GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) override { return m_pReal->GetDesc(pDesc); }
HRESULT STDMETHODCALLTYPE ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) override { return m_pReal->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); }
HRESULT STDMETHODCALLTYPE ResizeTarget(const DXGI_MODE_DESC* pNewTargetParameters) override { return m_pReal->ResizeTarget(pNewTargetParameters); }
HRESULT STDMETHODCALLTYPE GetContainingOutput(IDXGIOutput** ppOutput) override { return m_pReal->GetContainingOutput(ppOutput); }
HRESULT STDMETHODCALLTYPE GetFrameStatistics(DXGI_FRAME_STATISTICS* pStats) override { return m_pReal->GetFrameStatistics(pStats); }
HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT* pLastPresentCount) override { return m_pReal->GetLastPresentCount(pLastPresentCount); }
private:
IDXGISwapChain* m_pReal = nullptr;
} g_swapChainProxy;
ID3D11RenderTargetView* g_pRenderTargetView = nullptr;
ID3D11DepthStencilView* g_pDepthStencilView = nullptr;
ID3D11Texture2D* g_pDepthStencilBuffer = nullptr;
@@ -817,19 +868,33 @@ HRESULT InitDevice()
};
UINT numFeatureLevels = ARRAYSIZE( featureLevels );
// Check tearing support before device/swap chain creation
{
IDXGIFactory5* factory5 = nullptr;
if (SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory5), (void**)&factory5)))
{
BOOL allowTearing = FALSE;
if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing))))
g_bTearingSupported = (allowTearing == TRUE);
factory5->Release();
}
}
DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory( &sd, sizeof( sd ) );
sd.BufferCount = 1;
sd.BufferCount = 2;
sd.BufferDesc.Width = width;
sd.BufferDesc.Height = height;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.BufferDesc.RefreshRate.Numerator = 0;
sd.BufferDesc.RefreshRate.Denominator = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
{
@@ -890,7 +955,8 @@ HRESULT InitDevice()
vp.TopLeftY = 0;
g_pImmediateContext->RSSetViewports( 1, &vp );
RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
g_swapChainProxy.SetTarget(g_pSwapChain);
RenderManager.Initialise(g_pd3dDevice, &g_swapChainProxy);
PostProcesser::GetInstance().Init();
@@ -906,7 +972,7 @@ void Render()
const float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
g_pSwapChain->Present( 0, 0 );
g_pSwapChain->Present(0, g_bTearingSupported ? DXGI_PRESENT_ALLOW_TEARING : 0);
}
//--------------------------------------------------------------------------------------
@@ -981,59 +1047,61 @@ static bool ResizeD3D(int newW, int newH)
gdraw_D3D11_PreReset();
// Get IDXGIFactory from the existing device BEFORE destroying the old swap chain.
// If anything fails before we have a new swap chain, we abort without destroying
// the old one — leaving the Renderer in a valid (old-size) state.
IDXGISwapChain* pOldSwapChain = g_pSwapChain;
bool success = false;
HRESULT hr;
IDXGIDevice* dxgiDevice = NULL;
IDXGIAdapter* dxgiAdapter = NULL;
IDXGIFactory* dxgiFactory = NULL;
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
if (FAILED(hr)) goto postReset;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; }
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
dxgiAdapter->Release();
dxgiDevice->Release();
if (FAILED(hr)) goto postReset;
// Create new swap chain at backbuffer size
// Create a brand-new swap chain instead of ResizeBuffers.
// ResizeBuffers requires ALL backbuffer refs released, but the closed-source
// Renderer holds hidden refs we can't track — causing DXGI_ERROR_INVALID_CALL
// and leaving the Renderer with NULL views (black screen).
// Creating a new swap chain orphans the old backbuffer (tiny leak) but avoids
// the need to release every hidden reference.
{
IDXGIDevice* dxgiDevice = NULL;
IDXGIAdapter* dxgiAdapter = NULL;
IDXGIFactory* dxgiFactory = NULL;
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
if (FAILED(hr)) goto postReset;
hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
dxgiDevice->Release();
if (FAILED(hr)) goto postReset;
hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
dxgiAdapter->Release();
if (FAILED(hr)) goto postReset;
DXGI_SWAP_CHAIN_DESC sd = {};
sd.BufferCount = 1;
sd.BufferCount = 2;
sd.BufferDesc.Width = bbW;
sd.BufferDesc.Height = bbH;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
sd.BufferDesc.RefreshRate.Numerator = 0;
sd.BufferDesc.RefreshRate.Denominator = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = g_hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
IDXGISwapChain* pNewSwapChain = NULL;
hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain);
dxgiFactory->Release();
if (FAILED(hr) || pNewSwapChain == NULL)
if (FAILED(hr))
{
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr);
app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X\n", (unsigned)hr);
goto postReset;
}
// New swap chain created successfully — NOW destroy the old one.
// The Renderer's internal RTV/SRV still reference the old backbuffer —
// those COM objects become orphaned (tiny leak, harmless). We DON'T
// release them because unknown code may also hold refs.
// Destroy old, install new
pOldSwapChain->Release();
g_pSwapChain = pNewSwapChain;
g_swapChainProxy.SetTarget(g_pSwapChain);
}
// Patch Renderer's swap chain pointer
*ppRM_SC = g_pSwapChain;
// Patch Renderer's swap chain pointer (use proxy so VSync override stays active)
*ppRM_SC = &g_swapChainProxy;
// Create render target views from new backbuffer
{
@@ -1218,6 +1286,29 @@ void ToggleFullscreen()
g_KBMInput.SetWindowFocused(true);
}
// Called from UI thread — defers the actual transition to the main game loop
void SetExclusiveFullscreen(bool enabled)
{
if (enabled == g_isFullscreen)
return;
g_bPendingExclusiveFullscreen = true;
g_bPendingExclusiveFullscreenValue = enabled;
}
// Uses borderless fullscreen (ToggleFullscreen) rather than DXGI SetFullscreenState.
// With DXGI_SWAP_EFFECT_FLIP_DISCARD + DXGI_PRESENT_ALLOW_TEARING, borderless
// fullscreen gets the same direct-flip path as exclusive fullscreen on Windows 10+ —
// identical latency and uncapped FPS. True DXGI exclusive fullscreen is blocked by
// the 4J Renderer holding hidden backbuffer references that prevent ResizeBuffers.
static void ApplyExclusiveFullscreen(bool enabled)
{
// Toggle into/out of borderless fullscreen if state doesn't match
if (enabled && !g_isFullscreen)
ToggleFullscreen();
else if (!enabled && g_isFullscreen)
ToggleFullscreen();
}
//--------------------------------------------------------------------------------------
// Clean up the objects we've created
//--------------------------------------------------------------------------------------
@@ -1801,6 +1892,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
ToggleFullscreen();
}
// Apply deferred exclusive fullscreen toggle
if (g_bPendingExclusiveFullscreen)
{
g_bPendingExclusiveFullscreen = false;
ApplyExclusiveFullscreen(g_bPendingExclusiveFullscreenValue);
}
// TAB opens game info menu. - Vvis :3 - Updated by detectiveren
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_HOST_SETTINGS) && !ui.GetMenuDisplayed(0))
{