Skip to content

Commit 83b3af7

Browse files
committed
feat: 实现绘制光标 (p7)
处理光标色域
1 parent 34bd6b9 commit 83b3af7

File tree

9 files changed

+153
-40
lines changed

9 files changed

+153
-40
lines changed

src/Magpie.Core/ColorHelper.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
namespace Magpie {
4+
5+
struct ColorHelper {
6+
static float SrgbToLinear(uint8_t c) noexcept {
7+
static std::array<float, 256> lut = [] {
8+
std::array<float, 256> result{};
9+
for (uint32_t i = 0; i < 256; ++i) {
10+
float c = i / 255.0f;
11+
if (c <= 0.04045f) {
12+
result[i] = c / 12.92f * 255.0f;
13+
} else {
14+
result[i] = std::pow((c + 0.055f) / 1.055f, 2.4f) * 255.0f;
15+
}
16+
}
17+
return result;
18+
}();
19+
20+
return lut[c];
21+
}
22+
};
23+
24+
}

src/Magpie.Core/CursorDrawer2.cpp

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "pch.h"
22
#include "CursorDrawer2.h"
33
#include "ByteBuffer.h"
4+
#include "ColorHelper.h"
45
#include "CursorHelper.h"
56
#include "DynamicDescriptorHeap.h"
67
#include "GraphicsContext.h"
@@ -9,6 +10,7 @@
910
#include "shaders/CursorVS.h"
1011
#include "shaders/SimplePS.h"
1112
#include "Win32Helper.h"
13+
#include <DirectXPackedVector.h>
1214
#include <ShellScalingApi.h>
1315
#include <wil/registry.h>
1416

@@ -265,6 +267,12 @@ HRESULT CursorDrawer2::Draw(D3D12_GPU_DESCRIPTOR_HANDLE heapGpuHandle) noexcept
265267
return S_OK;
266268
}
267269

270+
HRESULT CursorDrawer2::OnColorInfoChanged(const ColorInfo& colorInfo) noexcept {
271+
_colorInfo = colorInfo;
272+
_colorPSO = nullptr;
273+
return S_OK;
274+
}
275+
268276
static Size CalcCursorSize(
269277
Size cursorBmpSize,
270278
uint32_t cursorDpi,
@@ -499,7 +507,7 @@ wil::unique_hcursor CursorDrawer2::_TryResolveStandardCursor(
499507
Logger::Get().ComError("wil::ExpandEnvironmentStringsW 失败", hr);
500508
return result;
501509
}
502-
510+
503511
result = CursorHelper::ExtractCursorFromCurFile(curPath.c_str(), preferedWidth);
504512
if (!result) {
505513
Logger::Get().Error("CursorHelper::ExtractCursorFromCurFile 失败");
@@ -513,7 +521,7 @@ bool CursorDrawer2::_ResolveCursorPixels(
513521
_CursorInfo& cursorInfo,
514522
HBITMAP hColorBmp,
515523
HBITMAP hMaskBmp
516-
) noexcept {
524+
) const noexcept {
517525
const Size bmpSize = {
518526
cursorInfo.originSize.width,
519527
hColorBmp ? cursorInfo.originSize.height : cursorInfo.originSize.height * 2
@@ -532,17 +540,15 @@ bool CursorDrawer2::_ResolveCursorPixels(
532540
}
533541
};
534542

535-
ByteBuffer& pixels = cursorInfo.originPixels;
536-
pixels.Resize(bi.bmiHeader.biSizeImage);
537-
543+
ByteBuffer pixels(bi.bmiHeader.biSizeImage);
538544
wil::unique_hdc_window hdcScreen(wil::window_dc(GetDC(NULL)));
539545
if (GetDIBits(hdcScreen.get(), hColorBmp ? hColorBmp : hMaskBmp, 0, bmpSize.height,
540546
pixels.Data(), &bi, DIB_RGB_COLORS) != (int)bmpSize.height
541547
) {
542548
Logger::Get().Win32Error("GetDIBits 失败");
543549
return false;
544550
}
545-
551+
546552
if (hColorBmp) {
547553
// 彩色掩码光标和彩色光标的区别在于前者的透明通道全为 0
548554
bool hasAlpha = false;
@@ -556,16 +562,32 @@ bool CursorDrawer2::_ResolveCursorPixels(
556562
if (hasAlpha) {
557563
// 彩色光标
558564
cursorInfo.type = _CursorType::Color;
565+
cursorInfo.originTextureData.Resize(bi.bmiHeader.biSizeImage * 2);
566+
567+
using namespace DirectX::PackedVector;
568+
569+
HALF* textureData = (HALF*)cursorInfo.originTextureData.Data();
559570

560571
for (uint32_t i = 0; i < bi.bmiHeader.biSizeImage; i += 4) {
561572
// 预乘 Alpha 通道
562-
double alpha = pixels[i + 3] / 255.0f;
573+
float alpha = pixels[i + 3] / 255.0f;
574+
float factor = alpha / 255.0f;
575+
576+
textureData[i + 3] = XMConvertFloatToHalf(1.0f - alpha);
577+
578+
if (_colorInfo.kind == winrt::AdvancedColorKind::StandardDynamicRange) {
579+
textureData[i] = XMConvertFloatToHalf(pixels[i + 2] * factor);
580+
textureData[i + 1] = XMConvertFloatToHalf(pixels[i + 1] * factor);
581+
textureData[i + 2] = XMConvertFloatToHalf(pixels[i] * factor);
582+
} else {
583+
if (_colorInfo.kind == winrt::AdvancedColorKind::HighDynamicRange) {
584+
factor *= _colorInfo.sdrWhiteLevel;
585+
}
563586

564-
uint8_t b = (uint8_t)std::lround(pixels[i] * alpha);
565-
pixels[i] = (uint8_t)std::lround(pixels[i + 2] * alpha);
566-
pixels[i + 1] = (uint8_t)std::lround(pixels[i + 1] * alpha);
567-
pixels[i + 2] = b;
568-
pixels[i + 3] = 255 - pixels[i + 3];
587+
textureData[i] = XMConvertFloatToHalf(ColorHelper::SrgbToLinear(pixels[i + 2]) * factor);
588+
textureData[i + 1] = XMConvertFloatToHalf(ColorHelper::SrgbToLinear(pixels[i + 1]) * factor);
589+
textureData[i + 2] = XMConvertFloatToHalf(ColorHelper::SrgbToLinear(pixels[i]) * factor);
590+
}
569591
}
570592
} else {
571593
// 彩色掩码光标。如果不需要应用 XOR 则转换成彩色光标
@@ -593,6 +615,7 @@ bool CursorDrawer2::_ResolveCursorPixels(
593615
if (canConvertToColor) {
594616
// 转换为彩色光标以获得更好的插值效果和渲染性能
595617
cursorInfo.type = _CursorType::Color;
618+
cursorInfo.originTextureData.Resize(bi.bmiHeader.biSizeImage * 2);
596619

597620
for (uint32_t i = 0; i < bi.bmiHeader.biSizeImage; i += 4) {
598621
if (maskPixels[i] == 0) {
@@ -612,6 +635,8 @@ bool CursorDrawer2::_ResolveCursorPixels(
612635
std::swap(pixels[i], pixels[i + 2]);
613636
pixels[i + 3] = maskPixels[i];
614637
}
638+
639+
cursorInfo.originTextureData = std::move(pixels);
615640
}
616641
}
617642
} else {
@@ -631,6 +656,7 @@ bool CursorDrawer2::_ResolveCursorPixels(
631656
if (canConvertToColor) {
632657
// 转换为彩色光标以获得更好的插值效果和渲染性能
633658
cursorInfo.type = _CursorType::Color;
659+
cursorInfo.originTextureData.Resize(bi.bmiHeader.biSizeImage * 2);
634660

635661
for (uint32_t i = 0; i < halfSize; i += 4) {
636662
// 上半部分是 AND 掩码,下半部分是 XOR 掩码
@@ -664,6 +690,8 @@ bool CursorDrawer2::_ResolveCursorPixels(
664690
upperPtr += 4;
665691
lowerPtr += 4;
666692
}
693+
694+
cursorInfo.originTextureData = std::move(pixels);
667695
}
668696
}
669697

@@ -678,7 +706,7 @@ HRESULT CursorDrawer2::_InitializeCursorTexture(_CursorInfo& cursorInfo) noexcep
678706
CD3DX12_HEAP_PROPERTIES heapProperties(D3D12_HEAP_TYPE_UPLOAD);
679707

680708
CD3DX12_RESOURCE_DESC texDesc = CD3DX12_RESOURCE_DESC::Tex2D(
681-
cursorInfo.type == _CursorType::Monochrome ? DXGI_FORMAT_R8_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM,
709+
cursorInfo.type == _CursorType::Monochrome ? DXGI_FORMAT_R8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT,
682710
cursorInfo.originSize.width, cursorInfo.originSize.height, 1, 1);
683711

684712
D3D12_PLACED_SUBRESOURCE_FOOTPRINT textureLayout;
@@ -688,7 +716,7 @@ HRESULT CursorDrawer2::_InitializeCursorTexture(_CursorInfo& cursorInfo) noexcep
688716
&textureLayout, nullptr, &textureRowSizeInBytes, &textureSize);
689717

690718
assert(textureRowSizeInBytes == cursorInfo.originSize.width *
691-
(cursorInfo.type == _CursorType::Monochrome ? 1 : 4));
719+
(cursorInfo.type == _CursorType::Monochrome ? 1 : 8));
692720

693721
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(textureSize);
694722

@@ -728,20 +756,20 @@ HRESULT CursorDrawer2::_InitializeCursorTexture(_CursorInfo& cursorInfo) noexcep
728756
}
729757

730758
if (textureRowSizeInBytes == textureLayout.Footprint.RowPitch) {
731-
std::memcpy(pData, cursorInfo.originPixels.Data(), textureRowSizeInBytes * cursorInfo.originSize.height);
759+
std::memcpy(pData, cursorInfo.originTextureData.Data(), textureRowSizeInBytes * cursorInfo.originSize.height);
732760
} else {
733761
for (uint32_t i = 0; i < cursorInfo.originSize.height; ++i) {
734762
std::memcpy(
735763
(uint8_t*)pData + textureLayout.Footprint.RowPitch * i,
736-
&cursorInfo.originPixels[(uint32_t)textureRowSizeInBytes * i],
764+
&cursorInfo.originTextureData[(uint32_t)textureRowSizeInBytes * i],
737765
textureRowSizeInBytes
738766
);
739767
}
740768
}
741769

742770
cursorInfo.originUploadBuffer->Unmap(0, nullptr);
743771

744-
cursorInfo.originPixels.Clear();
772+
cursorInfo.originTextureData.Clear();
745773

746774
ID3D12GraphicsCommandList* commandList = _graphicsContext->GetCommandList();
747775

@@ -833,7 +861,7 @@ HRESULT CursorDrawer2::_CreateColorPSO() noexcept {
833861
.PS = CD3DX12_SHADER_BYTECODE(SimplePS, sizeof(SimplePS)),
834862
.BlendState = {
835863
.RenderTarget = {{
836-
// FinalColor = ScreenColor * CursorColor.a + CursorColor.rgb
864+
// FinalColor = CursorColor.rgb + ScreenColor * CursorColor.a
837865
.BlendEnable = TRUE,
838866
.SrcBlend = D3D12_BLEND_ONE,
839867
.DestBlend = D3D12_BLEND_SRC_ALPHA,
@@ -852,8 +880,8 @@ HRESULT CursorDrawer2::_CreateColorPSO() noexcept {
852880
.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
853881
.NumRenderTargets = 1,
854882
.RTVFormats = { _colorInfo.kind == winrt::AdvancedColorKind::StandardDynamicRange ?
855-
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R16G16B16A16_FLOAT },
856-
.SampleDesc = {.Count = 1 }
883+
DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT },
884+
.SampleDesc = { .Count = 1 }
857885
};
858886
HRESULT hr = _graphicsContext->GetDevice()->CreateGraphicsPipelineState(
859887
&psoDesc, IID_PPV_ARGS(&_colorPSO));

src/Magpie.Core/CursorDrawer2.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,15 @@ class CursorDrawer2 {
5959
_destRect = destRect;
6060
}
6161

62+
HRESULT OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
63+
6264
private:
63-
// 所有光标纹理使用线性 RGB 空间
65+
// SDR 色域下使用 sRGB 空间,否则使用线性 RGB 空间。截至 Win11 25H2,Windows 在 WCG
66+
// 和 HDR 下光标的色域和透明度经常变化,没有统一标准。
6467
enum class _CursorType {
6568
// 彩色光标
66-
// 纹理格式: DXGI_FORMAT_R8G8B8A8_UNORM
67-
// 计算公式: FinalColor = ScreenColor * CursorColor.a + CursorColor.rgb
69+
// 纹理格式: DXGI_FORMAT_R16G16B16A16_FLOAT
70+
// 计算公式: FinalColor = CursorColor.rgb + ScreenColor * CursorColor.a
6871
// 纹理中 RGB 通道已预乘 A 通道 (premultiplied alpha),A 通道已预先取反,这是为了
6972
// 减少着色器的计算量以及确保 (可能进行的) 双线性插值的准确性。
7073
Color = 0,
@@ -87,7 +90,7 @@ class CursorDrawer2 {
8790
uint32_t _textureSrvIdx = std::numeric_limits<uint32_t>::max();
8891

8992
Size originSize;
90-
ByteBuffer originPixels;
93+
ByteBuffer originTextureData;
9194
winrt::com_ptr<ID3D12Resource> originUploadBuffer;
9295
winrt::com_ptr<ID3D12Resource> originTexture;
9396
};
@@ -105,7 +108,7 @@ class CursorDrawer2 {
105108
uint32_t preferedWidth
106109
) const noexcept;
107110

108-
bool _ResolveCursorPixels(_CursorInfo& cursorInfo, HBITMAP hColorBmp, HBITMAP hMaskBmp) noexcept;
111+
bool _ResolveCursorPixels(_CursorInfo& cursorInfo, HBITMAP hColorBmp, HBITMAP hMaskBmp) const noexcept;
109112

110113
HRESULT _InitializeCursorTexture(_CursorInfo& cursorInfo) noexcept;
111114

src/Magpie.Core/Magpie.Core.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<ItemGroup>
5050
<ClInclude Include="BackendDescriptorStore.h" />
5151
<ClInclude Include="CatmullRomDrawer.h" />
52+
<ClInclude Include="ColorHelper.h" />
5253
<ClInclude Include="CompSwapchainPresenter.h" />
5354
<ClInclude Include="CursorDrawer2.h" />
5455
<ClInclude Include="CursorManager.h" />

src/Magpie.Core/Magpie.Core.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@
195195
<ClInclude Include="DynamicDescriptorHeap.h">
196196
<Filter>Render</Filter>
197197
</ClInclude>
198+
<ClInclude Include="ColorHelper.h">
199+
<Filter>Helpers</Filter>
200+
</ClInclude>
198201
</ItemGroup>
199202
<ItemGroup>
200203
<ClCompile Include="ScalingRuntime.cpp" />

src/Magpie.Core/Renderer2.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,12 @@ HRESULT Renderer2::_UpdateColorSpace() noexcept {
498498
SimpleTask<HRESULT> task;
499499
_frameProducer.OnColorInfoChangedAsync(_colorInfo, task);
500500

501+
hr = _cursorDrawer.OnColorInfoChanged(_colorInfo);
502+
if (FAILED(hr)) {
503+
Logger::Get().ComError("CursorDrawer::OnColorInfoChanged 失败", hr);
504+
return hr;
505+
}
506+
501507
hr = _presenter->OnColorInfoChanged(_colorInfo);
502508
if (FAILED(hr)) {
503509
Logger::Get().ComError("SwapChainPresenter::OnColorInfoChanged 失败", hr);
@@ -531,8 +537,8 @@ HRESULT Renderer2::_RenderImpl(bool waitForGpu) noexcept {
531537
// SwapChain::BeginFrame 和 GraphicsContext::BeginFrame 无顺序要求,不过
532538
// 前者通常等待时间更久,将它放在前面可以减少等待次数。
533539
ID3D12Resource* frameTex;
534-
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle;
535-
_presenter->BeginFrame(&frameTex, rtvHandle);
540+
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle, rawRtvHandle;
541+
_presenter->BeginFrame(&frameTex, rtvHandle, rawRtvHandle);
536542

537543
uint32_t frameIndex;
538544
HRESULT hr = _graphicsContext.BeginFrame(frameIndex, _pipelineState.get());
@@ -587,6 +593,11 @@ HRESULT Renderer2::_RenderImpl(bool waitForGpu) noexcept {
587593
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
588594
commandList->DrawInstanced(3, 1, 0, 0);
589595

596+
// 为了和 OS 保持一致,SDR 下在 sRGB 空间中混合
597+
if (_colorInfo.kind == winrt::AdvancedColorKind::StandardDynamicRange) {
598+
commandList->OMSetRenderTargets(1, &rawRtvHandle, FALSE, nullptr);
599+
}
600+
590601
hr = _cursorDrawer.Draw(heapGpuHandle);
591602
if (FAILED(hr)) {
592603
Logger::Get().ComError("CursorDrawer2::Draw 失败", hr);

0 commit comments

Comments
 (0)