feat: 支持绘制动态光标 (p4)

This commit is contained in:
Xu 2026-03-19 16:52:01 +08:00
commit c6543c8228
3 changed files with 136 additions and 75 deletions

View file

@ -178,63 +178,88 @@ void CursorDrawer::PrepareForDraw(HCURSOR hCursor, POINT cursorPos, bool& needRe
}
}
if (hCursor) {
if (_isCursorVisible) {
static const HCURSOR hArrowCursor = STANDARD_CURSORS[0].handle;
// 不要保存指针_ResolveCursor 后可能失效
const _CursorInfoKey oldCursorKey = _curCursorInfoKeyValue ?
_curCursorInfoKeyValue->first : _CursorInfoKey{};
const uint32_t oldFrameSeqIdx = _curFrameSeqIdx;
_curCursorInfoKeyValue = nullptr;
_curFrameSeqIdx = 0;
// 无法解析的光标换为指针光标
if (!_unresolvableCursors.empty() && _unresolvableCursors.contains(hCursor)) {
if (hCursor != hArrowCursor && !_unresolvableCursors.contains(hArrowCursor)) {
hCursor = hArrowCursor;
} else {
hCursor = NULL;
}
if (hCursor && _isCursorVisible) {
static const HCURSOR hArrowCursor = STANDARD_CURSORS[0].handle;
// 无法解析的光标换为指针光标
if (!_unresolvableCursors.empty() && _unresolvableCursors.contains(hCursor)) {
if (hCursor != hArrowCursor && !_unresolvableCursors.contains(hArrowCursor)) {
hCursor = hArrowCursor;
} else {
hCursor = NULL;
}
}
if (hCursor) {
while (true) {
_curCursorInfoKeyValue = _ResolveCursor(hCursor, cursorPos, needRedraw);
if (_curCursorInfoKeyValue) {
const uint32_t cursorFrameIdx = 0;
const _CursorInfo& cursorInfo = _curCursorInfoKeyValue->second;
const _CursorFrame& curCursorFrame = _curCursorInfoKeyValue->second.frames[cursorFrameIdx];
// 检查光标是否在视口内。hCursor 不为 NULL 说明光标已在缩放窗口内,因此这个检查很少失败
const RECT cursorRect = {
cursorPos.x - (LONG)curCursorFrame.hotspot.x,
cursorPos.y - (LONG)curCursorFrame.hotspot.y,
cursorRect.left + (LONG)cursorInfo.size.width,
cursorRect.top + (LONG)cursorInfo.size.height
};
if (!Win32Helper::IsRectOverlap(cursorRect, _destRect)) {
hCursor = NULL;
while (hCursor) {
_curCursorInfoKeyValue = _ResolveCursor(hCursor, cursorPos);
if (_curCursorInfoKeyValue) {
const _CursorInfo& curCursorInfo = _curCursorInfoKeyValue->second;
if (curCursorInfo.IsAnimated()) {
auto now = std::chrono::steady_clock::now();
if (oldCursorKey == _curCursorInfoKeyValue->first) {
_curFrameSeqIdx = oldFrameSeqIdx;
// 计算新帧序号
while (now >= _curFrameSeqEndTime) {
_curFrameSeqIdx = (_curFrameSeqIdx + 1) %
(uint32_t)curCursorInfo.frameSequence.size();
_curFrameSeqEndTime += curCursorInfo.frameSequence[_curFrameSeqIdx].second;
}
break;
} else {
_unresolvableCursors.insert(hCursor);
if (hCursor == hArrowCursor) {
// 无法解析指针光标
hCursor = NULL;
break;
} else {
// 换为指针光标
hCursor = hArrowCursor;
}
_curFrameSeqEndTime = now + curCursorInfo.frameSequence[0].second;
}
}
const _CursorFrame& curCursorFrame =
curCursorInfo.frames[curCursorInfo.GetFrameIdx(_curFrameSeqIdx)];
// 检查光标是否在视口内。hCursor 不为 NULL 说明光标已在缩放窗口内,因此这个检查很少失败
const RECT cursorRect = {
cursorPos.x - (LONG)curCursorFrame.hotspot.x,
cursorPos.y - (LONG)curCursorFrame.hotspot.y,
cursorRect.left + (LONG)curCursorInfo.size.width,
cursorRect.top + (LONG)curCursorInfo.size.height
};
if (!Win32Helper::IsRectOverlap(cursorRect, _destRect)) {
_curCursorInfoKeyValue = nullptr;
}
break;
} else {
_unresolvableCursors.insert(hCursor);
if (hCursor == hArrowCursor) {
// 无法解析指针光标
break;
} else {
// 换为指针光标
hCursor = hArrowCursor;
}
}
} else {
// 截屏时暂时不渲染光标
hCursor = NULL;
}
}
// 光标形状或位置变化时总是需要重新绘制;有时即使不变也需要重新绘制,比如色域
// 或 _cursorBaseSize 变化后。
needRedraw |= hCursor != _hCurCursor || (hCursor && cursorPos != _curCursorPos);
_CursorInfoKey newCursorKey = _curCursorInfoKeyValue ?
_curCursorInfoKeyValue->first : _CursorInfoKey{};
needRedraw = newCursorKey != oldCursorKey;
if (_curCursorInfoKeyValue) {
needRedraw |= _curFrameSeqIdx != oldFrameSeqIdx;
needRedraw |= cursorPos != _curCursorPos;
}
if (needRedraw) {
_hCurCursor = hCursor;
_curCursorPos = cursorPos;
}
}
@ -246,13 +271,12 @@ HRESULT CursorDrawer::Draw(
uint32_t curFrameSrvOffset,
ID3D12Resource* backBuffer
) noexcept {
if (!_hCurCursor || !_curCursorInfoKeyValue) {
if (!_curCursorInfoKeyValue) {
return S_OK;
}
const uint32_t cursorFrameIdx = 0;
_CursorInfo& cursorInfo = _curCursorInfoKeyValue->second;
const uint32_t cursorFrameIdx = cursorInfo.GetFrameIdx(_curFrameSeqIdx);
_CursorFrame& cursorFrame = cursorInfo.frames[cursorFrameIdx];
cursorInfo.lastUseFenceValue = nextFenceValue;
@ -550,8 +574,7 @@ static bool GetCursorSizeUnderDpi96(HCURSOR hCursor, Size& cursorSize) noexcept
std::pair<const CursorDrawer::_CursorInfoKey, CursorDrawer::_CursorInfo>* CursorDrawer::_ResolveCursor(
HCURSOR hCursor,
POINT cursorPos,
bool& needRedraw
POINT cursorPos
) noexcept {
assert(hCursor);
@ -673,8 +696,6 @@ std::pair<const CursorDrawer::_CursorInfoKey, CursorDrawer::_CursorInfo>* Cursor
}
}
needRedraw = true;
return &*_cursorInfos.emplace(_CursorInfoKey{ hCursor, isCursorDpiAware ? monitorDpi : 0 },
std::move(cursorInfo)).first;
}
@ -1023,7 +1044,15 @@ HRESULT CursorDrawer::_InitializeCursorTexture(
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
if (!curCursorFrame.resTexture) {
if (curCursorFrame.resTexture) {
// 如果可以复用 resTexture 说明执行了缩放resTexture 目前处于
// PIXEL_SHADER_RESOURCE 状态。
graphicsContext.InsertTransitionBarrier(
curCursorFrame.resTexture.get(),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_COPY_DEST
);
} else {
HRESULT hr = device->CreateCommittedResource(
&heapProps,
heapFlags,
@ -1208,7 +1237,6 @@ HRESULT CursorDrawer::_InitializeCursorTexture(
void CursorDrawer::_ClearCursorInfos() noexcept {
_lastRawCursorHandle = NULL;
_hCurCursor = NULL;
_curCursorInfoKeyValue = nullptr;
auto& csuDescriptorHeap = _d3d12Context->GetDescriptorHeap();

View file

@ -124,6 +124,14 @@ private:
// 序列表 (帧索引值数组),使多帧可以复用同一个 _CursorFrame。为空表示顺序播放
SmallVector<std::pair<uint32_t, std::chrono::nanoseconds>, 0> frameSequence;
uint64_t lastUseFenceValue = 0;
bool IsAnimated() const noexcept {
return !frameSequence.empty();
}
uint32_t GetFrameIdx(uint32_t seqIdx) const noexcept {
return IsAnimated() ? frameSequence[seqIdx].first : 0;
}
void FreeDescriptors(
DescriptorHeap& csuDescriptorHeap,
@ -133,8 +141,7 @@ private:
std::pair<const _CursorInfoKey, _CursorInfo>* _ResolveCursor(
HCURSOR hCursor,
POINT cursorPos,
bool& needRedraw
POINT cursorPos
) noexcept;
Size _CalcCursorSize(
@ -207,9 +214,11 @@ private:
HCURSOR _lastRawCursorHandle = NULL;
std::chrono::steady_clock::time_point _lastCursorActiveTime;
// 上次绘制的光标形状和位置
HCURSOR _hCurCursor = NULL;
POINT _curCursorPos{ std::numeric_limits<LONG>::max(), std::numeric_limits<LONG>::max() };
std::pair<const _CursorInfoKey, _CursorInfo>* _curCursorInfoKeyValue = nullptr;
POINT _curCursorPos{ std::numeric_limits<LONG>::max(), std::numeric_limits<LONG>::max() };
// 这两个成员用于保存动态光标状态
uint32_t _curFrameSeqIdx = 0;
std::chrono::steady_clock::time_point _curFrameSeqEndTime;
// 用于从渲染目标复制光标下区域
winrt::com_ptr<ID3D12Resource> _tempOriginTexture;

View file

@ -315,11 +315,7 @@ static bool LoadAniFromFileMap(
ANIHEADER aniHeader{};
uint32_t curFrameIdx = 0;
while (true) {
if (fileData + sizeof(RTAG) > fileEnd) {
break;
}
while (fileData + sizeof(RTAG) < fileEnd) {
RTAG tag = *(RTAG*)fileData;
fileData += sizeof(RTAG);
@ -388,15 +384,11 @@ static bool LoadAniFromFileMap(
return false;
}
if (curFrameIdx >= aniHeader.cFrames) {
if (curFrameIdx == aniHeader.cFrames) {
break;
}
while (true) {
if (fileData + sizeof(RTAG) > chunkEnd) {
break;
}
while (fileData + sizeof(RTAG) < chunkEnd) {
tag = *(RTAG*)fileData;
fileData += sizeof(RTAG);
@ -414,7 +406,7 @@ static bool LoadAniFromFileMap(
return false;
}
if (curFrameIdx >= aniHeader.cFrames) {
if (curFrameIdx == aniHeader.cFrames) {
break;
}
}
@ -472,22 +464,54 @@ static bool LoadAniFromFileMap(
fileData = chunkEnd;
}
if (frames.empty() || curFrameIdx != frames.size()) {
// 确保所有帧都已提取
if (frames.empty() || curFrameIdx != aniHeader.cFrames) {
return false;
}
if (!frameSequence.empty()) {
// 只有一帧时 frameSequence 为空
if (frameSequence.empty()) {
return true;
}
for (const auto& pair : frameSequence) {
// 确保持续时间不为 0
if (pair.second.count() == 0) {
return false;
}
}
if (aniHeader.fl & AF_SEQUENCE) {
std::vector<bool> frameInUse(aniHeader.cFrames);
for (const auto& pair : frameSequence) {
if (pair.second.count() == 0) {
// 检查序列是否合法
if (pair.first >= aniHeader.cFrames) {
return false;
}
frameInUse[pair.first] = true;
}
// 删除未被使用的帧
for (int i = aniHeader.cFrames - 1; i >= 0; --i) {
if (frameInUse[i]) {
continue;
}
frames.erase(frames.begin() + i);
// 删除一帧后调整索引
for (auto& pair : frameSequence) {
if (pair.first > (uint32_t)i) {
--pair.first;
}
}
}
// 存在 AF_SEQUENCE 标志时确保存在 seq 块
if ((aniHeader.fl & AF_SEQUENCE) &&
frameSequence[0].first == std::numeric_limits<uint32_t>::max()) {
return false;
// 只剩一帧则不是动态光标
if (frames.size() == 1) {
frameSequence.clear();
}
}