Skip to content

Commit

Permalink
[d3d9] Implement a software cursor
Browse files Browse the repository at this point in the history
  • Loading branch information
WinterSnowfall committed Sep 29, 2024
1 parent b4e69dc commit 097ad99
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 15 deletions.
37 changes: 35 additions & 2 deletions src/d3d9/d3d9_cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ namespace dxvk {
}


void D3D9Cursor::RefreshSoftwareCursorPosition() {
POINT currentPos = { };
::GetCursorPos(&currentPos);

m_sCursor.X = static_cast<float>(currentPos.x);
m_sCursor.Y = static_cast<float>(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);
}
Expand Down Expand Up @@ -48,12 +55,32 @@ namespace dxvk {

return D3D_OK;
}


HRESULT D3D9Cursor::SetSoftwareCursor(UINT XHotSpot, UINT YHotSpot, Com<IDirect3DTexture9> pCursorBitmap) {
// Make sure to hide the win32 cursor
::SetCursor(nullptr);

m_sCursor.Bitmap = pCursorBitmap;
m_sCursor.X = static_cast<float>(XHotSpot);
m_sCursor.Y = static_cast<float>(YHotSpot);

ShowCursor(m_visible);

return D3D_OK;
}

#else
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);
Expand All @@ -65,6 +92,12 @@ namespace dxvk {

return D3D_OK;
}

HRESULT D3D9Cursor::SetSoftwareCursor(UINT XHotSpot, UINT YHotSpot, Com<IDirect3DTexture9> pCursorBitmap) {
Logger::warn("D3D9Cursor::SetSoftwareCursor: Not supported on current platform.");

return D3D_OK;
}
#endif

}
32 changes: 27 additions & 5 deletions src/d3d9/d3d9_cursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<IDirect3DTexture9> Bitmap;
float X;
float Y;
};

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 {

Expand All @@ -27,16 +36,29 @@ namespace dxvk {

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<IDirect3DTexture9> 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

};
Expand Down
142 changes: 134 additions & 8 deletions src/d3d9/d3d9_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const uint8_t*>(lockedBox.pBits);
const uint8_t* data = reinterpret_cast<const uint8_t*>(lockedBox.pBits);

if (hwCursor) {
// Windows works with a stride of 128, lets respect that.
// Copy data to the bitmap...
CursorBitmap bitmap = { 0 };
Expand All @@ -374,10 +374,64 @@ 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 = UINT_MAX;
uint32_t topEdge = UINT_MAX;
uint32_t rightEdge = 0;
uint32_t bottomEdge = 0;

uint32_t rowPitch = inputWidth * HardwareCursorFormatSize;

for (uint32_t h = 0; h < inputHeight; h++) {
for (uint32_t w = 0; w < rowPitch; w += HardwareCursorFormatSize) {
uint32_t rowOffset = h * rowPitch;
// Examine only pixels with non-zero alpha
if (data[rowOffset + w + 3] != 0) {
leftEdge = std::min(leftEdge, w);
topEdge = std::min(topEdge, h);
rightEdge = std::max(rightEdge, w);
bottomEdge = std::max(bottomEdge, h);
}
}
}
leftEdge /= HardwareCursorFormatSize;
rightEdge /= HardwareCursorFormatSize;

// 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<IDirect3DTexture9> 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<uint8_t*>(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;
}

Expand Down Expand Up @@ -3833,6 +3887,78 @@ 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<float>(srcDesc.Width), static_cast<float>(dstDesc.Width));
float dstBottom = std::min(pSoftwareCursor->Y + static_cast<float>(srcDesc.Height), static_cast<float>(dstDesc.Height));
// Calculate cursor texture coordinates based on quad rendering coordinates and render target edges
float srcRight = dstRight == static_cast<float>(dstDesc.Width) ?
dstRight - pSoftwareCursor->X : static_cast<float>(srcDesc.Width);
float srcBottom = dstBottom == static_cast<float>(dstDesc.Height) ?
dstBottom - pSoftwareCursor->Y : static_cast<float>(srcDesc.Height);
// Calculate normalized texture coordinates (will be < 1.0f to handle right/bottom edge cursor clipping)
srcRight /= static_cast<float>(srcDesc.Width);
srcBottom /= static_cast<float>(srcDesc.Height);

struct SWCURSORVERTEX {
FLOAT x, y, z, rhw;
FLOAT u, v;
};

std::array<SWCURSORVERTEX, 4> 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<IDirect3DVertexBuffer9> 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();

DWORD culling;
GetRenderState(D3DRS_CULLMODE, &culling);
DWORD fvf;
GetFVF(&fvf);
Com<IDirect3DBaseTexture9> 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);
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());
SetFVF(fvf);
SetRenderState(D3DRS_CULLMODE, culling);
}

return m_implicitSwapchain->Present(
pSourceRect,
pDestRect,
Expand Down

0 comments on commit 097ad99

Please sign in to comment.