1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2024-12-13 07:08:50 +01:00
dxvk/src/d3d9/d3d9_swapchain.cpp
2021-08-23 15:41:59 +01:00

1373 lines
44 KiB
C++

#include "d3d9_swapchain.h"
#include "d3d9_surface.h"
#include "d3d9_monitor.h"
#include "d3d9_hud.h"
namespace dxvk {
struct D3D9WindowData {
bool unicode;
bool filter;
WNDPROC proc;
D3D9SwapChainEx* swapchain;
};
static dxvk::recursive_mutex g_windowProcMapMutex;
static std::unordered_map<HWND, D3D9WindowData> g_windowProcMap;
template <typename T, typename J, typename ... Args>
auto CallCharsetFunction(T unicode, J ascii, bool isUnicode, Args... args) {
return isUnicode
? unicode(args...)
: ascii (args...);
}
class D3D9WindowMessageFilter {
public:
D3D9WindowMessageFilter(HWND window, bool filter = true)
: m_window(window) {
std::lock_guard lock(g_windowProcMapMutex);
auto it = g_windowProcMap.find(m_window);
m_filter = std::exchange(it->second.filter, filter);
}
~D3D9WindowMessageFilter() {
std::lock_guard lock(g_windowProcMapMutex);
auto it = g_windowProcMap.find(m_window);
it->second.filter = m_filter;
}
D3D9WindowMessageFilter (const D3D9WindowMessageFilter&) = delete;
D3D9WindowMessageFilter& operator = (const D3D9WindowMessageFilter&) = delete;
private:
HWND m_window;
bool m_filter;
};
LRESULT CALLBACK D3D9WindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam);
void ResetWindowProc(HWND window) {
std::lock_guard lock(g_windowProcMapMutex);
auto it = g_windowProcMap.find(window);
if (it == g_windowProcMap.end())
return;
auto proc = reinterpret_cast<WNDPROC>(
CallCharsetFunction(
GetWindowLongPtrW, GetWindowLongPtrA, it->second.unicode,
window, GWLP_WNDPROC));
if (proc == D3D9WindowProc)
CallCharsetFunction(
SetWindowLongPtrW, SetWindowLongPtrA, it->second.unicode,
window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(it->second.proc));
g_windowProcMap.erase(window);
}
void HookWindowProc(HWND window, D3D9SwapChainEx* swapchain) {
std::lock_guard lock(g_windowProcMapMutex);
ResetWindowProc(window);
D3D9WindowData windowData;
windowData.unicode = IsWindowUnicode(window);
windowData.filter = false;
windowData.proc = reinterpret_cast<WNDPROC>(
CallCharsetFunction(
SetWindowLongPtrW, SetWindowLongPtrA, windowData.unicode,
window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(D3D9WindowProc)));
windowData.swapchain = swapchain;
g_windowProcMap[window] = std::move(windowData);
}
LRESULT CALLBACK D3D9WindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
if (message == WM_NCCALCSIZE && wparam == TRUE)
return 0;
D3D9WindowData windowData = {};
{
std::lock_guard lock(g_windowProcMapMutex);
auto it = g_windowProcMap.find(window);
if (it != g_windowProcMap.end())
windowData = it->second;
}
bool unicode = windowData.proc
? windowData.unicode
: IsWindowUnicode(window);
if (!windowData.proc || windowData.filter)
return CallCharsetFunction(
DefWindowProcW, DefWindowProcA, unicode,
window, message, wparam, lparam);
if (message == WM_DESTROY)
ResetWindowProc(window);
else if (message == WM_ACTIVATEAPP) {
D3DDEVICE_CREATION_PARAMETERS create_parms;
windowData.swapchain->GetDevice()->GetCreationParameters(&create_parms);
if (!(create_parms.BehaviorFlags & D3DCREATE_NOWINDOWCHANGES)) {
if (wparam) {
// Heroes of Might and Magic V needs this to resume drawing after a focus loss
D3DPRESENT_PARAMETERS params;
RECT rect;
GetMonitorRect(GetDefaultMonitor(), &rect);
windowData.swapchain->GetPresentParameters(&params);
SetWindowPos(window, nullptr, rect.left, rect.top, params.BackBufferWidth, params.BackBufferHeight,
SWP_NOACTIVATE | SWP_NOZORDER);
}
else {
if (IsWindowVisible(window))
ShowWindow(window, SW_MINIMIZE);
}
}
}
return CallCharsetFunction(
CallWindowProcW, CallWindowProcA, unicode,
windowData.proc, window, message, wparam, lparam);
}
static uint16_t MapGammaControlPoint(float x) {
if (x < 0.0f) x = 0.0f;
if (x > 1.0f) x = 1.0f;
return uint16_t(65535.0f * x);
}
struct D3D9PresentInfo {
float scale[2];
float offset[2];
};
D3D9SwapChainEx::D3D9SwapChainEx(
D3D9DeviceEx* pDevice,
D3DPRESENT_PARAMETERS* pPresentParams,
const D3DDISPLAYMODEEX* pFullscreenDisplayMode)
: D3D9SwapChainExBase(pDevice)
, m_device (pDevice->GetDXVKDevice())
, m_context (m_device->createContext())
, m_frameLatencyCap (pDevice->GetOptions()->maxFrameLatency)
, m_frameLatencySignal(new sync::Fence(m_frameId))
, m_dialog (pDevice->GetOptions()->enableDialogMode) {
this->NormalizePresentParameters(pPresentParams);
m_presentParams = *pPresentParams;
m_window = m_presentParams.hDeviceWindow;
UpdatePresentRegion(nullptr, nullptr);
if (!pDevice->GetOptions()->deferSurfaceCreation)
CreatePresenter();
CreateBackBuffers(m_presentParams.BackBufferCount);
CreateBlitter();
CreateHud();
InitRamp();
// Apply initial window mode and fullscreen state
if (!m_presentParams.Windowed && FAILED(EnterFullscreenMode(pPresentParams, pFullscreenDisplayMode)))
throw DxvkError("D3D9: Failed to set initial fullscreen state");
}
D3D9SwapChainEx::~D3D9SwapChainEx() {
DestroyBackBuffers();
ResetWindowProc(m_window);
RestoreDisplayMode(m_monitor);
m_device->waitForSubmission(&m_presentStatus);
m_device->waitForIdle();
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::QueryInterface(REFIID riid, void** ppvObject) {
if (ppvObject == nullptr)
return E_POINTER;
*ppvObject = nullptr;
if (riid == __uuidof(IUnknown)
|| riid == __uuidof(IDirect3DSwapChain9)
|| (GetParent()->IsExtended() && riid == __uuidof(IDirect3DSwapChain9Ex))) {
*ppvObject = ref(this);
return S_OK;
}
Logger::warn("D3D9SwapChainEx::QueryInterface: Unknown interface query");
Logger::warn(str::format(riid));
return E_NOINTERFACE;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::Present(
const RECT* pSourceRect,
const RECT* pDestRect,
HWND hDestWindowOverride,
const RGNDATA* pDirtyRegion,
DWORD dwFlags) {
D3D9DeviceLock lock = m_parent->LockDevice();
uint32_t presentInterval = m_presentParams.PresentationInterval;
// This is not true directly in d3d9 to to timing differences that don't matter for us.
// For our purposes...
// D3DPRESENT_INTERVAL_DEFAULT (0) == D3DPRESENT_INTERVAL_ONE (1) which means VSYNC.
presentInterval = std::max(presentInterval, 1u);
if (presentInterval == D3DPRESENT_INTERVAL_IMMEDIATE || (dwFlags & D3DPRESENT_FORCEIMMEDIATE))
presentInterval = 0;
auto options = m_parent->GetOptions();
if (options->presentInterval >= 0)
presentInterval = options->presentInterval;
bool vsync = presentInterval != 0;
HWND window = m_presentParams.hDeviceWindow;
if (hDestWindowOverride != nullptr)
window = hDestWindowOverride;
bool recreate = false;
recreate |= m_presenter == nullptr;
recreate |= window != m_window;
recreate |= m_dialog != m_lastDialog;
m_window = window;
m_dirty |= vsync != m_vsync;
m_dirty |= UpdatePresentRegion(pSourceRect, pDestRect);
m_dirty |= recreate;
m_dirty |= m_presenter != nullptr &&
!m_presenter->hasSwapChain();
m_vsync = vsync;
m_lastDialog = m_dialog;
try {
if (recreate)
CreatePresenter();
if (std::exchange(m_dirty, false))
RecreateSwapChain(vsync);
// We aren't going to device loss simply because
// 99% of D3D9 games don't handle this properly and
// just end up crashing (like with alt-tab loss)
if (!m_presenter->hasSwapChain())
return D3D_OK;
PresentImage(presentInterval);
return D3D_OK;
} catch (const DxvkError& e) {
Logger::err(e.message());
return D3DERR_DEVICEREMOVED;
}
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetFrontBufferData(IDirect3DSurface9* pDestSurface) {
D3D9DeviceLock lock = m_parent->LockDevice();
// This function can do absolutely everything!
// Copies the front buffer between formats with an implicit resolve.
// Oh, and the dest is systemmem...
// This is a slow function anyway, it waits for the copy to finish.
// so there's no reason to not just make and throwaway temp images.
// If extent of dst > src, then we blit to a subrect of the size
// of src onto a temp image of dst's extents,
// then copy buffer back to dst (given dst is subresource)
D3D9Surface* dst = static_cast<D3D9Surface*>(pDestSurface);
if (unlikely(dst == nullptr))
return D3DERR_INVALIDCALL;
D3D9CommonTexture* dstTexInfo = dst->GetCommonTexture();
D3D9CommonTexture* srcTexInfo = m_backBuffers.back()->GetCommonTexture();
if (unlikely(dstTexInfo->Desc()->Pool != D3DPOOL_SYSTEMMEM))
return D3DERR_INVALIDCALL;
Rc<DxvkBuffer> dstBuffer = dstTexInfo->GetBuffer(dst->GetSubresource());
Rc<DxvkImage> srcImage = srcTexInfo->GetImage();
if (srcImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT) {
DxvkImageCreateInfo resolveInfo;
resolveInfo.type = VK_IMAGE_TYPE_2D;
resolveInfo.format = srcImage->info().format;
resolveInfo.flags = 0;
resolveInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
resolveInfo.extent = srcImage->info().extent;
resolveInfo.numLayers = 1;
resolveInfo.mipLevels = 1;
resolveInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT;
resolveInfo.stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
| VK_PIPELINE_STAGE_TRANSFER_BIT;
resolveInfo.access = VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_TRANSFER_WRITE_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
resolveInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
resolveInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
Rc<DxvkImage> resolvedSrc = m_device->createImage(
resolveInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
m_parent->EmitCs([
cDstImage = resolvedSrc,
cSrcImage = srcImage
] (DxvkContext* ctx) {
VkImageSubresourceLayers resolveSubresource;
resolveSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
resolveSubresource.mipLevel = 0;
resolveSubresource.baseArrayLayer = 0;
resolveSubresource.layerCount = 1;
VkImageResolve resolveRegion;
resolveRegion.srcSubresource = resolveSubresource;
resolveRegion.srcOffset = VkOffset3D { 0, 0, 0 };
resolveRegion.dstSubresource = resolveSubresource;
resolveRegion.dstOffset = VkOffset3D { 0, 0, 0 };
resolveRegion.extent = cSrcImage->info().extent;
ctx->resolveImage(
cDstImage, cSrcImage,
resolveRegion, VK_FORMAT_UNDEFINED);
});
srcImage = std::move(resolvedSrc);
}
D3D9Format srcFormat = srcTexInfo->Desc()->Format;
D3D9Format dstFormat = dstTexInfo->Desc()->Format;
bool similar = AreFormatsSimilar(srcFormat, dstFormat);
if (!similar || srcImage->info().extent != dstTexInfo->GetExtent()) {
DxvkImageCreateInfo blitCreateInfo;
blitCreateInfo.type = VK_IMAGE_TYPE_2D;
blitCreateInfo.format = dstTexInfo->GetFormatMapping().FormatColor;
blitCreateInfo.flags = 0;
blitCreateInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
blitCreateInfo.extent = dstTexInfo->GetExtent();
blitCreateInfo.numLayers = 1;
blitCreateInfo.mipLevels = 1;
blitCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_TRANSFER_DST_BIT;
blitCreateInfo.stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
| VK_PIPELINE_STAGE_TRANSFER_BIT;
blitCreateInfo.access = VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_TRANSFER_WRITE_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
blitCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
blitCreateInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
Rc<DxvkImage> blittedSrc = m_device->createImage(
blitCreateInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
const DxvkFormatInfo* dstFormatInfo = imageFormatInfo(blittedSrc->info().format);
const DxvkFormatInfo* srcFormatInfo = imageFormatInfo(srcImage->info().format);
const VkImageSubresource dstSubresource = dstTexInfo->GetSubresourceFromIndex(dstFormatInfo->aspectMask, 0);
const VkImageSubresource srcSubresource = srcTexInfo->GetSubresourceFromIndex(srcFormatInfo->aspectMask, 0);
VkImageSubresourceLayers dstSubresourceLayers = {
dstSubresource.aspectMask,
dstSubresource.mipLevel,
dstSubresource.arrayLayer, 1 };
VkImageSubresourceLayers srcSubresourceLayers = {
srcSubresource.aspectMask,
srcSubresource.mipLevel,
srcSubresource.arrayLayer, 1 };
VkExtent3D srcExtent = srcImage->mipLevelExtent(srcSubresource.mipLevel);
// Blit to a subrect of the src extents
VkImageBlit blitInfo;
blitInfo.dstSubresource = dstSubresourceLayers;
blitInfo.srcSubresource = srcSubresourceLayers;
blitInfo.dstOffsets[0] = VkOffset3D{ 0, 0, 0 };
blitInfo.dstOffsets[1] = VkOffset3D{ int32_t(srcExtent.width), int32_t(srcExtent.height), 1 };
blitInfo.srcOffsets[0] = VkOffset3D{ 0, 0, 0 };
blitInfo.srcOffsets[1] = VkOffset3D{ int32_t(srcExtent.width), int32_t(srcExtent.height), 1 };
m_parent->EmitCs([
cDstImage = blittedSrc,
cDstMap = dstTexInfo->GetMapping().Swizzle,
cSrcImage = srcImage,
cSrcMap = srcTexInfo->GetMapping().Swizzle,
cBlitInfo = blitInfo
] (DxvkContext* ctx) {
ctx->blitImage(
cDstImage, cDstMap,
cSrcImage, cSrcMap,
cBlitInfo, VK_FILTER_NEAREST);
});
srcImage = std::move(blittedSrc);
}
const DxvkFormatInfo* srcFormatInfo = imageFormatInfo(srcImage->info().format);
const VkImageSubresource srcSubresource = srcTexInfo->GetSubresourceFromIndex(srcFormatInfo->aspectMask, 0);
VkImageSubresourceLayers srcSubresourceLayers = {
srcSubresource.aspectMask,
srcSubresource.mipLevel,
srcSubresource.arrayLayer, 1 };
VkExtent3D srcExtent = srcImage->mipLevelExtent(srcSubresource.mipLevel);
m_parent->EmitCs([
cBuffer = dstBuffer,
cImage = srcImage,
cSubresources = srcSubresourceLayers,
cLevelExtent = srcExtent
] (DxvkContext* ctx) {
ctx->copyImageToBuffer(cBuffer, 0, 4, 0,
cImage, cSubresources, VkOffset3D { 0, 0, 0 },
cLevelExtent);
});
dstTexInfo->SetWrittenByGPU(dst->GetSubresource(), true);
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetBackBuffer(
UINT iBackBuffer,
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface9** ppBackBuffer) {
// Could be doing a device reset...
D3D9DeviceLock lock = m_parent->LockDevice();
if (unlikely(ppBackBuffer == nullptr))
return D3DERR_INVALIDCALL;
if (unlikely(iBackBuffer >= m_presentParams.BackBufferCount)) {
Logger::err(str::format("D3D9: GetBackBuffer: Invalid back buffer index: ", iBackBuffer));
return D3DERR_INVALIDCALL;
}
*ppBackBuffer = ref(m_backBuffers[iBackBuffer].ptr());
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetRasterStatus(D3DRASTER_STATUS* pRasterStatus) {
// We could use D3DKMTGetScanLine but Wine doesn't implement that.
// So... we lie here and make some stuff up
// enough that it makes games work.
// Assume there's 20 lines in a vBlank.
constexpr uint32_t vBlankLineCount = 20;
if (pRasterStatus == nullptr)
return D3DERR_INVALIDCALL;
D3DDISPLAYMODEEX mode;
mode.Size = sizeof(mode);
if (FAILED(this->GetDisplayModeEx(&mode, nullptr)))
return D3DERR_INVALIDCALL;
uint32_t scanLineCount = mode.Height + vBlankLineCount;
auto nowUs = std::chrono::time_point_cast<std::chrono::microseconds>(
dxvk::high_resolution_clock::now())
.time_since_epoch();
auto frametimeUs = std::chrono::microseconds(1000000u / mode.RefreshRate);
auto scanLineUs = frametimeUs / scanLineCount;
pRasterStatus->ScanLine = (nowUs % frametimeUs) / scanLineUs;
pRasterStatus->InVBlank = pRasterStatus->ScanLine >= mode.Height;
if (pRasterStatus->InVBlank)
pRasterStatus->ScanLine = 0;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetDisplayMode(D3DDISPLAYMODE* pMode) {
if (pMode == nullptr)
return D3DERR_INVALIDCALL;
*pMode = D3DDISPLAYMODE();
D3DDISPLAYMODEEX mode;
mode.Size = sizeof(mode);
HRESULT hr = this->GetDisplayModeEx(&mode, nullptr);
if (FAILED(hr))
return hr;
pMode->Width = mode.Width;
pMode->Height = mode.Height;
pMode->Format = mode.Format;
pMode->RefreshRate = mode.RefreshRate;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetPresentParameters(D3DPRESENT_PARAMETERS* pPresentationParameters) {
if (pPresentationParameters == nullptr)
return D3DERR_INVALIDCALL;
*pPresentationParameters = m_presentParams;
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetLastPresentCount(UINT* pLastPresentCount) {
Logger::warn("D3D9SwapChainEx::GetLastPresentCount: Stub");
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetPresentStats(D3DPRESENTSTATS* pPresentationStatistics) {
Logger::warn("D3D9SwapChainEx::GetPresentStats: Stub");
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetDisplayModeEx(D3DDISPLAYMODEEX* pMode, D3DDISPLAYROTATION* pRotation) {
if (pMode == nullptr && pRotation == nullptr)
return D3DERR_INVALIDCALL;
if (pRotation != nullptr)
*pRotation = D3DDISPLAYROTATION_IDENTITY;
if (pMode != nullptr) {
DEVMODEW devMode = DEVMODEW();
devMode.dmSize = sizeof(devMode);
if (!GetMonitorDisplayMode(GetDefaultMonitor(), ENUM_CURRENT_SETTINGS, &devMode)) {
Logger::err("D3D9SwapChainEx::GetDisplayModeEx: Failed to enum display settings");
return D3DERR_INVALIDCALL;
}
pMode->Size = sizeof(D3DDISPLAYMODEEX);
pMode->Width = devMode.dmPelsWidth;
pMode->Height = devMode.dmPelsHeight;
pMode->RefreshRate = devMode.dmDisplayFrequency;
pMode->Format = D3DFMT_X8R8G8B8;
pMode->ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
}
return D3D_OK;
}
HRESULT D3D9SwapChainEx::Reset(
D3DPRESENT_PARAMETERS* pPresentParams,
D3DDISPLAYMODEEX* pFullscreenDisplayMode) {
D3D9DeviceLock lock = m_parent->LockDevice();
this->SynchronizePresent();
this->NormalizePresentParameters(pPresentParams);
m_dirty |= m_presentParams.BackBufferFormat != pPresentParams->BackBufferFormat
|| m_presentParams.BackBufferCount != pPresentParams->BackBufferCount;
bool changeFullscreen = m_presentParams.Windowed != pPresentParams->Windowed;
if (pPresentParams->Windowed) {
if (changeFullscreen)
this->LeaveFullscreenMode();
// Adjust window position and size
RECT newRect = { 0, 0, 0, 0 };
RECT oldRect = { 0, 0, 0, 0 };
::GetWindowRect(m_window, &oldRect);
::MapWindowPoints(HWND_DESKTOP, ::GetParent(m_window), reinterpret_cast<POINT*>(&oldRect), 1);
::SetRect(&newRect, 0, 0, pPresentParams->BackBufferWidth, pPresentParams->BackBufferHeight);
::AdjustWindowRectEx(&newRect,
::GetWindowLongW(m_window, GWL_STYLE), FALSE,
::GetWindowLongW(m_window, GWL_EXSTYLE));
::SetRect(&newRect, 0, 0, newRect.right - newRect.left, newRect.bottom - newRect.top);
::OffsetRect(&newRect, oldRect.left, oldRect.top);
::MoveWindow(m_window, newRect.left, newRect.top,
newRect.right - newRect.left, newRect.bottom - newRect.top, TRUE);
}
else {
if (changeFullscreen) {
if (FAILED(this->EnterFullscreenMode(pPresentParams, pFullscreenDisplayMode)))
return D3DERR_INVALIDCALL;
}
D3D9WindowMessageFilter filter(m_window);
if (!changeFullscreen) {
if (FAILED(ChangeDisplayMode(pPresentParams, pFullscreenDisplayMode)))
return D3DERR_INVALIDCALL;
}
// Move the window so that it covers the entire output
RECT rect;
GetMonitorRect(GetDefaultMonitor(), &rect);
::SetWindowPos(m_window, HWND_TOPMOST,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOACTIVATE);
}
m_presentParams = *pPresentParams;
if (changeFullscreen)
SetGammaRamp(0, &m_ramp);
CreateBackBuffers(m_presentParams.BackBufferCount);
return D3D_OK;
}
HRESULT D3D9SwapChainEx::WaitForVBlank() {
static bool s_errorShown = false;
if (!std::exchange(s_errorShown, true))
Logger::warn("D3D9SwapChainEx::WaitForVBlank: Stub");
return D3D_OK;
}
static bool validateGammaRamp(const WORD (&ramp)[256]) {
if (ramp[0] >= ramp[std::size(ramp) - 1]) {
Logger::err("validateGammaRamp: ramp inverted or flat");
return false;
}
for (size_t i = 1; i < std::size(ramp); i++) {
if (ramp[i] < ramp[i - 1]) {
Logger::err("validateGammaRamp: ramp not monotonically increasing");
return false;
}
if (ramp[i] - ramp[i - 1] >= UINT16_MAX / 2) {
Logger::err("validateGammaRamp: huuuge jump");
return false;
}
}
return true;
}
void D3D9SwapChainEx::SetGammaRamp(
DWORD Flags,
const D3DGAMMARAMP* pRamp) {
D3D9DeviceLock lock = m_parent->LockDevice();
if (unlikely(pRamp == nullptr))
return;
if (unlikely(!validateGammaRamp(pRamp->red)
&& !validateGammaRamp(pRamp->blue)
&& !validateGammaRamp(pRamp->green)))
return;
m_ramp = *pRamp;
bool isIdentity = true;
std::array<DxvkGammaCp, NumControlPoints> cp;
for (uint32_t i = 0; i < NumControlPoints; i++) {
uint16_t identity = MapGammaControlPoint(float(i) / float(NumControlPoints - 1));
cp[i].r = pRamp->red[i];
cp[i].g = pRamp->green[i];
cp[i].b = pRamp->blue[i];
cp[i].a = 0;
isIdentity &= cp[i].r == identity
&& cp[i].g == identity
&& cp[i].b == identity;
}
if (!isIdentity && !m_presentParams.Windowed)
m_blitter->setGammaRamp(NumControlPoints, cp.data());
else
m_blitter->setGammaRamp(0, nullptr);
}
void D3D9SwapChainEx::GetGammaRamp(D3DGAMMARAMP* pRamp) {
D3D9DeviceLock lock = m_parent->LockDevice();
if (likely(pRamp != nullptr))
*pRamp = m_ramp;
}
void D3D9SwapChainEx::Invalidate(HWND hWindow) {
if (hWindow == nullptr)
hWindow = m_parent->GetWindow();
if (m_presentParams.hDeviceWindow == hWindow) {
m_presenter = nullptr;
m_device->waitForSubmission(&m_presentStatus);
m_device->waitForIdle();
}
}
HRESULT D3D9SwapChainEx::SetDialogBoxMode(bool bEnableDialogs) {
D3D9DeviceLock lock = m_parent->LockDevice();
// https://docs.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-setdialogboxmode
// The MSDN documentation says this will error out under many weird conditions.
// However it doesn't appear to error at all in any of my tests of these
// cases described in the documentation.
m_dialog = bEnableDialogs;
return D3D_OK;
}
D3D9Surface* D3D9SwapChainEx::GetBackBuffer(UINT iBackBuffer) {
if (iBackBuffer >= m_presentParams.BackBufferCount)
return nullptr;
return m_backBuffers[iBackBuffer].ptr();
}
void D3D9SwapChainEx::NormalizePresentParameters(D3DPRESENT_PARAMETERS* pPresentParams) {
if (pPresentParams->hDeviceWindow == nullptr)
pPresentParams->hDeviceWindow = m_parent->GetWindow();
pPresentParams->BackBufferCount = std::max(pPresentParams->BackBufferCount, 1u);
const int32_t forcedMSAA = m_parent->GetOptions()->forceSwapchainMSAA;
if (forcedMSAA != -1) {
pPresentParams->MultiSampleType = D3DMULTISAMPLE_TYPE(forcedMSAA);
pPresentParams->MultiSampleQuality = 0;
}
if (pPresentParams->Windowed) {
GetWindowClientSize(pPresentParams->hDeviceWindow,
pPresentParams->BackBufferWidth ? nullptr : &pPresentParams->BackBufferWidth,
pPresentParams->BackBufferHeight ? nullptr : &pPresentParams->BackBufferHeight);
}
else {
GetMonitorClientSize(GetDefaultMonitor(),
pPresentParams->BackBufferWidth ? nullptr : &pPresentParams->BackBufferWidth,
pPresentParams->BackBufferHeight ? nullptr : &pPresentParams->BackBufferHeight);
}
if (pPresentParams->BackBufferFormat == D3DFMT_UNKNOWN)
pPresentParams->BackBufferFormat = D3DFMT_X8R8G8B8;
if (env::getEnvVar("DXVK_FORCE_WINDOWED") == "1")
pPresentParams->Windowed = TRUE;
}
void D3D9SwapChainEx::PresentImage(UINT SyncInterval) {
m_parent->Flush();
// Retrieve the image and image view to present
auto swapImage = m_backBuffers[0]->GetCommonTexture()->GetImage();
auto swapImageView = m_backBuffers[0]->GetImageView(false);
// Bump our frame id.
++m_frameId;
for (uint32_t i = 0; i < SyncInterval || i < 1; i++) {
SynchronizePresent();
// Presentation semaphores and WSI swap chain image
vk::PresenterInfo info = m_presenter->info();
vk::PresenterSync sync;
uint32_t imageIndex = 0;
VkResult status = m_presenter->acquireNextImage(sync, imageIndex);
while (status != VK_SUCCESS && status != VK_SUBOPTIMAL_KHR) {
RecreateSwapChain(m_vsync);
info = m_presenter->info();
status = m_presenter->acquireNextImage(sync, imageIndex);
}
m_context->beginRecording(
m_device->createCommandList());
VkRect2D srcRect = {
{ int32_t(m_srcRect.left), int32_t(m_srcRect.top) },
{ uint32_t(m_srcRect.right - m_srcRect.left), uint32_t(m_srcRect.bottom - m_srcRect.top) } };
VkRect2D dstRect = {
{ int32_t(m_dstRect.left), int32_t(m_dstRect.top) },
{ uint32_t(m_dstRect.right - m_dstRect.left), uint32_t(m_dstRect.bottom - m_dstRect.top) } };
m_blitter->presentImage(m_context.ptr(),
m_imageViews.at(imageIndex), dstRect,
swapImageView, srcRect);
if (m_hud != nullptr)
m_hud->render(m_context, info.format, info.imageExtent);
if (i + 1 >= SyncInterval)
m_context->signal(m_frameLatencySignal, m_frameId);
SubmitPresent(sync, i);
}
SyncFrameLatency();
// Rotate swap chain buffers so that the back
// buffer at index 0 becomes the front buffer.
for (uint32_t i = 1; i < m_backBuffers.size(); i++)
m_backBuffers[i]->Swap(m_backBuffers[i - 1].ptr());
m_parent->m_flags.set(D3D9DeviceFlag::DirtyFramebuffer);
}
void D3D9SwapChainEx::SubmitPresent(const vk::PresenterSync& Sync, uint32_t FrameId) {
// Present from CS thread so that we don't
// have to synchronize with it first.
m_presentStatus.result = VK_NOT_READY;
m_parent->EmitCs([this,
cFrameId = FrameId,
cSync = Sync,
cHud = m_hud,
cCommandList = m_context->endRecording()
] (DxvkContext* ctx) {
m_device->submitCommandList(cCommandList,
cSync.acquire, cSync.present);
if (cHud != nullptr && !cFrameId)
cHud->update();
m_device->presentImage(m_presenter, &m_presentStatus);
});
m_parent->FlushCsChunk();
}
void D3D9SwapChainEx::SynchronizePresent() {
// Recreate swap chain if the previous present call failed
VkResult status = m_device->waitForSubmission(&m_presentStatus);
if (status != VK_SUCCESS)
RecreateSwapChain(m_vsync);
}
void D3D9SwapChainEx::RecreateSwapChain(BOOL Vsync) {
// Ensure that we can safely destroy the swap chain
m_device->waitForSubmission(&m_presentStatus);
m_device->waitForIdle();
m_presentStatus.result = VK_SUCCESS;
vk::PresenterDesc presenterDesc;
presenterDesc.imageExtent = GetPresentExtent();
presenterDesc.imageCount = PickImageCount(m_presentParams.BackBufferCount + 1);
presenterDesc.numFormats = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats);
presenterDesc.numPresentModes = PickPresentModes(Vsync, presenterDesc.presentModes);
presenterDesc.fullScreenExclusive = PickFullscreenMode();
if (m_presenter->recreateSwapChain(presenterDesc) != VK_SUCCESS)
throw DxvkError("D3D9SwapChainEx: Failed to recreate swap chain");
CreateRenderTargetViews();
}
void D3D9SwapChainEx::CreatePresenter() {
// Ensure that we can safely destroy the swap chain
m_device->waitForSubmission(&m_presentStatus);
m_device->waitForIdle();
m_presentStatus.result = VK_SUCCESS;
DxvkDeviceQueue graphicsQueue = m_device->queues().graphics;
vk::PresenterDevice presenterDevice;
presenterDevice.queueFamily = graphicsQueue.queueFamily;
presenterDevice.queue = graphicsQueue.queueHandle;
presenterDevice.adapter = m_device->adapter()->handle();
vk::PresenterDesc presenterDesc;
presenterDesc.imageExtent = GetPresentExtent();
presenterDesc.imageCount = PickImageCount(m_presentParams.BackBufferCount + 1);
presenterDesc.numFormats = PickFormats(EnumerateFormat(m_presentParams.BackBufferFormat), presenterDesc.formats);
presenterDesc.numPresentModes = PickPresentModes(false, presenterDesc.presentModes);
presenterDesc.fullScreenExclusive = PickFullscreenMode();
m_presenter = new vk::Presenter(m_window,
m_device->adapter()->vki(),
m_device->vkd(),
presenterDevice,
presenterDesc);
m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate);
m_presenter->setFrameRateLimiterRefreshRate(m_displayRefreshRate);
CreateRenderTargetViews();
}
void D3D9SwapChainEx::CreateRenderTargetViews() {
vk::PresenterInfo info = m_presenter->info();
m_imageViews.clear();
m_imageViews.resize(info.imageCount);
DxvkImageCreateInfo imageInfo;
imageInfo.type = VK_IMAGE_TYPE_2D;
imageInfo.format = info.format.format;
imageInfo.flags = 0;
imageInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
imageInfo.extent = { info.imageExtent.width, info.imageExtent.height, 1 };
imageInfo.numLayers = 1;
imageInfo.mipLevels = 1;
imageInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imageInfo.stages = 0;
imageInfo.access = 0;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
imageInfo.shared = VK_TRUE;
DxvkImageViewCreateInfo viewInfo;
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = info.format.format;
viewInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.minLevel = 0;
viewInfo.numLevels = 1;
viewInfo.minLayer = 0;
viewInfo.numLayers = 1;
for (uint32_t i = 0; i < info.imageCount; i++) {
VkImage imageHandle = m_presenter->getImage(i).image;
Rc<DxvkImage> image = new DxvkImage(
m_device->vkd(), imageInfo, imageHandle);
m_imageViews[i] = new DxvkImageView(
m_device->vkd(), image, viewInfo);
}
}
void D3D9SwapChainEx::DestroyBackBuffers() {
for (auto& backBuffer : m_backBuffers)
backBuffer->ClearContainer();
m_backBuffers.clear();
}
void D3D9SwapChainEx::CreateBackBuffers(uint32_t NumBackBuffers) {
// Explicitly destroy current swap image before
// creating a new one to free up resources
DestroyBackBuffers();
int NumFrontBuffer = m_parent->GetOptions()->noExplicitFrontBuffer ? 0 : 1;
m_backBuffers.resize(NumBackBuffers + NumFrontBuffer);
// Create new back buffer
D3D9_COMMON_TEXTURE_DESC desc;
desc.Width = std::max(m_presentParams.BackBufferWidth, 1u);
desc.Height = std::max(m_presentParams.BackBufferHeight, 1u);
desc.Depth = 1;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = EnumerateFormat(m_presentParams.BackBufferFormat);
desc.MultiSample = m_presentParams.MultiSampleType;
desc.MultisampleQuality = m_presentParams.MultiSampleQuality;
desc.Pool = D3DPOOL_DEFAULT;
desc.Usage = D3DUSAGE_RENDERTARGET;
desc.Discard = FALSE;
desc.IsBackBuffer = TRUE;
desc.IsAttachmentOnly = FALSE;
for (uint32_t i = 0; i < m_backBuffers.size(); i++)
m_backBuffers[i] = new D3D9Surface(m_parent, &desc, this);
auto swapImage = m_backBuffers[0]->GetCommonTexture()->GetImage();
// Initialize the image so that we can use it. Clearing
// to black prevents garbled output for the first frame.
VkImageSubresourceRange subresources;
subresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresources.baseMipLevel = 0;
subresources.levelCount = 1;
subresources.baseArrayLayer = 0;
subresources.layerCount = 1;
VkClearColorValue clearColor;
clearColor.float32[0] = 0.0f;
clearColor.float32[1] = 0.0f;
clearColor.float32[2] = 0.0f;
clearColor.float32[3] = 0.0f;
m_context->beginRecording(
m_device->createCommandList());
for (uint32_t i = 0; i < m_backBuffers.size(); i++) {
m_context->clearColorImage(
m_backBuffers[i]->GetCommonTexture()->GetImage(),
clearColor, subresources);
}
m_device->submitCommandList(
m_context->endRecording(),
VK_NULL_HANDLE,
VK_NULL_HANDLE);
}
void D3D9SwapChainEx::CreateBlitter() {
m_blitter = new DxvkSwapchainBlitter(m_device);
}
void D3D9SwapChainEx::CreateHud() {
m_hud = hud::Hud::createHud(m_device);
if (m_hud != nullptr) {
m_hud->addItem<hud::HudClientApiItem>("api", 1, GetApiName());
m_hud->addItem<hud::HudSamplerCount>("samplers", -1, m_parent);
}
}
void D3D9SwapChainEx::InitRamp() {
for (uint32_t i = 0; i < NumControlPoints; i++) {
DWORD identity = DWORD(MapGammaControlPoint(float(i) / float(NumControlPoints - 1)));
m_ramp.red[i] = identity;
m_ramp.green[i] = identity;
m_ramp.blue[i] = identity;
}
}
void D3D9SwapChainEx::SyncFrameLatency() {
// Wait for the sync event so that we respect the maximum frame latency
m_frameLatencySignal->wait(m_frameId - GetActualFrameLatency());
}
uint32_t D3D9SwapChainEx::GetActualFrameLatency() {
uint32_t maxFrameLatency = m_parent->GetFrameLatency();
if (m_frameLatencyCap)
maxFrameLatency = std::min(maxFrameLatency, m_frameLatencyCap);
maxFrameLatency = std::min(maxFrameLatency, m_presentParams.BackBufferCount + 1);
return maxFrameLatency;
}
uint32_t D3D9SwapChainEx::PickFormats(
D3D9Format Format,
VkSurfaceFormatKHR* pDstFormats) {
uint32_t n = 0;
switch (Format) {
default:
Logger::warn(str::format("D3D9SwapChainEx: Unexpected format: ", Format));
case D3D9Format::A8R8G8B8:
case D3D9Format::X8R8G8B8:
case D3D9Format::A8B8G8R8:
case D3D9Format::X8B8G8R8: {
pDstFormats[n++] = { VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
pDstFormats[n++] = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
} break;
case D3D9Format::A2R10G10B10:
case D3D9Format::A2B10G10R10: {
pDstFormats[n++] = { VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
pDstFormats[n++] = { VK_FORMAT_A2R10G10B10_UNORM_PACK32, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
} break;
case D3D9Format::X1R5G5B5:
case D3D9Format::A1R5G5B5: {
pDstFormats[n++] = { VK_FORMAT_B5G5R5A1_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
pDstFormats[n++] = { VK_FORMAT_R5G5B5A1_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
pDstFormats[n++] = { VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
}
case D3D9Format::R5G6B5: {
pDstFormats[n++] = { VK_FORMAT_B5G6R5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
pDstFormats[n++] = { VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
}
}
return n;
}
uint32_t D3D9SwapChainEx::PickPresentModes(
BOOL Vsync,
VkPresentModeKHR* pDstModes) {
uint32_t n = 0;
if (Vsync) {
if (m_parent->GetOptions()->tearFree == Tristate::False)
pDstModes[n++] = VK_PRESENT_MODE_FIFO_RELAXED_KHR;
pDstModes[n++] = VK_PRESENT_MODE_FIFO_KHR;
} else {
if (m_parent->GetOptions()->tearFree != Tristate::True)
pDstModes[n++] = VK_PRESENT_MODE_IMMEDIATE_KHR;
pDstModes[n++] = VK_PRESENT_MODE_MAILBOX_KHR;
}
return n;
}
uint32_t D3D9SwapChainEx::PickImageCount(
UINT Preferred) {
int32_t option = m_parent->GetOptions()->numBackBuffers;
return option > 0 ? uint32_t(option) : uint32_t(Preferred);
}
void D3D9SwapChainEx::NotifyDisplayRefreshRate(
double RefreshRate) {
m_displayRefreshRate = RefreshRate;
if (m_presenter != nullptr)
m_presenter->setFrameRateLimiterRefreshRate(RefreshRate);
}
HRESULT D3D9SwapChainEx::EnterFullscreenMode(
D3DPRESENT_PARAMETERS* pPresentParams,
const D3DDISPLAYMODEEX* pFullscreenDisplayMode) {
// Find a display mode that matches what we need
::GetWindowRect(m_window, &m_windowState.rect);
if (FAILED(ChangeDisplayMode(pPresentParams, pFullscreenDisplayMode))) {
Logger::err("D3D9: EnterFullscreenMode: Failed to change display mode");
return D3DERR_INVALIDCALL;
}
// Testing shows we shouldn't hook WM_NCCALCSIZE but we shouldn't change
// windows style either.
//
// Some games restore window styles after we have changed it, so hooking is
// also required. Doing it will allow us to create fullscreen windows
// regardless of their style and it also appears to work on Windows.
HookWindowProc(m_window, this);
D3D9WindowMessageFilter filter(m_window);
// Change the window flags to remove the decoration etc.
LONG style = ::GetWindowLongW(m_window, GWL_STYLE);
LONG exstyle = ::GetWindowLongW(m_window, GWL_EXSTYLE);
m_windowState.style = style;
m_windowState.exstyle = exstyle;
style &= ~WS_OVERLAPPEDWINDOW;
exstyle &= ~WS_EX_OVERLAPPEDWINDOW;
::SetWindowLongW(m_window, GWL_STYLE, style);
::SetWindowLongW(m_window, GWL_EXSTYLE, exstyle);
// Move the window so that it covers the entire output
RECT rect;
GetMonitorRect(GetDefaultMonitor(), &rect);
::SetWindowPos(m_window, HWND_TOPMOST,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOACTIVATE);
m_monitor = GetDefaultMonitor();
return D3D_OK;
}
HRESULT D3D9SwapChainEx::LeaveFullscreenMode() {
if (!IsWindow(m_window))
return D3DERR_INVALIDCALL;
if (FAILED(RestoreDisplayMode(m_monitor)))
Logger::warn("D3D9: LeaveFullscreenMode: Failed to restore display mode");
m_monitor = nullptr;
ResetWindowProc(m_window);
// Only restore the window style if the application hasn't
// changed them. This is in line with what native D3D9 does.
LONG curStyle = ::GetWindowLongW(m_window, GWL_STYLE) & ~WS_VISIBLE;
LONG curExstyle = ::GetWindowLongW(m_window, GWL_EXSTYLE) & ~WS_EX_TOPMOST;
if (curStyle == (m_windowState.style & ~(WS_VISIBLE | WS_OVERLAPPEDWINDOW))
&& curExstyle == (m_windowState.exstyle & ~(WS_EX_TOPMOST | WS_EX_OVERLAPPEDWINDOW))) {
::SetWindowLongW(m_window, GWL_STYLE, m_windowState.style);
::SetWindowLongW(m_window, GWL_EXSTYLE, m_windowState.exstyle);
}
// Restore window position and apply the style
const RECT rect = m_windowState.rect;
::SetWindowPos(m_window, 0,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE);
return D3D_OK;
}
HRESULT D3D9SwapChainEx::ChangeDisplayMode(
D3DPRESENT_PARAMETERS* pPresentParams,
const D3DDISPLAYMODEEX* pFullscreenDisplayMode) {
D3DDISPLAYMODEEX mode;
if (pFullscreenDisplayMode) {
mode = *pFullscreenDisplayMode;
} else {
mode.Width = pPresentParams->BackBufferWidth;
mode.Height = pPresentParams->BackBufferHeight;
mode.Format = pPresentParams->BackBufferFormat;
mode.RefreshRate = pPresentParams->FullScreen_RefreshRateInHz;
mode.ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
mode.Size = sizeof(D3DDISPLAYMODEEX);
}
DEVMODEW devMode = { };
devMode.dmSize = sizeof(devMode);
devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
devMode.dmPelsWidth = mode.Width;
devMode.dmPelsHeight = mode.Height;
devMode.dmBitsPerPel = GetMonitorFormatBpp(EnumerateFormat(mode.Format));
if (mode.RefreshRate != 0) {
devMode.dmFields |= DM_DISPLAYFREQUENCY;
devMode.dmDisplayFrequency = mode.RefreshRate;
}
HMONITOR monitor = GetDefaultMonitor();
if (!SetMonitorDisplayMode(monitor, &devMode))
return D3DERR_NOTAVAILABLE;
devMode.dmFields = DM_DISPLAYFREQUENCY;
if (GetMonitorDisplayMode(monitor, ENUM_CURRENT_SETTINGS, &devMode))
NotifyDisplayRefreshRate(double(devMode.dmDisplayFrequency));
else
NotifyDisplayRefreshRate(0.0);
return D3D_OK;
}
HRESULT D3D9SwapChainEx::RestoreDisplayMode(HMONITOR hMonitor) {
if (hMonitor == nullptr)
return D3DERR_INVALIDCALL;
if (!RestoreMonitorDisplayMode())
return D3DERR_NOTAVAILABLE;
NotifyDisplayRefreshRate(0.0);
return D3D_OK;
}
bool D3D9SwapChainEx::UpdatePresentRegion(const RECT* pSourceRect, const RECT* pDestRect) {
if (pSourceRect == nullptr) {
m_srcRect.top = 0;
m_srcRect.left = 0;
m_srcRect.right = m_presentParams.BackBufferWidth;
m_srcRect.bottom = m_presentParams.BackBufferHeight;
}
else
m_srcRect = *pSourceRect;
RECT dstRect;
if (pDestRect == nullptr) {
// TODO: Should we hook WM_SIZE message for this?
UINT width, height;
GetWindowClientSize(m_window, &width, &height);
dstRect.top = 0;
dstRect.left = 0;
dstRect.right = LONG(width);
dstRect.bottom = LONG(height);
}
else
dstRect = *pDestRect;
bool recreate =
m_dstRect.left != dstRect.left
|| m_dstRect.top != dstRect.top
|| m_dstRect.right != dstRect.right
|| m_dstRect.bottom != dstRect.bottom;
m_dstRect = dstRect;
return recreate;
}
VkExtent2D D3D9SwapChainEx::GetPresentExtent() {
return VkExtent2D {
std::max<uint32_t>(m_dstRect.right - m_dstRect.left, 1u),
std::max<uint32_t>(m_dstRect.bottom - m_dstRect.top, 1u) };
}
VkFullScreenExclusiveEXT D3D9SwapChainEx::PickFullscreenMode() {
return m_dialog
? VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT
: VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT;
}
std::string D3D9SwapChainEx::GetApiName() {
return this->GetParent()->IsExtended() ? "D3D9Ex" : "D3D9";
}
}