mirror of
https://github.com/Blinue/Magpie.git
synced 2026-06-24 02:04:10 +00:00
feat: 支持绘制动态光标 (p4)
This commit is contained in:
parent
f68d06d9b7
commit
c6543c8228
3 changed files with 136 additions and 75 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue