From 7ff532191012988b1401c6f2610f7b934a731b60 Mon Sep 17 00:00:00 2001 From: WinterSnowfall Date: Sat, 28 Sep 2024 15:53:02 +0300 Subject: [PATCH] [d3d9] Implement a software cursor --- src/d3d9/d3d9_cursor.cpp | 70 +++++++++++++++++++++++++++++++- src/d3d9/d3d9_cursor.h | 39 +++++++++++++++--- src/d3d9/d3d9_device.cpp | 80 ++++++++++++++++++++++++++++++++----- src/d3d9/d3d9_swapchain.cpp | 26 ++++++++++++ src/d3d9/d3d9_swapchain.h | 4 ++ 5 files changed, 203 insertions(+), 16 deletions(-) diff --git a/src/d3d9/d3d9_cursor.cpp b/src/d3d9/d3d9_cursor.cpp index 0224c776..999075df 100644 --- a/src/d3d9/d3d9_cursor.cpp +++ b/src/d3d9/d3d9_cursor.cpp @@ -6,6 +6,21 @@ namespace dxvk { #ifdef _WIN32 + void D3D9Cursor::ResetCursor() { + ShowCursor(FALSE); + + if (likely(m_hCursor != nullptr)) { + ::DestroyCursor(m_hCursor); + m_hCursor = nullptr; + } else { + m_sCursor.Width = 0; + m_sCursor.Height = 0; + m_sCursor.X = 0; + m_sCursor.Y = 0; + } + } + + void D3D9Cursor::UpdateCursor(int X, int Y) { POINT currentPos = { }; if (::GetCursorPos(¤tPos) && currentPos == POINT{ X, Y }) @@ -15,17 +30,31 @@ namespace dxvk { } + void D3D9Cursor::RefreshSoftwareCursorPosition() { + POINT currentPos = { }; + ::GetCursorPos(¤tPos); + + m_sCursor.X = static_cast(currentPos.x); + m_sCursor.Y = static_cast(currentPos.y); + } + + BOOL D3D9Cursor::ShowCursor(BOOL bShow) { if (likely(m_hCursor != nullptr)) ::SetCursor(bShow ? m_hCursor : nullptr); - else - Logger::debug("D3D9Cursor::ShowCursor: Software cursor not implemented."); return std::exchange(m_visible, bShow); } HRESULT D3D9Cursor::SetHardwareCursor(UINT XHotSpot, UINT YHotSpot, const CursorBitmap& bitmap) { + if (unlikely(IsSoftwareCursor())) { + m_sCursor.Width = 0; + m_sCursor.Height = 0; + m_sCursor.X = 0; + m_sCursor.Y = 0; + } + CursorMask mask; std::memset(mask, ~0, sizeof(mask)); @@ -48,12 +77,43 @@ namespace dxvk { return D3D_OK; } + + + HRESULT D3D9Cursor::SetSoftwareCursor(UINT Width, UINT Height, UINT XHotSpot, UINT YHotSpot) { + // Make sure to hide the win32 cursor + ::SetCursor(nullptr); + + if (unlikely(m_hCursor != nullptr)) { + ::DestroyCursor(m_hCursor); + m_hCursor = nullptr; + } + + m_sCursor.Width = Width; + m_sCursor.Height = Height; + m_sCursor.X = XHotSpot; + m_sCursor.Y = YHotSpot; + + ShowCursor(m_visible); + + return D3D_OK; + } + #else + void D3D9Cursor::ResetCursor() { + Logger::warn("D3D9Cursor::ResetCursor: Not supported on current platform."); + } + + void D3D9Cursor::UpdateCursor(int X, int Y) { Logger::warn("D3D9Cursor::UpdateCursor: Not supported on current platform."); } + void D3D9Cursor::RefreshSoftwareCursorPosition() { + Logger::warn("D3D9Cursor::RefreshSoftwareCursorPosition: Not supported on current platform."); + } + + BOOL D3D9Cursor::ShowCursor(BOOL bShow) { Logger::warn("D3D9Cursor::ShowCursor: Not supported on current platform."); return std::exchange(m_visible, bShow); @@ -65,6 +125,12 @@ namespace dxvk { return D3D_OK; } + + HRESULT D3D9Cursor::SetSoftwareCursor(UINT Width, UINT Height, UINT XHotSpot, UINT YHotSpot) { + Logger::warn("D3D9Cursor::SetSoftwareCursor: Not supported on current platform."); + + return D3D_OK; + } #endif } diff --git a/src/d3d9/d3d9_cursor.h b/src/d3d9/d3d9_cursor.h index b2ca5537..b2b858a3 100644 --- a/src/d3d9/d3d9_cursor.h +++ b/src/d3d9/d3d9_cursor.h @@ -4,15 +4,25 @@ namespace dxvk { - constexpr uint32_t HardwareCursorWidth = 32u; - constexpr uint32_t HardwareCursorHeight = 32u; + /** + * \brief D3D9 Software Cursor + */ + struct D3D9_SOFTWARE_CURSOR { + UINT Width = 0; + UINT Height = 0; + UINT X = 0; + UINT Y = 0; + }; + + constexpr uint32_t HardwareCursorWidth = 32u; + constexpr uint32_t HardwareCursorHeight = 32u; constexpr uint32_t HardwareCursorFormatSize = 4u; constexpr uint32_t HardwareCursorPitch = HardwareCursorWidth * HardwareCursorFormatSize; // Format Size of 4 bytes (ARGB) using CursorBitmap = uint8_t[HardwareCursorHeight * HardwareCursorPitch]; // Monochrome mask (1 bit) - using CursorMask = uint8_t[HardwareCursorHeight * HardwareCursorWidth / 8]; + using CursorMask = uint8_t[HardwareCursorHeight * HardwareCursorWidth / 8]; class D3D9Cursor { @@ -25,18 +35,37 @@ namespace dxvk { } #endif + void ResetCursor(); + void UpdateCursor(int X, int Y); + void RefreshSoftwareCursorPosition(); + BOOL ShowCursor(BOOL bShow); HRESULT SetHardwareCursor(UINT XHotSpot, UINT YHotSpot, const CursorBitmap& bitmap); + HRESULT SetSoftwareCursor(UINT Width, UINT Height, UINT XHotSpot, UINT YHotSpot); + + D3D9_SOFTWARE_CURSOR* GetSoftwareCursor() { + return &m_sCursor; + } + + BOOL IsSoftwareCursor() const { + return m_sCursor.Width > 0 && m_sCursor.Height > 0; + } + + BOOL IsCursorVisible() const { + return m_visible; + } + private: - BOOL m_visible = FALSE; + BOOL m_visible = FALSE; + D3D9_SOFTWARE_CURSOR m_sCursor; #ifdef _WIN32 - HCURSOR m_hCursor = nullptr; + HCURSOR m_hCursor = nullptr; #endif }; diff --git a/src/d3d9/d3d9_device.cpp b/src/d3d9/d3d9_device.cpp index 698dfe1b..ff34e8e0 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -354,14 +354,14 @@ namespace dxvk { hwCursor |= inputWidth <= HardwareCursorWidth || inputHeight <= HardwareCursorHeight; + D3DLOCKED_BOX lockedBox; + HRESULT hr = LockImage(cursorTex, 0, 0, &lockedBox, nullptr, D3DLOCK_READONLY); + if (FAILED(hr)) + return hr; + + const uint8_t* data = reinterpret_cast(lockedBox.pBits); + if (hwCursor) { - D3DLOCKED_BOX lockedBox; - HRESULT hr = LockImage(cursorTex, 0, 0, &lockedBox, nullptr, D3DLOCK_READONLY); - if (FAILED(hr)) - return hr; - - const uint8_t* data = reinterpret_cast(lockedBox.pBits); - // Windows works with a stride of 128, lets respect that. // Copy data to the bitmap... CursorBitmap bitmap = { 0 }; @@ -376,10 +376,58 @@ namespace dxvk { // Set this as our cursor. return m_cursor.SetHardwareCursor(XHotSpot, YHotSpot, bitmap); + } else { + // The cursor bitmap passed by the application has the potential + // to not be clipped to the correct dimensions, so we need to + // discard any transparent edges and keep only a tight rectangle + // bounded by the cursor's visible edge pixels + uint32_t leftEdge = inputWidth * HardwareCursorFormatSize; + uint32_t topEdge = inputHeight; + uint32_t rightEdge = 0; + uint32_t bottomEdge = 0; + + uint32_t rowPitch = inputWidth * HardwareCursorFormatSize; + + for (uint32_t h = 0; h < inputHeight; h++) { + uint32_t rowOffset = h * rowPitch; + for (uint32_t w = 0; w < rowPitch; w += HardwareCursorFormatSize) { + // Examine only pixels with non-zero alpha + if (data[rowOffset + w + 3] != 0) { + if (leftEdge > w) leftEdge = w; + if (topEdge > h) topEdge = h; + if (rightEdge < w) rightEdge = w; + if (bottomEdge < h) bottomEdge = h; + } + } + } + leftEdge /= HardwareCursorFormatSize; + rightEdge /= HardwareCursorFormatSize; + + if (leftEdge > rightEdge || topEdge > bottomEdge) { + UnlockImage(cursorTex, 0, 0); + + return D3DERR_INVALIDCALL; + } + + // Calculate clipped bitmap dimensions + uint32_t clippedInputWidth = rightEdge + 1 - leftEdge + 1; + uint32_t clippedInputHeight = bottomEdge + 1 - topEdge + 1; + // Windows works with a stride of 128, lets respect that. + uint32_t clippedCopyPitch = clippedInputWidth * HardwareCursorFormatSize; + + std::vector clippedBitmap(clippedInputHeight * clippedCopyPitch, 0); + + for (uint32_t h = 0; h < clippedInputHeight; h++) + std::memcpy(&clippedBitmap[h * clippedCopyPitch], + &data[(h + topEdge) * lockedBox.RowPitch + leftEdge * HardwareCursorFormatSize], clippedCopyPitch); + + UnlockImage(cursorTex, 0, 0); + + m_implicitSwapchain->SetCursorTexture(clippedInputWidth, clippedInputHeight, &clippedBitmap[0]); + + return m_cursor.SetSoftwareCursor(clippedInputWidth, clippedInputHeight, XHotSpot, YHotSpot); } - // Software Cursor... - Logger::warn("D3D9DeviceEx::SetCursorProperties: Software cursor not implemented."); return D3D_OK; } @@ -459,6 +507,7 @@ namespace dxvk { } m_flags.clr(D3D9DeviceFlag::InScene); + m_cursor.ResetCursor(); /* * Before calling the IDirect3DDevice9::Reset method for a device, @@ -3852,6 +3901,19 @@ namespace dxvk { HWND hDestWindowOverride, const RGNDATA* pDirtyRegion, DWORD dwFlags) { + + if (m_cursor.IsSoftwareCursor()) { + m_cursor.RefreshSoftwareCursorPosition(); + + D3D9_SOFTWARE_CURSOR* pSoftwareCursor = m_cursor.GetSoftwareCursor(); + + UINT cursorWidth = m_cursor.IsCursorVisible() ? pSoftwareCursor->Width : 0; + UINT cursorHeight = m_cursor.IsCursorVisible() ? pSoftwareCursor->Height : 0; + + m_implicitSwapchain->SetCursorPosition(pSoftwareCursor->X, pSoftwareCursor->Y, + cursorWidth, cursorHeight); + } + return m_implicitSwapchain->Present( pSourceRect, pDestRect, diff --git a/src/d3d9/d3d9_swapchain.cpp b/src/d3d9/d3d9_swapchain.cpp index 902e7f6f..a36df9df 100644 --- a/src/d3d9/d3d9_swapchain.cpp +++ b/src/d3d9/d3d9_swapchain.cpp @@ -748,6 +748,32 @@ namespace dxvk { } + void D3D9SwapChainEx::SetCursorTexture(UINT Width, UINT Height, uint8_t* pCursorBitmap) { + VkExtent2D cursorSize = { uint32_t(Width), uint32_t(Height) }; + + m_blitter->setCursorTexture( + cursorSize, + VK_FORMAT_B8G8R8A8_UNORM, + (void *) pCursorBitmap); + } + + + void D3D9SwapChainEx::SetCursorPosition(UINT X, UINT Y, UINT Width, UINT Height) { + VkOffset2D cursorPosition = { int32_t(X), int32_t(Y) }; + VkExtent2D cursorSize = { uint32_t(Width), uint32_t(Height) }; + + VkRect2D cursorRect = { cursorPosition, cursorSize }; + + m_parent->EmitCs([ + cBlitter = m_blitter, + cRect = cursorRect + ] (DxvkContext* ctx) { + cBlitter->setCursorPos( + cRect); + }); + } + + HRESULT D3D9SwapChainEx::SetDialogBoxMode(bool bEnableDialogs) { D3D9DeviceLock lock = m_parent->LockDevice(); diff --git a/src/d3d9/d3d9_swapchain.h b/src/d3d9/d3d9_swapchain.h index ac0b36ec..b28f3232 100644 --- a/src/d3d9/d3d9_swapchain.h +++ b/src/d3d9/d3d9_swapchain.h @@ -118,6 +118,10 @@ namespace dxvk { void Invalidate(HWND hWindow); + void SetCursorTexture(UINT Width, UINT Height, uint8_t* pCursorBitmap); + + void SetCursorPosition(UINT X, UINT Y, UINT Width, UINT Height); + HRESULT SetDialogBoxMode(bool bEnableDialogs); D3D9Surface* GetBackBuffer(UINT iBackBuffer);