mirror of
https://github.com/EduApps-CDG/OpenDX
synced 2024-12-30 09:45:37 +01:00
The Vulkan swapchain is unaffected by this, but we don't create an "internal" frontbuffer in D3D9SwapChainEx if this option is set. This breaks GetFrontBufferData (which returns backbuffer data if the option is enabled), but it disables front/backbuffer flipping. Most windows drivers apparently always use the same backbuffer for all frames in windowed mode. At least one game (ZUSI 3) seems to rely on this behavior, and only redraws dirty regions for each frame instead of redrawing everything. With buffer flips, this leads to flickering. When enabling this new noExplicitFrontBuffer option, the flickering disappears.
1493 lines
52 KiB
C++
1493 lines
52 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 {
|
|
|
|
|
|
struct D3D9WindowData {
|
|
bool unicode;
|
|
WNDPROC proc;
|
|
};
|
|
|
|
|
|
static std::recursive_mutex windowProcMapMutex;
|
|
static std::unordered_map<HWND, D3D9WindowData> windowProcMap;
|
|
|
|
|
|
static D3D9WindowData GetD3D9WindowData(HWND window) {
|
|
std::lock_guard<std::recursive_mutex> lock(windowProcMapMutex);
|
|
|
|
auto it = windowProcMap.find(window);
|
|
if (it == windowProcMap.end())
|
|
return {};
|
|
|
|
return it->second;
|
|
}
|
|
|
|
|
|
static LRESULT CALLBACK D3D9WindowProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam)
|
|
{
|
|
if (message == WM_NCCALCSIZE && wparam == TRUE)
|
|
return 0;
|
|
|
|
auto windowData = GetD3D9WindowData(window);
|
|
if (!windowData.proc)
|
|
return IsWindowUnicode(window)
|
|
? DefWindowProcW(window, message, wparam, lparam)
|
|
: DefWindowProcA(window, message, wparam, lparam);
|
|
|
|
return windowData.unicode
|
|
? CallWindowProcW(windowData.proc, window, message, wparam, lparam)
|
|
: CallWindowProcA(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);
|
|
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() {
|
|
ResetWindowProc();
|
|
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, VkExtent2D { 0u, 0u },
|
|
cImage, cSubresources, VkOffset3D { 0, 0, 0 },
|
|
cLevelExtent);
|
|
});
|
|
|
|
dstTexInfo->SetDirty(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 = 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 (!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;
|
|
}
|
|
|
|
|
|
void 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);
|
|
::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);
|
|
|
|
CreateBackBuffers(m_presentParams.BackBufferCount);
|
|
}
|
|
|
|
|
|
HRESULT D3D9SwapChainEx::WaitForVBlank() {
|
|
Logger::warn("D3D9SwapChainEx::WaitForVBlank: Stub");
|
|
return D3D_OK;
|
|
}
|
|
|
|
|
|
void D3D9SwapChainEx::SetGammaRamp(
|
|
DWORD Flags,
|
|
const D3DGAMMARAMP* pRamp) {
|
|
D3D9DeviceLock lock = m_parent->LockDevice();
|
|
|
|
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) {
|
|
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_resolveImageView;
|
|
|
|
if (swapImageView == nullptr)
|
|
swapImageView = m_backBuffers[0]->GetImageView(false);
|
|
|
|
// 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_resolveImage != 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 = swapImage->info().extent;
|
|
|
|
m_context->resolveImage(
|
|
m_resolveImage, 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 = m_dstRect.left;
|
|
scissor.offset.y = m_dstRect.top;
|
|
scissor.extent.width = m_dstRect.right - m_dstRect.left;
|
|
scissor.extent.height = m_dstRect.bottom - m_dstRect.top;
|
|
|
|
m_context->setViewports(1, &viewport, &scissor);
|
|
|
|
// Use an appropriate texture filter depending on whether
|
|
// the back buffer size matches the swap image size
|
|
bool fitSize = m_dstRect.right - m_dstRect.left == m_srcRect.right - m_srcRect.left
|
|
&& m_dstRect.bottom - m_dstRect.top == m_srcRect.bottom - m_srcRect.top;
|
|
|
|
D3D9PresentInfo presentInfoConsts;
|
|
presentInfoConsts.scale[0] = float(m_srcRect.right - m_srcRect.left) / float(swapImage->info().extent.width);
|
|
presentInfoConsts.scale[1] = float(m_srcRect.bottom - m_srcRect.top) / float(swapImage->info().extent.height);
|
|
|
|
presentInfoConsts.offset[0] = float(m_srcRect.left) / float(swapImage->info().extent.width);
|
|
presentInfoConsts.offset[1] = float(m_srcRect.top) / float(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, fitSize ? m_samplerFitting : m_samplerScaling);
|
|
m_context->bindResourceSampler(BindingIds::Gamma, m_gammaSampler);
|
|
|
|
m_context->bindResourceView(BindingIds::Image, 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);
|
|
}
|
|
|
|
// 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,
|
|
cSync.present, &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);
|
|
|
|
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_resolveImage = nullptr;
|
|
m_resolveImageView = nullptr;
|
|
|
|
int NumFrontBuffer = m_parent->GetOptions()->noExplicitFrontBuffer ? 0 : 1;
|
|
m_backBuffers.clear();
|
|
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;
|
|
|
|
for (uint32_t i = 0; i < m_backBuffers.size(); i++)
|
|
m_backBuffers[i] = new D3D9Surface(m_parent, &desc);
|
|
|
|
auto 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 (swapImage->info().sampleCount != VK_SAMPLE_COUNT_1_BIT) {
|
|
DxvkImageCreateInfo resolveInfo;
|
|
resolveInfo.type = VK_IMAGE_TYPE_2D;
|
|
resolveInfo.format = swapImage->info().format;
|
|
resolveInfo.flags = 0;
|
|
resolveInfo.sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
resolveInfo.extent = 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_resolveImage = m_device->createImage(
|
|
resolveInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
|
|
DxvkImageViewCreateInfo viewInfo;
|
|
viewInfo.type = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = m_resolveImage->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_resolveImageView = m_device->createImageView(m_resolveImage, 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());
|
|
|
|
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::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::HudClientApiItem>("api", 1, GetApiName());
|
|
m_hud->addItem<hud::HudSamplerCount>("samplers", -1, 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;
|
|
}
|
|
|
|
// 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();
|
|
|
|
// 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();
|
|
|
|
// 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;
|
|
}
|
|
|
|
return SetMonitorDisplayMode(GetDefaultMonitor(), &devMode)
|
|
? D3D_OK
|
|
: D3DERR_NOTAVAILABLE;
|
|
}
|
|
|
|
|
|
HRESULT D3D9SwapChainEx::RestoreDisplayMode(HMONITOR hMonitor) {
|
|
if (hMonitor == nullptr)
|
|
return D3DERR_INVALIDCALL;
|
|
|
|
return RestoreMonitorDisplayMode(hMonitor)
|
|
? D3D_OK
|
|
: D3DERR_NOTAVAILABLE;
|
|
}
|
|
|
|
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";
|
|
}
|
|
|
|
|
|
void D3D9SwapChainEx::HookWindowProc() {
|
|
std::lock_guard<std::recursive_mutex> lock(windowProcMapMutex);
|
|
|
|
ResetWindowProc();
|
|
|
|
D3D9WindowData windowData;
|
|
windowData.unicode = IsWindowUnicode(m_window);
|
|
windowData.proc = windowData.unicode
|
|
? (WNDPROC)SetWindowLongPtrW(m_window, GWLP_WNDPROC, (LONG_PTR)D3D9WindowProc)
|
|
: (WNDPROC)SetWindowLongPtrA(m_window, GWLP_WNDPROC, (LONG_PTR)D3D9WindowProc);
|
|
|
|
windowProcMap[m_window] = std::move(windowData);
|
|
}
|
|
|
|
void D3D9SwapChainEx::ResetWindowProc() {
|
|
std::lock_guard<std::recursive_mutex> lock(windowProcMapMutex);
|
|
|
|
auto it = windowProcMap.find(m_window);
|
|
if (it == windowProcMap.end())
|
|
return;
|
|
|
|
auto proc = it->second.unicode
|
|
? (WNDPROC)GetWindowLongPtrW(m_window, GWLP_WNDPROC)
|
|
: (WNDPROC)GetWindowLongPtrA(m_window, GWLP_WNDPROC);
|
|
|
|
if (proc == D3D9WindowProc && it->second.unicode)
|
|
SetWindowLongPtrW(m_window, GWLP_WNDPROC, (LONG_PTR)it->second.proc);
|
|
else if (proc == D3D9WindowProc && !it->second.unicode)
|
|
SetWindowLongPtrA(m_window, GWLP_WNDPROC, (LONG_PTR)it->second.proc);
|
|
|
|
windowProcMap.erase(m_window);
|
|
}
|
|
|
|
} |