d3d: apply aspect ratio correction to the backbuffer (#1956)

* d3d: apply aspect ratio correction to the backbuffer

* d3d: optimise aspect ratio correction + allow run-time aspect change (eg: dashboard)

* d3d: fix typo

* d3d: fix borders on aspect ratio change + apply aspect ratio correction to FMV

* d3d: fix indenting in SetAspectRatioScale

* d3d: add comment to explain clear
This commit is contained in:
Luke Usher 2020-09-02 10:45:04 +01:00 committed by GitHub
parent 79391fc55a
commit ee6a61c364
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 106 additions and 17 deletions

View File

@ -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,