From c12889218c1c9d318d8cc34c18c387714227c0fc 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 | 56 +++++++++++++- src/d3d9/d3d9_cursor.h | 34 +++++++-- src/d3d9/d3d9_device.cpp | 157 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 232 insertions(+), 15 deletions(-) diff --git a/src/d3d9/d3d9_cursor.cpp b/src/d3d9/d3d9_cursor.cpp index 0224c776b4ee..b64c8cbbe936 100644 --- a/src/d3d9/d3d9_cursor.cpp +++ b/src/d3d9/d3d9_cursor.cpp @@ -6,6 +6,20 @@ namespace dxvk { #ifdef _WIN32 + void D3D9Cursor::ResetCursor() { + ShowCursor(FALSE); + + if (likely(m_hCursor != nullptr)) { + ::DestroyCursor(m_hCursor); + m_hCursor = nullptr; + } else { + m_sCursor.Bitmap = nullptr; + m_sCursor.X = 0.0f; + m_sCursor.Y = 0.0f; + } + } + + void D3D9Cursor::UpdateCursor(int X, int Y) { POINT currentPos = { }; if (::GetCursorPos(¤tPos) && currentPos == POINT{ X, Y }) @@ -15,11 +29,18 @@ 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); } @@ -48,12 +69,37 @@ namespace dxvk { return D3D_OK; } + + + HRESULT D3D9Cursor::SetSoftwareCursor(UINT XHotSpot, UINT YHotSpot, Com pCursorBitmap) { + // Make sure to hide the win32 cursor + ::SetCursor(nullptr); + + m_sCursor.Bitmap = pCursorBitmap; + m_sCursor.X = static_cast(XHotSpot); + m_sCursor.Y = static_cast(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 +111,12 @@ namespace dxvk { return D3D_OK; } + + HRESULT D3D9Cursor::SetSoftwareCursor(UINT XHotSpot, UINT YHotSpot, Com pCursorBitmap) { + 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 b2ca5537d023..b2a9eb05ed84 100644 --- a/src/d3d9/d3d9_cursor.h +++ b/src/d3d9/d3d9_cursor.h @@ -4,15 +4,24 @@ namespace dxvk { - constexpr uint32_t HardwareCursorWidth = 32u; - constexpr uint32_t HardwareCursorHeight = 32u; + /** + * \brief D3D9 Software Cursor + */ + struct D3D9_SOFTWARE_CURSOR { + Com Bitmap; + float X = 0.0f; + float Y = 0.0f; + }; + + 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 +34,33 @@ 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 XHotSpot, UINT YHotSpot, Com pCursorBitmap); + + D3D9_SOFTWARE_CURSOR* GetSoftwareCursor() { + return &m_sCursor; + } + + 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 4fa7b8c9854c..75c158c11947 100644 --- a/src/d3d9/d3d9_device.cpp +++ b/src/d3d9/d3d9_device.cpp @@ -352,14 +352,14 @@ namespace dxvk { hwCursor |= inputWidth <= HardwareCursorWidth || inputHeight <= HardwareCursorHeight; - if (hwCursor) { - D3DLOCKED_BOX lockedBox; - HRESULT hr = LockImage(cursorTex, 0, 0, &lockedBox, nullptr, D3DLOCK_READONLY); - if (FAILED(hr)) - return hr; + 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); + const uint8_t* data = reinterpret_cast(lockedBox.pBits); + if (hwCursor) { // Windows works with a stride of 128, lets respect that. // Copy data to the bitmap... CursorBitmap bitmap = { 0 }; @@ -374,10 +374,67 @@ 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) + 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; + + // Create a texture buffer to copy the cursor bitmap into + Com tex; + HRESULT hr = CreateTexture(clippedInputWidth, clippedInputHeight, 0, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &tex, NULL); + if (FAILED(hr)) + return hr; + + auto* destTex = GetCommonTexture(tex.ptr()); + + D3DLOCKED_BOX lockedBoxDest; + hr = LockImage(destTex, 0, 0, &lockedBoxDest, nullptr, D3DLOCK_DISCARD); + if (FAILED(hr)) + return hr; + + uint8_t* dataDest = reinterpret_cast(lockedBoxDest.pBits); + + for (uint32_t h = 0; h < clippedInputHeight; h++) + std::memcpy(&dataDest[h * clippedCopyPitch], + &data[(h + topEdge) * lockedBox.RowPitch + leftEdge * HardwareCursorFormatSize], clippedCopyPitch); + + UnlockImage(cursorTex, 0, 0); + UnlockImage(destTex, 0, 0); + + return m_cursor.SetSoftwareCursor(XHotSpot, YHotSpot, tex); } - // Software Cursor... - Logger::warn("D3D9DeviceEx::SetCursorProperties: Software cursor not implemented."); return D3D_OK; } @@ -457,6 +514,7 @@ namespace dxvk { } m_flags.clr(D3D9DeviceFlag::InScene); + m_cursor.ResetCursor(); /* * Before calling the IDirect3DDevice9::Reset method for a device, @@ -3833,6 +3891,89 @@ namespace dxvk { HWND hDestWindowOverride, const RGNDATA* pDirtyRegion, DWORD dwFlags) { + D3D9_SOFTWARE_CURSOR* pSoftwareCursor = m_cursor.GetSoftwareCursor(); + + if (unlikely(pSoftwareCursor->Bitmap != nullptr && m_cursor.IsCursorVisible())) { + D3DSURFACE_DESC srcDesc; + pSoftwareCursor->Bitmap->GetLevelDesc(0, &srcDesc); + D3DSURFACE_DESC dstDesc; + m_state.renderTargets[0]->GetDesc(&dstDesc); + + m_cursor.RefreshSoftwareCursorPosition(); + + // Calculate cursor quad vertex coordinates based on render target edges and the win32 cursor position + float dstLeft = pSoftwareCursor->X; + float dstTop = pSoftwareCursor->Y; + float dstRight = std::min(pSoftwareCursor->X + static_cast(srcDesc.Width), static_cast(dstDesc.Width)); + float dstBottom = std::min(pSoftwareCursor->Y + static_cast(srcDesc.Height), static_cast(dstDesc.Height)); + // Calculate cursor texture coordinates based on quad rendering coordinates and render target edges + float srcRight = dstRight == static_cast(dstDesc.Width) ? + dstRight - pSoftwareCursor->X : static_cast(srcDesc.Width); + float srcBottom = dstBottom == static_cast(dstDesc.Height) ? + dstBottom - pSoftwareCursor->Y : static_cast(srcDesc.Height); + // Calculate normalized texture coordinates (will be < 1.0f to handle right/bottom edge cursor clipping) + srcRight /= static_cast(srcDesc.Width); + srcBottom /= static_cast(srcDesc.Height); + + struct SWCURSORVERTEX { + FLOAT x, y, z, rhw; + FLOAT u, v; + }; + + std::array quadVertices = {{ + { dstLeft, dstTop, 0.0f, 1.0f, 0.0f, 0.0f }, + { dstRight, dstTop, 0.0f, 1.0f, srcRight, 0.0f }, + { dstLeft, dstBottom, 0.0f, 1.0f, 0.0f, srcBottom }, + { dstRight, dstBottom, 0.0f, 1.0f, srcRight, srcBottom }, + }}; + const size_t quadVerticesSize = quadVertices.size() * sizeof(SWCURSORVERTEX); + + Com pVertexBuffer; + CreateVertexBuffer(quadVerticesSize, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, + D3DFVF_XYZRHW | D3DFVF_TEX1, D3DPOOL_DEFAULT, &pVertexBuffer, NULL); + + void* pVertices; + pVertexBuffer->Lock(0, quadVerticesSize, (void**)&pVertices, D3DLOCK_DISCARD); + memcpy(pVertices, quadVertices.data(), quadVerticesSize); + pVertexBuffer->Unlock(); + + // Capture pre-cursor draw state + DWORD culling; + GetRenderState(D3DRS_CULLMODE, &culling); + DWORD fvf; + GetFVF(&fvf); + DWORD minFilter; + GetSamplerState(0, D3DSAMP_MINFILTER, &minFilter); + DWORD magFilter; + GetSamplerState(0, D3DSAMP_MAGFILTER, &magFilter); + Com tex; + GetTexture(0, &tex); + + // Temporarily disable culling while drawing the cursor quad, as some + // games may not render it at all otherwise, due to their own preset + SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + SetFVF(D3DFVF_XYZRHW | D3DFVF_TEX1); + // Filtering is not needed since our dimensions are exact + // and it only makes the cursor look more blurry + SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_NONE); + SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_NONE); + HRESULT hr = SetTexture(0, pSoftwareCursor->Bitmap.ptr()); + if (FAILED(hr)) + Logger::warn("Failed to set software cursor bitmap texture"); + // TODO: Also check if we need to set D3DRS_ALPHABLENDENABLE manually + + hr = DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, pVertices, sizeof(SWCURSORVERTEX)); + if (FAILED(hr)) + Logger::warn("Failed on software cursor draw call"); + + // Reinstate pre-cursor draw state + SetTexture(0, tex.ptr()); + SetSamplerState(0, D3DSAMP_MINFILTER, minFilter); + SetSamplerState(0, D3DSAMP_MAGFILTER, magFilter); + SetFVF(fvf); + SetRenderState(D3DRS_CULLMODE, culling); + } + return m_implicitSwapchain->Present( pSourceRect, pDestRect,