diff --git a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp index 961bca1d1..144b2325a 100644 --- a/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp +++ b/src/core/hle/D3D8/Direct3D9/Direct3D9.cpp @@ -108,7 +108,10 @@ static bool g_bHack_UnlockFramerate = false; // ignore t static bool g_bHasDepth = false; // Does device have a Depth Buffer? static bool g_bHasStencil = false; // Does device have a Stencil Buffer? static DWORD g_dwPrimPerFrame = 0; // Number of primitives within one frame - +static float g_AspectRatioScale = 1.0f; +static UINT g_AspectRatioScaleWidth = 0; +static UINT g_AspectRatioScaleHeight = 0; +static D3DSURFACE_DESC g_HostBackBufferDesc; static Settings::s_video g_XBVideo; // D3D based variables @@ -2826,6 +2829,48 @@ ConvertedIndexBuffer& CxbxUpdateActiveIndexBuffer return CacheEntry; } +void UpdateHostBackBufferDesc() +{ + IDirect3DSurface *pCurrentHostBackBuffer = nullptr; + auto hRet = g_pD3DDevice->GetBackBuffer( + 0, // iSwapChain + 0, D3DBACKBUFFER_TYPE_MONO, &pCurrentHostBackBuffer); + + if (hRet != D3D_OK) { + CxbxKrnlCleanup("Unable to get host backbuffer surface"); + } + + hRet = pCurrentHostBackBuffer->GetDesc(&g_HostBackBufferDesc); + if (hRet != D3D_OK) { + pCurrentHostBackBuffer->Release(); + CxbxKrnlCleanup("Unable to determine host backbuffer dimensions"); + } + + pCurrentHostBackBuffer->Release(); +} + +void SetAspectRatioScale(xbox::X_D3DPRESENT_PARAMETERS* pPresentationParameters) +{ + // NOTE: Some games use anamorphic widesceen (expecting a 4:3 surface to be displayed at 16:9) + // For those, we *lie* about the default width, for the scaler + // 720p / 1080i are *always* widescreen, and will have the correct backbuffer size, so we only + // apply this 'hack' for non-hd resolutions + g_AspectRatioScaleWidth = pPresentationParameters->BackBufferWidth; + g_AspectRatioScaleHeight = pPresentationParameters->BackBufferHeight; + + if (pPresentationParameters->Flags & X_D3DPRESENTFLAG_WIDESCREEN && + pPresentationParameters->BackBufferHeight < 720) { + // Lie and pretend we are 1280x720, this works because this ratio is only used in calculations + // and not used as actual raw input values + g_AspectRatioScaleWidth = 1280; + g_AspectRatioScaleHeight = 720; + } + + const auto imageAspect = (float)g_AspectRatioScaleWidth / (float)g_AspectRatioScaleHeight; + const auto screenAspect = (float)g_HostBackBufferDesc.Width / (float)g_HostBackBufferDesc.Height; + g_AspectRatioScale = screenAspect > imageAspect ? (float)g_HostBackBufferDesc.Height / (float)g_AspectRatioScaleHeight : (float)g_HostBackBufferDesc.Width / (float)g_AspectRatioScaleWidth; +} + #define CXBX_D3DMULTISAMPLE_XSCALE(type) (((type) & xbox::X_D3DMULTISAMPLE_XSCALE_MASK) >> xbox::X_D3DMULTISAMPLE_XSCALE_SHIFT) #define CXBX_D3DMULTISAMPLE_YSCALE(type) (((type) & xbox::X_D3DMULTISAMPLE_YSCALE_MASK) >> xbox::X_D3DMULTISAMPLE_YSCALE_SHIFT) @@ -2953,6 +2998,10 @@ void Direct3D_CreateDevice_End() g_Xbox_D3DDevice = (DWORD*)it->second; } #endif + + UpdateHostBackBufferDesc(); + SetAspectRatioScale(&g_EmuCDPD.XboxPresentationParameters); + // If the Xbox version of CreateDevice didn't call SetRenderTarget, we must derive the default backbuffer ourselves // This works because CreateDevice always sets the current render target to the Xbox Backbuffer // In later XDKs, it does this inline rather than by calling D3DDevice_SetRenderTarget @@ -3166,6 +3215,9 @@ HRESULT WINAPI xbox::EMUPATCH(D3DDevice_Reset) // Store the new multisampling configuration SetXboxMultiSampleType(pPresentationParameters->MultiSampleType); + // Update scaling aspect ratio + SetAspectRatioScale(pPresentationParameters); + // Since Reset will call create a new backbuffer surface, we can clear our current association // NOTE: We don't actually free the Xbox data, the Xbox side will do this for us when we call the trampoline below. // We must not reset the values to nullptr, since the XDK will re-use the same addresses for the data headers @@ -4990,14 +5042,27 @@ DWORD WINAPI xbox::EMUPATCH(D3DDevice_Swap) HRESULT hRet = g_pD3DDevice->GetBackBuffer( 0, // iSwapChain 0, D3DBACKBUFFER_TYPE_MONO, &pCurrentHostBackBuffer); + DEBUG_D3DRESULT(hRet, "g_pD3DDevice->GetBackBuffer - Unable to get backbuffer surface!"); if (hRet == D3D_OK) { assert(pCurrentHostBackBuffer != nullptr); - // Get backbuffer dimensions; TODO : remember this once, at creation/resize time - D3DSURFACE_DESC BackBufferDesc; - pCurrentHostBackBuffer->GetDesc(&BackBufferDesc); - + // Clear the backbuffer surface, this prevents artifacts when switching aspect-ratio + // Test-case: Dashboard + IDirect3DSurface* pExistingRenderTarget = nullptr; + hRet = g_pD3DDevice->GetRenderTarget(0, &pExistingRenderTarget); + if (hRet == D3D_OK) { + g_pD3DDevice->SetRenderTarget(0, pCurrentHostBackBuffer); + g_pD3DDevice->Clear( + /*Count=*/0, + /*pRects=*/nullptr, + D3DCLEAR_TARGET | (g_bHasDepth ? D3DCLEAR_ZBUFFER : 0) | (g_bHasStencil ? D3DCLEAR_STENCIL : 0), + /*Color=*/0xFF000000, // TODO : Use constant for this + /*Z=*/g_bHasDepth ? 1.0f : 0.0f, + /*Stencil=*/0); + g_pD3DDevice->SetRenderTarget(0, pExistingRenderTarget); + } + // TODO: Implement a hot-key to change the filter? // Note: LoadSurfaceFilter Must be D3DTEXF_NONE, D3DTEXF_POINT or D3DTEXF_LINEAR // Before StretchRects we used D3DX_FILTER_POINT here, but that gave jagged edges in Dashboard. @@ -5008,13 +5073,23 @@ DWORD WINAPI xbox::EMUPATCH(D3DDevice_Swap) auto pXboxBackBufferHostSurface = GetHostSurface(g_pXbox_BackBufferSurface, D3DUSAGE_RENDERTARGET); if (pXboxBackBufferHostSurface) { - // Blit Xbox BackBuffer to host BackBuffer - // TODO: Respect aspect ratio + // Calculate the target width/height + const auto width = g_AspectRatioScaleWidth * g_AspectRatioScale; + const auto height = g_AspectRatioScaleHeight * g_AspectRatioScale; + + // Calculate the centered rectangle + RECT dest{}; + dest.top = (LONG)((g_HostBackBufferDesc.Height - height) / 2); + dest.left = (LONG)((g_HostBackBufferDesc.Width - width) / 2); + dest.right = (LONG)(dest.left + width); + dest.bottom = (LONG)(dest.top + height); + + // Blit Xbox BackBuffer to host BackBuffer hRet = g_pD3DDevice->StretchRect( /* pSourceSurface = */ pXboxBackBufferHostSurface, /* pSourceRect = */ nullptr, /* pDestSurface = */ pCurrentHostBackBuffer, - /* pDestRect = */ nullptr, + /* pDestRect = */ &dest, /* Filter = */ LoadSurfaceFilter ); @@ -5083,17 +5158,32 @@ DWORD WINAPI xbox::EMUPATCH(D3DDevice_Swap) float xScale, yScale; GetMultiSampleScale(xScale, yScale); - xScale = (float)BackBufferDesc.Width / ((float)XboxBackBufferWidth / xScale); - yScale = (float)BackBufferDesc.Height / ((float)XboxBackBufferHeight / yScale); + const auto width = g_AspectRatioScaleWidth * g_AspectRatioScale; + const auto height = g_AspectRatioScaleHeight * g_AspectRatioScale; + xScale = (float)width / ((float)XboxBackBufferWidth / xScale); + yScale = (float)height / ((float)XboxBackBufferHeight / yScale); + // Scale the destination co-ordinates by the correct scale factor EmuDestRect.top = (LONG)(EmuDestRect.top * yScale); EmuDestRect.left = (LONG)(EmuDestRect.left * xScale); EmuDestRect.bottom = (LONG)(EmuDestRect.bottom * yScale); EmuDestRect.right = (LONG)(EmuDestRect.right * xScale); + + // Finally, adjust to correct on-screen position ( + EmuDestRect.top += (LONG)((g_HostBackBufferDesc.Height - height) / 2); + EmuDestRect.left += (LONG)((g_HostBackBufferDesc.Width - width) / 2); + EmuDestRect.right += (LONG)((g_HostBackBufferDesc.Width - width) / 2); + EmuDestRect.bottom += (LONG)((g_HostBackBufferDesc.Height - height) / 2); } else { // Use backbuffer width/height since that may differ from the Window size - EmuDestRect.right = BackBufferDesc.Width; - EmuDestRect.bottom = BackBufferDesc.Height; + const auto width = g_AspectRatioScaleWidth * g_AspectRatioScale; + const auto height = g_AspectRatioScaleHeight * g_AspectRatioScale; + + // Calculate the centered rectangle + EmuDestRect.top = (LONG)((g_HostBackBufferDesc.Height - height) / 2); + EmuDestRect.left = (LONG)((g_HostBackBufferDesc.Width - width) / 2); + EmuDestRect.right = (LONG)(EmuDestRect.left + width); + EmuDestRect.bottom = (LONG)(EmuDestRect.top + height); } // load the YUY2 into the backbuffer @@ -5103,12 +5193,12 @@ DWORD WINAPI xbox::EMUPATCH(D3DDevice_Swap) // (see https://github.com/Cxbx-Reloaded/Cxbx-Reloaded/issues/285) { // Use our (bounded) copy when bounds exceed : - if (EmuDestRect.right > (LONG)BackBufferDesc.Width) { - EmuDestRect.right = (LONG)BackBufferDesc.Width; + if (EmuDestRect.right > (LONG)g_HostBackBufferDesc.Width) { + EmuDestRect.right = (LONG)g_HostBackBufferDesc.Width; } - if (EmuDestRect.bottom > (LONG)BackBufferDesc.Height) { - EmuDestRect.bottom = (LONG)BackBufferDesc.Height; + if (EmuDestRect.bottom > (LONG)g_HostBackBufferDesc.Height) { + EmuDestRect.bottom = (LONG)g_HostBackBufferDesc.Height; } } @@ -5149,7 +5239,6 @@ DWORD WINAPI xbox::EMUPATCH(D3DDevice_Swap) if (hRet != D3D_OK) { EmuLog(LOG_LEVEL::WARNING, "Couldn't load Xbox overlay to host surface : %X", hRet); } else { - // TODO: Respect aspect ratio hRet = g_pD3DDevice->StretchRect( /* pSourceSurface = */ pTemporaryOverlaySurface, /* pSourceRect = */ &EmuSourRect,