dxvk/src/d3d9/d3d9_swapchain.cpp

1197 lines
39 KiB
C++

#include "d3d9_swapchain.h"
#include "d3d9_surface.h"
#include "d3d9_monitor.h"
#include "d3d9_hud.h"
#include "d3d9_window.h"
namespace dxvk {
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(DxvkContextType::Supplementary))
, 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 (m_window) {
CreatePresenter();
if (!pDevice->GetOptions()->deferSurfaceCreation)
RecreateSwapChain(false);
}
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() {
// Avoids hanging when in this state, see comment
// in DxvkDevice::~DxvkDevice.
if (this_thread::isInModuleDetachment())
return;
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 && dstTexInfo->Desc()->Pool != D3DPOOL_SCRATCH))
return D3DERR_INVALIDCALL;
VkExtent3D dstTexExtent = dstTexInfo->GetExtentMip(dst->GetMipLevel());
VkExtent3D srcTexExtent = srcTexInfo->GetExtentMip(0);
const bool clearDst = dstTexInfo->Desc()->MipLevels > 1
|| dstTexExtent.width > srcTexExtent.width
|| dstTexExtent.height > srcTexExtent.height;
dstTexInfo->CreateBuffer(clearDst);
DxvkBufferSlice dstBufferSlice = dstTexInfo->GetBufferSlice(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 = lookupFormatInfo(blittedSrc->info().format);
const DxvkFormatInfo* srcFormatInfo = lookupFormatInfo(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 = lookupFormatInfo(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([
cBufferSlice = std::move(dstBufferSlice),
cImage = std::move(srcImage),
cSubresources = srcSubresourceLayers,
cLevelExtent = srcExtent
] (DxvkContext* ctx) {
ctx->copyImageToBuffer(cBufferSlice.buffer(),
cBufferSlice.offset(), 4, 0, cImage,
cSubresources, VkOffset3D { 0, 0, 0 },
cLevelExtent);
});
dstTexInfo->SetNeedsReadback(dst->GetSubresource(), true);
m_parent->TrackTextureMappingBufferSequenceNumber(dstTexInfo, dst->GetSubresource());
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) {
wsi::WsiMode devMode = { };
if (!wsi::getCurrentDisplayMode(wsi::getDefaultMonitor(), &devMode)) {
Logger::err("D3D9SwapChainEx::GetDisplayModeEx: Failed to enum display settings");
return D3DERR_INVALIDCALL;
}
*pMode = ConvertDisplayMode(devMode);
}
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();
}
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;
wsi::updateFullscreenWindow(m_monitor, m_window, true);
}
}
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) {
wsi::getWindowSize(pPresentParams->hDeviceWindow,
pPresentParams->BackBufferWidth ? nullptr : &pPresentParams->BackBufferWidth,
pPresentParams->BackBufferHeight ? nullptr : &pPresentParams->BackBufferHeight);
}
else {
wsi::getMonitorClientSize(wsi::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->EndFrame();
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) {
cCommandList->setWsiSemaphores(cSync);
m_device->submitCommandList(cCommandList);
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();
VkResult vr = m_presenter->recreateSwapChain(presenterDesc);
if (vr == VK_ERROR_SURFACE_LOST_KHR) {
vr = m_presenter->recreateSurface([this] (VkSurfaceKHR* surface) {
return CreateSurface(surface);
});
if (vr)
throw DxvkError(str::format("D3D9SwapChainEx: Failed to recreate surface: ", vr));
vr = m_presenter->recreateSwapChain(presenterDesc);
}
if (vr)
throw DxvkError(str::format("D3D9SwapChainEx: Failed to recreate swap chain: ", vr));
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_device->adapter()->vki(),
m_device->vkd(),
presenterDevice,
presenterDesc);
m_presenter->setFrameRateLimit(m_parent->GetOptions()->maxFrameRate);
}
VkResult D3D9SwapChainEx::CreateSurface(VkSurfaceKHR* pSurface) {
auto vki = m_device->adapter()->vki();
return wsi::createSurface(m_window,
vki->getLoaderProc(),
vki->instance(),
pSurface);
}
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.ptr(), 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;
// Docs: Also note that - unlike textures - swap chain back buffers, render targets [..] can be locked
desc.IsLockable = TRUE;
for (uint32_t i = 0; i < m_backBuffers.size(); i++)
m_backBuffers[i] = new D3D9Surface(m_parent, &desc, this, nullptr);
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;
m_context->beginRecording(
m_device->createCommandList());
for (uint32_t i = 0; i < m_backBuffers.size(); i++) {
m_context->initImage(
m_backBuffers[i]->GetCommonTexture()->GetImage(),
subresources, VK_IMAGE_LAYOUT_UNDEFINED);
}
m_device->submitCommandList(
m_context->endRecording());
}
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);
#ifdef D3D9_ALLOW_UNMAPPING
m_hud->addItem<hud::HudTextureMemory>("memory", -1, m_parent);
#endif
}
}
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));
[[fallthrough]];
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 };
} break;
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 };
} break;
}
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;
}
HRESULT D3D9SwapChainEx::EnterFullscreenMode(
D3DPRESENT_PARAMETERS* pPresentParams,
const D3DDISPLAYMODEEX* pFullscreenDisplayMode) {
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);
m_monitor = wsi::getDefaultMonitor();
if (!wsi::enterFullscreenMode(m_monitor, m_window, &m_windowState, true)) {
Logger::err("D3D9: EnterFullscreenMode: Failed to enter fullscreen mode");
return D3DERR_INVALIDCALL;
}
return D3D_OK;
}
HRESULT D3D9SwapChainEx::LeaveFullscreenMode() {
if (!wsi::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);
if (!wsi::leaveFullscreenMode(m_window, &m_windowState, false)) {
Logger::err("D3D9: LeaveFullscreenMode: Failed to exit fullscreen mode");
return D3DERR_NOTAVAILABLE;
}
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);
}
wsi::WsiMode wsiMode = ConvertDisplayMode(mode);
HMONITOR monitor = wsi::getDefaultMonitor();
if (!wsi::setWindowMode(monitor, m_window, wsiMode))
return D3DERR_NOTAVAILABLE;
if (wsi::getCurrentDisplayMode(monitor, &wsiMode))
NotifyDisplayRefreshRate(double(wsiMode.refreshRate.numerator) / double(wsiMode.refreshRate.denominator));
else
NotifyDisplayRefreshRate(0.0);
return D3D_OK;
}
HRESULT D3D9SwapChainEx::RestoreDisplayMode(HMONITOR hMonitor) {
if (hMonitor == nullptr)
return D3DERR_INVALIDCALL;
if (!wsi::restoreDisplayMode())
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;
wsi::getWindowSize(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";
}
}