1
0
mirror of https://github.com/EduApps-CDG/OpenDX synced 2024-12-30 09:45:37 +01:00
OpenDX/src/d3d9/d3d9_swapchain.cpp
Philip Rebohle 905d69e77b [d3d9] Don't pass format mapping to D3D9CommonTexture
Instead, infer it from the format. This is basically being done
already, however the mapping we pass in is not correct if the
image format is Unknown.
2020-01-17 17:47:08 +01:00

1395 lines
49 KiB
C++

#include "d3d9_swapchain.h"
#include "d3d9_surface.h"
#include "d3d9_monitor.h"
#include "d3d9_hud.h"
#include <d3d9_presenter_frag.h>
#include <d3d9_presenter_vert.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())
, m_frameLatencyCap (pDevice->GetOptions()->maxFrameLatency)
, m_frameLatencySignal(new sync::Fence(m_frameId))
, m_dialog (pDevice->GetOptions()->enableDialogMode) {
UpdateMonitorInfo();
this->NormalizePresentParameters(pPresentParams);
m_presentParams = *pPresentParams;
m_window = m_presentParams.hDeviceWindow;
UpdatePresentRegion(nullptr, nullptr);
if (!pDevice->GetOptions()->deferSurfaceCreation)
CreatePresenter();
CreateBackBuffers(m_presentParams.BackBufferCount);
CreateHud();
InitRenderState();
InitSamplers();
InitShaders();
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() {
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) {
auto 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) {
auto 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;
// TODO: Make the source what we consider the frontbuffer!
// rather than the first backbuffer...
D3D9CommonTexture* dstTexInfo = dst->GetCommonTexture();
D3D9CommonTexture* srcTexInfo = m_backBuffers[0]->GetCommonTexture();
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 dstExtent = blittedSrc->mipLevelExtent(dstSubresource.mipLevel);
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, VkExtent2D { 0u, 0u },
cImage, cSubresources, VkOffset3D { 0, 0, 0 },
cLevelExtent);
});
// We need to force a wait here
// as some applications depend on
// DO_NOT_WAIT not applying after
// this has happened.
// (this is a blocking call)
m_parent->WaitForResource(dstBuffer, 0);
return D3D_OK;
}
HRESULT STDMETHODCALLTYPE D3D9SwapChainEx::GetBackBuffer(
UINT iBackBuffer,
D3DBACKBUFFER_TYPE Type,
IDirect3DSurface9** ppBackBuffer) {
InitReturnPtr(ppBackBuffer);
if (ppBackBuffer == nullptr)
return D3DERR_INVALIDCALL;
if (iBackBuffer >= m_backBuffers.size()) {
Logger::err(str::format("D3D9: GetBackBuffer: Invalid back buffer index: ", iBackBuffer));
return D3DERR_INVALIDCALL;
}
*ppBackBuffer = m_backBuffers[iBackBuffer].ref();
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 (!::EnumDisplaySettingsW(m_monInfo.szDevice, 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;
}
void D3D9SwapChainEx::Reset(
D3DPRESENT_PARAMETERS* pPresentParams,
D3DDISPLAYMODEEX* pFullscreenDisplayMode) {
auto lock = m_parent->LockDevice();
this->SynchronizePresent();
this->NormalizePresentParameters(pPresentParams);
m_dirty |= m_presentParams.BackBufferFormat != pPresentParams->BackBufferFormat
|| m_presentParams.BackBufferWidth != pPresentParams->BackBufferWidth
|| m_presentParams.BackBufferHeight != pPresentParams->BackBufferHeight
|| 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);
::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)
this->EnterFullscreenMode(pPresentParams, pFullscreenDisplayMode);
else
ChangeDisplayMode(pPresentParams, pFullscreenDisplayMode);
// 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);
UpdatePresentRegion(nullptr, nullptr);
CreateBackBuffers(m_presentParams.BackBufferCount);
}
HRESULT D3D9SwapChainEx::WaitForVBlank() {
Logger::warn("D3D9SwapChainEx::WaitForVBlank: Stub");
return D3D_OK;
}
void D3D9SwapChainEx::SetGammaRamp(
DWORD Flags,
const D3DGAMMARAMP* pRamp) {
if (unlikely(pRamp == nullptr))
return;
m_ramp = *pRamp;
bool isIdentity = true;
std::array<D3D9_VK_GAMMA_CP, 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)
DestroyGammaTexture();
else
CreateGammaTexture(NumControlPoints, cp.data());
}
void D3D9SwapChainEx::GetGammaRamp(D3DGAMMARAMP* pRamp) {
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;
}
HRESULT D3D9SwapChainEx::SetDialogBoxMode(bool bEnableDialogs) {
// 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_backBuffers.size())
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();
// Wait for the sync event so that we respect the maximum frame latency
uint64_t frameId = ++m_frameId;
m_frameLatencySignal->wait(frameId - GetActualFrameLatency());
for (uint32_t i = 0; i < SyncInterval || i < 1; i++) {
SynchronizePresent();
m_context->beginRecording(
m_device->createCommandList());
// Resolve back buffer if it is multisampled. We
// only have to do it only for the first frame.
if (m_swapImageResolve != nullptr && i == 0) {
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 = m_swapImage->info().extent;
m_context->resolveImage(
m_swapImageResolve, m_swapImage,
resolveRegion, VK_FORMAT_UNDEFINED);
}
// Presentation semaphores and WSI swap chain image
vk::PresenterInfo info = m_presenter->info();
vk::PresenterSync sync = m_presenter->getSyncSemaphores();
uint32_t imageIndex = 0;
VkResult status = m_presenter->acquireNextImage(
sync.acquire, VK_NULL_HANDLE, imageIndex);
while (status != VK_SUCCESS && status != VK_SUBOPTIMAL_KHR) {
RecreateSwapChain(m_vsync);
info = m_presenter->info();
sync = m_presenter->getSyncSemaphores();
status = m_presenter->acquireNextImage(
sync.acquire, VK_NULL_HANDLE, imageIndex);
}
// Use an appropriate texture filter depending on whether
// the back buffer size matches the swap image size
m_context->bindShader(VK_SHADER_STAGE_VERTEX_BIT, m_vertShader);
m_context->bindShader(VK_SHADER_STAGE_FRAGMENT_BIT, m_fragShader);
DxvkRenderTargets renderTargets;
renderTargets.color[0].view = m_imageViews.at(imageIndex);
renderTargets.color[0].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
m_context->bindRenderTargets(renderTargets);
VkViewport viewport;
viewport.x = float(m_dstRect.left);
viewport.y = float(m_dstRect.top);
viewport.width = float(m_dstRect.right - m_dstRect.left);
viewport.height = float(m_dstRect.bottom - m_dstRect.top);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor;
scissor.offset.x = 0;
scissor.offset.y = 0;
scissor.extent.width = m_dstRect.right - m_dstRect.left;
scissor.extent.height = m_dstRect.bottom - m_dstRect.top;
m_context->setViewports(1, &viewport, &scissor);
D3D9PresentInfo presentInfoConsts;
presentInfoConsts.scale[0] = float(m_srcRect.right - m_srcRect.left) / float(m_swapImage->info().extent.width);
presentInfoConsts.scale[1] = float(m_srcRect.bottom - m_srcRect.top) / float(m_swapImage->info().extent.height);
presentInfoConsts.offset[0] = float(m_srcRect.left) / float(m_swapImage->info().extent.width);
presentInfoConsts.offset[1] = float(m_srcRect.top) / float(m_swapImage->info().extent.height);
m_context->pushConstants(0, sizeof(D3D9PresentInfo), &presentInfoConsts);
m_context->setRasterizerState(m_rsState);
m_context->setMultisampleState(m_msState);
m_context->setDepthStencilState(m_dsState);
m_context->setLogicOpState(m_loState);
m_context->setBlendMode(0, m_blendMode);
m_context->setInputAssemblyState(m_iaState);
m_context->setInputLayout(0, nullptr, 0, nullptr);
m_context->bindResourceSampler(BindingIds::Image, m_samplerFitting);
m_context->bindResourceSampler(BindingIds::Gamma, m_gammaSampler);
m_context->bindResourceView(BindingIds::Image, m_swapImageView, nullptr);
m_context->bindResourceView(BindingIds::Gamma, m_gammaTextureView, nullptr);
m_context->draw(3, 1, 0, 0);
if (m_hud != nullptr)
m_hud->render(m_context, info.format, info.imageExtent);
if (i + 1 >= SyncInterval)
m_context->signal(m_frameLatencySignal, frameId);
SubmitPresent(sync, i);
}
}
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,
cSync.present, &m_presentStatus);
// Shift back buffers so that m_backBuffers[1] will
// contain the image that we just presented
for (uint32_t i = m_backBuffers.size(); i > 1; i--) {
ctx->swapImages(
m_backBuffers[i - 1]->GetCommonTexture()->GetImage(),
m_backBuffers[i - 2]->GetCommonTexture()->GetImage());
}
});
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() {
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);
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;
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::CreateBackBuffers(uint32_t NumBackBuffers) {
// Explicitly destroy current swap image before
// creating a new one to free up resources
m_swapImage = nullptr;
m_swapImageResolve = nullptr;
m_swapImageView = nullptr;
m_backBuffers.clear();
m_backBuffers.resize(NumBackBuffers);
// 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;
for (uint32_t i = 0; i < NumBackBuffers; i++)
m_backBuffers[i] = new D3D9Surface(m_parent, &desc);
m_swapImage = m_backBuffers[0]->GetCommonTexture()->GetImage();
// If the image is multisampled, we need to create
// another image which we'll use as a resolve target
if (m_swapImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT) {
DxvkImageCreateInfo resolveInfo;
resolveInfo.type = VK_IMAGE_TYPE_2D;
resolveInfo.format = m_swapImage->info().format;
resolveInfo.flags = 0;
resolveInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
resolveInfo.extent = m_swapImage->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;
m_swapImageResolve = m_device->createImage(
resolveInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
// Create an image view that allows the
// image to be bound as a shader resource.
DxvkImageViewCreateInfo viewInfo;
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = m_swapImage->info().format;
viewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.minLevel = 0;
viewInfo.numLevels = 1;
viewInfo.minLayer = 0;
viewInfo.numLayers = 1;
m_swapImageView = m_device->createImageView(
m_swapImageResolve != nullptr
? m_swapImageResolve
: m_swapImage,
viewInfo);
// 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());
m_context->clearColorImage(
m_swapImage, clearColor, subresources);
m_device->submitCommandList(
m_context->endRecording(),
VK_NULL_HANDLE,
VK_NULL_HANDLE);
}
void D3D9SwapChainEx::CreateGammaTexture(
UINT NumControlPoints,
const D3D9_VK_GAMMA_CP* pControlPoints) {
if (m_gammaTexture == nullptr
|| m_gammaTexture->info().extent.width != NumControlPoints) {
DxvkImageCreateInfo imgInfo;
imgInfo.type = VK_IMAGE_TYPE_1D;
imgInfo.format = VK_FORMAT_R16G16B16A16_UNORM;
imgInfo.flags = 0;
imgInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
imgInfo.extent = { NumControlPoints, 1, 1 };
imgInfo.numLayers = 1;
imgInfo.mipLevels = 1;
imgInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT
| VK_IMAGE_USAGE_SAMPLED_BIT;
imgInfo.stages = VK_PIPELINE_STAGE_TRANSFER_BIT
| VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
imgInfo.access = VK_ACCESS_TRANSFER_WRITE_BIT
| VK_ACCESS_SHADER_READ_BIT;
imgInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imgInfo.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
m_gammaTexture = m_device->createImage(
imgInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
DxvkImageViewCreateInfo viewInfo;
viewInfo.type = VK_IMAGE_VIEW_TYPE_1D;
viewInfo.format = VK_FORMAT_R16G16B16A16_UNORM;
viewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
viewInfo.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.minLevel = 0;
viewInfo.numLevels = 1;
viewInfo.minLayer = 0;
viewInfo.numLayers = 1;
m_gammaTextureView = m_device->createImageView(m_gammaTexture, viewInfo);
}
m_context->beginRecording(
m_device->createCommandList());
m_context->updateImage(m_gammaTexture,
VkImageSubresourceLayers { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 },
VkOffset3D { 0, 0, 0 },
VkExtent3D { NumControlPoints, 1, 1 },
pControlPoints, 0, 0);
m_device->submitCommandList(
m_context->endRecording(),
VK_NULL_HANDLE,
VK_NULL_HANDLE);
}
void D3D9SwapChainEx::DestroyGammaTexture() {
m_gammaTexture = nullptr;
m_gammaTextureView = nullptr;
}
void D3D9SwapChainEx::CreateHud() {
m_hud = hud::Hud::createHud(m_device);
if (m_hud != nullptr)
m_hud->addItem<hud::HudSamplerCount>("samplers", m_parent);
}
void D3D9SwapChainEx::InitRenderState() {
m_iaState.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
m_iaState.primitiveRestart = VK_FALSE;
m_iaState.patchVertexCount = 0;
m_rsState.polygonMode = VK_POLYGON_MODE_FILL;
m_rsState.cullMode = VK_CULL_MODE_BACK_BIT;
m_rsState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
m_rsState.depthClipEnable = VK_FALSE;
m_rsState.depthBiasEnable = VK_FALSE;
m_rsState.sampleCount = VK_SAMPLE_COUNT_1_BIT;
m_msState.sampleMask = 0xffffffff;
m_msState.enableAlphaToCoverage = VK_FALSE;
VkStencilOpState stencilOp;
stencilOp.failOp = VK_STENCIL_OP_KEEP;
stencilOp.passOp = VK_STENCIL_OP_KEEP;
stencilOp.depthFailOp = VK_STENCIL_OP_KEEP;
stencilOp.compareOp = VK_COMPARE_OP_ALWAYS;
stencilOp.compareMask = 0xFFFFFFFF;
stencilOp.writeMask = 0xFFFFFFFF;
stencilOp.reference = 0;
m_dsState.enableDepthTest = VK_FALSE;
m_dsState.enableDepthWrite = VK_FALSE;
m_dsState.enableStencilTest = VK_FALSE;
m_dsState.depthCompareOp = VK_COMPARE_OP_ALWAYS;
m_dsState.stencilOpFront = stencilOp;
m_dsState.stencilOpBack = stencilOp;
m_loState.enableLogicOp = VK_FALSE;
m_loState.logicOp = VK_LOGIC_OP_NO_OP;
m_blendMode.enableBlending = VK_FALSE;
m_blendMode.colorSrcFactor = VK_BLEND_FACTOR_ONE;
m_blendMode.colorDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
m_blendMode.colorBlendOp = VK_BLEND_OP_ADD;
m_blendMode.alphaSrcFactor = VK_BLEND_FACTOR_ONE;
m_blendMode.alphaDstFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
m_blendMode.alphaBlendOp = VK_BLEND_OP_ADD;
m_blendMode.writeMask = VK_COLOR_COMPONENT_R_BIT
| VK_COLOR_COMPONENT_G_BIT
| VK_COLOR_COMPONENT_B_BIT
| VK_COLOR_COMPONENT_A_BIT;
}
void D3D9SwapChainEx::InitSamplers() {
DxvkSamplerCreateInfo samplerInfo;
samplerInfo.magFilter = VK_FILTER_NEAREST;
samplerInfo.minFilter = VK_FILTER_NEAREST;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
samplerInfo.mipmapLodBias = 0.0f;
samplerInfo.mipmapLodMin = 0.0f;
samplerInfo.mipmapLodMax = 0.0f;
samplerInfo.useAnisotropy = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
samplerInfo.compareToDepth = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.borderColor = VkClearColorValue();
samplerInfo.usePixelCoord = VK_FALSE;
m_samplerFitting = m_device->createSampler(samplerInfo);
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
m_samplerScaling = m_device->createSampler(samplerInfo);
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
m_gammaSampler = m_device->createSampler(samplerInfo);
}
void D3D9SwapChainEx::InitShaders() {
const SpirvCodeBuffer vsCode(d3d9_presenter_vert);
const SpirvCodeBuffer fsCode(d3d9_presenter_frag);
const std::array<DxvkResourceSlot, 2> fsResourceSlots = {{
{ BindingIds::Image, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_2D },
{ BindingIds::Gamma, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_IMAGE_VIEW_TYPE_1D },
}};
m_vertShader = m_device->createShader(
VK_SHADER_STAGE_VERTEX_BIT,
0, nullptr,
{ 0u, 1u,
0u, sizeof(D3D9PresentInfo) },
vsCode);
m_fragShader = m_device->createShader(
VK_SHADER_STAGE_FRAGMENT_BIT,
fsResourceSlots.size(),
fsResourceSlots.data(),
{ 1u, 1u }, fsCode);
}
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;
}
}
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) {
pDstModes[n++] = VK_PRESENT_MODE_FIFO_KHR;
} else {
pDstModes[n++] = VK_PRESENT_MODE_IMMEDIATE_KHR;
pDstModes[n++] = VK_PRESENT_MODE_MAILBOX_KHR;
pDstModes[n++] = VK_PRESENT_MODE_FIFO_RELAXED_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);
}
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;
}
// 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;
// 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 == nullptr) {
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);
}
return SetMonitorDisplayMode(GetDefaultMonitor(), pFullscreenDisplayMode == nullptr ? &mode : pFullscreenDisplayMode);
}
HRESULT D3D9SwapChainEx::RestoreDisplayMode(HMONITOR hMonitor) {
if (hMonitor == nullptr)
return D3DERR_INVALIDCALL;
DEVMODEW devMode = { };
devMode.dmSize = sizeof(devMode);
if (!::EnumDisplaySettingsW(m_monInfo.szDevice, ENUM_REGISTRY_SETTINGS, &devMode))
return D3DERR_INVALIDCALL;
Logger::info(str::format("D3D9: Setting display mode: ",
devMode.dmPelsWidth, "x", devMode.dmPelsHeight, "@",
devMode.dmDisplayFrequency));
D3DDISPLAYMODEEX mode;
mode.Width = devMode.dmPelsWidth;
mode.Height = devMode.dmPelsHeight;
mode.RefreshRate = devMode.dmDisplayFrequency;
mode.Format = D3DFMT_X8R8G8B8; // Fix me
mode.ScanLineOrdering = D3DSCANLINEORDERING_PROGRESSIVE;
mode.Size = sizeof(D3DDISPLAYMODEEX);
return SetMonitorDisplayMode(GetDefaultMonitor(), &mode);
}
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) };
}
void D3D9SwapChainEx::UpdateMonitorInfo() {
m_monInfo.cbSize = sizeof(m_monInfo);
if (!::GetMonitorInfoW(GetDefaultMonitor(), reinterpret_cast<MONITORINFO*>(&m_monInfo)))
throw DxvkError("D3D9SwapChainEx::GetDisplayModeEx: Failed to query monitor info");
}
VkFullScreenExclusiveEXT D3D9SwapChainEx::PickFullscreenMode() {
return m_dialog
? VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT
: VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT;
}
}