Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[d3d9] Implement a software cursor #4302

Merged
merged 2 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions src/d3d9/d3d9_cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(&currentPos) && currentPos == POINT{ X, Y })
Expand All @@ -15,17 +30,35 @@ namespace dxvk {
}


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

m_sCursor.X = static_cast<UINT>(currentPos.x);
m_sCursor.Y = static_cast<UINT>(currentPos.y);
}


BOOL D3D9Cursor::ShowCursor(BOOL bShow) {
// Cursor visibility remains unchanged (typically FALSE) if the cursor isn't set.
if (unlikely(m_hCursor == nullptr && m_sCursor.Width == 0 && m_sCursor.Height == 0))
return m_visible;

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));

Expand All @@ -48,12 +81,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);
Expand All @@ -65,6 +129,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

}
39 changes: 34 additions & 5 deletions src/d3d9/d3d9_cursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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

};
Expand Down
90 changes: 81 additions & 9 deletions src/d3d9/d3d9_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,23 +345,33 @@ namespace dxvk {
uint32_t inputWidth = cursorTex->Desc()->Width;
uint32_t inputHeight = cursorTex->Desc()->Height;

// Always use a hardware cursor when windowed.
// Check if surface dimensions are powers of two.
if ((inputWidth && (inputWidth & (inputWidth - 1)))
|| (inputHeight && (inputHeight & (inputHeight - 1))))
return D3DERR_INVALIDCALL;

D3DPRESENT_PARAMETERS params;
m_implicitSwapchain->GetPresentParameters(&params);

if (inputWidth > params.BackBufferWidth
|| inputHeight > params.BackBufferHeight)
return D3DERR_INVALIDCALL;

// Always use a hardware cursor when windowed.
bool hwCursor = params.Windowed;

// Always use a hardware cursor w/h <= 32 px
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 @@ -376,10 +386,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<uint8_t> 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;
}

Expand Down Expand Up @@ -459,6 +517,7 @@ namespace dxvk {
}

m_flags.clr(D3D9DeviceFlag::InScene);
m_cursor.ResetCursor();

/*
* Before calling the IDirect3DDevice9::Reset method for a device,
Expand Down Expand Up @@ -3852,6 +3911,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,
Expand Down
26 changes: 26 additions & 0 deletions src/d3d9/d3d9_swapchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
4 changes: 4 additions & 0 deletions src/d3d9/d3d9_swapchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down