#include "d3d9_swapchain.h" #include "d3d9_surface.h" #include "d3d9_monitor.h" #include "d3d9_hud.h" #include #include 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) { 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) { 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(pDestSurface); if (unlikely(dst == nullptr)) return D3DERR_INVALIDCALL; D3D9CommonTexture* dstTexInfo = dst->GetCommonTexture(); D3D9CommonTexture* srcTexInfo = m_backBuffers[m_presentParams.BackBufferCount]->GetCommonTexture(); if (unlikely(dstTexInfo->Desc()->Pool != D3DPOOL_SYSTEMMEM)) return D3DERR_INVALIDCALL; Rc dstBuffer = dstTexInfo->GetBuffer(dst->GetSubresource()); Rc 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 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 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( 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 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 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; m_backBuffers.clear(); m_backBuffers.resize(NumBackBuffers + 1); // 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("api", 1, GetApiName()); m_hud->addItem("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 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) { 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(m_dstRect.right - m_dstRect.left, 1u), std::max(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"; } }