Compare commits

..

12 commits

Author SHA1 Message Date
Xu
2980a69156 feat: 支持芬兰语 2026-05-12 09:11:04 +08:00
allcontributors[bot]
1840bded7b
docs: add rezorrand as a contributor for translation (#1407)
* docs: update README.md [skip ci]

* docs: update README_ZH.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-05-12 08:57:03 +08:00
Weblate (bot)
8daceb51f9
Translations update from Hosted Weblate (#1406)
* Added translation using Weblate (Finnish)

* Translated using Weblate (Finnish)

Currently translated at 100.0% (312 of 312 strings)

Translation: Magpie/UI
Translate-URL: https://hosted.weblate.org/projects/magpie/ui/fi/

---------

Co-authored-by: Pauli Laatikainen <paarma@gmail.com>
2026-05-12 08:56:04 +08:00
Weblate (bot)
e73483a272
Translated using Weblate (Turkish) (#1404)
Currently translated at 100.0% (312 of 312 strings)

Translation: Magpie/UI
Translate-URL: https://hosted.weblate.org/projects/magpie/ui/tr/

Co-authored-by: İsa Beyit <isabeyit79@gmail.com>
2026-05-05 19:48:52 +08:00
Karl Hook
4a2c3499a3
update: 更新效果 k7 的权重 (#1402) 2026-04-22 22:03:39 +08:00
Karl Hook
cd2388fbbd
feat: 增加效果 k7 (#1400) 2026-04-20 15:59:47 +08:00
allcontributors[bot]
1f809e80b0
docs: add Androidlate as a contributor for translation (#1399)
* docs: update README.md [skip ci]

* docs: update README_ZH.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-04-15 21:20:43 +08:00
Weblate (bot)
626eb5612b
Translated using Weblate (German) (#1398)
Currently translated at 97.7% (305 of 312 strings)

Translation: Magpie/UI
Translate-URL: https://hosted.weblate.org/projects/magpie/ui/de/

Co-authored-by: Raphael <hertzpatrick13@gmail.com>
2026-04-15 21:20:26 +08:00
Weblate (bot)
405fe46f22
Translated using Weblate (Tamil) (#1388)
Currently translated at 100.0% (312 of 312 strings)

Translation: Magpie/UI
Translate-URL: https://hosted.weblate.org/projects/magpie/ui/ta/

Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
2026-03-21 11:15:45 +08:00
Xu
b57b1aec43 chore: 不再使用全局 WholeProgramOptimization 属性 2026-03-14 12:01:31 +08:00
Weblate (bot)
d1178d4ac4
Translated using Weblate (Italian) (#1386)
Currently translated at 68.5% (214 of 312 strings)

Translation: Magpie/UI
Translate-URL: https://hosted.weblate.org/projects/magpie/ui/it/

Co-authored-by: Matteo Crocetti <matteocrocetti@proton.me>
2026-03-14 11:38:43 +08:00
Xu
359c7ac176 chore: 修复 VS 更新后 _ConanDeps 可能不会触发重新编译 2026-03-12 18:59:45 +08:00
164 changed files with 4974 additions and 10260 deletions

View file

@ -324,6 +324,24 @@
"contributions": [
"translation"
]
},
{
"login": "Androidlate",
"name": "Raphael",
"avatar_url": "https://avatars.githubusercontent.com/u/194900061?v=4",
"profile": "https://github.com/Androidlate",
"contributions": [
"translation"
]
},
{
"login": "rezorrand",
"name": "Pate L",
"avatar_url": "https://avatars.githubusercontent.com/u/7170353?v=4",
"profile": "https://github.com/rezorrand",
"contributions": [
"translation"
]
}
],
"contributorsPerLine": 7,

View file

@ -110,6 +110,10 @@ Thanks go to these wonderful people:
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Howard20181"><img src="https://avatars.githubusercontent.com/u/40033067?v=4?s=100" width="100px;" alt="Howard Wu"/><br /><sub><b>Howard Wu</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=Howard20181" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/arifpedia"><img src="https://avatars.githubusercontent.com/u/4081293?v=4?s=100" width="100px;" alt="Arif Budiman"/><br /><sub><b>Arif Budiman</b></sub></a><br /><a href="#translation-arifpedia" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Androidlate"><img src="https://avatars.githubusercontent.com/u/194900061?v=4?s=100" width="100px;" alt="Raphael"/><br /><sub><b>Raphael</b></sub></a><br /><a href="#translation-Androidlate" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rezorrand"><img src="https://avatars.githubusercontent.com/u/7170353?v=4?s=100" width="100px;" alt="Pate L"/><br /><sub><b>Pate L</b></sub></a><br /><a href="#translation-rezorrand" title="Translation">🌍</a></td>
</tr>
</tbody>
</table>

View file

@ -109,6 +109,10 @@ Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Howard20181"><img src="https://avatars.githubusercontent.com/u/40033067?v=4?s=100" width="100px;" alt="Howard Wu"/><br /><sub><b>Howard Wu</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=Howard20181" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/arifpedia"><img src="https://avatars.githubusercontent.com/u/4081293?v=4?s=100" width="100px;" alt="Arif Budiman"/><br /><sub><b>Arif Budiman</b></sub></a><br /><a href="#translation-arifpedia" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Androidlate"><img src="https://avatars.githubusercontent.com/u/194900061?v=4?s=100" width="100px;" alt="Raphael"/><br /><sub><b>Raphael</b></sub></a><br /><a href="#translation-Androidlate" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rezorrand"><img src="https://avatars.githubusercontent.com/u/7170353?v=4?s=100" width="100px;" alt="Pate L"/><br /><sub><b>Pate L</b></sub></a><br /><a href="#translation-rezorrand" title="Translation">🌍</a></td>
</tr>
</tbody>
</table>

View file

@ -196,6 +196,9 @@ Magpie ships with a handful of effects that can be used in combinations. Most of
* Sinc Param: The larger the value is the sharper the images become. Must be greater than 0. Default value: 0.825
* Anti-ringing Strength: The greater the value is the better the effect becomes, but the images will be more blurry.
* k7_modernAnime: anime-targeted super-resolution algorithm
* Output size: twice that of the input
* Lanczos: Scaling with the Lanczos algorithm.
* Output size: determined by scale configuration
* Parameters

View file

@ -196,6 +196,9 @@ Magpie 内置了大量效果供组合使用,大部分提供了参数选项以
* Sinc Param值越大图像越锐利
* Anti-ringing Strength抗振铃强度
* k7_modernAnime适合动漫类风格的超分算法
* 输出尺寸:输入的两倍
* Lanczos使用 Lanczos 算法缩放输入。
* 输出尺寸:取决于缩放选项
* 参数

View file

@ -60,7 +60,7 @@ versionNumProps = f";MajorVersion={args.version_major};MinorVersion={args.versio
versionStrProp = "" if args.version_string == "" else f";VersionString={args.version_string}"
p = subprocess.run(
f'"{msbuildPath}" Magpie.slnx -m -t:Rebuild -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={args.platform};DisablePDB=true;UseClangCL={args.compiler == "ClangCL"};UseNativeMicroArch={args.use_native_march};OutBaseDir={os.getcwd()}\\publish\\{args.platform}\\;CommitId={commitId}{versionNumProps}{versionStrProp}'
f'"{msbuildPath}" Magpie.slnx -m -t:Rebuild -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={args.platform};DisablePDB=true;UseClangCL={args.compiler == "ClangCL"};UseNativeMicroArch={args.use_native_march};OutDir={os.getcwd()}\\publish\\{args.platform}\\;CommitId={commitId}{versionNumProps}{versionStrProp}'
)
if p.returncode != 0:
raise Exception("编译失败")
@ -102,7 +102,7 @@ if args.pfx_path != "":
)
passwordOption = "" if args.pfx_password == "" else f'/p "{args.pfx_password}"'
p = subprocess.run(
f'"{windowsSdkDir}\\x64\\signtool.exe" sign /fd SHA256 /a /f "{pfxPath}" {passwordOption} app\\TouchHelper.exe'
f'"{windowsSdkDir}\\x64\\signtool.exe" sign /fd SHA256 /a /f "{pfxPath}" {passwordOption} TouchHelper.exe'
)
if p.returncode != 0:
raise Exception("签名失败")

View file

@ -8,21 +8,18 @@
<UseNativeMicroArch>false</UseNativeMicroArch>
<!-- 编译为打包应用 (暂不支持) -->
<IsPackaged>false</IsPackaged>
<!-- 启用调试信息 -->
<DebugInfo>false</DebugInfo>
<!-- 窗口模式缩放时把用于调整窗口尺寸的辅助窗口标示出来 -->
<DebugBorder>false</DebugBorder>
<!-- 在性能分析器上显示调试信息 -->
<DebugInfoOnOverlay>false</DebugInfoOnOverlay>
<!-- 使用 composition swapchain 呈现 -->
<UseCompSwapchain>false</UseCompSwapchain>
<!-- 禁止生成 PDB -->
<DisablePDB>false</DisablePDB>
<OutBaseDir></OutBaseDir>
<MajorVersion></MajorVersion>
<MinorVersion></MinorVersion>
<PatchVersion></PatchVersion>
<VersionString></VersionString>
<CommitId></CommitId>
<RtxVideoSDKDir></RtxVideoSDKDir>
</PropertyGroup>
<!-- 用户自定义编译选项 -->

View file

@ -10,9 +10,9 @@
<PreprocessorDefinitions>MP_MAJOR_VERSION=$(MajorVersion);MP_MINOR_VERSION=$(MinorVersion);MP_PATCH_VERSION=$(PatchVersion);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(VersionString)' != ''">MP_VERSION_STRING=$(VersionString);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(CommitId)' != ''">MP_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(DebugInfo)">MP_DEBUG_INFO;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(DebugBorder)">MP_DEBUG_BORDER;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(DebugInfoOnOverlay)">MP_DEBUG_INFO_ON_OVERLAY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(UseCompSwapchain)">MP_USE_COMPSWAPCHAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(RtxVideoSDKDir)' != ''">MP_ENABLE_RTX_TRUE_HDR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat Condition="'$(DisablePDB)' == 'true'">None</DebugInformationFormat>
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
<!-- /await:strict: 禁用协程的非标准语言扩展 -->
@ -53,6 +53,14 @@
<ClCompile>
<!-- Release 下不允许编译警告 -->
<TreatWarningAsError>true</TreatWarningAsError>
<!-- 存在同名全局属性,但表现很奇怪。首先,在导入 Microsoft.Cpp.Default.props 之前和之后定义 -->
<!-- 效果不同,似乎在导入前定义才能起作用;其次,全局 WholeProgramOptimization 属性起作用时由 -->
<!-- Microsoft.Cpp.targets 而不是 Microsoft.Cpp.props 更改编译选项,将用户设置覆盖;最后,该 -->
<!-- 属性起作用时会将 LinkTimeCodeGeneration 改为 UseFastLinkTimeCodeGeneration而我们想要 -->
<!-- UseLinkTimeCodeGeneration。为了精确控制编译参数我们不使用全局 -->
<!-- WholeProgramOptimization而是自己定义编译选项。全局 WholeProgramOptimization 影响的属 -->
<!-- 性见 $(VCTargetsPath)\Microsoft.Cpp.WholeProgramOptimization.props。 -->
<WholeProgramOptimization>true</WholeProgramOptimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -60,7 +68,7 @@
<!-- /Zc:checkGwOdr: 防止 /Gw 导致某些 ODR 违规被忽略 -->
<AdditionalOptions>/Gw %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="!$(UseClangCL)">/Zc:checkGwOdr %(AdditionalOptions)</AdditionalOptions>
<!-- clang-cl 不支持 LTCG应使用 LTO -->
<!-- clang-cl 不支持 /LTCG应使用 LTO -->
<AdditionalOptions Condition="$(UseClangCL)">/clang:-flto %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
@ -68,6 +76,9 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
<Lib>
<LinkTimeCodeGeneration>true</LinkTimeCodeGeneration>
</Lib>
</ItemDefinitionGroup>
<!-- 所有项目共享的头文件 -->

View file

@ -29,8 +29,6 @@
<VCProjectVersion Condition="$(VS17)">17.0</VCProjectVersion>
<VCProjectVersion Condition="!$(VS17)">18.0</VCProjectVersion>
<DefaultLanguage>en-US</DefaultLanguage>
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<OutBaseDir Condition="'$(OutBaseDir)' == ''">$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutBaseDir>
<MajorVersion Condition="'$(MajorVersion)' == ''">0</MajorVersion>
<MinorVersion Condition="'$(MinorVersion)' == ''">0</MinorVersion>
<PatchVersion Condition="'$(PatchVersion)' == ''">0</PatchVersion>
@ -43,7 +41,5 @@
<PlatformToolset Condition="!$(UseClangCL) And $(VS17)">v143</PlatformToolset>
<PlatformToolset Condition="!$(UseClangCL) And !$(VS17)">v145</PlatformToolset>
<UseDebugLibraries Condition="'$(Configuration)' == 'Debug'">true</UseDebugLibraries>
<!-- clang-cl 不支持 LTCG -->
<WholeProgramOptimization Condition="'$(Configuration)' == 'Release' And !$(UseClangCL)">true</WholeProgramOptimization>
</PropertyGroup>
</Project>

View file

@ -48,7 +48,7 @@ float weight(float x) {
if (ax < 1.0) {
return (x * x * ((12.0 - 9.0 * B - 6.0 * C) * ax + (-18.0 + 12.0 * B + 6.0 * C)) + (6.0 - 2.0 * B)) / 6.0;
} else if (ax < 2.0) {
} else if (ax >= 1.0 && ax < 2.0) {
return (x * x * ((-B - 6.0 * C) * ax + (6.0 * B + 30.0 * C)) + (-12.0 * B - 48.0 * C) * ax + (8.0 * B + 24.0 * C)) / 6.0;
} else {
return 0.0;
@ -76,6 +76,10 @@ float4 Pass1(float2 pos) {
float4 rowtaps = weight4(1 - f.x);
float4 coltaps = weight4(1 - f.y);
// make sure all taps added together is exactly 1.0, otherwise some (very small) distortion can occur
rowtaps /= rowtaps.r + rowtaps.g + rowtaps.b + rowtaps.a;
coltaps /= coltaps.r + coltaps.g + coltaps.b + coltaps.a;
float2 uv1 = pos1 * inputPt;
float2 uv0 = uv1 - inputPt;
float2 uv2 = uv1 + inputPt;

View file

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\Common.Pre.props" />
<PropertyGroup Label="Globals">
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{62503530-b84b-4cc2-80b6-3f89618172b7}</ProjectGuid>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<OutDir>$(OutBaseDir)\app\effects\</OutDir>
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\Common.Pre.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Utility</ConfigurationType>
</PropertyGroup>
@ -17,7 +18,7 @@
</ImportGroup>
<ItemDefinitionGroup>
<CopyFileToFolders>
<DestinationFolders>$(OutDir)\shaders\</DestinationFolders>
<DestinationFolders>$(OutDir)\effects</DestinationFolders>
<DestinationFileName>%(RelativeDir)%(Filename)%(Extension)</DestinationFileName>
</CopyFileToFolders>
</ItemDefinitionGroup>
@ -460,6 +461,9 @@
<CopyFileToFolders Include="SGSR.hlsl">
<FileType>Document</FileType>
</CopyFileToFolders>
<CopyFileToFolders Include="k7_modernAnime_FHD_x2.hlsl">
<FileType>Document</FileType>
</CopyFileToFolders>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View file

@ -452,6 +452,7 @@
<Filter>CuNNy2</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="SGSR.hlsl" />
<CopyFileToFolders Include="k7_modernAnime_FHD_x2.hlsl" />
</ItemGroup>
<ItemGroup>
<Filter Include="Anime4K">

File diff suppressed because it is too large Load diff

View file

@ -187,7 +187,7 @@ void AdaptivePresenter::EndFrame(bool waitForGpu) noexcept {
bool AdaptivePresenter::OnResize() noexcept {
_isResized = true;
if (ScalingWindow::Get().IsResizing() || !_dxgiSwapChain) {
if (ScalingWindow::Get().IsResizingOrMoving() || !_dxgiSwapChain) {
// 切换到 DirectComposition 呈现,失败则回落到交换链
_isDCompPresenting = _ResizeDCompVisual();
if (_isDCompPresenting) {

View file

@ -1,260 +0,0 @@
#include "pch.h"
#include "CatmullRomDrawer.h"
#include "DirectXHelper.h"
#include "GraphicsContext.h"
#include "DescriptorHeap.h"
#include "Logger.h"
#include "shaders/CatmullRomCS.h"
#include "shaders/CatmullRomCS_sRGB.h"
#include "shaders/CopyCS.h"
#include "shaders/CopyCS_sRGB.h"
namespace Magpie {
void CatmullRomDrawer::Initialize(GraphicsContext& graphicsContext) noexcept {
_graphicsContext = &graphicsContext;
}
HRESULT CatmullRomDrawer::Draw(
Size inputSize,
Size outputSize,
uint32_t inputSrvOffset,
uint32_t outputUavOffset,
bool outputSrgb
) noexcept {
ID3D12GraphicsCommandList* commandList = _graphicsContext->GetCommandList();
auto& descriptorHeap = _graphicsContext->GetDescriptorHeap();
// 作为性能优化,输入和输出尺寸相同时原样复制
if (inputSize == outputSize) {
if (!_copyRootSignature) {
HRESULT hr = _InitializeCopyRootSignature();
if (FAILED(hr)) {
Logger::Get().ComError("_InitializeCopyRootSignature 失败", hr);
return hr;
}
}
commandList->SetComputeRootSignature(_copyRootSignature.get());
if (outputSrgb) {
if (!_copySrgbPSO) {
D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = _copyRootSignature.get(),
.CS = CD3DX12_SHADER_BYTECODE(CopyCS_sRGB, sizeof(CopyCS_sRGB))
};
HRESULT hr = _graphicsContext->GetDevice()->CreateComputePipelineState(
&psoDesc, IID_PPV_ARGS(&_copySrgbPSO));
if (FAILED(hr)) {
Logger::Get().ComError("CreateComputePipelineState 失败", hr);
return hr;
}
}
commandList->SetPipelineState(_copySrgbPSO.get());
} else {
if (!_copyPSO) {
D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = _copyRootSignature.get(),
.CS = CD3DX12_SHADER_BYTECODE(CopyCS, sizeof(CopyCS))
};
HRESULT hr = _graphicsContext->GetDevice()->CreateComputePipelineState(
&psoDesc, IID_PPV_ARGS(&_copyPSO));
if (FAILED(hr)) {
Logger::Get().ComError("CreateComputePipelineState 失败", hr);
return hr;
}
}
commandList->SetPipelineState(_copyPSO.get());
}
commandList->SetComputeRootDescriptorTable(0, descriptorHeap.GetGpuHandle(inputSrvOffset));
commandList->SetComputeRootDescriptorTable(1, descriptorHeap.GetGpuHandle(outputUavOffset));
} else {
if (!_catmullRomRootSignature) {
HRESULT hr = _InitializeCatmullRomRootSignature();
if (FAILED(hr)) {
Logger::Get().ComError("_InitializeCatmullRomRootSignature 失败", hr);
return hr;
}
}
commandList->SetComputeRootSignature(_catmullRomRootSignature.get());
if (outputSrgb) {
if (!_catmullRomSrgbPSO) {
D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = _catmullRomRootSignature.get(),
.CS = CD3DX12_SHADER_BYTECODE(CatmullRomCS_sRGB, sizeof(CatmullRomCS_sRGB))
};
HRESULT hr = _graphicsContext->GetDevice()->CreateComputePipelineState(
&psoDesc, IID_PPV_ARGS(&_catmullRomSrgbPSO));
if (FAILED(hr)) {
Logger::Get().ComError("CreateComputePipelineState 失败", hr);
return hr;
}
}
commandList->SetPipelineState(_catmullRomSrgbPSO.get());
} else {
if (!_catmullRomPSO) {
D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = _catmullRomRootSignature.get(),
.CS = CD3DX12_SHADER_BYTECODE(CatmullRomCS, sizeof(CatmullRomCS))
};
HRESULT hr = _graphicsContext->GetDevice()->CreateComputePipelineState(
&psoDesc, IID_PPV_ARGS(&_catmullRomPSO));
if (FAILED(hr)) {
Logger::Get().ComError("CreateComputePipelineState 失败", hr);
return hr;
}
}
commandList->SetPipelineState(_catmullRomPSO.get());
}
DirectXHelper::Constant32 constants[] = {
{.uintVal = inputSize.width},
{.uintVal = inputSize.height},
{.floatVal = 1.0f / inputSize.width},
{.floatVal = 1.0f / inputSize.height},
{.floatVal = 1.0f / outputSize.width},
{.floatVal = 1.0f / outputSize.height}
};
commandList->SetComputeRoot32BitConstants(0, (UINT)std::size(constants), constants, 0);
commandList->SetComputeRootDescriptorTable(1, descriptorHeap.GetGpuHandle(inputSrvOffset));
commandList->SetComputeRootDescriptorTable(2, descriptorHeap.GetGpuHandle(outputUavOffset));
}
constexpr uint32_t BLOCK_SIZE = 16;
commandList->Dispatch(
(outputSize.width + BLOCK_SIZE - 1) / BLOCK_SIZE,
(outputSize.height + BLOCK_SIZE - 1) / BLOCK_SIZE,
1
);
return S_OK;
}
HRESULT CatmullRomDrawer::_InitializeCatmullRomRootSignature() noexcept {
winrt::com_ptr<ID3DBlob> signature;
CD3DX12_DESCRIPTOR_RANGE1 srvRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);
CD3DX12_DESCRIPTOR_RANGE1 uavRange(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE);
D3D12_ROOT_PARAMETER1 rootParams[] = {
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
.Constants = {
.Num32BitValues = 6
}
},
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &srvRange
}
},
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &uavRange
}
}
};
D3D12_STATIC_SAMPLER_DESC samplerDesc = {
.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR,
.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP,
.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER,
.ShaderRegister = 0
};
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc(
(UINT)std::size(rootParams), rootParams, 1, &samplerDesc);
HRESULT hr = D3DX12SerializeVersionedRootSignature(
&rootSignatureDesc,
_graphicsContext->GetRootSignatureVersion(),
signature.put(),
nullptr
);
if (FAILED(hr)) {
Logger::Get().ComError("D3DX12SerializeVersionedRootSignature 失败", hr);
return hr;
}
hr = _graphicsContext->GetDevice()->CreateRootSignature(
0,
signature->GetBufferPointer(),
signature->GetBufferSize(),
IID_PPV_ARGS(&_catmullRomRootSignature)
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateRootSignature 失败", hr);
return hr;
}
return S_OK;
}
HRESULT CatmullRomDrawer::_InitializeCopyRootSignature() noexcept {
winrt::com_ptr<ID3DBlob> signature;
CD3DX12_DESCRIPTOR_RANGE1 srvRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);
CD3DX12_DESCRIPTOR_RANGE1 uavRange(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE);
D3D12_ROOT_PARAMETER1 rootParams[] = {
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &srvRange
}
},
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &uavRange
}
}
};
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc(
(UINT)std::size(rootParams), rootParams, 0, nullptr);
HRESULT hr = D3DX12SerializeVersionedRootSignature(
&rootSignatureDesc,
_graphicsContext->GetRootSignatureVersion(),
signature.put(),
nullptr
);
if (FAILED(hr)) {
Logger::Get().ComError("D3DX12SerializeVersionedRootSignature 失败", hr);
return hr;
}
hr = _graphicsContext->GetDevice()->CreateRootSignature(
0,
signature->GetBufferPointer(),
signature->GetBufferSize(),
IID_PPV_ARGS(&_copyRootSignature)
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateRootSignature 失败", hr);
return hr;
}
return S_OK;
}
}

View file

@ -1,34 +0,0 @@
#pragma once
namespace Magpie {
class GraphicsContext;
class CatmullRomDrawer {
public:
void Initialize(GraphicsContext& graphicsContext) noexcept;
HRESULT Draw(
Size inputSize,
Size outputSize,
uint32_t inputSrvOffset,
uint32_t outputUavOffset,
bool outputSrgb
) noexcept;
private:
GraphicsContext* _graphicsContext = nullptr;
HRESULT _InitializeCatmullRomRootSignature() noexcept;
HRESULT _InitializeCopyRootSignature() noexcept;
winrt::com_ptr<ID3D12RootSignature> _catmullRomRootSignature;
winrt::com_ptr<ID3D12PipelineState> _catmullRomPSO;
winrt::com_ptr<ID3D12PipelineState> _catmullRomSrgbPSO;
winrt::com_ptr<ID3D12RootSignature> _copyRootSignature;
winrt::com_ptr<ID3D12PipelineState> _copyPSO;
winrt::com_ptr<ID3D12PipelineState> _copySrgbPSO;
};
}

View file

@ -1,24 +0,0 @@
#pragma once
namespace Magpie {
struct ColorHelper {
static float SrgbToLinear(uint8_t c) noexcept {
static std::array<float, 256> lut = [] {
std::array<float, 256> result{};
for (uint32_t i = 0; i < 256; ++i) {
float c = i / 255.0f;
if (c <= 0.04045f) {
result[i] = c / 12.92f * 255.0f;
} else {
result[i] = std::pow((c + 0.055f) / 1.055f, 2.4f) * 255.0f;
}
}
return result;
}();
return lut[c];
}
};
}

View file

@ -11,7 +11,7 @@ static winrt::com_ptr<IPresentationFactory> CreatePresentationFactory(ID3D11Devi
winrt::com_ptr<IPresentationFactory> result;
static const auto createPresentationFactory =
Win32Helper::LoadFunction<decltype(::CreatePresentationFactory)>(
Win32Helper::LoadSystemFunction<decltype(::CreatePresentationFactory)>(
L"dcomp.dll", "CreatePresentationFactory");
if (!createPresentationFactory) {
return result;

View file

@ -92,7 +92,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
if (isCursorActive) {
// 启用自动隐藏时光标形状或位置变化后应记录新的形状、位置和变化时间。位置由
// _curCursorPos 记录。
// _lastCursorPos 记录。
_lastRawCursorHandle = scalingWindow.CursorManager().CursorHandle();
_lastCursorActiveTime = std::chrono::steady_clock::now();
}
@ -111,22 +111,22 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
const ScalingOptions& options = scalingWindow.Options();
float cursorScale = options.cursorScale;
if (cursorScale < FLOAT_EPSILON<float>) {
float cursorScaling = options.cursorScaling;
if (cursorScaling < FLOAT_EPSILON<float>) {
// 光标缩放和源窗口相同
const Renderer& renderer = scalingWindow.Renderer();
const SIZE srcSize = Win32Helper::GetSizeOfRect(renderer.SrcRect());
const SIZE destSize = Win32Helper::GetSizeOfRect(renderer.DestRect());
cursorScale = (((float)destSize.cx / srcSize.cx) + ((float)destSize.cy / srcSize.cy)) / 2;
cursorScaling = (((float)destSize.cx / srcSize.cx) + ((float)destSize.cy / srcSize.cy)) / 2;
}
const SIZE cursorSize{
lroundf(cursorInfo->size.cx * cursorScale),
lroundf(cursorInfo->size.cy * cursorScale)
lroundf(cursorInfo->size.cx * cursorScaling),
lroundf(cursorInfo->size.cy * cursorScaling)
};
RECT cursorRect{
.left = lroundf(cursorPos.x - cursorInfo->hotSpot.x * cursorScale),
.top = lroundf(cursorPos.y - cursorInfo->hotSpot.y * cursorScale),
.left = lroundf(cursorPos.x - cursorInfo->hotSpot.x * cursorScaling),
.top = lroundf(cursorPos.y - cursorInfo->hotSpot.y * cursorScaling),
.right = cursorRect.left + cursorSize.cx,
.bottom = cursorRect.top + cursorSize.cy
};
@ -219,7 +219,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
d3dDC->PSSetShaderResources(0, 1, &cursorSrv);
const bool useBilinear = options.cursorInterpolationMode == CursorInterpolationMode::Bilinear &&
std::abs(options.cursorScale - 1.0f) > 1e-3;
std::abs(options.cursorScaling - 1.0f) > 1e-3;
ID3D11SamplerState* cursorSampler = _deviceResources->GetSampler(
useBilinear ? D3D11_FILTER_MIN_MAG_MIP_LINEAR : D3D11_FILTER_MIN_MAG_MIP_POINT,
D3D11_TEXTURE_ADDRESS_CLAMP
@ -346,7 +346,7 @@ std::pair<HCURSOR, POINT> CursorDrawer::_GetCursorState(bool& isActive) const no
// 变化:箭头->隐藏->箭头,只要位置不变,自动隐藏功能应让光标始终隐藏;反之如果光
// 标隐藏时移动了或显示时形状变化了应正常显示。
if (cursorManager.IsCursorCaptured() &&
!scalingWindow.IsMoving() &&
!scalingWindow.IsResizingOrMoving() &&
!scalingWindow.SrcTracker().IsMoving() &&
_lastCursorPos == cursorPos &&
(_lastRawCursorHandle == cursorHandle || !cursorHandle))

File diff suppressed because it is too large Load diff

View file

@ -1,165 +0,0 @@
#pragma once
#include <wil/registry.h>
#include <parallel_hashmap/phmap.h>
#include "ByteBuffer.h"
namespace Magpie {
class GraphicsContext;
class CursorDrawer2 {
public:
CursorDrawer2() noexcept = default;
CursorDrawer2(const CursorDrawer2&) = delete;
CursorDrawer2(CursorDrawer2&&) = delete;
~CursorDrawer2() noexcept;
bool Initialize(
GraphicsContext& graphicsContext,
const RECT& srcRect,
const RECT& rendererRect,
const RECT& destRect,
const ColorInfo& colorInfo
) noexcept;
bool CheckForRedraw(HCURSOR hCursor, POINT cursorPos) noexcept;
HRESULT Draw(uint32_t curFrameSrvOffset) noexcept;
void OnCursorVirtualizationStarted() noexcept {
_isCursorVirtualized = true;
}
void OnCursorVirtualizationEnded() noexcept {
_isCursorVirtualized = false;
}
void OnMoveStarted() noexcept {
_isMoving = true;
}
void OnMoveEnded() noexcept {
_isMoving = false;
}
void OnSrcMoveStarted() noexcept {
_isSrcMoving = true;
}
void OnSrcMoveEnded() noexcept {
_isSrcMoving = false;
}
void OnMoved(const RECT& rendererRect, const RECT& destRect) noexcept;
void OnResized(const RECT& rendererRect, const RECT& destRect) noexcept;
void OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
private:
// SDR 色域下使用 sRGB 空间,否则使用线性 RGB 空间。截至 Win11 25H2Windows 在 WCG
// 和 HDR 下光标的色域和透明度经常变化,没有统一标准。
enum class _CursorType {
// 彩色光标
// 纹理格式: DXGI_FORMAT_R16G16B16A16_FLOAT
// 计算公式: FinalColor = CursorColor.rgb + ScreenColor * CursorColor.a
// 纹理中 RGB 通道已预乘 A 通道 (premultiplied alpha)A 通道已预先取反,这是为了
// 减少着色器的计算量以及确保 (可能进行的) 双线性插值的准确性。
Color = 0,
// 单色光标
// 纹理格式: DXGI_FORMAT_R8_UINT
// 高四位为 AND 掩码,低四位为 XOR 掩码,值只能是 0 或 0xf。
Monochrome,
// 彩色掩码光标
// 纹理格式: DXGI_FORMAT_R8G8B8A8_UNORM
// A 通道只能是 0 或 255。为 0 时用 RGB 通道取代屏幕颜色,为 255 时将 RGB 通道和
// 屏幕颜色进行异或操作。
MaskedColor
};
struct _CursorInfo {
_CursorType type;
Size size;
Point hotspot;
winrt::com_ptr<ID3D12Resource> texture;
uint32_t textureSrvOffset = std::numeric_limits<uint32_t>::max();
Size originSize;
ByteBuffer originTextureData;
winrt::com_ptr<ID3D12Resource> originUploadBuffer;
winrt::com_ptr<ID3D12Resource> originTexture;
};
_CursorInfo* _ResolveCursor(HCURSOR hCursor, POINT cursorPos, bool isAni) noexcept;
Size _CalcCursorSize(
Size cursorBmpSize,
uint32_t cursorDpi,
uint32_t monitorDpi
) const noexcept;
wil::unique_hcursor _TryResolveCursorResource(
const ICONINFOEX& iconInfoEx,
uint32_t preferedWidth
) const noexcept;
wil::unique_hcursor _TryResolveStandardCursor(
const wchar_t* regValueName,
int resId,
uint32_t preferedWidth
) const noexcept;
bool _ResolveCursorPixels(_CursorInfo& cursorInfo, HBITMAP hColorBmp, HBITMAP hMaskBmp) const noexcept;
HRESULT _InitializeCursorTexture(_CursorInfo& cursorInfo) noexcept;
// 只能在同步 GPU 后调用
void _ClearCursorInfos() noexcept;
HRESULT _CreateColorPSO() noexcept;
HRESULT _CreateMaskPSO(
bool isMonochrome,
bool isSrgb,
winrt::com_ptr<ID3D12PipelineState>& result
) noexcept;
GraphicsContext* _graphicsContext = nullptr;
Size _srcSize{};
RECT _rendererRect{};
RECT _destRect{};
ColorInfo _colorInfo;
// (HCURSOR, DPI) -> _CursorInfo
// DPI 为 0 表示此光标不随 DPI 缩放
phmap::flat_hash_map<std::pair<HCURSOR, uint32_t>, _CursorInfo> _cursorInfos;
// 这两个成员用于检查自动隐藏光标
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() };
_CursorInfo* _curCursorInfo = nullptr;
// 监控“指针大小”选项变化
wil::unique_registry_watcher_nothrow _regWatcher;
DWORD _cursorBaseSize = 32;
winrt::com_ptr<ID3D12RootSignature> _colorRootSignature;
winrt::com_ptr<ID3D12PipelineState> _colorPSO;
winrt::com_ptr<ID3D12RootSignature> _maskRootSignature;
// 这些 PSO 共用 _maskRootSignature
winrt::com_ptr<ID3D12PipelineState> _monochromePSO;
winrt::com_ptr<ID3D12PipelineState> _monochromeSrgbPSO;
winrt::com_ptr<ID3D12PipelineState> _maskedColorPSO;
winrt::com_ptr<ID3D12PipelineState> _maskedColorSrgbPSO;
bool _isCursorVisible = true;
bool _isMoving = false;
bool _isCursorVirtualized = false;
bool _isSrcMoving = false;
};
}

View file

@ -1,251 +0,0 @@
#include "pch.h"
#include "CursorHelper.h"
#include "Logger.h"
#include <SmallVector.h>
namespace Magpie {
static WORD GetRealIconSize(WORD size) noexcept {
// 0 等价于 256
return size == 0 ? (WORD)256 : size;
}
wil::unique_hcursor CursorHelper::ExtractCursorFromModule(
HMODULE hModule,
LPCWSTR resName,
uint32_t preferredWidth
) noexcept {
HRSRC hRes = FindResource(hModule, resName, RT_GROUP_CURSOR);
if (!hRes) {
Logger::Get().Win32Error("FindResource 失败");
return nullptr;
}
HGLOBAL hResLoad = LoadResource(hModule, hRes);
if (!hResLoad) {
Logger::Get().Win32Error("LoadResource 失败");
return nullptr;
}
// 解析光标资源
#pragma pack(push, 2)
// 来自 https://learn.microsoft.com/en-us/windows/win32/menurc/resdir
struct RESDIR {
WORD Width;
WORD Height;
WORD Planes;
WORD BitCount;
DWORD BytesInRes;
WORD IconCursorId;
};
// 来自 https://learn.microsoft.com/en-us/windows/win32/menurc/newheader
struct NEWHEADER {
WORD Reserved;
WORD ResType;
WORD ResCount;
RESDIR entries[1];
};
#pragma pack(pop)
const NEWHEADER& header = *(const NEWHEADER*)LockResource(hResLoad);
if (header.Reserved != 0 || header.ResType != 2) {
Logger::Get().Error("不是光标资源");
return nullptr;
}
const uint32_t resCount = header.ResCount;
if (resCount == 0 || resCount > 256) {
Logger::Get().Error("无可用光标资源");
return nullptr;
}
struct IconInfo {
WORD width;
WORD bitCount;
WORD id;
};
SmallVector<IconInfo, 0> iconInfos(resCount);
for (uint32_t i = 0; i < resCount; ++i) {
const RESDIR& entry = header.entries[i];
// 宽度和高度的 0 等价于 256
iconInfos[i] = IconInfo{
GetRealIconSize(entry.Width),
entry.BitCount,
entry.IconCursorId
};
}
// 尺寸从小到大排序;如果尺寸相同,色深从大到小排序,以便获得色深最大的光标
std::sort(iconInfos.begin(), iconInfos.end(), [](const IconInfo& l, const IconInfo& r) {
return l.width < r.width || (l.width == r.width && l.bitCount > r.bitCount);
});
// 寻找完美匹配或更大的资源
WORD targetResId;
{
auto it = std::lower_bound(
iconInfos.begin(),
iconInfos.end(),
preferredWidth,
[](const IconInfo& iconInfo, uint32_t target) {
return iconInfo.width < target;
}
);
if (it == iconInfos.end()) {
targetResId = iconInfos.back().id;
} else {
targetResId = it->id;
}
}
hRes = FindResource(hModule, MAKEINTRESOURCE(targetResId), RT_CURSOR);
if (!hRes) {
Logger::Get().Win32Error("FindResource 失败");
return nullptr;
}
hResLoad = LoadResource(hModule, hRes);
if (!hResLoad) {
Logger::Get().Win32Error("LoadResource 失败");
return nullptr;
}
HICON hIcon = CreateIconFromResourceEx((PBYTE)LockResource(hResLoad),
SizeofResource(hModule, hRes), FALSE, 0x30000, 0, 0, LR_DEFAULTCOLOR);
if (!hIcon) {
Logger::Get().Win32Error("CreateIconFromResourceEx 失败");
return nullptr;
}
return wil::unique_hcursor(hIcon);
}
wil::unique_hcursor CursorHelper::ExtractCursorFromCurFile(
const wchar_t* fileName,
uint32_t preferredWidth
) noexcept {
CREATEFILE2_EXTENDED_PARAMETERS extendedParams{
.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
.dwFileAttributes = FILE_ATTRIBUTE_NORMAL,
.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN
};
wil::unique_hfile hFile(CreateFile2(
fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, &extendedParams));
if (!hFile) {
Logger::Get().Win32Error("CreateFile2 失败");
return nullptr;
}
// 解析 cur 文件,参考自
// https://learn.microsoft.com/en-us/previous-versions/ms997538(v=msdn.10)
// https://en.wikipedia.org/wiki/ICO_(file_format)#File_structure
#pragma pack(push, 2)
struct ICONDIR {
WORD idReserved;
WORD idType;
WORD idCount;
};
struct ICONDIRENTRY {
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD xHotSpot;
WORD yHotSpot;
DWORD dwBytesInRes;
DWORD dwImageOffset;
};
struct LOCALHEADER {
WORD xHotSpot;
WORD yHotSpot;
};
#pragma pack(pop)
ICONDIR header{};
DWORD bytesRead;
if (!ReadFile(hFile.get(), &header, sizeof(header), &bytesRead, nullptr) ||
bytesRead != sizeof(header)) {
Logger::Get().Win32Error("ReadFile 失败");
return nullptr;
}
if (header.idReserved != 0 || header.idType != 2) {
Logger::Get().Error("不是光标资源");
return nullptr;
}
const uint32_t resCount = header.idCount;
if (resCount == 0 || resCount > 256) {
Logger::Get().Error("无可用光标资源");
return nullptr;
}
const uint32_t entiesByteCount = sizeof(ICONDIRENTRY) * resCount;
auto entriesData = std::make_unique<ICONDIRENTRY[]>(resCount);
if (!ReadFile(hFile.get(), entriesData.get(), entiesByteCount, &bytesRead, nullptr) ||
bytesRead != entiesByteCount) {
Logger::Get().Win32Error("ReadFile 失败");
return nullptr;
}
std::vector<const ICONDIRENTRY*> entries(resCount);
for (uint32_t i = 0; i < resCount; ++i) {
entries[i] = &entriesData[i];
}
// 尺寸从小到大排序和资源不同cur 文件不区分色深
std::sort(entries.begin(), entries.end(), [](const ICONDIRENTRY* l, const ICONDIRENTRY* r) {
return GetRealIconSize(l->bWidth) < GetRealIconSize(r->bWidth);
});
// 寻找完美匹配或更大的资源
const ICONDIRENTRY* targetEntry;
{
auto it = std::lower_bound(
entries.begin(),
entries.end(),
preferredWidth,
[](const ICONDIRENTRY* entry, uint32_t target) {
return GetRealIconSize(entry->bWidth) < target;
}
);
if (it == entries.end()) {
targetEntry = entries.back();
} else {
targetEntry = *it;
}
}
if (!SetFilePointer(hFile.get(), targetEntry->dwImageOffset, 0, FILE_BEGIN)) {
Logger::Get().Win32Error("SetFilePointer 失败");
return nullptr;
}
// RT_CURSOR 结构为 LOCALHEADER 后跟位图数据
// https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats#cursor-and-icon-resources
auto cursorData = std::make_unique<uint8_t[]>(sizeof(LOCALHEADER) + targetEntry->dwBytesInRes);
// 设置热点
*(LOCALHEADER*)cursorData.get() = { targetEntry->xHotSpot, targetEntry->yHotSpot };
// 读取位图数据
if (!ReadFile(hFile.get(), cursorData.get() + sizeof(LOCALHEADER), targetEntry->dwBytesInRes, &bytesRead, nullptr) ||
bytesRead != targetEntry->dwBytesInRes) {
Logger::Get().Win32Error("ReadFile 失败");
return nullptr;
}
HICON hIcon = CreateIconFromResourceEx(cursorData.get(),
sizeof(LOCALHEADER) + targetEntry->dwBytesInRes, FALSE, 0x30000, 0, 0, LR_DEFAULTCOLOR);
if (!hIcon) {
Logger::Get().Win32Error("CreateIconFromResourceEx 失败");
return nullptr;
}
return wil::unique_hcursor(hIcon);
}
}

View file

@ -1,19 +0,0 @@
#pragma once
namespace Magpie {
struct CursorHelper {
// 如果没有完美匹配则倾向于提取较大的资源
static wil::unique_hcursor ExtractCursorFromModule(
HMODULE hModule,
LPCWSTR resName,
uint32_t preferredWidth
) noexcept;
static wil::unique_hcursor ExtractCursorFromCurFile(
const wchar_t* fileName,
uint32_t preferredWidth
) noexcept;
};
}

File diff suppressed because it is too large Load diff

View file

@ -8,50 +8,34 @@ public:
CursorManager(const CursorManager&) = delete;
CursorManager(CursorManager&&) = delete;
void Initialize(
const RECT& srcRect,
const RECT& rendererRect,
const RECT& destRect,
bool isSrcMoving,
bool isSrcFocused
) noexcept;
~CursorManager() noexcept;
std::pair<HCURSOR, POINT> Update() noexcept;
void Update() noexcept;
void OnResizeStarted() noexcept;
void OnScalingPosChanged() noexcept;
void OnResizeEnded() noexcept;
void OnSrcStartMove() noexcept;
void OnResized(const RECT& rendererRect, const RECT& destRect) noexcept;
void OnSrcEndMove() noexcept;
void OnMoveStarted() noexcept;
void OnStartMove() noexcept;
void OnMoveEnded() noexcept;
void OnEndResizeMove() noexcept;
void OnMoved(const RECT& rendererRect, const RECT& destRect) noexcept;
void OnSrcMoveStarted() noexcept;
void OnSrcMoveEnded() noexcept;
void OnSrcMoved(const RECT& srcRect) noexcept;
void OnSrcFocusChanged(bool focused) noexcept;
void OnSrcRectChanged() noexcept;
// 光标不在缩放窗口上或隐藏时为 NULL
HCURSOR CursorHandle() const noexcept {
return NULL;
return _hCursor;
}
// 屏幕坐标
POINT CursorPos() const noexcept {
return {};
return _cursorPos;
}
bool IsCursorCaptured() const noexcept {
return _isVirtualized;
return _isUnderCapture;
}
bool IsCursorCapturedOnForeground() const noexcept {
@ -73,16 +57,6 @@ public:
}
private:
POINT _SrcToScaling(POINT pt, bool skipBorder) const noexcept;
enum class _RoundMethod {
Round,
Floor,
Ceil
};
POINT _ScalingToSrc(POINT pt, _RoundMethod roundType = _RoundMethod::Round) const noexcept;
void _ShowSystemCursor(bool show, bool onDestory = false);
void _AdjustCursorSpeed() noexcept;
@ -103,18 +77,14 @@ private:
void _UpdateCursorPos() noexcept;
void _StartVirtualization(POINT& cursorPos) noexcept;
void _StartCapture(POINT& cursorPos) noexcept;
bool _StopVirtualization(POINT& cursorPos, bool onDestroy = false) noexcept;
bool _StopCapture(POINT& cursorPos, bool onDestroy = false) noexcept;
void _SetClipCursor(const RECT& clipRect, bool is3DGameMode = false) noexcept;
void _RestoreClipCursor() noexcept;
RECT _srcRect{};
RECT _rendererRect{};
RECT _destRect{};
HCURSOR _hCursor = NULL;
POINT _cursorPos{ std::numeric_limits<LONG>::max() };
@ -134,13 +104,8 @@ private:
POINT _lastCompletedHitTestPos{ std::numeric_limits<LONG>::max() };
int16_t _lastCompletedHitTestResult = HTNOWHERE;
bool _isMoving = false;
bool _isResizing = false;
bool _isSrcMoving = false;
bool _isSrcFocused = false;
bool _isVirtualized = false;
// 当缩放后的光标位置在渲染矩形内且没有被其他窗口挡住时应绘制光标
bool _isUnderCapture = false;
// 当缩放后的光标位置在交换链窗口上且没有被其他窗口挡住时应绘制光标
bool _shouldDrawCursor = false;
bool _isCapturedOnForeground = false;

View file

@ -1,101 +0,0 @@
#include "pch.h"
#include "DescriptorHeap.h"
#include "Logger.h"
namespace Magpie {
// 如果描述符大小为 32 字节,描述符堆消耗 2MiB 显存
static uint32_t HEAP_CAPACITY = 65536;
DescriptorHeap::~DescriptorHeap() noexcept {
// DEBUG 配置下退出前确保所有槽位都已释放
assert(_freeBlocks.size() == 1 && *_freeBlocks.begin() == std::make_pair(HEAP_CAPACITY, HEAP_CAPACITY));
}
bool DescriptorHeap::Initialize(ID3D12Device5* device) noexcept {
_freeBlocks.emplace(HEAP_CAPACITY, HEAP_CAPACITY);
_descriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
D3D12_DESCRIPTOR_HEAP_DESC desc = {
.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
.NumDescriptors = HEAP_CAPACITY,
.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
};
HRESULT hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&_heap));
if (FAILED(hr)) {
Logger::Get().ComError("CreateDescriptorHeap 失败", hr);
return false;
}
_cpuHandle = _heap->GetCPUDescriptorHandleForHeapStart();
_gpuHandle = _heap->GetGPUDescriptorHandleForHeapStart();
return true;
}
HRESULT DescriptorHeap::Alloc(uint32_t count, uint32_t& offset) noexcept {
auto lk = _freeBlocksLock.lock_exclusive();
for (auto it = _freeBlocks.begin(); it != _freeBlocks.end(); ++it) {
auto& [blockEnd, blockSize] = *it;
// 寻找第一个足够大的空闲块
if (blockSize >= count) {
offset = blockEnd - blockSize;
if (blockSize == count) {
_freeBlocks.erase(it);
} else {
blockSize -= count;
}
return S_OK;
}
}
Logger::Get().Error("描述符用尽");
return E_OUTOFMEMORY;
}
static uint32_t GetBlockOffset(const std::pair<const uint32_t, uint32_t>& freeBlock) noexcept {
return freeBlock.first - freeBlock.second;
}
void DescriptorHeap::Free(uint32_t offset, uint32_t count) noexcept {
assert(offset != std::numeric_limits<uint32_t>::max() && offset + count <= HEAP_CAPACITY);
auto lk = _freeBlocksLock.lock_exclusive();
const auto freeBlocksEnd = _freeBlocks.end();
// 寻找 offset 之后的第一个空闲块
auto upperBoundIt = _freeBlocks.upper_bound(offset);
auto prevIt = upperBoundIt == _freeBlocks.begin() ? freeBlocksEnd : std::prev(upperBoundIt);
assert(upperBoundIt == freeBlocksEnd || offset + count <= GetBlockOffset(*upperBoundIt));
assert(prevIt == freeBlocksEnd || offset >= prevIt->first);
const bool canMergePrev = prevIt != freeBlocksEnd && offset == prevIt->first;
const bool canMergeNext = upperBoundIt != freeBlocksEnd &&
offset + count == GetBlockOffset(*upperBoundIt);
if (canMergeNext) {
upperBoundIt->second += count;
if (canMergePrev) {
upperBoundIt->second += prevIt->second;
_freeBlocks.erase(prevIt);
}
} else {
uint32_t newBlockSize = count;
if (canMergePrev) {
newBlockSize += prevIt->second;
_freeBlocks.erase(prevIt);
}
_freeBlocks.emplace(offset + count, newBlockSize);
}
}
}

View file

@ -1,56 +0,0 @@
#pragma once
#ifndef _DEBUG
#include <parallel_hashmap/btree.h>
#endif
namespace Magpie {
class DescriptorHeap {
public:
DescriptorHeap() = default;
DescriptorHeap(const DescriptorHeap&) = delete;
DescriptorHeap(DescriptorHeap&&) = delete;
~DescriptorHeap() noexcept;
bool Initialize(ID3D12Device5* device) noexcept;
HRESULT Alloc(uint32_t count, uint32_t& offset) noexcept;
void Free(uint32_t offset, uint32_t count) noexcept;
ID3D12DescriptorHeap* GetHeap() const noexcept {
return _heap.get();
}
uint32_t GetDescriptorSize() const noexcept {
return _descriptorSize;
}
D3D12_CPU_DESCRIPTOR_HANDLE GetCpuHandle(uint32_t offset) const noexcept {
return CD3DX12_CPU_DESCRIPTOR_HANDLE(_cpuHandle, offset, _descriptorSize);
}
D3D12_GPU_DESCRIPTOR_HANDLE GetGpuHandle(uint32_t offset) const noexcept {
return CD3DX12_GPU_DESCRIPTOR_HANDLE(_gpuHandle, offset, _descriptorSize);
}
private:
winrt::com_ptr<ID3D12DescriptorHeap> _heap;
D3D12_CPU_DESCRIPTOR_HANDLE _cpuHandle{};
D3D12_GPU_DESCRIPTOR_HANDLE _gpuHandle{};
uint32_t _descriptorSize = 0;
wil::srwlock _freeBlocksLock;
// end(offset+size) -> size
// 以 offset+size 作为键可以大大降低删除和插入键的频率
#ifdef _DEBUG
// phmap::btree_map 没有 natvis调试不方便
std::map<uint32_t, uint32_t> _freeBlocks;
#else
phmap::btree_map<uint32_t, uint32_t> _freeBlocks;
#endif
};
}

View file

@ -1,359 +0,0 @@
#include "pch.h"
#include "DirtyRectsOptimizer.h"
#include "DebugInfo.h"
#include "RectHelper.h"
namespace Magpie {
static bool IsCornerInRect(Point p, const Rect& r) noexcept {
return p.x >= r.left && p.x <= r.right && p.y >= r.top && p.y <= r.bottom;
}
static bool OptimizeDirtyRectPair(Rect& rect1, Rect& rect2, bool reversed = false) noexcept {
if (RectHelper::IsEmpty(rect1) || RectHelper::IsEmpty(rect2)) {
return false;
}
// 计算 rect2 有几个角在 rect1 内
bool lt = IsCornerInRect(Point{ rect2.left, rect2.top }, rect1);
bool rt = IsCornerInRect(Point{ rect2.right, rect2.top }, rect1);
bool rb = IsCornerInRect(Point{ rect2.right, rect2.bottom }, rect1);
bool lb = IsCornerInRect(Point{ rect2.left, rect2.bottom }, rect1);
uint32_t count = (uint32_t)lt + (uint32_t)rt + (uint32_t)rb + (uint32_t)lb;
if (count <= 1) {
if (!reversed) {
// 尝试反向
return OptimizeDirtyRectPair(rect2, rect1, true);
}
if (count == 0) {
// 有小间隙也合并,因为检查重复帧使用 16x16 分块,而且多余的像素因为纹理缓存复制代价很小
constexpr uint32_t MERGE_THRESHOLD = DUP_FRAME_DISPATCH_BLOCK_SIZE / 2;
if (rect1.top == rect2.top && rect1.bottom == rect2.bottom) {
if (rect1.right < rect2.left) {
if (rect1.right + MERGE_THRESHOLD >= rect2.left) {
// rect2 合并进 rect1
rect1.right = rect2.right;
rect2.right = rect2.left;
return true;
}
} else {
assert(rect1.left > rect2.right);
if (rect2.right + MERGE_THRESHOLD >= rect1.left) {
rect1.left = rect2.left;
rect2.right = rect2.left;
return true;
}
}
} else if (rect1.left == rect2.left && rect1.right == rect2.right) {
if (rect1.bottom < rect2.top) {
if (rect1.bottom + MERGE_THRESHOLD >= rect2.top) {
rect1.bottom = rect2.bottom;
rect2.right = rect2.left;
return true;
}
} else {
assert(rect1.top > rect2.bottom);
if (rect2.bottom + MERGE_THRESHOLD >= rect1.top) {
rect1.top = rect2.top;
rect2.right = rect2.left;
return true;
}
}
}
}
} else if (count == 2) {
// rect2 有两个角在 rect1 内时可以合并或裁剪
if (lt) {
if (rt) {
if (rect2.left == rect1.left && rect2.right == rect1.right) {
// rect2 合并进 rect1
rect1.bottom = rect2.bottom;
rect2.right = rect2.left;
return true;
} else if (rect2.top != rect1.bottom) {
// 裁剪 rect2
rect2.top = rect1.bottom;
assert(rect2.bottom >= rect2.top);
return true;
}
} else {
assert(lb);
if (rect2.top == rect1.top && rect2.bottom == rect1.bottom) {
rect1.right = rect2.right;
rect2.right = rect2.left;
return true;
} else if (rect2.left != rect1.right) {
rect2.left = rect1.right;
assert(rect2.right >= rect2.left);
return true;
}
}
} else {
assert(rb);
if (rt) {
if (rect2.top == rect1.top && rect2.bottom == rect1.bottom) {
rect1.left = rect2.left;
rect2.right = rect2.left;
return true;
} else if (rect2.right != rect1.left) {
rect2.right = rect1.left;
assert(rect2.right >= rect2.left);
return true;
}
} else {
if (rect2.left == rect1.left && rect2.right == rect1.right) {
rect1.top = rect2.top;
rect2.right = rect2.left;
return true;
} else if (rect2.bottom != rect1.top) {
rect2.bottom = rect1.top;
assert(rect2.bottom >= rect2.top);
return true;
}
}
}
} else if (count == 4) {
// rect2 在 rect1 内
rect2.right = rect2.left;
return true;
}
return false;
}
static void BasicOptimize(SmallVectorImpl<Rect>& dirtyRects) noexcept {
// 持续循环直到不再能优化
while (true) {
const uint32_t count = (uint32_t)dirtyRects.size();
assert(count > 0);
bool optimized = false;
for (uint32_t i = 0; i < count; ++i) {
for (uint32_t j = i + 1; j < count; ++j) {
if (OptimizeDirtyRectPair(dirtyRects[i], dirtyRects[j])) {
optimized = true;
}
}
}
if (!optimized) {
return;
}
// 从后向前删除空矩形
for (int i = int(count - 1); i >= 0; --i) {
const Rect& rect = dirtyRects[i];
if (RectHelper::IsEmpty(rect)) {
dirtyRects.erase(dirtyRects.begin() + i);
}
}
}
}
static uint32_t CalcTotalPixels(const SmallVectorImpl<Rect>& rects) noexcept {
uint32_t result = 0;
for (const Rect& rect : rects) {
result += RectHelper::CalcArea(rect);
}
return result;
}
#ifdef MP_DEBUG_INFO
// 验证优化算法的正确性
static void ValidateOptimize(const SmallVectorImpl<Rect>& originRects, const SmallVectorImpl<Rect>& newRects) noexcept {
if (originRects.empty()) {
return;
}
std::vector<bool> pixels;
for (const Rect& originRect : originRects) {
// 作为优化先检查有没有被优化后的某个矩形包含
bool contained = false;
for (const Rect& newRect : newRects) {
if (RectHelper::Contains(newRect, originRect)) {
contained = true;
break;
}
}
if (contained) {
continue;
}
// 可能被多个矩形共同包含,需要逐像素检查
const uint32_t originWidth = originRect.right - originRect.left;
pixels.assign(size_t((originRect.bottom - originRect.top) * originWidth), false);
for (Rect newRect : newRects) {
if (!RectHelper::Intersect(newRect, newRect, originRect)) {
continue;
}
for (uint32_t i = newRect.top; i < newRect.bottom; ++i) {
uint32_t start = (i - originRect.top) * originWidth;
std::fill(pixels.begin() + size_t(start + newRect.left - originRect.left),
pixels.begin() + size_t(start + newRect.right - originRect.left), true);
}
}
if (std::find(pixels.begin(), pixels.end(), false) != pixels.end()) {
OutputDebugString(L"优化脏矩形算法错误!\n");
// 打印脏矩形供调试
for (const Rect& rect : originRects) {
OutputDebugString(fmt::format(L"{},{},{},{}\n",
rect.left, rect.top, rect.right, rect.bottom).c_str());
}
return;
}
}
}
#endif
void DirtyRectsOptimizer::Execute(SmallVectorImpl<Rect>& dirtyRects) noexcept {
uint32_t rectCount = (uint32_t)dirtyRects.size();
if (rectCount <= 1) {
return;
}
#ifdef MP_DEBUG_INFO
auto se = wil::scope_exit(std::bind_front(ValidateOptimize, DEBUG_INFO.validateDirtyRectsOptimizer ?
SmallVector<Rect>(dirtyRects.begin(), dirtyRects.end()) : SmallVector<Rect>(), std::ref(dirtyRects)));
#endif
if (rectCount <= MAX_CAPTURE_DIRTY_RECT_COUNT * 4) {
BasicOptimize(dirtyRects);
rectCount = (uint32_t)dirtyRects.size();
}
// 深度优化的复杂度为 n^4输入矩形数量太多时应削减。花太多时间优化脏矩形是得不偿失的
constexpr uint32_t DEEP_OPTIMIZE_LIMIT = MAX_CAPTURE_DIRTY_RECT_COUNT * 2;
if (rectCount > DEEP_OPTIMIZE_LIMIT) {
Rect& lastRect = dirtyRects[DEEP_OPTIMIZE_LIMIT - 1];
for (auto it = dirtyRects.begin() + DEEP_OPTIMIZE_LIMIT; it != dirtyRects.end(); ++it) {
lastRect = RectHelper::Union(lastRect, *it);
}
dirtyRects.erase(dirtyRects.begin() + DEEP_OPTIMIZE_LIMIT, dirtyRects.end());
BasicOptimize(dirtyRects);
rectCount = (uint32_t)dirtyRects.size();
}
if (rectCount == 1) {
return;
}
uint32_t totalPixels = CalcTotalPixels(dirtyRects);
while (true) {
uint32_t minTotalPixels = std::numeric_limits<uint32_t>::max();
uint32_t targetRectCount = 0;
bool targetCanOptimize = false;
uint32_t targetIdx1 = 0;
uint32_t targetIdx2 = 0;
// 遍历所有的两两合并找出总像素数最少的
for (uint32_t i = 0; i < rectCount; ++i) {
for (uint32_t j = i + 1; j < rectCount; ++j) {
const Rect& rect1 = dirtyRects[i];
const Rect& rect2 = dirtyRects[j];
// 两个矩形必须相交才有优化的可能,但脏矩形数量过多时需要强制合并
if (!RectHelper::IsOverlap(rect1, rect2) && rectCount <= MAX_CAPTURE_DIRTY_RECT_COUNT) {
continue;
}
Rect unionedRect = RectHelper::Union(rect1, rect2);
uint32_t newTotalPixels = 0;
uint32_t newRectCount = 0;
bool optimized = false;
// 这里只优化一轮而不是调用 OptimizeDirtyRects既降低复杂度又能避免堆分配
for (uint32_t k = 0; k < rectCount; ++k) {
if (k == i || k == j) {
continue;
}
Rect curRect = dirtyRects[k];
if (OptimizeDirtyRectPair(curRect, unionedRect)) {
optimized = true;
}
if (!RectHelper::IsEmpty(curRect)) {
newTotalPixels += RectHelper::CalcArea(curRect);
++newRectCount;
}
}
if (!RectHelper::IsEmpty(unionedRect)) {
newTotalPixels += RectHelper::CalcArea(unionedRect);
++newRectCount;
}
if (newTotalPixels < minTotalPixels ||
(newTotalPixels == minTotalPixels && newRectCount < targetRectCount)) {
minTotalPixels = newTotalPixels;
targetRectCount = newRectCount;
targetCanOptimize = optimized;
targetIdx1 = i;
targetIdx2 = j;
}
}
}
// 总像素数持平也采用,因为脏矩形数量减少了
if (minTotalPixels > totalPixels && rectCount <= MAX_CAPTURE_DIRTY_RECT_COUNT) {
return;
}
assert(targetIdx1 < targetIdx2);
dirtyRects[targetIdx1] = RectHelper::Union(dirtyRects[targetIdx1], dirtyRects[targetIdx2]);
dirtyRects.erase(dirtyRects.begin() + targetIdx2);
if (targetCanOptimize) {
BasicOptimize(dirtyRects);
totalPixels = CalcTotalPixels(dirtyRects);
} else {
totalPixels = minTotalPixels;
}
rectCount = (uint32_t)dirtyRects.size();
if (rectCount == 1) {
return;
}
}
}
#ifdef _DEBUG
static Ignore _ = [] {
auto rectComp = [](const Rect& l, const Rect& r) {
return std::tuple(l.left, l.top, l.right, l.bottom) <
std::tuple(r.left, r.top, r.right, r.bottom);
};
SmallVector<Rect, 0> dirtyRects;
dirtyRects.reserve(16);
dirtyRects.emplace_back(0, 0, 2, 2);
dirtyRects.emplace_back(1, 1, 3, 4);
dirtyRects.emplace_back(2, 1, 4, 3);
dirtyRects.emplace_back(0, 1, 3, 2);
dirtyRects.emplace_back(3, 3, 4, 4);
BasicOptimize(dirtyRects);
std::sort(dirtyRects.begin(), dirtyRects.end(), rectComp);
assert(dirtyRects.size() == 2);
assert((dirtyRects[0] == Rect{ 0, 0, 2, 2 }));
assert((dirtyRects[1] == Rect{ 1, 1, 4, 4 }));
dirtyRects.clear();
dirtyRects.emplace_back(0, 0, 1, 1);
dirtyRects.emplace_back(0, 0, 2, 2);
BasicOptimize(dirtyRects);
assert(dirtyRects.size() == 1);
assert((dirtyRects[0] == Rect{ 0, 0, 2, 2 }));
return Ignore();
}();
#endif
}

View file

@ -1,11 +0,0 @@
#pragma once
#include <SmallVector.h>
namespace Magpie {
struct DirtyRectsOptimizer {
// 尝试减少脏矩形数量和总像素数
static void Execute(SmallVectorImpl<Rect>& dirtyRects) noexcept;
};
}

View file

@ -1,336 +0,0 @@
#include "pch.h"
#include "DuplicateFrameChecker.h"
#include "DebugInfo.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "shaders/DuplicateFrameCS.h"
#include "shaders/DuplicateFrameCS_NoBoundsChecking.h"
namespace Magpie {
static constexpr uint16_t INITIAL_CHECK_COUNT = 16;
static constexpr uint16_t INITIAL_SKIP_COUNT = 1;
static constexpr uint16_t MAX_SKIP_COUNT = 16;
DuplicateFrameChecker::DuplicateFrameChecker() noexcept :
_nextSkipCount(INITIAL_SKIP_COUNT), _framesLeft(INITIAL_CHECK_COUNT) {}
// 使用 D3D11 而不是 D3D12 检查重复帧。有两个原因:
// 1. D3D11 支持 IDXGIDevice::SetGPUThreadPriority可以提高 GPU 优先级,
// 而 D3D12 没有等价接口。
// 2. 对于小任务 D3D11 启动渲染的耗时比 D3D12 短,差距可以达到 50us 以上。
//
// 对于不支持脏矩形且捕获帧右下两边没有多余像素的捕获方式,可以禁用边界检查获得
// 性能提升。
bool DuplicateFrameChecker::Initialize(
ID3D11Device5* d3d11Device,
ID3D11DeviceContext4* d3d11DC,
const ColorInfo& colorInfo,
Size frameSize,
uint32_t captureFrameCount,
bool disableBoundsChecking
) noexcept {
assert(ScalingWindow::Get().Options().duplicateFrameDetectionMode !=
DuplicateFrameDetectionMode::Never);
_device = d3d11Device;
_deviceContext = d3d11DC;
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
_frameSize = frameSize;
#ifdef _DEBUG
_isBoundsCheckingDisabled = disableBoundsChecking;
#endif
_frameSrvs.resize(captureFrameCount);
HRESULT hr = d3d11Device->CreateComputeShader(
disableBoundsChecking ? DuplicateFrameCS_NoBoundsChecking : DuplicateFrameCS,
disableBoundsChecking ? sizeof(DuplicateFrameCS_NoBoundsChecking) : sizeof(DuplicateFrameCS),
nullptr,
_dupFrameCS.put()
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateComputeShader 失败", hr);
return false;
}
{
D3D11_BUFFER_DESC desc = {
// CSSetConstantBuffers1 要求偏移量以 256 字节对齐
.ByteWidth = (MAX_CAPTURE_DIRTY_RECT_COUNT - 1) * 256 + 8 * sizeof(uint32_t),
.Usage = D3D11_USAGE_DYNAMIC,
.BindFlags = D3D11_BIND_CONSTANT_BUFFER,
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
.StructureByteStride = desc.ByteWidth
};
hr = d3d11Device->CreateBuffer(&desc, nullptr, _constantBuffer.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateBuffer 失败", hr);
return false;
}
desc.ByteWidth = MAX_CAPTURE_DIRTY_RECT_COUNT * sizeof(uint32_t);
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
desc.StructureByteStride = desc.ByteWidth;
hr = d3d11Device->CreateBuffer(&desc, nullptr, _resultBuffer.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateBuffer 失败", hr);
return false;
}
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.BindFlags = 0;
hr = d3d11Device->CreateBuffer(&desc, nullptr, _readBackBuffer.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateBuffer 失败", hr);
return false;
}
}
{
D3D11_UNORDERED_ACCESS_VIEW_DESC desc = {
.Format = DXGI_FORMAT_R32_UINT,
.ViewDimension = D3D11_UAV_DIMENSION_BUFFER,
.Buffer = {
.NumElements = MAX_CAPTURE_DIRTY_RECT_COUNT
}
};
hr = d3d11Device->CreateUnorderedAccessView(_resultBuffer.get(), &desc, _resultBufferUav.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateUnorderedAccessView 失败", hr);
return false;
}
}
{
D3D11_SAMPLER_DESC desc{
.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT,
.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP,
.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP,
.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP,
.ComparisonFunc = D3D11_COMPARISON_NEVER
};
hr = d3d11Device->CreateSamplerState(&desc, _sampler.put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateSamplerState 失败", hr);
return false;
}
}
_deviceContext->CSSetShader(_dupFrameCS.get(), nullptr, 0);
{
ID3D11UnorderedAccessView* uav = _resultBufferUav.get();
_deviceContext->CSSetUnorderedAccessViews(0, 1, &uav, nullptr);
}
{
ID3D11SamplerState* t = _sampler.get();
_deviceContext->CSSetSamplers(0, 1, &t);
}
return true;
}
HRESULT DuplicateFrameChecker::CheckFrame(
ID3D11Texture2D* frameResource,
uint32_t frameIdx,
SmallVectorImpl<Rect>& dirtyRects
) noexcept {
assert(!dirtyRects.empty() && dirtyRects.size() <= MAX_CAPTURE_DIRTY_RECT_COUNT);
#ifdef _DEBUG
{
D3D11_TEXTURE2D_DESC desc;
frameResource->GetDesc(&desc);
assert(desc.Width == _frameSize.width && desc.Height == _frameSize.height);
if (_isBoundsCheckingDisabled) {
// 确保捕获帧右下两边没有多余像素
for (const Rect& rect : dirtyRects) {
assert(rect.right == desc.Width && rect.bottom == desc.Height);
}
}
}
#endif
if (!_frameSrvs[frameIdx]) {
HRESULT hr = _device->CreateShaderResourceView(frameResource, nullptr, _frameSrvs[frameIdx].put());
if (FAILED(hr)) {
Logger::Get().ComError("CreateShaderResourceView 失败", hr);
return hr;
}
}
// 第一帧无需检查重复帧
if (_oldFrameIdx == std::numeric_limits<uint32_t>::max()) {
return S_OK;
}
if (ScalingWindow::Get().Options().duplicateFrameDetectionMode == DuplicateFrameDetectionMode::Always) {
HRESULT hr = _CheckDirtyRects(frameIdx, dirtyRects);
if (FAILED(hr)) {
Logger::Get().ComError("_CheckDirtyRects 失败", hr);
return hr;
}
return S_OK;
}
// 动态检查重复帧,见 #787
if (_isCheckingForDuplicateFrame) {
if (--_framesLeft == 0) {
_isCheckingForDuplicateFrame = false;
_framesLeft = _nextSkipCount;
if (_nextSkipCount < MAX_SKIP_COUNT) {
// 增加下一次连续跳过检查的帧数
++_nextSkipCount;
}
}
HRESULT hr = _CheckDirtyRects(frameIdx, dirtyRects);
if (FAILED(hr)) {
Logger::Get().ComError("_CheckDirtyRects 失败", hr);
return hr;
}
if (dirtyRects.empty()) {
_isCheckingForDuplicateFrame = true;
_framesLeft = INITIAL_CHECK_COUNT;
_nextSkipCount = INITIAL_SKIP_COUNT;
}
} else {
if (--_framesLeft == 0) {
_isCheckingForDuplicateFrame = true;
// 第 2 次连续检查 10 帧,之后逐渐减少,从第 16 次开始只连续检查 2 帧
_framesLeft = uint32_t((-4 * (int)_nextSkipCount + 78) / 7);
}
#ifdef MP_DEBUG_INFO
if (DEBUG_INFO.enableStatisticsForDynamicDuplicateFrameDetection) {
// 预测此帧不会重复,验证是否正确
SmallVector<Rect> tempRects(dirtyRects.begin(), dirtyRects.end());
HRESULT hr = _CheckDirtyRects(frameIdx, tempRects);
if (FAILED(hr)) {
Logger::Get().ComError("_CheckDirtyRects 失败", hr);
return hr;
}
auto lk = DEBUG_INFO.lock.lock_exclusive();
++DEBUG_INFO.ddfdSkippedFrameCount;
if (tempRects.empty()) {
++DEBUG_INFO.ddfdWrongPredictionCount;
}
}
#endif
}
return S_OK;
}
void DuplicateFrameChecker::OnFrameAdopted(uint32_t frameIdx) noexcept {
_oldFrameIdx = frameIdx;
}
void DuplicateFrameChecker::OnCaptureStopped() noexcept {
_oldFrameIdx = std::numeric_limits<uint32_t>::max();
std::fill(_frameSrvs.begin(), _frameSrvs.end(), nullptr);
}
HRESULT DuplicateFrameChecker::_CheckDirtyRects(uint32_t newFrameIdx, SmallVectorImpl<Rect>& dirtyRects) noexcept {
assert(dirtyRects.size() <= MAX_CAPTURE_DIRTY_RECT_COUNT);
{
assert(_frameSrvs[_oldFrameIdx] && _frameSrvs[newFrameIdx]);
ID3D11ShaderResourceView* srvs[]{ _frameSrvs[_oldFrameIdx].get(), _frameSrvs[newFrameIdx].get()};
_deviceContext->CSSetShaderResources(0, 2, srvs);
}
const uint32_t dirtyRectCount = (uint32_t)dirtyRects.size();
D3D11_MAPPED_SUBRESOURCE ms;
HRESULT hr = _deviceContext->Map(_constantBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D11DeviceContext::Map 失败", hr);
return hr;
}
++_curTargetValue;
for (uint32_t i = 0; i < dirtyRectCount; ++i) {
const Rect& dirtyRect = dirtyRects[i];
alignas(32) DirectXHelper::Constant32 constants[] = {
{.uintVal = dirtyRect.left},
{.uintVal = dirtyRect.top},
{.uintVal = dirtyRect.right},
{.uintVal = dirtyRect.bottom},
{.floatVal = 1.0f / _frameSize.width},
{.floatVal = 1.0f / _frameSize.height},
{.uintVal = _curTargetValue},
{.uintVal = i}
};
// CSSetConstantBuffers1 要求偏移量以 256 字节对齐
std::memcpy((uint8_t*)ms.pData + i * 256, constants, sizeof(constants));
}
_deviceContext->Unmap(_constantBuffer.get(), 0);
for (uint32_t i = 0; i < dirtyRectCount; ++i) {
{
ID3D11Buffer* buffer = _constantBuffer.get();
UINT firstConstant = i * 16;
UINT numConstants = 16;
_deviceContext->CSSetConstantBuffers1(0, 1, &buffer, &firstConstant, &numConstants);
}
const Rect& dirtyRect = dirtyRects[i];
_deviceContext->Dispatch(
(dirtyRect.right - dirtyRect.left + DUP_FRAME_DISPATCH_BLOCK_SIZE - 1) / DUP_FRAME_DISPATCH_BLOCK_SIZE,
(dirtyRect.bottom - dirtyRect.top + DUP_FRAME_DISPATCH_BLOCK_SIZE - 1) / DUP_FRAME_DISPATCH_BLOCK_SIZE,
1
);
}
{
D3D11_BOX box = {
.right = dirtyRectCount * 4,
.bottom = 1,
.back = 1
};
_deviceContext->CopySubresourceRegion(_readBackBuffer.get(), 0, 0, 0, 0, _resultBuffer.get(), 0, &box);
}
// 读取结果
SmallVector<uint32_t, 4> removeList;
hr = _deviceContext->Map(_readBackBuffer.get(), 0, D3D11_MAP_READ, 0, &ms);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D11DeviceContext::Map 失败", hr);
return hr;
}
for (uint32_t i = 0; i < dirtyRectCount; ++i) {
if (((uint32_t*)ms.pData)[i] != _curTargetValue) {
// 此矩形内画面无变化
removeList.push_back(i);
}
}
_deviceContext->Unmap(_readBackBuffer.get(), 0);
if (!removeList.empty()) {
// 从后向前删除
std::sort(removeList.begin(), removeList.end(), std::greater<uint32_t>());
for (uint32_t idx : removeList) {
dirtyRects.erase(dirtyRects.begin() + idx);
}
}
return S_OK;
}
}

View file

@ -1,59 +0,0 @@
#pragma once
#include "SmallVector.h"
namespace Magpie {
class DuplicateFrameChecker {
public:
DuplicateFrameChecker() noexcept;
DuplicateFrameChecker(const DuplicateFrameChecker&) = delete;
DuplicateFrameChecker(DuplicateFrameChecker&&) = delete;
~DuplicateFrameChecker() = default;
bool Initialize(
ID3D11Device5* d3d11Device,
ID3D11DeviceContext4* d3d11DC,
const ColorInfo& colorInfo,
Size frameSize,
uint32_t captureFrameCount,
bool disableBoundsChecking
) noexcept;
HRESULT CheckFrame(ID3D11Texture2D* frameResource, uint32_t frameIdx, SmallVectorImpl<Rect>& dirtyRects) noexcept;
void OnFrameAdopted(uint32_t frameIdx) noexcept;
void OnCaptureStopped() noexcept;
private:
HRESULT _CheckDirtyRects(uint32_t newFrameIdx, SmallVectorImpl<Rect>& dirtyRects) noexcept;
ID3D11Device5* _device = nullptr;
ID3D11DeviceContext4* _deviceContext = nullptr;
Size _frameSize{};
winrt::com_ptr<ID3D11ComputeShader> _dupFrameCS;
winrt::com_ptr<ID3D11Buffer> _constantBuffer;
winrt::com_ptr<ID3D11Buffer> _resultBuffer;
winrt::com_ptr<ID3D11UnorderedAccessView> _resultBufferUav;
winrt::com_ptr<ID3D11Buffer> _readBackBuffer;
winrt::com_ptr<ID3D11SamplerState> _sampler;
std::vector<winrt::com_ptr<ID3D11ShaderResourceView>> _frameSrvs;
uint32_t _oldFrameIdx = std::numeric_limits<uint32_t>::max();
uint32_t _curTargetValue = 0;
// 用于检查重复帧
uint16_t _nextSkipCount;
uint16_t _framesLeft;
bool _isScRGB = false;
#ifdef _DEBUG
bool _isBoundsCheckingDisabled = false;
#endif
bool _isCheckingForDuplicateFrame = true;
};
}

View file

@ -8,7 +8,7 @@
namespace Magpie {
using FnDwmGetDxSharedSurface = BOOL WINAPI(
using DwmGetDxSharedSurfaceFunc = BOOL(
HWND hWnd,
HANDLE* phSurface,
LUID* pAdapterLuid,
@ -17,11 +17,11 @@ using FnDwmGetDxSharedSurface = BOOL WINAPI(
ULONGLONG* pWin32KUpdateId
);
static FnDwmGetDxSharedSurface* DwmGetDxSharedSurface = nullptr;
static DwmGetDxSharedSurfaceFunc* DwmGetDxSharedSurface = nullptr;
bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
[[maybe_unused]] static Ignore _ = [] {
DwmGetDxSharedSurface = Win32Helper::LoadFunction<FnDwmGetDxSharedSurface>(
DwmGetDxSharedSurface = Win32Helper::LoadSystemFunction<DwmGetDxSharedSurfaceFunc>(
L"user32.dll", "DwmGetDxSharedSurface");
return Ignore();
}();

View file

@ -985,8 +985,9 @@ static uint32_t ResolvePasses(SmallVector<std::string_view>& blocks, EffectDesc&
return 1;
}
// INPUT 和从文件读取的纹理不能作为输出
if (it->second == 0 || !desc.textures[it->second].source.empty()) {
// INPUT 和从文件读取的纹理不能作为输出。
// 只有最后一个通道能输出到 OUTPUT这是为了方便截图。
if (it->second == 0 || it->second == 1 || !desc.textures[it->second].source.empty()) {
return 1;
}
@ -1118,10 +1119,6 @@ static uint32_t ResolvePasses(SmallVector<std::string_view>& blocks, EffectDesc&
if (passDesc.desc.empty()) {
passDesc.desc = fmt::format("Pass {}", i + 1);
}
// 排序输入和输出纹理以便于渲染
std::sort(passDesc.inputs.begin(), passDesc.inputs.end());
std::sort(passDesc.outputs.begin(), passDesc.outputs.end());
}
return 0;

View file

@ -417,7 +417,7 @@ bool EffectDrawer::_UpdateConstants(
) noexcept {
const bool isInlineParams = desc.flags & EffectFlags::InlineParams;
SmallVector<DirectXHelper::Constant32, 32> constants;
SmallVector<EffectHelper::Constant32, 32> constants;
// 大小必须为 4 的倍数
const size_t builtinConstantCount = 10;
@ -448,7 +448,7 @@ bool EffectDrawer::_UpdateConstants(
constants[9].floatVal = outputSize.cy / (FLOAT)inputSize.cy;
// PS 样式的通道需要的参数
DirectXHelper::Constant32* pCurParam = constants.data() + builtinConstantCount;
EffectHelper::Constant32* pCurParam = constants.data() + builtinConstantCount;
if (psStylePassParams > 0) {
for (UINT i = 0, end = (UINT)desc.passes.size() - 1; i < end; ++i) {
if (desc.passes[i].flags & EffectPassFlags::PSStyle) {

View file

@ -1,14 +0,0 @@
#pragma once
namespace Magpie {
class EffectDrawerBase {
public:
EffectDrawerBase() = default;
EffectDrawerBase(const EffectDrawerBase&) = delete;
EffectDrawerBase(EffectDrawerBase&&) = delete;
virtual ~EffectDrawerBase() noexcept = default;
};
}

View file

@ -37,30 +37,10 @@ struct EffectHelper {
{"UNKNOWN", DXGI_FORMAT_UNKNOWN, 4, "float4", "float4"}
};
struct ShaderEffectTextureFormatDesc {
const char* name;
DXGI_FORMAT dxgiFormat;
uint32_t nChannel;
const char* srvTexelType;
const char* uavTexelType;
};
static constexpr ShaderEffectTextureFormatDesc SHADER_TEXTURE_FORMAT_DESCS[] = {
{"UNKNOWN", DXGI_FORMAT_UNKNOWN, 4, "float4", "float4"},
{"R8_UNORM", DXGI_FORMAT_R8_UNORM, 1, "MF", "unorm MF"},
{"R16_UNORM", DXGI_FORMAT_R16_UNORM, 1, "MF", "unorm MF"},
{"R16_FLOAT", DXGI_FORMAT_R16_FLOAT, 1, "MF", "MF"},
{"R8G8_UNORM", DXGI_FORMAT_R8G8_UNORM, 2, "MF2", "unorm MF2"},
{"R32_FLOAT" ,DXGI_FORMAT_R32_FLOAT, 1, "float", "float"},
{"R16G16_UNORM", DXGI_FORMAT_R16G16_UNORM, 2, "MF2", "unorm MF2"},
{"R16G16_FLOAT", DXGI_FORMAT_R16G16_FLOAT, 2, "MF2", "MF2"},
{"R11G11B10_FLOAT", DXGI_FORMAT_R11G11B10_FLOAT, 3, "MF3", "MF3"},
{"R8G8B8A8_UNORM", DXGI_FORMAT_R8G8B8A8_UNORM, 4, "MF4", "unorm MF4"},
{"R10G10B10A2_UNORM", DXGI_FORMAT_R10G10B10A2_UNORM, 4, "MF4", "unorm MF4"},
{"R32G32_FLOAT", DXGI_FORMAT_R32G32_FLOAT, 2, "float2", "float2"},
{"R16G16B16A16_UNORM", DXGI_FORMAT_R16G16B16A16_UNORM, 4, "MF4", "unorm MF4"},
{"R16G16B16A16_FLOAT", DXGI_FORMAT_R16G16B16A16_FLOAT, 4, "MF4", "MF4"},
{"R32G32B32A32_FLOAT", DXGI_FORMAT_R32G32B32A32_FLOAT, 4, "float4", "float4"}
union Constant32 {
float floatVal;
uint32_t uintVal;
int intVal;
};
};

View file

@ -1,229 +0,0 @@
#include "pch.h"
#include "CatmullRomDrawer.h"
#include "DescriptorHeap.h"
#include "EffectsDrawer.h"
#include "GraphicsContext.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "StrHelper.h"
namespace Magpie {
EffectsDrawer::~EffectsDrawer() noexcept {
#ifdef _DEBUG
if (_rtxTrueHdrOutputDescriptorBaseOffset != std::numeric_limits<uint32_t>::max()) {
_graphicsContext->GetDescriptorHeap()
.Free(_rtxTrueHdrOutputDescriptorBaseOffset, 1);
}
#endif
}
bool EffectsDrawer::Initialize(
GraphicsContext& graphicsContext,
const ColorInfo& colorInfo,
Size inputSize,
Size rendererSize
) noexcept {
_graphicsContext = &graphicsContext;
_colorInfo = colorInfo;
_inputSize = inputSize;
ID3D12Device5* device = graphicsContext.GetDevice();
// 检查半精度浮点支持
{
D3D12_FEATURE_DATA_D3D12_OPTIONS featureData{};
HRESULT hr = device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &featureData, sizeof(featureData));
if (SUCCEEDED(hr)) {
_isFP16Supported = featureData.MinPrecisionSupport & D3D12_SHADER_MIN_PRECISION_SUPPORT_16_BIT;
Logger::Get().Info(StrHelper::Concat("FP16 支持: ", _isFP16Supported ? "" : ""));
} else {
Logger::Get().ComError("CheckFeatureSupport 失败", hr);
}
}
if (ScalingWindow::Get().Options().IsWindowedMode()) {
_outputSize = rendererSize;
} else {
const float fillScale = std::min(
float(rendererSize.width) / inputSize.width,
float(rendererSize.height) / inputSize.height
);
_outputSize.width = std::lroundf(inputSize.width * fillScale);
_outputSize.height = std::lroundf(inputSize.height * fillScale);
}
#ifdef MP_ENABLE_RTX_TRUE_HDR
if (colorInfo.kind == winrt::AdvancedColorKind::HighDynamicRange) {
_rtxTrueHdrDrawer.emplace();
HRESULT hr = _rtxTrueHdrDrawer->Initialize(graphicsContext, inputSize, colorInfo);
if (FAILED(hr)) {
Logger::Get().ComError("RtxTrueHdrDrawer::Initialize 失败", hr);
return false;
}
Size rtxTrueHdrOutputSize = _rtxTrueHdrDrawer->GetOutputSize();
if (rtxTrueHdrOutputSize != _outputSize) {
{
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
D3D12_HEAP_FLAGS heapFlag = graphicsContext.IsHeapFlagCreateNotZeroedSupported() ?
D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
CD3DX12_RESOURCE_DESC texDesc = CD3DX12_RESOURCE_DESC::Tex2D(
DXGI_FORMAT_R16G16B16A16_FLOAT,
rtxTrueHdrOutputSize.width,
rtxTrueHdrOutputSize.height,
1, 1, 1, 0,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
);
hr = device->CreateCommittedResource(
&heapProps, heapFlag, &texDesc, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE,
nullptr, IID_PPV_ARGS(&_rtxTrueHdrOutput));
if (FAILED(hr)) {
return false;
}
}
auto& descriptorHeap = graphicsContext.GetDescriptorHeap();
hr = descriptorHeap.Alloc(1, _rtxTrueHdrOutputDescriptorBaseOffset);
if (FAILED(hr)) {
return false;
}
CD3DX12_SHADER_RESOURCE_VIEW_DESC srcDesc =
CD3DX12_SHADER_RESOURCE_VIEW_DESC::Tex2D(DXGI_FORMAT_R16G16B16A16_FLOAT, 1);
device->CreateShaderResourceView(_rtxTrueHdrOutput.get(), &srcDesc,
descriptorHeap.GetCpuHandle(_rtxTrueHdrOutputDescriptorBaseOffset));
}
}
#endif
_catmullRomDrawer.emplace();
_catmullRomDrawer->Initialize(graphicsContext);
{
// 每帧两个时间戳
const uint32_t timestampCount = 2 * ScalingWindow::Get().Options().maxProducerInFlightFrames;
D3D12_QUERY_HEAP_DESC queryHeapDesc = {
.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP,
.Count = timestampCount
};
HRESULT hr = device->CreateQueryHeap(&queryHeapDesc, IID_PPV_ARGS(&_queryHeap));
if (FAILED(hr)) {
Logger::Get().ComError("CreateQueryHeap 失败", hr);
return false;
}
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_READBACK);
CD3DX12_RESOURCE_DESC bufferDesc =
CD3DX12_RESOURCE_DESC::Buffer(timestampCount * sizeof(UINT64));
hr = device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&_queryResultBuffer)
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateCommittedResource 失败", hr);
return false;
}
hr = graphicsContext.GetCommandQueue()->GetTimestampFrequency(&_timestampFrequency);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::GetTimestampFrequency 失败", hr);
return false;
}
}
return true;
}
HRESULT EffectsDrawer::Draw(
uint32_t frameIndex,
ID3D12Resource* /*inputResource*/,
ID3D12Resource* outputResource,
uint32_t inputSrvOffset,
uint32_t outputUavOffset
) noexcept {
// 获取渲染时间
const uint32_t queryHeapIndex = 2 * frameIndex;
{
CD3DX12_RANGE range(queryHeapIndex * sizeof(UINT64), (queryHeapIndex + 2) * sizeof(UINT64));
void* pData;
HRESULT hr = _queryResultBuffer->Map(0, nullptr, &pData);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12Resource::Map 失败", hr);
return hr;
}
// UINT64* timestampes = (UINT64*)pData + queryHeapIndex;
range = {};
_queryResultBuffer->Unmap(0, &range);
}
ID3D12GraphicsCommandList* commandList = _graphicsContext->GetCommandList();
commandList->EndQuery(_queryHeap.get(), D3D12_QUERY_TYPE_TIMESTAMP, queryHeapIndex);
Size curInputSize = _inputSize;
bool postResize = true;
#ifdef MP_ENABLE_RTX_TRUE_HDR
if (_colorInfo.kind == winrt::AdvancedColorKind::HighDynamicRange) {
if (!_rtxTrueHdrOutput) {
postResize = false;
}
HRESULT hr = _rtxTrueHdrDrawer->Draw(
inputSrvOffset, _rtxTrueHdrOutput ? _rtxTrueHdrOutput.get() : outputResource);
if (FAILED(hr)) {
return hr;
}
if (_rtxTrueHdrOutput) {
inputSrvOffset = _rtxTrueHdrOutputDescriptorBaseOffset;
curInputSize = _rtxTrueHdrDrawer->GetOutputSize();
}
}
#endif
if (postResize) {
_graphicsContext->SetDescriptorHeap(_graphicsContext->GetDescriptorHeap().GetHeap());
_catmullRomDrawer->Draw(
curInputSize,
_outputSize,
inputSrvOffset,
outputUavOffset,
false
);
}
commandList->EndQuery(_queryHeap.get(), D3D12_QUERY_TYPE_TIMESTAMP, queryHeapIndex + 1);
commandList->ResolveQueryData(_queryHeap.get(), D3D12_QUERY_TYPE_TIMESTAMP, queryHeapIndex, 2,
_queryResultBuffer.get(), queryHeapIndex * sizeof(UINT64));
return S_OK;
}
void EffectsDrawer::OnResized(Size rendererSize) noexcept {
_outputSize = rendererSize;
}
void EffectsDrawer::OnColorInfoChanged(const ColorInfo& colorInfo) noexcept {
_colorInfo = colorInfo;
#ifdef MP_ENABLE_RTX_TRUE_HDR
if (_rtxTrueHdrDrawer) {
_rtxTrueHdrDrawer->OnColorInfoChanged(colorInfo);
}
#endif
}
}

View file

@ -1,62 +0,0 @@
#pragma once
#include "CatmullRomDrawer.h"
#ifdef MP_ENABLE_RTX_TRUE_HDR
#include "RtxTrueHdrDrawer.h"
#endif
namespace Magpie {
class EffectsDrawer {
public:
EffectsDrawer() noexcept = default;
EffectsDrawer(const EffectsDrawer&) = delete;
EffectsDrawer(EffectsDrawer&&) = delete;
~EffectsDrawer() noexcept;
bool Initialize(
GraphicsContext& graphicsContext,
const ColorInfo& colorInfo,
Size inputSize,
Size rendererSize
) noexcept;
HRESULT Draw(
uint32_t frameIndex,
ID3D12Resource* inputResource,
ID3D12Resource* outputResource,
uint32_t inputSrvOffset,
uint32_t outputUavOffset
) noexcept;
Size GetOutputSize() const noexcept {
return _outputSize;
}
void OnResized(Size rendererSize) noexcept;
void OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
private:
GraphicsContext* _graphicsContext = nullptr;
ColorInfo _colorInfo;
Size _inputSize{};
Size _outputSize{};
#ifdef MP_ENABLE_RTX_TRUE_HDR
std::optional<RtxTrueHdrDrawer> _rtxTrueHdrDrawer;
winrt::com_ptr<ID3D12Resource> _rtxTrueHdrOutput;
// UAV + SRV
uint32_t _rtxTrueHdrOutputDescriptorBaseOffset = std::numeric_limits<uint32_t>::max();
#endif
std::optional<CatmullRomDrawer> _catmullRomDrawer;
winrt::com_ptr<ID3D12QueryHeap> _queryHeap;
winrt::com_ptr<ID3D12Resource> _queryResultBuffer;
UINT64 _timestampFrequency = 0;
bool _isFP16Supported = false;
};
}

View file

@ -1,607 +0,0 @@
#include "pch.h"
#include "FrameProducer.h"
#include "CommonSharedConstants.h"
#include "DuplicateFrameChecker.h"
#include "DescriptorHeap.h"
#include "GraphicsCaptureFrameSource.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include <dispatcherqueue.h>
namespace Magpie {
FrameProducer::~FrameProducer() noexcept {
if (_producerThread.joinable()) {
const HANDLE hThread = _producerThread.native_handle();
if (!wil::handle_wait(hThread, 0)) {
const DWORD threadId = GetThreadId(_producerThread.native_handle());
while (true) {
// 持续尝试直到 _producerThread 创建了消息队列
PostThreadMessage(threadId, WM_QUIT, 0, 0);
if (wil::handle_wait(hThread, 1)) {
break;
}
}
}
_producerThread.join();
}
#ifdef _DEBUG
if (_inputSrvBaseOffset != std::numeric_limits<uint32_t>::max()) {
auto& descriptorHeap = _graphicsContext.GetDescriptorHeap();
uint32_t maxInFlightFrameCount = ScalingWindow::Get().Options().maxProducerInFlightFrames;
descriptorHeap.Free(_inputSrvBaseOffset, 3 * maxInFlightFrameCount + 2);
}
#endif
}
void FrameProducer::InitializeAsync(
const GraphicsContext& graphicsContext,
const ColorInfo& colorInfo,
HMONITOR hMonSrc,
const RECT& srcRect,
Size rendererSize,
Size& outputSize,
SimpleTask<bool>& task
) noexcept {
_graphicsContext.CopyDevice(graphicsContext);
_producerThread = std::thread(
&FrameProducer::_ProducerThreadProc,
this,
colorInfo,
hMonSrc,
srcRect,
rendererSize,
std::ref(outputSize),
std::ref(task)
);
}
ComponentState FrameProducer::GetState() const noexcept {
return _state.load(std::memory_order_relaxed);
}
uint64_t FrameProducer::GetLatestFrameNumber() const noexcept {
return _frameRingBuffer.GetLatestFrameNumber();
}
bool FrameProducer::ConsumerBeginFrame(
ID3D12Resource*& frame,
uint32_t& frameSrvOffset,
UINT64& fenceValueToSignal
) noexcept {
uint32_t bufferIdx;
if (!_frameRingBuffer.ConsumerBeginFrame(bufferIdx, frame, fenceValueToSignal)) {
return false;
}
frameSrvOffset = _outputSrvBaseOffset + bufferIdx;
return true;
}
HRESULT FrameProducer::ConsumerEndFrame(
ID3D12CommandQueue* commandQueue,
UINT64 fenceValueToSignal
) const noexcept {
return _frameRingBuffer.ConsumerEndFrame(commandQueue, fenceValueToSignal);
}
void FrameProducer::OnResizedAsync(
Size rendererSize,
Size& outputSize,
SimpleTask<HRESULT>& task
) noexcept {
_dispatcher.TryEnqueue([&, rendererSize] {
HRESULT hr = S_OK;
auto se = wil::scope_exit([&] {
// 同步 outputSize
task.SetResult(hr, std::memory_order_release);
});
ComponentState state = _state.load(std::memory_order_relaxed);
if (state != ComponentState::NoError) {
hr = state == ComponentState::DeviceLost ? DXGI_ERROR_DEVICE_REMOVED : E_FAIL;
return;
}
hr = _graphicsContext.WaitForGpu();
if (!_CheckResult(hr, "GraphicsContext::WaitForGpu 失败")) {
return;
}
_effectsDrawer.OnResized(rendererSize);
outputSize = _effectsDrawer.GetOutputSize();
hr = _frameRingBuffer.OnResized(outputSize);
if (!_CheckResult(hr, "FrameRingBuffer::OnResized 失败")) {
return;
}
_CreateOutputDescriptors();
hr = _Render();
if (!_CheckResult(hr, "_Render 失败")) {
return;
}
// 等待渲染完成
hr = _graphicsContext.WaitForGpu();
if (!_CheckResult(hr, "GraphicsContext::WaitForGpu 失败")) {
return;
}
});
}
void FrameProducer::OnColorInfoChangedAsync(
const ColorInfo& colorInfo,
SimpleTask<HRESULT>& task
) noexcept {
_dispatcher.TryEnqueue([&] {
HRESULT hr = S_OK;
auto se = wil::scope_exit([&] {
task.SetResult(hr);
});
ComponentState state = _state.load(std::memory_order_relaxed);
if (state != ComponentState::NoError) {
hr = state == ComponentState::DeviceLost ? DXGI_ERROR_DEVICE_REMOVED : E_FAIL;
return;
}
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
hr = _graphicsContext.WaitForGpu();
if (!_CheckResult(hr, "GraphicsContext::WaitForGpu 失败")) {
return;
}
hr = _frameSource->OnColorInfoChanged(colorInfo);
if (!_CheckResult(hr, "GraphicsCaptureFrameSource::OnColorInfoChanged 失败")) {
return;
}
_effectsDrawer.OnColorInfoChanged(colorInfo);
hr = _frameRingBuffer.OnColorInfoChanged(colorInfo);
if (!_CheckResult(hr, "FrameRingBuffer::OnColorInfoChanged 失败")) {
return;
}
_CreateInputDescriptors();
_CreateOutputDescriptors();
// 等待新帧
while (true) {
bool isNewFrameAvailable;
hr = _frameSource->CheckForNewFrame(isNewFrameAvailable);
if (!_CheckResult(hr, "GraphicsCaptureFrameSource::CheckForNewFrame 失败")) {
return;
}
if (isNewFrameAvailable) {
break;
} else {
WaitMessage();
}
}
hr = _Render();
if (!_CheckResult(hr, "_Render 失败")) {
return;
}
// 等待渲染完成
hr = _graphicsContext.WaitForGpu();
if (!_CheckResult(hr, "GraphicsContext::WaitForGpu 失败")) {
return;
}
});
}
void FrameProducer::OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept {
_dispatcher.TryEnqueue([this, isVisible, onDestory] {
if (_state.load(std::memory_order_relaxed) != ComponentState::NoError) {
return;
}
_CheckResult(_frameSource->OnCursorVisibilityChanged(isVisible, onDestory),
"GraphicsCaptureFrameSource::OnCursorVisibilityChanged 失败");
});
}
void FrameProducer::_ProducerThreadProc(
const ColorInfo& colorInfo,
HMONITOR hMonSrc,
RECT srcRect,
Size rendererSize,
Size& outputSize,
SimpleTask<bool>& initializeTask
) noexcept {
#ifdef _DEBUG
SetThreadDescription(GetCurrentThread(), L"Magpie-缩放生产者线程");
#endif
if (_Initialize(colorInfo, hMonSrc, srcRect, rendererSize, outputSize)) {
// 同步 outputSize
initializeTask.SetResult(true, std::memory_order_release);
} else {
Logger::Get().Error("_Initialize 失败");
initializeTask.SetResult(false);
return;
}
StepTimerStatus stepTimerStatus = StepTimerStatus::WaitingForNewFrame;
const bool waitMsgForNewFrame = _frameSource->ShouldWaitMessageForNewFrame();
bool isWaitingForFirstFrame = true;
MSG msg;
while (true) {
bool fpsUpdated = false;
// WaitingForFPSLimiter 状态下新帧消息可能已被处理,不要等待消息,直到状态变化
stepTimerStatus = _stepTimer.WaitForNextFrame(
waitMsgForNewFrame && stepTimerStatus != StepTimerStatus::WaitingForFPSLimiter,
fpsUpdated
);
if (fpsUpdated) {
// FPS 变化时要求前端重新渲染以更新叠加层
PostMessage(ScalingWindow::Get().Handle(),
CommonSharedConstants::WM_FRONTEND_RENDER, 0, 0);
}
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
break;
}
DispatchMessage(&msg);
}
// 异步检查回调是否出错
if (msg.message == WM_QUIT ||
_state.load(std::memory_order_relaxed) != ComponentState::NoError) {
break;
}
if (stepTimerStatus == StepTimerStatus::WaitingForFPSLimiter) {
continue;
}
bool isNewFrameAvailable;
if (!_CheckResult(_frameSource->CheckForNewFrame(isNewFrameAvailable),
"GraphicsCaptureFrameSource::CheckForNewFrame 失败")) {
break;
}
// 强制等待第一帧
if (!isNewFrameAvailable && (isWaitingForFirstFrame || stepTimerStatus != StepTimerStatus::ForceNewFrame)) {
continue;
}
isWaitingForFirstFrame = false;
if (!_CheckResult(_Render(), "_Render 失败")) {
break;
}
}
_graphicsContext.WaitForGpu();
// 必须在创建线程释放
_frameSource.reset();
if (_monitorThread.joinable()) {
const HANDLE hThread = _monitorThread.native_handle();
if (!wil::handle_wait(hThread, 0)) {
const DWORD threadId = GetThreadId(_monitorThread.native_handle());
while (true) {
// 持续尝试直到创建了消息队列
PostThreadMessage(threadId, WM_QUIT, 0, 0);
if (wil::handle_wait(hThread, 1)) {
break;
}
}
}
_monitorThread.join();
}
}
bool FrameProducer::_Initialize(
const ColorInfo& colorInfo,
HMONITOR hMonSrc,
const RECT& srcRect,
Size rendererSize,
Size& outputSize
) noexcept {
winrt::init_apartment(winrt::apartment_type::single_threaded);
// 创建 DispatcherQueue
{
winrt::Windows::System::DispatcherQueueController dqc{ nullptr };
HRESULT hr = CreateDispatcherQueueController(
DispatcherQueueOptions{
.dwSize = sizeof(DispatcherQueueOptions),
.threadType = DQTYPE_THREAD_CURRENT
},
(PDISPATCHERQUEUECONTROLLER*)winrt::put_abi(dqc)
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateDispatcherQueueController 失败", hr);
return false;
}
_dispatcher = dqc.DispatcherQueue();
}
const ScalingOptions& options = ScalingWindow::Get().Options();
const uint32_t maxInFlightFrameCount = options.maxProducerInFlightFrames;
if (!_graphicsContext.InitializeAfterCopyDevice(
maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY_NORMAL,
D3D12_COMMAND_LIST_TYPE_COMPUTE,
true
)) {
Logger::Get().Error("初始化 GraphicsContext 失败");
return false;
}
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
_frameSource = std::make_unique<GraphicsCaptureFrameSource>();
if (!_frameSource->Initialize(_graphicsContext, srcRect, hMonSrc, colorInfo)) {
Logger::Get().Error("初始化 GraphicsCaptureFrameSource 失败");
return false;
}
{
const Size inputSize = {
uint32_t(srcRect.right - srcRect.left),
uint32_t(srcRect.bottom - srcRect.top)
};
if (!_effectsDrawer.Initialize(_graphicsContext, colorInfo, inputSize, rendererSize)) {
Logger::Get().Error("EffectsDrawer::Initialize 失败");
return false;
}
outputSize = _effectsDrawer.GetOutputSize();
if (!_frameRingBuffer.Initialize(_graphicsContext, outputSize, colorInfo)) {
Logger::Get().Error("初始化 FrameRingBuffer 失败");
return false;
}
}
{
auto& descriptorHeap = _graphicsContext.GetDescriptorHeap();
// maxInFlightFrameCount + (maxInFlightFrameCount + 1) + (maxInFlightFrameCount + 1)
HRESULT hr = descriptorHeap.Alloc(maxInFlightFrameCount * 3 + 2, _inputSrvBaseOffset);
if (FAILED(hr)) {
Logger::Get().ComError("DescriptorHeap::Alloc 失败", hr);
return false;
}
_outputUavBaseOffset = _inputSrvBaseOffset + maxInFlightFrameCount;
_outputSrvBaseOffset = _outputUavBaseOffset + maxInFlightFrameCount + 1;
_CreateInputDescriptors();
_CreateOutputDescriptors();
}
_monitorThread = std::thread(&FrameProducer::_MonitorThreadProc, this);
if (options.IsBenchmarkMode()) {
// 不要使用无限大,/fp:fast 下无限大值不可靠
_stepTimer.Initialize(std::numeric_limits<float>::max(), std::nullopt);
} else {
_stepTimer.Initialize(options.minFrameRate, options.maxFrameRate);
}
// 最后启动捕获以尽可能推迟显示黄色边框 (Win10) 或禁用圆角 (Win11)
if (!_frameSource->Start()) {
Logger::Get().Error("GraphicsCaptureFrameSource::Start 失败");
return false;
}
return true;
}
void FrameProducer::_CreateInputDescriptors() noexcept {
uint32_t bufferCount = ScalingWindow::Get().Options().maxProducerInFlightFrames;
ID3D12Device5* device = _graphicsContext.GetDevice();
auto& descriptorHeap = _graphicsContext.GetDescriptorHeap();
uint32_t descriptorSize = descriptorHeap.GetDescriptorSize();
CD3DX12_CPU_DESCRIPTOR_HANDLE descriptorCpuHandle(descriptorHeap.GetCpuHandle(_inputSrvBaseOffset));
CD3DX12_SHADER_RESOURCE_VIEW_DESC srvDesc = CD3DX12_SHADER_RESOURCE_VIEW_DESC::Tex2D(
_isScRGB ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, 1);
for (uint32_t i = 0; i < bufferCount; ++i) {
device->CreateShaderResourceView(_frameSource->GetOutput(i), &srvDesc, descriptorCpuHandle);
descriptorCpuHandle.Offset(descriptorSize);
}
}
void FrameProducer::_CreateOutputDescriptors() noexcept {
uint32_t bufferCount = ScalingWindow::Get().Options().maxProducerInFlightFrames + 1;
ID3D12Device5* device = _graphicsContext.GetDevice();
auto& descriptorHeap = _graphicsContext.GetDescriptorHeap();
uint32_t descriptorSize = descriptorHeap.GetDescriptorSize();
CD3DX12_CPU_DESCRIPTOR_HANDLE uavCpuHandle(descriptorHeap.GetCpuHandle(_outputUavBaseOffset));
CD3DX12_CPU_DESCRIPTOR_HANDLE srvCpuHandle(descriptorHeap.GetCpuHandle(_outputSrvBaseOffset));
DXGI_FORMAT format = _isScRGB ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM;
CD3DX12_UNORDERED_ACCESS_VIEW_DESC uavDesc = CD3DX12_UNORDERED_ACCESS_VIEW_DESC::Tex2D(format);
CD3DX12_SHADER_RESOURCE_VIEW_DESC srvDesc = CD3DX12_SHADER_RESOURCE_VIEW_DESC::Tex2D(format, 1);
for (uint32_t i = 0; i < bufferCount; ++i) {
ID3D12Resource* resource = _frameRingBuffer.GetBuffer(i);
device->CreateUnorderedAccessView(resource, nullptr, &uavDesc, uavCpuHandle);
device->CreateShaderResourceView(resource, &srvDesc, srvCpuHandle);
uavCpuHandle.Offset(descriptorSize);
srvCpuHandle.Offset(descriptorSize);
}
}
HRESULT FrameProducer::_Render() noexcept {
_stepTimer.PrepareForRender();
uint32_t frameIndex;
HRESULT hr = _graphicsContext.BeginFrame(frameIndex, nullptr);
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::BeginFrame 失败", hr);
return hr;
}
ID3D12CommandQueue* commandQueue = _graphicsContext.GetCommandQueue();
uint32_t frameRingBufferIdx;
hr = _frameRingBuffer.ProducerBeginFrame(commandQueue, frameRingBufferIdx);
if (FAILED(hr)) {
Logger::Get().ComError("FrameRingBuffer::ProducerBeginFrame 失败", hr);
return hr;
}
uint32_t frameSourceOutputIdx;
hr = _frameSource->Update(frameSourceOutputIdx);
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsCaptureFrameSource::Update 失败", hr);
return hr;
}
ID3D12GraphicsCommandList* commandList = _graphicsContext.GetCommandList();
{
ID3D12DescriptorHeap* heap = _graphicsContext.GetDescriptorHeap().GetHeap();
commandList->SetDescriptorHeaps(1, &heap);
}
// 输出和输出纹理都处于 COMMON 状态,使用结束后也应处于此状态
ID3D12Resource* inputResource = _frameSource->GetOutput(frameSourceOutputIdx);
ID3D12Resource* outputResource = _frameRingBuffer.GetBuffer(frameRingBufferIdx);
{
D3D12_RESOURCE_BARRIER barriers[] = {
CD3DX12_RESOURCE_BARRIER::Transition(
inputResource, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, 0),
CD3DX12_RESOURCE_BARRIER::Transition(
outputResource, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, 0)
};
commandList->ResourceBarrier((UINT)std::size(barriers), barriers);
}
hr = _effectsDrawer.Draw(
frameIndex,
inputResource,
outputResource,
_inputSrvBaseOffset + frameSourceOutputIdx,
_outputUavBaseOffset + frameRingBufferIdx
);
if (FAILED(hr)) {
Logger::Get().ComError("EffectsDrawer::Draw 失败", hr);
return hr;
}
{
D3D12_RESOURCE_BARRIER barriers[] = {
CD3DX12_RESOURCE_BARRIER::Transition(
inputResource, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COMMON, 0),
CD3DX12_RESOURCE_BARRIER::Transition(
outputResource, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COMMON, 0)
};
commandList->ResourceBarrier((UINT)std::size(barriers), barriers);
}
hr = commandList->Close();
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12GraphicsCommandList::Close 失败", hr);
return hr;
}
commandQueue->ExecuteCommandLists(1, CommandListCast(&commandList));
hr = _frameRingBuffer.ProducerEndFrame(commandQueue);
if (FAILED(hr)) {
Logger::Get().ComError("FrameRingBuffer::ProducerEndFrame 失败", hr);
return hr;
}
hr = _graphicsContext.EndFrame();
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::EndFrame 失败", hr);
return hr;
}
return S_OK;
}
void FrameProducer::_MonitorThreadProc() noexcept {
wil::unique_event_nothrow event;
if (!event.try_create(wil::EventOptions::None, nullptr)) {
Logger::Get().Win32Error("创建事件失败");
return;
}
uint64_t frameNumber = 1;
// 绑定新帧渲染完成时触发的事件
HRESULT hr = _frameRingBuffer.SetEventOnNewFrame(frameNumber, event.get());
if (FAILED(hr)) {
Logger::Get().ComError("FrameRingBuffer::SetEventOnNewFrame 失败", hr);
return;
}
MSG msg;
while (true) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
return;
}
DispatchMessage(&msg);
}
// 有新帧可用时返回 WAIT_OBJECT_0有新消息时返回 WAIT_OBJECT_0 + 1
HANDLE hEvent = event.get();
if (MsgWaitForMultipleObjectsEx(1, &hEvent, INFINITE, QS_ALLINPUT, MWMO_INPUTAVAILABLE) == WAIT_OBJECT_0) {
// 通知消费者渲染
PostMessage(ScalingWindow::Get().Handle(), CommonSharedConstants::WM_FRONTEND_RENDER, 0, 0);
hr = _frameRingBuffer.SetEventOnNewFrame(frameNumber, event.get());
if (FAILED(hr)) {
Logger::Get().ComError("FrameRingBuffer::SetEventOnNewFrame 失败", hr);
return;
}
}
}
}
bool FrameProducer::_CheckResult(HRESULT hr, std::string_view errorMsg) noexcept {
assert(_state.load(std::memory_order_relaxed) == ComponentState::NoError);
if (SUCCEEDED(hr)) {
return true;
}
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
_state.store(ComponentState::DeviceLost, std::memory_order_relaxed);
} else {
_state.store(ComponentState::Error, std::memory_order_relaxed);
}
Logger::Get().ComError(errorMsg, hr);
return false;
}
}

View file

@ -1,99 +0,0 @@
#pragma once
#include "GraphicsContext.h"
#include "EffectsDrawer.h"
#include "FrameRingBuffer.h"
#include "StepTimer.h"
#include "SimpleTask.h"
namespace Magpie {
class GraphicsCaptureFrameSource;
class FrameProducer {
public:
FrameProducer() = default;
FrameProducer(const FrameProducer&) = delete;
FrameProducer(FrameProducer&&) = delete;
~FrameProducer() noexcept;
void InitializeAsync(
const GraphicsContext& graphicsContext,
const ColorInfo& colorInfo,
HMONITOR hMonSrc,
const RECT& srcRect,
Size rendererSize,
Size& outputSize,
SimpleTask<bool>& task
) noexcept;
ComponentState GetState() const noexcept;
uint64_t GetLatestFrameNumber() const noexcept;
bool ConsumerBeginFrame(
ID3D12Resource*& frame,
uint32_t& frameSrvOffset,
UINT64& fenceValueToSignal
) noexcept;
HRESULT ConsumerEndFrame(
ID3D12CommandQueue* commandQueue,
UINT64 fenceValueToSignal
) const noexcept;
void OnResizedAsync(Size rendererSize, Size& outputSize, SimpleTask<HRESULT>& task) noexcept;
void OnColorInfoChangedAsync(const ColorInfo& colorInfo, SimpleTask<HRESULT>& task) noexcept;
void OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept;
private:
void _ProducerThreadProc(
const ColorInfo& colorInfo,
HMONITOR hMonSrc,
RECT srcRect,
Size rendererSize,
Size& outputSize,
SimpleTask<bool>& initializeTask
) noexcept;
bool _Initialize(
const ColorInfo& colorInfo,
HMONITOR hMonSrc,
const RECT& srcRect,
Size rendererSize,
Size& outputSize
) noexcept;
void _CreateInputDescriptors() noexcept;
void _CreateOutputDescriptors() noexcept;
HRESULT _Render() noexcept;
void _MonitorThreadProc() noexcept;
bool _CheckResult(HRESULT hr, std::string_view errorMsg) noexcept;
std::atomic<ComponentState> _state = ComponentState::NoError;
std::thread _producerThread;
winrt::DispatcherQueue _dispatcher{ nullptr };
std::thread _monitorThread;
GraphicsContext _graphicsContext;
FrameRingBuffer _frameRingBuffer;
StepTimer _stepTimer;
std::unique_ptr<GraphicsCaptureFrameSource> _frameSource;
EffectsDrawer _effectsDrawer;
uint32_t _inputSrvBaseOffset = std::numeric_limits<uint32_t>::max();
uint32_t _outputUavBaseOffset = std::numeric_limits<uint32_t>::max();
uint32_t _outputSrvBaseOffset = std::numeric_limits<uint32_t>::max();
bool _isScRGB = false;
};
}

View file

@ -1,264 +0,0 @@
#include "pch.h"
#include "FrameRingBuffer.h"
#include "GraphicsContext.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "DebugInfo.h"
#include "DescriptorHeap.h"
namespace Magpie {
bool FrameRingBuffer::Initialize(
GraphicsContext& graphicsContext,
Size size,
const ColorInfo& colorInfo
) noexcept {
_graphicsContext = &graphicsContext;
_size = size;
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
const uint32_t slotCount = ScalingWindow::Get().Options().maxProducerInFlightFrames + 1;
_slots.resize(slotCount);
// 消费者应落后于生产者
_curConsumerIdx = slotCount - 1;
ID3D12Device5* device = graphicsContext.GetDevice();
HRESULT hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_consumerFence));
if (FAILED(hr)) {
Logger::Get().ComError("CreateFence 失败", hr);
return false;
}
hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_producerFence));
if (FAILED(hr)) {
Logger::Get().ComError("CreateFence 失败", hr);
return false;
}
hr = _CreateBuffers();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateBuffers 失败", hr);
return false;
}
return true;
}
ID3D12Resource* FrameRingBuffer::GetBuffer(uint32_t index) noexcept {
auto lk = _lock.lock_shared();
return _slots[index].resource.get();
}
HRESULT FrameRingBuffer::ProducerBeginFrame(
ID3D12CommandQueue* commandQueue,
uint32_t& bufferIdx
) noexcept {
auto lk = _lock.lock_exclusive();
// 等待消费者命令队列不再使用 _curProducerSlot
_FrameResourceSlot& curSlot = _slots[_curProducerIdx];
if (_consumerFence->GetCompletedValue() < curSlot.consumerFenceValue) {
HRESULT hr = commandQueue->Wait(_consumerFence.get(), curSlot.consumerFenceValue);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::Wait 失败", hr);
return hr;
}
}
bufferIdx = _curProducerIdx;
return S_OK;
}
HRESULT FrameRingBuffer::ProducerEndFrame(ID3D12CommandQueue* commandQueue) noexcept {
auto lk = _lock.lock_exclusive();
HRESULT hr = commandQueue->Signal(_producerFence.get(), _slots[_curProducerIdx].producerFenceValue);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::Signal 失败", hr);
return hr;
}
const uint32_t slotCount = (uint32_t)_slots.size();
const uint32_t nextProducerSlot = (_curProducerIdx + 1) % slotCount;
if (nextProducerSlot == _curConsumerIdx) {
uint32_t nextConsumerSlot = (_curConsumerIdx + 1) % slotCount;
uint64_t fenceValueToWait = _slots[nextConsumerSlot].producerFenceValue;
if (_producerFence->GetCompletedValue() < fenceValueToWait) {
lk.reset();
// 等待新缓冲区可用
hr = _producerFence->SetEventOnCompletion(fenceValueToWait, nullptr);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12Fence::SetEventOnCompletion 失败", hr);
return hr;
}
lk = _lock.lock_exclusive();
if (_curConsumerIdx == nextProducerSlot) {
_curConsumerIdx = nextConsumerSlot;
}
} else {
_curConsumerIdx = nextConsumerSlot;
}
}
uint64_t nextFenceValue = _slots[_curProducerIdx].producerFenceValue + 1;
_curProducerIdx = nextProducerSlot;
_slots[nextProducerSlot].producerFenceValue = nextFenceValue;
#ifdef MP_DEBUG_INFO
{
auto debugLock = DEBUG_INFO.lock.lock_exclusive();
// 在这里计算的 consumerLatency 不准确
DEBUG_INFO.producerFrameNumber = (uint32_t)nextFenceValue;
}
#endif
return S_OK;
}
bool FrameRingBuffer::ConsumerBeginFrame(
uint32_t& bufferIdx,
ID3D12Resource*& frame,
UINT64& fenceValueToSignal
) noexcept {
auto lk = _lock.lock_exclusive();
if (_curConsumerIdx != _curProducerIdx) {
const uint64_t completedFenceValue = _producerFence->GetCompletedValue();
if (completedFenceValue == 0) {
// 第一帧尚未完成
return false;
}
const uint32_t slotCount = (uint32_t)_slots.size();
uint32_t nextConsumerSlot = (_curConsumerIdx + 1) % slotCount;
if (completedFenceValue >= _slots[nextConsumerSlot].producerFenceValue) {
_curConsumerIdx = nextConsumerSlot;
// 寻找最新帧
while (true) {
nextConsumerSlot = (_curConsumerIdx + 1) % slotCount;
if (completedFenceValue < _slots[nextConsumerSlot].producerFenceValue) {
break;
}
_curConsumerIdx = nextConsumerSlot;
// 窗口很小,但有发生的可能
if (_curConsumerIdx == _curProducerIdx) {
break;
}
}
}
}
_FrameResourceSlot& curSlot = _slots[_curConsumerIdx];
fenceValueToSignal = ++_curConsumerFenceValue;
curSlot.consumerFenceValue = fenceValueToSignal;
bufferIdx = _curConsumerIdx;
frame = curSlot.resource.get();
return true;
}
HRESULT FrameRingBuffer::ConsumerEndFrame(
ID3D12CommandQueue* commandQueue,
UINT64 fenceValueToSignal
) const noexcept {
HRESULT hr = commandQueue->Signal(_consumerFence.get(), fenceValueToSignal);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::Signal 失败", hr);
return hr;
}
#ifdef MP_DEBUG_INFO
{
auto debugLock = DEBUG_INFO.lock.lock_exclusive();
DEBUG_INFO.consumerFrameNumber = (uint32_t)_slots[_curConsumerIdx].producerFenceValue;
DEBUG_INFO.consumerLatency = DEBUG_INFO.producerFrameNumber - DEBUG_INFO.consumerFrameNumber;
}
#endif
return S_OK;
}
HRESULT FrameRingBuffer::SetEventOnNewFrame(uint64_t& frameNumber, HANDLE hEvent) const noexcept {
assert(frameNumber != 0);
HRESULT hr = _producerFence->SetEventOnCompletion(frameNumber, hEvent);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12Fence::SetEventOnCompletion 失败", hr);
return hr;
}
// 下一个要等待的值
frameNumber = std::max(GetLatestFrameNumber(), frameNumber) + 1;
return S_OK;
}
uint64_t FrameRingBuffer::GetLatestFrameNumber() const noexcept {
return _producerFence->GetCompletedValue();
}
HRESULT FrameRingBuffer::OnResized(Size size) noexcept {
_size = size;
HRESULT hr = _CreateBuffers();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateBuffers 失败", hr);
return hr;
}
return S_OK;
}
HRESULT FrameRingBuffer::OnColorInfoChanged(const ColorInfo& colorInfo) noexcept {
const bool wasScRGB = _isScRGB;
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
if (_isScRGB == wasScRGB) {
return S_OK;
}
HRESULT hr = _CreateBuffers();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateBuffers 失败", hr);
return hr;
}
return S_OK;
}
HRESULT FrameRingBuffer::_CreateBuffers() noexcept {
ID3D12Device5* device = _graphicsContext->GetDevice();
CD3DX12_HEAP_PROPERTIES heapProperties(D3D12_HEAP_TYPE_DEFAULT);
D3D12_HEAP_FLAGS heapFlag = _graphicsContext->IsHeapFlagCreateNotZeroedSupported() ?
D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
CD3DX12_RESOURCE_DESC texDesc = CD3DX12_RESOURCE_DESC::Tex2D(
_isScRGB ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
_size.width,
_size.height,
1, 1, 1, 0,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
);
for (_FrameResourceSlot& slot : _slots) {
HRESULT hr = device->CreateCommittedResource(&heapProperties, heapFlag,
&texDesc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&slot.resource));
if (FAILED(hr)) {
Logger::Get().ComError("CreateCommittedResource 失败", hr);
return hr;
}
}
return S_OK;
}
}

View file

@ -1,78 +0,0 @@
#pragma once
#include "SmallVector.h"
namespace Magpie {
class GraphicsContext;
class FrameRingBuffer {
public:
FrameRingBuffer() = default;
FrameRingBuffer(const FrameRingBuffer&) = delete;
FrameRingBuffer(FrameRingBuffer&&) = delete;
bool Initialize(
GraphicsContext& graphicsContext,
Size size,
const ColorInfo& colorInfo
) noexcept;
ID3D12Resource* GetBuffer(uint32_t index) noexcept;
HRESULT ProducerBeginFrame(
ID3D12CommandQueue* commandQueue,
uint32_t& bufferIdx
) noexcept;
HRESULT ProducerEndFrame(ID3D12CommandQueue* commandQueue) noexcept;
bool ConsumerBeginFrame(
uint32_t& bufferIdx,
ID3D12Resource*& frame,
UINT64& fenceValueToSignal
) noexcept;
HRESULT ConsumerEndFrame(
ID3D12CommandQueue* commandQueue,
UINT64 fenceValueToSignal
) const noexcept;
HRESULT SetEventOnNewFrame(uint64_t& frameNumber, HANDLE hEvent) const noexcept;
uint64_t GetLatestFrameNumber() const noexcept;
HRESULT OnResized(Size size) noexcept;
HRESULT OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
private:
HRESULT _CreateBuffers() noexcept;
// 只在生产者线程访问
GraphicsContext* _graphicsContext = nullptr;
wil::srwlock _lock;
struct _FrameResourceSlot {
// 生产者和消费者应确保使用结束后此资源处于 COPY_SOURCE 状态。有两个方面的考虑:
// 1. 文档说:当一个资源在某个队列上转换到了可写状态时,该资源被视为由该队列独占拥有。在该
// 资源被另一个队列访问之前,它必须先转换到只读或 COMMON 状态。
// 2. 消费者使用共享纹理的频率比生产者更高,因此选择对消费者更友好的 COPY_SOURCE 状态。
winrt::com_ptr<ID3D12Resource> resource;
uint64_t consumerFenceValue = 0;
uint64_t producerFenceValue = 1;
};
std::vector<_FrameResourceSlot> _slots;
uint32_t _curConsumerIdx = 0;
uint32_t _curProducerIdx = 0;
winrt::com_ptr<ID3D12Fence1> _consumerFence;
uint64_t _curConsumerFenceValue = 0;
winrt::com_ptr<ID3D12Fence1> _producerFence;
Size _size{};
bool _isScRGB = false;
};
}

View file

@ -4,6 +4,7 @@
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingOptions.h"
#include "ScalingWindow.h"
#include "shaders/DuplicateFrameCS.h"
#include "Win32Helper.h"
@ -11,7 +12,12 @@
namespace Magpie {
FrameSourceBase::FrameSourceBase() noexcept {}
static constexpr uint16_t INITIAL_CHECK_COUNT = 16;
static constexpr uint16_t INITIAL_SKIP_COUNT = 1;
static constexpr uint16_t MAX_SKIP_COUNT = 16;
FrameSourceBase::FrameSourceBase() noexcept :
_nextSkipCount(INITIAL_SKIP_COUNT), _framesLeft(INITIAL_CHECK_COUNT) {}
FrameSourceBase::~FrameSourceBase() noexcept {
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
@ -51,7 +57,98 @@ bool FrameSourceBase::Initialize(DeviceResources& deviceResources, BackendDescri
}
FrameSourceState FrameSourceBase::Update() noexcept {
return FrameSourceState::NewFrame;
const FrameSourceState state = _Update();
const ScalingOptions& options = ScalingWindow::Get().Options();
const auto duplicateFrameDetectionMode = options.duplicateFrameDetectionMode;
if (state != FrameSourceState::NewFrame || options.Is3DGameMode() ||
duplicateFrameDetectionMode == DuplicateFrameDetectionMode::Never) {
return state;
}
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
if (!_prevFrame) {
if (_InitCheckingForDuplicateFrame()) {
d3dDC->CopyResource(_prevFrame.get(), _output.get());
} else {
Logger::Get().Error("_InitCheckingForDuplicateFrame 失败");
_prevFrame = nullptr;
_prevFrameSrv = nullptr;
}
return FrameSourceState::NewFrame;
}
if (duplicateFrameDetectionMode == DuplicateFrameDetectionMode::Always) {
// 总是检查重复帧
if (_IsDuplicateFrame()) {
return FrameSourceState::Waiting;
} else {
d3dDC->CopyResource(_prevFrame.get(), _output.get());
return FrameSourceState::NewFrame;
}
}
///////////////////////////////////////////////
//
// 动态检查重复帧,见 #787
//
///////////////////////////////////////////////
const bool isStatisticsEnabled = options.IsStatisticsForDynamicDetectionEnabled();
if (_isCheckingForDuplicateFrame) {
if (--_framesLeft == 0) {
_isCheckingForDuplicateFrame = false;
_framesLeft = _nextSkipCount;
if (_nextSkipCount < MAX_SKIP_COUNT) {
// 增加下一次连续跳过检查的帧数
++_nextSkipCount;
}
}
if (_IsDuplicateFrame()) {
_isCheckingForDuplicateFrame = true;
_framesLeft = INITIAL_CHECK_COUNT;
_nextSkipCount = INITIAL_SKIP_COUNT;
return FrameSourceState::Waiting;
} else {
if (_isCheckingForDuplicateFrame || isStatisticsEnabled) {
d3dDC->CopyResource(_prevFrame.get(), _output.get());
}
return FrameSourceState::NewFrame;
}
} else {
if (--_framesLeft == 0) {
_isCheckingForDuplicateFrame = true;
// 第 2 次连续检查 10 帧,之后逐渐减少,从第 16 次开始只连续检查 2 帧
_framesLeft = uint32_t((-4 * (int)_nextSkipCount + 78) / 7);
if (!isStatisticsEnabled) {
// 下一帧将检查重复帧,需要复制此帧
d3dDC->CopyResource(_prevFrame.get(), _output.get());
}
}
if (isStatisticsEnabled) {
const bool isDuplicate = _IsDuplicateFrame();
if (!isDuplicate) {
d3dDC->CopyResource(_prevFrame.get(), _output.get());
}
std::pair<uint32_t, uint32_t> statistics = _statistics.load(std::memory_order_relaxed);
if (isDuplicate) {
// 预测错误
++statistics.first;
}
// 总帧数
++statistics.second;
_statistics.store(statistics, std::memory_order_relaxed);
}
return FrameSourceState::NewFrame;
}
}
std::pair<uint32_t, uint32_t> FrameSourceBase::GetStatisticsForDynamicDetection() const noexcept {

View file

@ -83,6 +83,10 @@ private:
// 用于检查重复帧
winrt::com_ptr<ID3D11Texture2D> _prevFrame;
winrt::com_ptr<ID3D11ShaderResourceView> _prevFrameSrv;
uint16_t _nextSkipCount;
uint16_t _framesLeft;
bool _isCheckingForDuplicateFrame = true;
protected:
bool _roundCornerDisabled = false;

File diff suppressed because it is too large Load diff

View file

@ -1,138 +1,52 @@
#pragma once
#include "SmallVector.h"
#include "FrameSourceBase.h"
#include <ShlObj.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <winrt/Windows.Graphics.Capture.h>
namespace Magpie {
class GraphicsContext;
class DuplicateFrameChecker;
enum class FrameSourceState {
WaitingForFirstFrame,
Waiting,
NewFrameAvailable
};
// 使用 Windows.Graphics.Capture 接口捕获窗口,见
// https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture
class GraphicsCaptureFrameSource {
// 使用 Window Runtime 的 Windows.Graphics.Capture API 抓取窗口
// 见 https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture
class GraphicsCaptureFrameSource final : public FrameSourceBase {
public:
GraphicsCaptureFrameSource() = default;
GraphicsCaptureFrameSource(const GraphicsCaptureFrameSource&) = delete;
GraphicsCaptureFrameSource(GraphicsCaptureFrameSource&&) = delete;
virtual ~GraphicsCaptureFrameSource();
~GraphicsCaptureFrameSource() noexcept;
bool Start() noexcept override;
bool Initialize(
GraphicsContext& graphicsContext,
const RECT& srcRect,
HMONITOR hMonSrc,
const ColorInfo& colorInfo
) noexcept;
bool Start() noexcept;
ID3D12Resource* GetOutput(uint32_t index) noexcept {
return _slots[index].output.get();
FrameSourceWaitType WaitType() const noexcept override {
return FrameSourceWaitType::WaitForMessage;
}
bool ShouldWaitMessageForNewFrame() const noexcept {
return true;
const char* Name() const noexcept override {
return "Graphics Capture";
}
HRESULT CheckForNewFrame(bool& isNewFrameAvailable) noexcept;
void OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept override;
HRESULT Update(uint32_t& outputIdx) noexcept;
protected:
bool _Initialize() noexcept override;
HRESULT OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
HRESULT OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept;
FrameSourceState _Update() noexcept override;
private:
bool _CreateCaptureDevice(HMONITOR hMonSrc) noexcept;
bool _CreateBridgeDeviceResources(IDXGIAdapter1* dxgiAdapter) noexcept;
HRESULT _CreateDisplayDependentResources() noexcept;
bool _InitializeCaptureItem() noexcept;
void _Direct3D11CaptureFramePool_FrameArrived(
const winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool& pool,
const winrt::IInspectable&
);
void _DisableRoundCornerInWin11() noexcept;
HRESULT _StartCapture() noexcept;
bool _StartCapture() noexcept;
void _StopCapture() noexcept;
GraphicsContext* _graphicsContext = nullptr;
bool _CaptureWindow(IGraphicsCaptureItemInterop* interop) noexcept;
std::atomic<DWORD> _producerThreadId;
bool _TryCreateGraphicsCaptureItem(IGraphicsCaptureItemInterop* interop) noexcept;
winrt::com_ptr<ID3D11Device5> _d3d11Device;
winrt::com_ptr<ID3D11DeviceContext4> _d3d11DC;
D3D11_BOX _frameBox{};
winrt::com_ptr<ID3D12CommandQueue> _copyCommandQueue;
winrt::com_ptr<ID3D12GraphicsCommandList> _copyCommandList;
// 用于跨适配器捕获
winrt::com_ptr<ID3D12Device5> _bridgeDevice;
winrt::com_ptr<ID3D12CommandQueue> _bridgeCopyCommandQueue;
winrt::com_ptr<ID3D12GraphicsCommandList> _bridgeCopyCommandList;
winrt::com_ptr<ID3D12Heap> _bridgeHeap;
winrt::com_ptr<ID3D12Heap> _sharedHeap;
winrt::com_ptr<ID3D12Fence1> _bridgeFence;
winrt::com_ptr<ID3D12Fence1> _sharedFence;
uint64_t _curCrossAdapterFenceValue = 0;
wil::srwlock _latestFrameLock;
// 不要在持有 _latestFrameLock 时释放 _latestFrame 或调用其中的方法,和 WGC 内部的
// 同步机制冲突。如果此时_Direct3D11CaptureFramePool_FrameArrived 正在执行会死锁。
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame _latestFrame{ nullptr };
SmallVector<Rect> _latestFrameDirtyRects;
std::vector<std::pair<ID3D11Texture2D*, winrt::com_ptr<ID3D12Resource>>> _captureFrameResourceTable;
std::unique_ptr<DuplicateFrameChecker> _duplicateFrameChecker;
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame _newFrame{ nullptr };
SmallVector<Rect> _newFrameDirtyRects;
uint32_t _newCaptureFrameResourceIdx = 0;
struct _FrameCrossAdapterResourceSlot {
winrt::com_ptr<ID3D12CommandAllocator> commandAllocator;
winrt::com_ptr<ID3D12Resource> bridgeResource;
winrt::com_ptr<ID3D12Resource> sharedResource;
};
std::vector<_FrameCrossAdapterResourceSlot> _crossAdapterSlots;
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice _wrappedDevice{ nullptr };
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice _wrappedD3DDevice{ nullptr };
winrt::Windows::Graphics::Capture::GraphicsCaptureItem _captureItem{ nullptr };
winrt::Windows::Graphics::Capture::GraphicsCaptureSession _captureSession{ nullptr };
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool _captureFramePool{ nullptr };
winrt::com_ptr<ITaskbarList> _taskbarList;
struct _FrameResourceSlot {
winrt::com_ptr<ID3D12CommandAllocator> commandAllocator;
// 保留引用防止 WGC 再次写入
winrt::Windows::Graphics::Capture::Direct3D11CaptureFrame captureFrame{ nullptr };
uint32_t captureFrameResourceIdx = 0;
SmallVector<Rect> dirtyRects;
winrt::com_ptr<ID3D12Resource> output;
};
std::vector<_FrameResourceSlot> _slots;
uint32_t _curFrameIdx = 0;
D3D12_BOX _frameBox{};
bool _isScRGB = false;
bool _isSrcStyleChanged = false;
bool _isRoundCornerDisabled = false;
bool _isDirtyRegionSupported = false;
};
}

View file

@ -1,478 +0,0 @@
#include "pch.h"
#include "GraphicsContext.h"
#include "DebugInfo.h"
#include "Logger.h"
#include "DirectXHelper.h"
#include "StrHelper.h"
#include "DescriptorHeap.h"
namespace Magpie {
bool GraphicsContext::Initialize(
const GraphicsCardId& graphicsCardId,
uint32_t maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY priority,
D3D12_COMMAND_LIST_TYPE commandListType,
DescriptorHeap& descriptorHeap,
bool disableFrameFenceTracking
) noexcept {
_descriptorHeap = &descriptorHeap;
HRESULT hr = _CreateDXGIFactory();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateDXGIFactory 失败", hr);
return false;
}
if (!_CreateAdapterAndDevice(graphicsCardId)) {
Logger::Get().Error("_CreateAdapterAndDevice 失败");
return false;
}
#ifdef _DEBUG
// 调试层汇报错误或警告时中断
if (winrt::com_ptr<ID3D12InfoQueue> infoQueue = _device.try_as<ID3D12InfoQueue>()) {
infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE);
}
#endif
// 检查根签名版本
{
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = { .HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1 };
hr = _device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData));
if (FAILED(hr)) {
Logger::Get().ComError("CheckFeatureSupport 失败", hr);
return false;
}
_rootSignatureVersion = featureData.HighestVersion;
}
// 检查是否是集成显卡
{
D3D12_FEATURE_DATA_ARCHITECTURE1 data{};
if (SUCCEEDED(_device->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE1, &data, sizeof(data)))) {
_isUMA = data.UMA;
}
}
// 检查 D3D12_HEAP_FLAG_CREATE_NOT_ZEROED 支持
// https://devblogs.microsoft.com/directx/coming-to-directx-12-more-control-over-memory-allocation/
_isHeapFlagCreateNotZeroedSupported = (bool)_device.try_as<ID3D12Device8>();
// 检查 Resizable BAR 支持
{
D3D12_FEATURE_DATA_D3D12_OPTIONS16 data{};
if (SUCCEEDED(_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS16, &data, sizeof(data)))) {
_isGPUUploadHeapSupported = data.GPUUploadHeapSupported;
}
}
// 检查 shader model 6.0 支持
{
D3D12_FEATURE_DATA_SHADER_MODEL data = { .HighestShaderModel = D3D_SHADER_MODEL_6_0 };
if (SUCCEEDED(_device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &data, sizeof(data)))) {
_isSM6Supported = data.HighestShaderModel == D3D_SHADER_MODEL_6_0;
}
}
if (!_InitializeDeviceResources(maxInFlightFrameCount, priority, commandListType, disableFrameFenceTracking)) {
Logger::Get().Error("_InitializeDeviceResources 失败");
return false;
}
if (!_descriptorHeap->Initialize(_device.get())) {
Logger::Get().Error("DescriptorHeap::Initialize 失败");
return false;
}
return true;
}
void GraphicsContext::CopyDevice(const GraphicsContext& other) {
_descriptorHeap = other._descriptorHeap;
_device = other._device;
_rootSignatureVersion = other._rootSignatureVersion;
_isHeapFlagCreateNotZeroedSupported = other._isHeapFlagCreateNotZeroedSupported;
}
bool GraphicsContext::InitializeAfterCopyDevice(
uint32_t maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY priority,
D3D12_COMMAND_LIST_TYPE commandListType,
bool disableFrameFenceTracking
) noexcept {
HRESULT hr = _CreateDXGIFactory();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateDXGIFactory 失败", hr);
return false;
}
if (!_CreateAdapterFromDevice()) {
Logger::Get().ComError("_CreateDXGIFactory 失败", hr);
return false;
}
if (!_InitializeDeviceResources(maxInFlightFrameCount, priority, commandListType, disableFrameFenceTracking)) {
Logger::Get().Error("_InitializeDeviceResources 失败");
return false;
}
return true;
}
IDXGIFactory7* GraphicsContext::GetDXGIFactoryForEnumingAdapters() noexcept {
if (!_dxgiFactory->IsCurrent()) {
HRESULT hr = _CreateDXGIFactory();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateDXGIFactory 失败", hr);
return nullptr;
}
}
return _dxgiFactory.get();
}
HRESULT GraphicsContext::Signal(uint64_t& fenceValue) noexcept {
fenceValue = ++_curFenceValue;
return _commandQueue->Signal(_fence.get(), _curFenceValue);
}
HRESULT GraphicsContext::WaitForFenceValue(uint64_t fenceValue) noexcept {
if (_fence->GetCompletedValue() >= fenceValue) {
return S_OK;
} else {
return _fence->SetEventOnCompletion(fenceValue, nullptr);
}
}
HRESULT GraphicsContext::WaitForGpu() noexcept {
HRESULT hr = _commandQueue->Signal(_fence.get(), ++_curFenceValue);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::Signal 失败", hr);
return hr;
}
return WaitForFenceValue(_curFenceValue);
}
HRESULT GraphicsContext::WaitForCommandQueue(ID3D12CommandQueue* commandQueue) noexcept {
HRESULT hr = commandQueue->Signal(_fence.get(), ++_curFenceValue);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::Signal 失败", hr);
return hr;
}
hr = _commandQueue->Wait(_fence.get(), _curFenceValue);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandQueue::Wait 失败", hr);
return hr;
}
return S_OK;
}
HRESULT GraphicsContext::BeginFrame(uint32_t& curFrameIndex, ID3D12PipelineState* initialState) noexcept {
if (!_frameFenceValues.empty()) {
HRESULT hr = WaitForFenceValue(_frameFenceValues[_curFrameIndex]);
if (FAILED(hr)) {
Logger::Get().ComError("WaitForFenceValue 失败", hr);
return hr;
}
}
HRESULT hr = _commandAllocators[_curFrameIndex]->Reset();
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12CommandAllocator::Reset 失败", hr);
return hr;
}
hr = _commandList->Reset(_commandAllocators[_curFrameIndex].get(), initialState);
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12GraphicsCommandList::Reset 失败", hr);
return hr;
}
curFrameIndex = _curFrameIndex;
return S_OK;
}
HRESULT GraphicsContext::EndFrame() noexcept {
InvalidateDescriptorHeapCache();
if (!_frameFenceValues.empty()) {
HRESULT hr = Signal(_frameFenceValues[_curFrameIndex]);
if (FAILED(hr)) {
Logger::Get().ComError("Signal 失败", hr);
return hr;
}
}
_curFrameIndex = (_curFrameIndex + 1) % (uint32_t)_commandAllocators.size();
return S_OK;
}
void GraphicsContext::SetDescriptorHeap(ID3D12DescriptorHeap* descriptorHeap) noexcept {
if (descriptorHeap != _curDescriptorHeap) {
_curDescriptorHeap = descriptorHeap;
_commandList->SetDescriptorHeaps(1, &descriptorHeap);
}
}
HRESULT GraphicsContext::_CreateDXGIFactory() noexcept {
UINT flags = 0;
#ifdef _DEBUG
flags |= DXGI_CREATE_FACTORY_DEBUG;
#endif
HRESULT hr = CreateDXGIFactory2(flags, IID_PPV_ARGS(&_dxgiFactory));
if (FAILED(hr)) {
Logger::Get().ComError("CreateDXGIFactory2 失败", hr);
}
return hr;
}
bool GraphicsContext::_InitializeDeviceResources(
uint32_t maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY priority,
D3D12_COMMAND_LIST_TYPE commandListType,
bool disableFrameFenceTracking
) noexcept {
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {
.Type = commandListType,
.Priority = priority,
.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE
};
HRESULT hr = _device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_commandQueue));
if (FAILED(hr)) {
Logger::Get().ComError("CreateCommandQueue 失败", hr);
return false;
}
}
HRESULT hr = _device->CreateCommandList1(0, commandListType,
D3D12_COMMAND_LIST_FLAG_NONE, IID_PPV_ARGS(&_commandList));
if (FAILED(hr)) {
Logger::Get().ComError("CreateCommandList1 失败", hr);
return false;
}
_commandAllocators.resize(maxInFlightFrameCount);
for (winrt::com_ptr<ID3D12CommandAllocator>& commandAllocator : _commandAllocators) {
hr = _device->CreateCommandAllocator(commandListType, IID_PPV_ARGS(&commandAllocator));
if (FAILED(hr)) {
Logger::Get().ComError("CreateCommandAllocator 失败", hr);
return false;
}
}
hr = _device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
if (FAILED(hr)) {
Logger::Get().ComError("CreateFence 失败", hr);
return false;
}
// 如果已在外部同步则无需追踪每帧的栅栏值
if (!disableFrameFenceTracking) {
_frameFenceValues.resize(maxInFlightFrameCount);
}
return true;
}
bool GraphicsContext::_CreateAdapterAndDevice(const GraphicsCardId& graphicsCardId) noexcept {
winrt::com_ptr<IDXGIAdapter1> adapter;
#ifdef MP_DEBUG_INFO
if (!DEBUG_INFO.useWarp) {
#endif
// 记录不支持 D3D12 的显卡索引,防止重复尝试
int failedIdx = -1;
if (graphicsCardId.idx >= 0) {
assert(graphicsCardId.vendorId != 0 && graphicsCardId.deviceId != 0);
// 先使用索引
HRESULT hr = _dxgiFactory->EnumAdapters1(graphicsCardId.idx, adapter.put());
if (SUCCEEDED(hr)) {
DXGI_ADAPTER_DESC1 desc;
hr = adapter->GetDesc1(&desc);
if (SUCCEEDED(hr)) {
if (desc.VendorId == graphicsCardId.vendorId && desc.DeviceId == graphicsCardId.deviceId) {
if (_TryCreateD3DDevice(adapter, desc)) {
return true;
}
failedIdx = graphicsCardId.idx;
Logger::Get().Warn("用户指定的显示卡不支持 D3D12");
} else {
Logger::Get().Warn("显卡配置已变化");
}
}
}
// 如果已确认该显卡不支持 D3D12不再重复尝试
if (failedIdx == -1) {
// 枚举查找 vendorId 和 deviceId 匹配的显卡
for (UINT adapterIdx = 0;
SUCCEEDED(_dxgiFactory->EnumAdapters1(adapterIdx, adapter.put()));
++adapterIdx
) {
if ((int)adapterIdx == graphicsCardId.idx) {
// 已经检查了 graphicsCardId.idx
continue;
}
DXGI_ADAPTER_DESC1 desc;
hr = adapter->GetDesc1(&desc);
if (FAILED(hr)) {
continue;
}
if (desc.VendorId == graphicsCardId.vendorId && desc.DeviceId == graphicsCardId.deviceId) {
if (_TryCreateD3DDevice(adapter, desc)) {
return true;
}
failedIdx = (int)adapterIdx;
Logger::Get().Warn("用户指定的显示卡不支持 D3D12");
break;
}
}
}
}
// 枚举查找第一个支持 D3D12 的显卡
for (UINT adapterIdx = 0;
SUCCEEDED(_dxgiFactory->EnumAdapters1(adapterIdx, adapter.put()));
++adapterIdx
) {
if ((int)adapterIdx == failedIdx) {
// 无需再次尝试
continue;
}
DXGI_ADAPTER_DESC1 desc;
HRESULT hr = adapter->GetDesc1(&desc);
if (FAILED(hr) || DirectXHelper::IsWARP(desc)) {
continue;
}
if (_TryCreateD3DDevice(adapter, desc)) {
return true;
}
}
#ifdef MP_DEBUG_INFO
}
#endif
// 作为最后手段,回落到 CPU 渲染 (WARP)
// https://docs.microsoft.com/en-us/windows/win32/direct3darticles/directx-warp
HRESULT hr = _dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&adapter));
if (FAILED(hr)) {
Logger::Get().ComError("EnumWarpAdapter 失败", hr);
return false;
}
DXGI_ADAPTER_DESC1 desc;
hr = adapter->GetDesc1(&desc);
if (FAILED(hr) || !_TryCreateD3DDevice(adapter, desc)) {
Logger::Get().Error("创建 WARP 设备失败");
return false;
}
return true;
}
bool GraphicsContext::_TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& adapter, const DXGI_ADAPTER_DESC1& adapterDesc) noexcept {
HRESULT hr = D3D12CreateDevice(adapter.get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device));
if (FAILED(hr)) {
Logger::Get().ComError("D3D12CreateDevice 失败", hr);
return false;
}
{
D3D_FEATURE_LEVEL featureLevels[] = {
D3D_FEATURE_LEVEL_12_2,
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0
};
D3D12_FEATURE_DATA_FEATURE_LEVELS featureData{
.NumFeatureLevels = (UINT)std::size(featureLevels),
.pFeatureLevelsRequested = featureLevels
};
hr = _device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &featureData, sizeof(featureData));
if (SUCCEEDED(hr)) {
std::string_view flStr;
switch (featureData.MaxSupportedFeatureLevel) {
case D3D_FEATURE_LEVEL_12_2:
flStr = "12.2";
break;
case D3D_FEATURE_LEVEL_12_1:
flStr = "12.1";
break;
case D3D_FEATURE_LEVEL_12_0:
flStr = "12.0";
break;
case D3D_FEATURE_LEVEL_11_1:
flStr = "11.1";
break;
case D3D_FEATURE_LEVEL_11_0:
flStr = "11.0";
break;
default:
flStr = "未知";
break;
}
Logger::Get().Info(fmt::format("已创建 D3D12 设备\n\t功能级别: {}", flStr));
} else {
Logger::Get().ComError("CheckFeatureSupport 失败", hr);
}
}
_dxgiAdapter = adapter.try_as<IDXGIAdapter4>();
if (!_dxgiAdapter) {
Logger::Get().Error("获取 IDXGIAdapter4 失败");
return false;
}
Logger::Get().Info(fmt::format("当前图形适配器: \n\tVendorId: {:#x}\n\tDeviceId: {:#x}\n\tDescription: {}",
adapterDesc.VendorId, adapterDesc.DeviceId, StrHelper::UTF16ToUTF8(adapterDesc.Description)));
return true;
}
bool GraphicsContext::_CreateAdapterFromDevice() noexcept {
const LUID adapterLuid = _device->GetAdapterLuid();
winrt::com_ptr<IDXGIAdapter1> adapter;
for (UINT adapterIdx = 0;
SUCCEEDED(_dxgiFactory->EnumAdapters1(adapterIdx, adapter.put()));
++adapterIdx
) {
DXGI_ADAPTER_DESC1 desc;
HRESULT hr = adapter->GetDesc1(&desc);
if (FAILED(hr)) {
continue;
}
if (desc.AdapterLuid != adapterLuid) {
continue;
}
_dxgiAdapter = adapter.try_as<IDXGIAdapter4>();
if (_dxgiAdapter) {
return true;
} else {
Logger::Get().Error("获取 IDXGIAdapter4 失败");
return false;
}
}
return false;
}
}

View file

@ -1,144 +0,0 @@
#pragma once
#include "ScalingOptions.h"
namespace Magpie {
class DescriptorHeap;
class GraphicsContext {
public:
GraphicsContext() = default;
GraphicsContext(const GraphicsContext&) = delete;
GraphicsContext(GraphicsContext&&) = delete;
bool Initialize(
const GraphicsCardId& graphicsCardId,
uint32_t maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY priority,
D3D12_COMMAND_LIST_TYPE commandListType,
DescriptorHeap& descriptorHeap,
bool disableFrameFenceTracking = false
) noexcept;
void CopyDevice(const GraphicsContext& other);
bool InitializeAfterCopyDevice(
uint32_t maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY priority,
D3D12_COMMAND_LIST_TYPE commandListType,
bool disableFrameTracking = false
) noexcept;
DescriptorHeap& GetDescriptorHeap() const noexcept {
return *_descriptorHeap;
}
IDXGIFactory7* GetDXGIFactory() const noexcept {
return _dxgiFactory.get();
}
IDXGIFactory7* GetDXGIFactoryForEnumingAdapters() noexcept;
IDXGIAdapter4* GetDXGIAdapter() const noexcept {
return _dxgiAdapter.get();
}
ID3D12Device5* GetDevice() const noexcept {
return _device.get();
}
ID3D12CommandQueue* GetCommandQueue() const noexcept {
return _commandQueue.get();
}
ID3D12GraphicsCommandList* GetCommandList() const noexcept {
return _commandList.get();
}
D3D_ROOT_SIGNATURE_VERSION GetRootSignatureVersion() const noexcept {
return _rootSignatureVersion;
}
bool IsUMA() const noexcept {
return _isUMA;
}
bool IsHeapFlagCreateNotZeroedSupported() const noexcept {
return _isHeapFlagCreateNotZeroedSupported;
}
bool IsGPUUploadHeapSupported() const noexcept {
return _isGPUUploadHeapSupported;
}
bool IsSM6Supported() const noexcept {
return _isSM6Supported;
}
uint32_t GetMaxInFlightFrameCount() const noexcept {
return (uint32_t)_commandAllocators.size();
}
HRESULT Signal(uint64_t& fenceValue) noexcept;
HRESULT WaitForFenceValue(uint64_t fenceValue) noexcept;
HRESULT WaitForGpu() noexcept;
HRESULT WaitForCommandQueue(ID3D12CommandQueue* commandQueue) noexcept;
HRESULT BeginFrame(
uint32_t& curFrameIndex,
ID3D12PipelineState* initialState = nullptr
) noexcept;
HRESULT EndFrame() noexcept;
void SetDescriptorHeap(ID3D12DescriptorHeap* descriptorHeap) noexcept;
void InvalidateDescriptorHeapCache() noexcept {
_curDescriptorHeap = nullptr;
}
private:
HRESULT _CreateDXGIFactory() noexcept;
bool _InitializeDeviceResources(
uint32_t maxInFlightFrameCount,
D3D12_COMMAND_QUEUE_PRIORITY priority,
D3D12_COMMAND_LIST_TYPE commandListType,
bool disableFrameFenceTracking
) noexcept;
bool _CreateAdapterAndDevice(const GraphicsCardId& graphicsCardId) noexcept;
bool _TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& adapter, const DXGI_ADAPTER_DESC1& adapterDesc) noexcept;
bool _CreateAdapterFromDevice() noexcept;
DescriptorHeap* _descriptorHeap = nullptr;
winrt::com_ptr<IDXGIFactory7> _dxgiFactory;
winrt::com_ptr<IDXGIAdapter4> _dxgiAdapter;
winrt::com_ptr<ID3D12Device5> _device;
winrt::com_ptr<ID3D12CommandQueue> _commandQueue;
std::vector<winrt::com_ptr<ID3D12CommandAllocator>> _commandAllocators;
winrt::com_ptr<ID3D12GraphicsCommandList> _commandList;
winrt::com_ptr<ID3D12Fence1> _fence;
uint64_t _curFenceValue = 0;
std::vector<uint64_t> _frameFenceValues;
uint32_t _curFrameIndex = 0;
ID3D12DescriptorHeap* _curDescriptorHeap = nullptr;
D3D_ROOT_SIGNATURE_VERSION _rootSignatureVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
bool _isUMA = false;
bool _isHeapFlagCreateNotZeroedSupported = false;
bool _isGPUUploadHeapSupported = false;
bool _isSM6Supported = false;
};
}

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\Common.Pre.props" />
<Import Project="..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props" Condition="Exists('..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props')" />
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTFastAbi>true</CppWinRTFastAbi>
@ -12,12 +10,12 @@
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{0e5205ae-dfa9-4cb8-b662-e43cd6512e2a}</ProjectGuid>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<OutDir>$(OutBaseDir)</OutDir>
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
<GeneratedFilesDir>$(IntDir)\Generated Files\</GeneratedFilesDir>
<Microsoft_Direct3D_D3D12_SkipLibraryCopy>true</Microsoft_Direct3D_D3D12_SkipLibraryCopy>
<Microsoft_Direct3D_D3D12_SkipDebugLayerCopy>true</Microsoft_Direct3D_D3D12_SkipDebugLayerCopy>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\Common.Pre.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
@ -35,7 +33,6 @@
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(RtxVideoSDKDir)' != ''">$(RtxVideoSDKDir)\include;;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<FxCompile>
<ShaderModel>5.0</ShaderModel>
@ -48,39 +45,22 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="BackendDescriptorStore.h" />
<ClInclude Include="CatmullRomDrawer.h" />
<ClInclude Include="ColorHelper.h" />
<ClInclude Include="CompSwapchainPresenter.h" />
<ClInclude Include="CursorDrawer2.h" />
<ClInclude Include="CursorManager.h" />
<ClInclude Include="CursorDrawer.h" />
<ClInclude Include="DDS.h" />
<ClInclude Include="DDSHelper.h" />
<ClInclude Include="DesktopDuplicationFrameSource.h" />
<ClInclude Include="DeviceResources.h" />
<ClInclude Include="DirtyRectsOptimizer.h" />
<ClInclude Include="DuplicateFrameChecker.h" />
<ClInclude Include="DwmSharedSurfaceFrameSource.h" />
<ClInclude Include="DescriptorHeap.h" />
<ClInclude Include="EffectCacheManager.h" />
<ClInclude Include="EffectDrawer.h" />
<ClInclude Include="EffectDrawerBase.h" />
<ClInclude Include="EffectHelper.h" />
<ClInclude Include="EffectsDrawer.h" />
<ClInclude Include="EffectsProfiler.h" />
<ClInclude Include="ExclModeHelper.h" />
<ClInclude Include="FrameProducer.h" />
<ClInclude Include="FrameSourceBase.h" />
<ClInclude Include="CursorHelper.h" />
<ClInclude Include="include\EffectInfo.h" />
<ClInclude Include="include\ShaderEffectParser.h" />
<ClInclude Include="RtxTrueHdrDrawer.h" />
<ClInclude Include="ShaderEffectCompilerService.h" />
<ClInclude Include="ShaderEffectDesc.h" />
<ClInclude Include="ShaderEffectDrawer.h" />
<ClInclude Include="GDIFrameSource.h" />
<ClInclude Include="GraphicsCaptureFrameSource.h" />
<ClInclude Include="GraphicsContext.h" />
<ClInclude Include="ImGuiBackend.h" />
<ClInclude Include="ImGuiFontsCacheManager.h" />
<ClInclude Include="OverlayHelper.h" />
@ -96,50 +76,34 @@
<ClInclude Include="include\Event.h" />
<ClInclude Include="OverlayDrawer.h" />
<ClInclude Include="PresenterBase.h" />
<ClInclude Include="RectHelper.h" />
<ClInclude Include="Renderer.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="Renderer2.h" />
<ClInclude Include="ScalingWindow.h" />
<ClInclude Include="ScreenshotHelper.h" />
<ClInclude Include="FrameRingBuffer.h" />
<ClInclude Include="SrcTracker.h" />
<ClInclude Include="StepTimer.h" />
<ClInclude Include="AdaptivePresenter.h" />
<ClInclude Include="SwapChainPresenter.h" />
<ClInclude Include="TextureHelper.h" />
<ClInclude Include="YasHelper.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="BackendDescriptorStore.cpp" />
<ClCompile Include="CatmullRomDrawer.cpp" />
<ClCompile Include="CompSwapchainPresenter.cpp" />
<ClCompile Include="CursorDrawer2.cpp" />
<ClCompile Include="CursorManager.cpp" />
<ClCompile Include="CursorDrawer.cpp" />
<ClCompile Include="DDSHelper.cpp" />
<ClCompile Include="DesktopDuplicationFrameSource.cpp" />
<ClCompile Include="DeviceResources.cpp" />
<ClCompile Include="DirectXHelper.cpp" />
<ClCompile Include="DirtyRectsOptimizer.cpp" />
<ClCompile Include="DuplicateFrameChecker.cpp" />
<ClCompile Include="DwmSharedSurfaceFrameSource.cpp" />
<ClCompile Include="DescriptorHeap.cpp" />
<ClCompile Include="EffectCacheManager.cpp" />
<ClCompile Include="EffectCompiler.cpp" />
<ClCompile Include="EffectDrawer.cpp" />
<ClCompile Include="EffectsDrawer.cpp" />
<ClCompile Include="EffectsProfiler.cpp" />
<ClCompile Include="ExclModeHelper.cpp" />
<ClCompile Include="FrameProducer.cpp" />
<ClCompile Include="FrameSourceBase.cpp" />
<ClCompile Include="CursorHelper.cpp" />
<ClCompile Include="RtxTrueHdrDrawer.cpp" />
<ClCompile Include="ShaderEffectCompilerService.cpp" />
<ClCompile Include="ShaderEffectDrawer.cpp" />
<ClCompile Include="GDIFrameSource.cpp" />
<ClCompile Include="GraphicsCaptureFrameSource.cpp" />
<ClCompile Include="GraphicsContext.cpp" />
<ClCompile Include="ImGuiBackend.cpp" />
<ClCompile Include="ImGuiFontsCacheManager.cpp" />
<ClCompile Include="OverlayHelper.cpp" />
@ -150,40 +114,21 @@
</ClCompile>
<ClCompile Include="PresenterBase.cpp" />
<ClCompile Include="Renderer.cpp" />
<ClCompile Include="Renderer2.cpp" />
<ClCompile Include="ScalingOptions.cpp" />
<ClCompile Include="ScalingRuntime.cpp" />
<ClCompile Include="ScalingWindow.cpp" />
<ClCompile Include="ScreenshotHelper.cpp" />
<ClCompile Include="FrameRingBuffer.cpp" />
<ClCompile Include="ShaderEffectParser.cpp" />
<ClCompile Include="SrcTracker.cpp" />
<ClCompile Include="StepTimer.cpp" />
<ClCompile Include="AdaptivePresenter.cpp" />
<ClCompile Include="SwapChainPresenter.cpp" />
<ClCompile Include="TextureHelper.cpp" />
<ClCompile Include="Win32Helper.cpp" />
<ClCompile Include="WindowHelper.cpp" />
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\CatmullRomCS.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\CatmullRomCS_sRGB.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\CursorVS.hlsl">
<ShaderType>Vertex</ShaderType>
</FxCompile>
<FxCompile Include="shaders\DuplicateFrameCS.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\DuplicateFrameCS_NoBoundsChecking.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\CopyFrameVS.hlsl">
<ShaderType>Vertex</ShaderType>
</FxCompile>
<FxCompile Include="shaders\ImGuiImplPS.hlsl">
<ShaderType>Pixel</ShaderType>
</FxCompile>
@ -193,24 +138,9 @@
<FxCompile Include="shaders\MaskedCursorPS.hlsl">
<ShaderType>Pixel</ShaderType>
</FxCompile>
<FxCompile Include="shaders\MaskedCursorPS_sRGB.hlsl">
<ShaderType>Pixel</ShaderType>
</FxCompile>
<FxCompile Include="shaders\MonochromeCursorPS.hlsl">
<ShaderType>Pixel</ShaderType>
</FxCompile>
<FxCompile Include="shaders\CopyCS.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\CopyCS_sRGB.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\MonochromeCursorPS_sRGB.hlsl">
<ShaderType>Pixel</ShaderType>
</FxCompile>
<FxCompile Include="shaders\RtxTrueHdrPreCS.hlsl">
<ShaderType>Compute</ShaderType>
</FxCompile>
<FxCompile Include="shaders\SimplePS.hlsl">
<ShaderType>Pixel</ShaderType>
</FxCompile>
@ -220,13 +150,11 @@
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="shaders\Common.hlsli" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets" Condition="Exists('..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@ -235,7 +163,5 @@
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.260126.7\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Direct3D.D3D12.1.619.0\build\native\Microsoft.Direct3D.D3D12.targets'))" />
</Target>
</Project>

View file

@ -19,12 +19,6 @@
<Filter Include="Presenter">
<UniqueIdentifier>{bf1f1896-560d-4f37-9c3a-891b7c9cd2af}</UniqueIdentifier>
</Filter>
<Filter Include="EffectDrawer">
<UniqueIdentifier>{ae93c055-e8c1-41c3-ba76-3422dac27328}</UniqueIdentifier>
</Filter>
<Filter Include="Services">
<UniqueIdentifier>{1ca5aed2-f451-476c-92cb-a0a4d6f3412e}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -40,6 +34,9 @@
</ClInclude>
<ClInclude Include="ScalingWindow.h" />
<ClInclude Include="CursorManager.h" />
<ClInclude Include="GraphicsCaptureFrameSource.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="GDIFrameSource.h">
<Filter>Capture</Filter>
</ClInclude>
@ -88,6 +85,9 @@
<ClInclude Include="DeviceResources.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="EffectDrawer.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="ExclModeHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
@ -134,73 +134,6 @@
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="SrcTracker.h" />
<ClInclude Include="Renderer2.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="SwapChainPresenter.h">
<Filter>Presenter</Filter>
</ClInclude>
<ClInclude Include="GraphicsCaptureFrameSource.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="GraphicsContext.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="FrameProducer.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="FrameRingBuffer.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="EffectDrawer.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="EffectDrawerBase.h">
<Filter>EffectDrawer</Filter>
</ClInclude>
<ClInclude Include="CatmullRomDrawer.h">
<Filter>EffectDrawer</Filter>
</ClInclude>
<ClInclude Include="DuplicateFrameChecker.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="RectHelper.h" />
<ClInclude Include="DirtyRectsOptimizer.h">
<Filter>Capture</Filter>
</ClInclude>
<ClInclude Include="EffectsDrawer.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="CursorDrawer2.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="ShaderEffectDrawer.h">
<Filter>EffectDrawer</Filter>
</ClInclude>
<ClInclude Include="include\EffectInfo.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="include\ShaderEffectParser.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="ShaderEffectCompilerService.h">
<Filter>Services</Filter>
</ClInclude>
<ClInclude Include="ShaderEffectDesc.h">
<Filter>EffectDrawer</Filter>
</ClInclude>
<ClInclude Include="CursorHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="DescriptorHeap.h">
<Filter>Render</Filter>
</ClInclude>
<ClInclude Include="ColorHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="RtxTrueHdrDrawer.h">
<Filter>EffectDrawer</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ScalingRuntime.cpp" />
@ -218,6 +151,9 @@
</ClCompile>
<ClCompile Include="ScalingWindow.cpp" />
<ClCompile Include="CursorManager.cpp" />
<ClCompile Include="GraphicsCaptureFrameSource.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="GDIFrameSource.cpp">
<Filter>Capture</Filter>
</ClCompile>
@ -289,55 +225,6 @@
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="SrcTracker.cpp" />
<ClCompile Include="Renderer2.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="SwapChainPresenter.cpp">
<Filter>Presenter</Filter>
</ClCompile>
<ClCompile Include="GraphicsCaptureFrameSource.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="GraphicsContext.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="FrameProducer.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="FrameRingBuffer.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="CatmullRomDrawer.cpp">
<Filter>EffectDrawer</Filter>
</ClCompile>
<ClCompile Include="DuplicateFrameChecker.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="DirtyRectsOptimizer.cpp">
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="EffectsDrawer.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="CursorDrawer2.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="ShaderEffectDrawer.cpp">
<Filter>EffectDrawer</Filter>
</ClCompile>
<ClCompile Include="ShaderEffectParser.cpp" />
<ClCompile Include="ShaderEffectCompilerService.cpp">
<Filter>Services</Filter>
</ClCompile>
<ClCompile Include="CursorHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="DescriptorHeap.cpp">
<Filter>Render</Filter>
</ClCompile>
<ClCompile Include="RtxTrueHdrDrawer.cpp">
<Filter>EffectDrawer</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\SimpleVS.hlsl">
@ -361,42 +248,9 @@
<FxCompile Include="shaders\ImGuiImplPS.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\DuplicateFrameCS_NoBoundsChecking.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\CopyFrameVS.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\CursorVS.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\CatmullRomCS.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\CatmullRomCS_sRGB.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\CopyCS.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\CopyCS_sRGB.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\MonochromeCursorPS_sRGB.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\MaskedCursorPS_sRGB.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
<FxCompile Include="shaders\RtxTrueHdrPreCS.hlsl">
<Filter>Shaders</Filter>
</FxCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="shaders\Common.hlsli">
<Filter>Shaders</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />

View file

@ -340,7 +340,7 @@ SmallVector<ImWchar> OverlayDrawer::_BuildFontUI(
SetGlyphRanges(ranges, OverlayHelper::BASIC_LATIN_RANGES);
} else if (language == L"ru" || language == L"uk") {
SetGlyphRanges(ranges, fontAtlas.GetGlyphRangesCyrillic());
} else if (language == L"tr" || language == L"pl") {
} else if (language == L"tr" || language == L"pl" || language == L"fi") {
SetGlyphRanges(ranges, OverlayHelper::EXTENDED_LATIN_RANGES);
} else if (language == L"vi") {
SetGlyphRanges(ranges, fontAtlas.GetGlyphRangesVietnamese());
@ -670,7 +670,7 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
ImGuiWindowFlags_NoScrollWithMouse))
{
// 通过工具栏拖拽缩放窗口时不要更新 _isCursorOnCaptionArea
if (!ScalingWindow::Get().IsMoving()) {
if (!ScalingWindow::Get().IsResizingOrMoving()) {
// 鼠标被 ImGui 捕获时禁止拖拽缩放窗口
_isCursorOnCaptionArea = !ImGui::IsAnyMouseDown();
if (_isCursorOnCaptionArea) {
@ -900,7 +900,7 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
return needRedraw;
}
#ifdef MP_DEBUG_INFO
#ifdef MP_DEBUG_INFO_ON_OVERLAY
static std::string RectToStr(const RECT& rect) noexcept {
return fmt::format("{},{},{},{} ({}x{})",
rect.left, rect.top, rect.right, rect.bottom,
@ -970,6 +970,17 @@ bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint3
ImGui::TextUnformatted(StrHelper::Concat("GPU: ", _hardwareInfo.gpuName).c_str());
const std::string& captureMethodStr = _GetResourceString(L"Overlay_Profiler_CaptureMethod");
ImGui::TextUnformatted(StrHelper::Concat(captureMethodStr.c_str(), ": ", renderer.FrameSource().Name()).c_str());
if (options.IsStatisticsForDynamicDetectionEnabled() &&
options.duplicateFrameDetectionMode == DuplicateFrameDetectionMode::Dynamic) {
const std::pair<uint32_t, uint32_t> statistics =
renderer.FrameSource().GetStatisticsForDynamicDetection();
ImGui::TextUnformatted(StrHelper::Concat(_GetResourceString(L"Overlay_Profiler_DynamicDetection"), ": ").c_str());
ImGui::SameLine(0, 0);
ImGui::PushFont(_fontMonoNumbers);
ImGui::TextUnformatted(fmt::format("{}/{} ({:.1f}%)", statistics.first, statistics.second,
statistics.second == 0 ? 0.0f : statistics.first * 100.0f / statistics.second).c_str());
ImGui::PopFont();
}
const std::string& frameRateStr = _GetResourceString(L"Overlay_Profiler_FrameRate");
ImGui::TextUnformatted(fmt::format("{}: {} FPS", frameRateStr, fps).c_str());
ImGui::PopTextWrapPos();
@ -1028,7 +1039,7 @@ bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint3
showPasses = false;
}
#ifdef MP_DEBUG_INFO
#ifdef MP_DEBUG_INFO_ON_OVERLAY
ImGui::Spacing();
if (ImGui::CollapsingHeader("调试信息", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::TextUnformatted(StrHelper::Concat("源矩形: ",

View file

@ -1,44 +0,0 @@
#pragma once
namespace Magpie {
struct RectHelper {
static bool IsOverlap(const Rect& r1, const Rect& r2) noexcept {
return r1.right > r2.left && r1.bottom > r2.top && r1.left < r2.right && r1.top < r2.bottom;
}
static bool Contains(const Rect& r1, const Rect& r2) noexcept {
return r1.left <= r2.left && r1.top <= r2.top && r1.right >= r2.right && r1.bottom >= r2.bottom;
}
static bool Contains(const Rect& rect, Point p) noexcept {
return p.x >= rect.left && p.x < rect.right && p.y >= rect.top && p.y < rect.bottom;
}
static bool IsEmpty(const Rect& rect) noexcept {
return rect.left == rect.right || rect.top == rect.bottom;
}
static bool Intersect(Rect& result, const Rect& r1, const Rect& r2) noexcept {
// 计算重叠部分
result.left = std::max(r1.left, r2.left);
result.top = std::max(r1.top, r2.top);
result.right = std::min(r1.right, r2.right);
result.bottom = std::min(r1.bottom, r2.bottom);
// 判断重叠部分是否是正面积
return result.left < result.right && result.top < result.bottom;
}
static Rect Union(const Rect& r1, const Rect& r2) noexcept {
return Rect{ std::min(r1.left, r2.left), std::min(r1.top, r2.top),
std::max(r1.right, r2.right), std::max(r1.bottom, r2.bottom) };
}
static uint32_t CalcArea(const Rect& rect) noexcept {
assert(rect.right >= rect.left && rect.bottom >= rect.top);
return (rect.right - rect.left) * (rect.bottom - rect.top);
}
};
}

View file

@ -8,6 +8,7 @@
#include "EffectDrawer.h"
#include "EffectsProfiler.h"
#include "GDIFrameSource.h"
#include "GraphicsCaptureFrameSource.h"
#include "Logger.h"
#include "OverlayDrawer.h"
#include "Renderer.h"
@ -394,6 +395,9 @@ const RECT& Renderer::SrcRect() const noexcept {
bool Renderer::_InitFrameSource() noexcept {
switch (ScalingWindow::Get().Options().captureMethod) {
case CaptureMethod::GraphicsCapture:
_frameSource = std::make_unique<GraphicsCaptureFrameSource>();
break;
case CaptureMethod::DesktopDuplication:
_frameSource = std::make_unique<DesktopDuplicationFrameSource>();
break;
@ -694,6 +698,44 @@ ID3D11Texture2D* Renderer::_ResizeEffects() noexcept {
}
void Renderer::_UpdateDestRect() noexcept {
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
DestAlignment alignment = ScalingWindow::Get().Options().destAlignment;
LONG destWidth;
LONG destHeight;
{
D3D11_TEXTURE2D_DESC desc;
_frontendSharedTexture->GetDesc(&desc);
destWidth = (LONG)desc.Width;
destHeight = (LONG)desc.Height;
}
using enum DestAlignment;
if (alignment == LeftTop || alignment == Left || alignment == LeftBottom) {
_destRect.left = 0;
_destRect.right = destWidth;
} else if (alignment == Top || alignment == Center || alignment == Bottom) {
_destRect.left = (rendererRect.left + rendererRect.right - destWidth) / 2;
_destRect.right = _destRect.left + destWidth;
} else {
_destRect.left = rendererRect.right - destWidth;
_destRect.right = rendererRect.right;
}
if (alignment == LeftTop || alignment == Top || alignment == RightTop) {
_destRect.top = 0;
_destRect.bottom = destHeight;
} else if (alignment == Left || alignment == Center || alignment == Right) {
_destRect.top = (rendererRect.top + rendererRect.bottom - destHeight) / 2;
_destRect.bottom = _destRect.top + destHeight;
} else {
_destRect.top = rendererRect.bottom - destHeight;
_destRect.bottom = rendererRect.bottom;
}
assert(_destRect.left + destWidth == _destRect.right);
assert(_destRect.top + destHeight == _destRect.bottom);
}
HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept {
@ -754,7 +796,7 @@ void Renderer::_BackendThreadProc() noexcept {
return;
}
StepTimerStatus stepTimerStatus = StepTimerStatus::WaitingForNewFrame;
StepTimerStatus stepTimerStatus = StepTimerStatus::WaitForNewFrame;
const bool waitMsgForNewFrame =
_frameSource->WaitType() == FrameSourceWaitType::WaitForMessage;
@ -762,7 +804,7 @@ void Renderer::_BackendThreadProc() noexcept {
while (true) {
bool fpsUpdated = false;
stepTimerStatus = _stepTimer.WaitForNextFrame(
waitMsgForNewFrame && stepTimerStatus != StepTimerStatus::WaitingForFPSLimiter,
waitMsgForNewFrame && stepTimerStatus != StepTimerStatus::WaitForFPSLimiter,
fpsUpdated
);
@ -776,7 +818,7 @@ void Renderer::_BackendThreadProc() noexcept {
DispatchMessage(&msg);
}
if (stepTimerStatus == StepTimerStatus::WaitingForFPSLimiter) {
if (stepTimerStatus == StepTimerStatus::WaitForFPSLimiter) {
// 新帧消息可能已被处理,之后的 WaitForNextFrame 不要等待消息,直到状态变化
continue;
}

View file

@ -144,7 +144,7 @@ private:
// INVALID_HANDLE_VALUE 表示后端初始化失败
std::atomic<HANDLE> _sharedTextureHandle{ NULL };
// 下面四个成员由 _sharedTextureHandle 同步
winrt::DispatcherQueue _backendThreadDispatcher{ nullptr };
winrt::Windows::System::DispatcherQueue _backendThreadDispatcher{ nullptr };
ScalingError _backendInitError = ScalingError::NoError;
std::vector<EffectDesc> _effectDescs;
// 包含追加的 Bicubic

View file

@ -1,691 +0,0 @@
#include "pch.h"
#include "Renderer2.h"
#include "DebugInfo.h"
#include "FrameProducer.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "SwapChainPresenter.h"
#include "shaders/CopyFrameVS.h"
#include "shaders/SimplePS.h"
#include "GraphicsCaptureFrameSource.h"
#include <d3dkmthk.h>
#include <windows.graphics.display.interop.h>
namespace Magpie {
Renderer2::Renderer2() noexcept {}
Renderer2::~Renderer2() noexcept {
_graphicsContext.WaitForGpu();
}
static void SetGpuPriority() noexcept {
// 不使用 REALTIME 优先级,它可能造成系统不稳定。
// 来自 https://github.com/obsproject/obs-studio/blob/16cb051a57bb357fe866252c1360ce2c38e2deec/libobs-d3d11/d3d11-subsystem.cpp#L429
NTSTATUS status = D3DKMTSetProcessSchedulingPriorityClass(
GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH);
if (status != STATUS_SUCCESS) {
Logger::Get().NTError("D3DKMTSetProcessSchedulingPriorityClass 失败", status);
}
}
ScalingError Renderer2::Initialize(
HWND hwndAttach,
HMONITOR hMonitor,
const RECT& srcRect,
const RECT& rendererRect,
OverlayOptions& /*overlayOptions*/,
RECT& destRect
) noexcept {
_hCurMonitor = hMonitor;
SetGpuPriority();
const ScalingOptions& options = ScalingWindow::Get().Options();
if (!_graphicsContext.Initialize(
options.graphicsCardId,
options.Is3DGameMode() ? 2 : 6,
D3D12_COMMAND_QUEUE_PRIORITY_HIGH,
D3D12_COMMAND_LIST_TYPE_DIRECT,
_descriptorHeap
)) {
Logger::Get().Error("初始化 GraphicsContext 失败");
return ScalingError::ScalingFailedGeneral;
}
ID3D12Device5* device = _graphicsContext.GetDevice();
#ifdef MP_DEBUG_INFO
{
// 禁用动态时钟频率调整
if (DEBUG_INFO.enableStablePower) {
HRESULT hr = device->SetStablePowerState(TRUE);
if (FAILED(hr)) {
Logger::Get().ComError("SetStablePowerState 失败", hr);
}
}
#ifdef _DEBUG
// 模拟低速 GPU
winrt::com_ptr<ID3D12DebugDevice1> debugDevice;
HRESULT hr = device->QueryInterface<ID3D12DebugDevice1>(debugDevice.put());
if (SUCCEEDED(hr)) {
D3D12_DEBUG_DEVICE_GPU_SLOWDOWN_PERFORMANCE_FACTOR value = {
.SlowdownFactor = DEBUG_INFO.gpuSlowDownFactor
};
hr = debugDevice->SetDebugParameter(
D3D12_DEBUG_DEVICE_PARAMETER_GPU_SLOWDOWN_PERFORMANCE_FACTOR, &value, sizeof(value));
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12DebugDevice1::SetDebugParameter 失败", hr);
}
} else {
Logger::Get().ComError("获取 ID3D12DebugDevice1 失败", hr);
}
#endif
}
#endif
// 失败则回落到使用传统方法获取颜色显示能力
_TryInitDisplayInfo();
if (!_UpdateColorInfo()) {
Logger::Get().Error("_UpdateColorInfo 失败");
return ScalingError::ScalingFailedGeneral;
}
const Size rendererSize = {
uint32_t(rendererRect.right - rendererRect.left),
uint32_t(rendererRect.bottom - rendererRect.top)
};
Size outputSize;
SimpleTask<bool> task;
_frameProducer.InitializeAsync(
_graphicsContext, _colorInfo, hMonitor, srcRect, rendererSize, outputSize, task);
_presenter = std::make_unique<SwapChainPresenter>();
if (!_presenter->Initialize(_graphicsContext, hwndAttach, rendererSize, _colorInfo)) {
Logger::Get().Error("SwapChainPresenter::Initialize 失败");
return ScalingError::ScalingFailedGeneral;
}
{
CD3DX12_DESCRIPTOR_RANGE1 srvRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);
D3D12_ROOT_PARAMETER1 rootParams[] = {
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
.Constants = {
.Num32BitValues = 4
},
.ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX
},
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &srvRange
},
.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL
}
};
D3D12_STATIC_SAMPLER_DESC samplerDesc = {
.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT,
.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER,
.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER,
.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER,
.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER,
// 边界外使用黑色填充
.BorderColor = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK,
.ShaderRegister = 0
};
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc(
(UINT)std::size(rootParams), rootParams, 1, &samplerDesc, D3D12_ROOT_SIGNATURE_FLAG_NONE);
winrt::com_ptr<ID3DBlob> signature;
HRESULT hr = D3DX12SerializeVersionedRootSignature(
&rootSignatureDesc, _graphicsContext.GetRootSignatureVersion(), signature.put(), nullptr);
if (FAILED(hr)) {
Logger::Get().ComError("D3DX12SerializeVersionedRootSignature 失败", hr);
return ScalingError::ScalingFailedGeneral;
}
hr = device->CreateRootSignature(
0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&_rootSignature));
if (FAILED(hr)) {
Logger::Get().ComError("CreateRootSignature 失败", hr);
return ScalingError::ScalingFailedGeneral;
}
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = _rootSignature.get(),
.VS = CD3DX12_SHADER_BYTECODE(CopyFrameVS, sizeof(CopyFrameVS)),
.PS = CD3DX12_SHADER_BYTECODE(SimplePS, sizeof(SimplePS)),
.BlendState = {
.RenderTarget = {{ .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL }}
},
.SampleMask = UINT_MAX,
.RasterizerState = {
.FillMode = D3D12_FILL_MODE_SOLID,
.CullMode = D3D12_CULL_MODE_NONE
},
.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
.NumRenderTargets = 1,
.RTVFormats = { _colorInfo.kind == winrt::AdvancedColorKind::StandardDynamicRange ?
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R16G16B16A16_FLOAT },
.SampleDesc = { .Count = 1 }
};
hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&_pipelineState));
if (FAILED(hr)) {
Logger::Get().ComError("CreateGraphicsPipelineState 失败", hr);
return ScalingError::ScalingFailedGeneral;
}
}
// 同步点,使生产者线程的修改可见
if (!task.GetResult(std::memory_order_acquire)) {
Logger::Get().Error("FrameProducer::InitializeAsync 失败");
return ScalingError::ScalingFailedGeneral;
}
_UpdateOutputRect(outputSize);
destRect.left = rendererRect.left + (LONG)_outputRect.left;
destRect.top = rendererRect.top + (LONG)_outputRect.top;
destRect.right = rendererRect.left + (LONG)_outputRect.right;
destRect.bottom = rendererRect.top + (LONG)_outputRect.bottom;
if (!_cursorDrawer.Initialize(_graphicsContext, srcRect, rendererRect, destRect, _colorInfo)) {
Logger::Get().Error("CursorDrawer2::Initialize 失败");
return ScalingError::ScalingFailedGeneral;
}
return ScalingError::NoError;
}
ComponentState Renderer2::Render(
HCURSOR hCursor,
POINT cursorPos,
bool waitForGpu,
bool* waitingForFirstFrame
) noexcept {
assert(!waitingForFirstFrame || !*waitingForFirstFrame);
if (_state != ComponentState::NoError) {
return _state;
}
_state = _frameProducer.GetState();
if (_state != ComponentState::NoError) {
return _state;
}
{
const uint64_t latestProducerFrameNumber = _frameProducer.GetLatestFrameNumber();
if (latestProducerFrameNumber == 0) {
if (waitingForFirstFrame && latestProducerFrameNumber == 0) {
*waitingForFirstFrame = true;
}
return _state;
}
if (latestProducerFrameNumber == _lastProducerFrameNumber &&
!_cursorDrawer.CheckForRedraw(hCursor, cursorPos)) {
return _state;
}
_lastProducerFrameNumber = latestProducerFrameNumber;
}
_CheckResult(_RenderImpl(waitForGpu), "_RenderImpl 失败");
return _state;
}
void Renderer2::OnMonitorChanged(HMONITOR hMonitor) noexcept {
if (_state != ComponentState::NoError) {
return;
}
_acInfoChangedRevoker.revoke();
_displayInfo = nullptr;
_hCurMonitor = hMonitor;
_TryInitDisplayInfo();
_CheckResult(_UpdateColorSpace(), "_UpdateColorSpace 失败");
}
void Renderer2::OnResizeStarted() noexcept {
if (_state == ComponentState::NoError) {
_presenter->OnResizeStarted();
}
}
void Renderer2::OnResizeEnded() noexcept {
if (_state == ComponentState::NoError) {
_CheckResult(_presenter->OnResizeEnded(), "SwapChainPresenter::OnResizeEnded 失败");
}
}
void Renderer2::OnResized(const RECT& rendererRect, RECT& destRect) noexcept {
if (_state != ComponentState::NoError) {
return;
}
// 确保消费者不再使用环形缓冲区
if (!_CheckResult(_graphicsContext.WaitForGpu(), "GraphicsContext::WaitForGpu 失败")) {
return;
}
const Size rendererSize = {
uint32_t(rendererRect.right - rendererRect.left),
uint32_t(rendererRect.bottom - rendererRect.top)
};
Size outputSize;
SimpleTask<HRESULT> task;
_frameProducer.OnResizedAsync(rendererSize, outputSize, task);
if (!_CheckResult(_presenter->OnResized(rendererSize), "SwapChainPresenter::OnResized 失败")) {
return;
}
if (!_CheckResult(task.GetResult(std::memory_order_acquire),
"FrameProducer::OnResizedAsync 失败")) {
return;
}
_UpdateOutputRect(outputSize);
destRect.left = rendererRect.left + (LONG)_outputRect.left;
destRect.top = rendererRect.top + (LONG)_outputRect.top;
destRect.right = rendererRect.left + (LONG)_outputRect.right;
destRect.bottom = rendererRect.top + (LONG)_outputRect.bottom;
_cursorDrawer.OnResized(rendererRect, destRect);
}
void Renderer2::OnMoveStarted() noexcept {
_cursorDrawer.OnMoveStarted();
}
void Renderer2::OnMoveEnded() noexcept {
_cursorDrawer.OnMoveEnded();
}
void Renderer2::OnMoved(const RECT& rendererRect, RECT& destRect) noexcept {
destRect.left = rendererRect.left + (LONG)_outputRect.left;
destRect.top = rendererRect.top + (LONG)_outputRect.top;
destRect.right = rendererRect.left + (LONG)_outputRect.right;
destRect.bottom = rendererRect.top + (LONG)_outputRect.bottom;
_cursorDrawer.OnMoved(rendererRect, destRect);
}
void Renderer2::OnCursorVirtualizationStarted() noexcept {
_cursorDrawer.OnCursorVirtualizationStarted();
}
void Renderer2::OnCursorVirtualizationEnded() noexcept {
_cursorDrawer.OnCursorVirtualizationEnded();
}
void Renderer2::OnSrcMoveStarted() noexcept {
_cursorDrawer.OnSrcMoveStarted();
}
void Renderer2::OnSrcMoveEnded() noexcept {
_cursorDrawer.OnSrcMoveEnded();
}
void Renderer2::OnMsgDisplayChanged() noexcept {
// winrt::DisplayInformation 可用时已通过事件监听颜色配置变化
if (_state != ComponentState::NoError || _displayInfo) {
return;
}
_CheckResult(_UpdateColorSpace(), "_UpdateColorSpace 失败");
}
void Renderer2::OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept {
_frameProducer.OnCursorVisibilityChanged(isVisible, onDestory);
}
void Renderer2::_TryInitDisplayInfo() noexcept {
// 从 Win11 22H2 开始支持
winrt::com_ptr<IDisplayInformationStaticsInterop> interop =
winrt::try_get_activation_factory<winrt::DisplayInformation, IDisplayInformationStaticsInterop>();
if (!interop) {
return;
}
HRESULT hr = interop->GetForMonitor(
_hCurMonitor, winrt::guid_of<winrt::DisplayInformation>(), winrt::put_abi(_displayInfo));
if (FAILED(hr)) {
Logger::Get().ComError("IDisplayInformationStaticsInterop::GetForMonitor 失败", hr);
return;
}
_acInfoChangedRevoker = _displayInfo.AdvancedColorInfoChanged(
winrt::auto_revoke,
[this](winrt::DisplayInformation const&, winrt::IInspectable const&) {
_CheckResult(_UpdateColorSpace(), "_UpdateColorSpace 失败");
}
);
}
static float GetSDRWhiteLevel(std::wstring_view monitorName) noexcept {
UINT32 pathCount = 0, modeCount = 0;
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount) != ERROR_SUCCESS) {
return 1.0f;
}
std::vector<DISPLAYCONFIG_PATH_INFO> paths(pathCount);
std::vector<DISPLAYCONFIG_MODE_INFO> modes(modeCount);
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths.data(), &modeCount, modes.data(), nullptr) != ERROR_SUCCESS) {
return 1.0f;
}
for (const DISPLAYCONFIG_PATH_INFO& path : paths) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName = {
.header = {
.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME,
.size = sizeof(sourceName),
.adapterId = path.sourceInfo.adapterId,
.id = path.sourceInfo.id
}
};
if (DisplayConfigGetDeviceInfo(&sourceName.header) != ERROR_SUCCESS) {
continue;
}
if (monitorName == sourceName.viewGdiDeviceName) {
DISPLAYCONFIG_SDR_WHITE_LEVEL sdr = {
.header = {
.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL,
.size = sizeof(sdr),
.adapterId = path.targetInfo.adapterId,
.id = path.targetInfo.id
}
};
if (DisplayConfigGetDeviceInfo(&sdr.header) == ERROR_SUCCESS) {
return sdr.SDRWhiteLevel / 1000.0f;
} else {
return 1.0f;
}
}
}
return 1.0f;
}
bool Renderer2::_UpdateColorInfo() noexcept {
if (_displayInfo) {
winrt::AdvancedColorInfo acInfo = _displayInfo.GetAdvancedColorInfo();
_colorInfo.kind = acInfo.CurrentAdvancedColorKind();
if (_colorInfo.kind == winrt::AdvancedColorKind::HighDynamicRange) {
_colorInfo.maxLuminance = acInfo.MaxLuminanceInNits() / SCENE_REFERRED_SDR_WHITE_LEVEL;
_colorInfo.sdrWhiteLevel = acInfo.SdrWhiteLevelInNits() / SCENE_REFERRED_SDR_WHITE_LEVEL;
} else {
_colorInfo.maxLuminance = 1.0f;
_colorInfo.sdrWhiteLevel = 1.0f;
}
return true;
}
IDXGIFactory7* dxgiFactory = _graphicsContext.GetDXGIFactoryForEnumingAdapters();
if (!dxgiFactory) {
return false;
}
winrt::com_ptr<IDXGIAdapter1> adapter;
winrt::com_ptr<IDXGIOutput> output;
for (UINT adapterIdx = 0;
SUCCEEDED(dxgiFactory->EnumAdapters1(adapterIdx, adapter.put()));
++adapterIdx
) {
for (UINT outputIdx = 0;
SUCCEEDED(adapter->EnumOutputs(outputIdx, output.put()));
++outputIdx
) {
DXGI_OUTPUT_DESC1 desc;
if (SUCCEEDED(output.try_as<IDXGIOutput6>()->GetDesc1(&desc))) {
if (desc.Monitor == _hCurMonitor) {
// DXGI 将 WCG 视为 SDR
if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
_colorInfo.kind = winrt::AdvancedColorKind::HighDynamicRange;
_colorInfo.maxLuminance = desc.MaxLuminance / SCENE_REFERRED_SDR_WHITE_LEVEL;
_colorInfo.sdrWhiteLevel = GetSDRWhiteLevel(desc.DeviceName);
} else {
_colorInfo.kind = winrt::AdvancedColorKind::StandardDynamicRange;
_colorInfo.maxLuminance = 1.0f;
_colorInfo.sdrWhiteLevel = 1.0f;
}
return true;
}
}
}
}
// 未找到视为 SDR
_colorInfo.kind = winrt::AdvancedColorKind::StandardDynamicRange;
_colorInfo.maxLuminance = 1.0f;
_colorInfo.sdrWhiteLevel = 1.0f;
return true;
}
HRESULT Renderer2::_UpdateColorSpace() noexcept {
ColorInfo oldColorInfo = _colorInfo;
if (!_UpdateColorInfo()) {
return E_FAIL;
}
if (oldColorInfo == _colorInfo) {
return S_OK;
}
// 确保消费者不再使用环形缓冲区
HRESULT hr = _graphicsContext.WaitForGpu();
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::WaitForGpu 失败", hr);
return hr;
}
SimpleTask<HRESULT> task;
_frameProducer.OnColorInfoChangedAsync(_colorInfo, task);
_cursorDrawer.OnColorInfoChanged(_colorInfo);
hr = _presenter->OnColorInfoChanged(_colorInfo);
if (FAILED(hr)) {
Logger::Get().ComError("SwapChainPresenter::OnColorInfoChanged 失败", hr);
return hr;
}
hr = task.GetResult();
if (FAILED(hr)) {
Logger::Get().ComError("FrameProducer::OnColorInfoChangedAsync 失败", hr);
return hr;
}
// 生产者渲染完成会通知消费者渲染新帧
return S_OK;
}
HRESULT Renderer2::_RenderImpl(bool waitForGpu) noexcept {
// 处于 COMMON 状态,依赖隐式状态转换
ID3D12Resource* curFrame;
uint32_t curFrameSrvOffset;
UINT64 fenceValueToSignal;
if (!_frameProducer.ConsumerBeginFrame(curFrame, curFrameSrvOffset, fenceValueToSignal)) {
// 不应出现第一帧未完成的情况
assert(false);
return S_OK;
}
// SwapChain::BeginFrame 和 GraphicsContext::BeginFrame 无顺序要求,不过
// 前者通常等待时间更久,将它放在前面可以减少等待次数。
ID3D12Resource* frameTex;
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle, rawRtvHandle;
_presenter->BeginFrame(&frameTex, rtvHandle, rawRtvHandle);
uint32_t frameIndex;
HRESULT hr = _graphicsContext.BeginFrame(frameIndex, _pipelineState.get());
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::BeginFrame 失败", hr);
return hr;
}
_graphicsContext.SetDescriptorHeap(_graphicsContext.GetDescriptorHeap().GetHeap());
ID3D12GraphicsCommandList* commandList = _graphicsContext.GetCommandList();
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
{
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
frameTex, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET, 0);
commandList->ResourceBarrier(1, &barrier);
}
commandList->SetGraphicsRootSignature(_rootSignature.get());
const Size rendererSize = _presenter->GetSize();
{
float outputWidth = float(_outputRect.right - _outputRect.left);
float outputHeight = float(_outputRect.bottom - _outputRect.top);
// 这些参数将输出区域 uv 变换为 0~1
float constants[] = {
rendererSize.width / outputWidth,
rendererSize.height / outputHeight,
_outputRect.left / -outputWidth,
_outputRect.top / -outputHeight
};
commandList->SetGraphicsRoot32BitConstants(0, (UINT)std::size(constants), constants, 0);
}
commandList->SetGraphicsRootDescriptorTable(1, _descriptorHeap.GetGpuHandle(curFrameSrvOffset));
{
CD3DX12_VIEWPORT viewport(0.0f, 0.0f, (float)rendererSize.width, (float)rendererSize.height);
commandList->RSSetViewports(1, &viewport);
}
{
CD3DX12_RECT scissorRect(0, 0, (LONG)rendererSize.width, (LONG)rendererSize.height);
commandList->RSSetScissorRects(1, &scissorRect);
}
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
commandList->DrawInstanced(3, 1, 0, 0);
// 为了和 OS 保持一致SDR 下在 sRGB 空间中混合
if (_colorInfo.kind == winrt::AdvancedColorKind::StandardDynamicRange) {
commandList->OMSetRenderTargets(1, &rawRtvHandle, FALSE, nullptr);
}
hr = _cursorDrawer.Draw(curFrameSrvOffset);
if (FAILED(hr)) {
Logger::Get().ComError("CursorDrawer2::Draw 失败", hr);
return hr;
}
{
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
frameTex, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT, 0);
commandList->ResourceBarrier(1, &barrier);
}
hr = commandList->Close();
if (FAILED(hr)) {
Logger::Get().ComError("ID3D12GraphicsCommandList::Close 失败", hr);
return hr;
}
ID3D12CommandQueue* commandQueue = _graphicsContext.GetCommandQueue();
commandQueue->ExecuteCommandLists(1, CommandListCast(&commandList));
hr =_frameProducer.ConsumerEndFrame(commandQueue, fenceValueToSignal);
if (FAILED(hr)) {
Logger::Get().ComError("FrameProducer::ConsumerEndFrame 失败", hr);
return hr;
}
hr = _presenter->EndFrame(waitForGpu);
if (FAILED(hr)) {
Logger::Get().ComError("SwapChainPresenter::EndFrame 失败", hr);
return hr;
}
// GraphicsContext::EndFrame 必须在 SwapChain::EndFrame 之后
hr = _graphicsContext.EndFrame();
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::EndFrame 失败", hr);
return hr;
}
return S_OK;
}
void Renderer2::_UpdateOutputRect(Size outputSize) noexcept {
// TODO: 窗口模式缩放始终填充画面
const Size rendererSize = _presenter->GetSize();
OutputAlignment alignment = ScalingWindow::Get().Options().outputAlignment;
using enum OutputAlignment;
if (alignment == LeftTop || alignment == Left || alignment == LeftBottom) {
_outputRect.left = 0;
_outputRect.right = outputSize.width;
} else if (alignment == Top || alignment == Center || alignment == Bottom) {
_outputRect.left = (rendererSize.width - outputSize.width) / 2;
_outputRect.right = _outputRect.left + outputSize.width;
} else {
_outputRect.left = rendererSize.width - outputSize.width;
_outputRect.right = rendererSize.width;
}
if (alignment == LeftTop || alignment == Top || alignment == RightTop) {
_outputRect.top = 0;
_outputRect.bottom = outputSize.height;
} else if (alignment == Left || alignment == Center || alignment == Right) {
_outputRect.top = (rendererSize.height - outputSize.height) / 2;
_outputRect.bottom = _outputRect.top + outputSize.height;
} else {
_outputRect.top = rendererSize.height - outputSize.height;
_outputRect.bottom = rendererSize.height;
}
assert(_outputRect.left + outputSize.width == _outputRect.right);
assert(_outputRect.top + outputSize.height == _outputRect.bottom);
}
bool Renderer2::_CheckResult(bool success, std::string_view errorMsg) noexcept {
assert(_state == ComponentState::NoError);
if (!success) {
_state = ComponentState::Error;
Logger::Get().Error(errorMsg);
}
return success;
}
bool Renderer2::_CheckResult(HRESULT hr, std::string_view errorMsg) noexcept {
assert(_state == ComponentState::NoError);
if (SUCCEEDED(hr)) {
return true;
}
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
_state = ComponentState::DeviceLost;
} else {
_state = ComponentState::Error;
}
Logger::Get().ComError(errorMsg, hr);
return false;
}
}

View file

@ -1,105 +0,0 @@
#pragma once
#include "CursorDrawer2.h"
#include "FrameProducer.h"
#include "GraphicsContext.h"
#include "DescriptorHeap.h"
#include "ScalingOptions.h"
namespace Magpie {
class SwapChainPresenter;
class Renderer2 {
public:
Renderer2() noexcept;
~Renderer2() noexcept;
Renderer2(const Renderer2&) = delete;
Renderer2(Renderer2&&) = delete;
ScalingError Initialize(
HWND hwndAttach,
HMONITOR hMonitor,
const RECT& srcRect,
const RECT& rendererRect,
OverlayOptions& overlayOptions,
RECT& destRect
) noexcept;
ComponentState Render(
HCURSOR hCursor,
POINT cursorPos,
bool waitForGpu = false,
bool* waitingForFirstFrame = nullptr
) noexcept;
const Rect& GetOutputRect() const noexcept {
return _outputRect;
}
void OnMonitorChanged(HMONITOR hMonitor) noexcept;
void OnResizeStarted() noexcept;
void OnResizeEnded() noexcept;
void OnResized(const RECT& rendererRect, RECT& destRect) noexcept;
void OnMoveStarted() noexcept;
void OnMoveEnded() noexcept;
void OnMoved(const RECT& rendererRect, RECT& destRect) noexcept;
void OnCursorVirtualizationStarted() noexcept;
void OnCursorVirtualizationEnded() noexcept;
void OnSrcMoveStarted() noexcept;
void OnSrcMoveEnded() noexcept;
void OnMsgDisplayChanged() noexcept;
void OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept;
private:
void _TryInitDisplayInfo() noexcept;
bool _UpdateColorInfo() noexcept;
HRESULT _UpdateColorSpace() noexcept;
HRESULT _RenderImpl(bool waitForGpu = false) noexcept;
void _UpdateOutputRect(Size outputSize) noexcept;
bool _CheckResult(bool success, std::string_view errorMsg) noexcept;
bool _CheckResult(HRESULT hr, std::string_view errorMsg) noexcept;
// 不使用 Initializing 状态
ComponentState _state = ComponentState::NoError;
winrt::DisplayInformation _displayInfo{ nullptr };
winrt::DisplayInformation::AdvancedColorInfoChanged_revoker _acInfoChangedRevoker;
Rect _outputRect{};
// 由多个 GraphicsContext 共享
DescriptorHeap _descriptorHeap;
GraphicsContext _graphicsContext;
FrameProducer _frameProducer;
CursorDrawer2 _cursorDrawer;
std::unique_ptr<SwapChainPresenter> _presenter;
HMONITOR _hCurMonitor = NULL;
ColorInfo _colorInfo;
uint64_t _lastProducerFrameNumber = 0;
winrt::com_ptr<ID3D12RootSignature> _rootSignature;
winrt::com_ptr<ID3D12PipelineState> _pipelineState;
};
}

View file

@ -1,287 +0,0 @@
#include "pch.h"
#ifdef MP_ENABLE_RTX_TRUE_HDR
#include "RtxTrueHdrDrawer.h"
#include "GraphicsContext.h"
#include "DescriptorHeap.h"
#include "shaders/RtxTrueHdrPreCS.h"
#include "Logger.h"
#include "Win32Helper.h"
#include <nvsdk_ngx.h>
#include <nvsdk_ngx_defs_truehdr.h>
namespace Magpie {
// nvsdk_ngx_defs.h 中的 NVSDK_NGX_FAILED 会产生编译警告
#define NGX_FAILED(value) (((value) & 0xFFF00000) == (uint32_t)NVSDK_NGX_Result_Fail)
RtxTrueHdrDrawer::~RtxTrueHdrDrawer() noexcept {
#ifdef _DEBUG
if (_descriptorBaseOffset != std::numeric_limits<uint32_t>::max()) {
_graphicsContext->GetDescriptorHeap().Free(_descriptorBaseOffset, 1);
}
#endif
if (_trueHdrFeature) {
NVSDK_NGX_D3D12_ReleaseFeature(_trueHdrFeature);
}
if (_ngxParameters) {
NVSDK_NGX_D3D12_DestroyParameters(_ngxParameters);
}
if (_isNgxInitialized) {
NVSDK_NGX_D3D12_Shutdown1(_graphicsContext->GetDevice());
}
}
HRESULT RtxTrueHdrDrawer::Initialize(
GraphicsContext& graphicsContext,
Size inputSize,
const ColorInfo& colorInfo
) noexcept {
_graphicsContext = &graphicsContext;
_inputSize = inputSize;
_colorInfo = colorInfo;
{
std::filesystem::path appPath = Win32Helper::GetExePath().parent_path() / L"app";
const wchar_t* appPathStr = appPath.c_str();
NVSDK_NGX_FeatureCommonInfo info = {
.PathListInfo = {
.Path = &appPathStr,
.Length = 1
}
};
NVSDK_NGX_Result status = NVSDK_NGX_D3D12_Init(0, L".", graphicsContext.GetDevice(), &info);
if (NGX_FAILED(status)) {
return E_FAIL;
}
}
_isNgxInitialized = true;
NVSDK_NGX_Result status = NVSDK_NGX_D3D12_GetCapabilityParameters(&_ngxParameters);
if (NGX_FAILED(status)) {
Logger::Get().Error("NVSDK_NGX_D3D12_GetCapabilityParameters 失败");
return E_FAIL;
}
int available = 0;
status = _ngxParameters->Get(NVSDK_NGX_Parameter_TrueHDR_Available, &available);
if (NGX_FAILED(status) || !available) {
Logger::Get().Error("TrueHDR 不可用");
return E_FAIL;
}
ID3D12Device5* device = graphicsContext.GetDevice();
{
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_DEFAULT);
D3D12_HEAP_FLAGS heapFlag = graphicsContext.IsHeapFlagCreateNotZeroedSupported() ?
D3D12_HEAP_FLAG_CREATE_NOT_ZEROED : D3D12_HEAP_FLAG_NONE;
// 使用 R10G10B10A2 格式以尽可能保留精度
CD3DX12_RESOURCE_DESC texDesc = CD3DX12_RESOURCE_DESC::Tex2D(
DXGI_FORMAT_R10G10B10A2_UNORM, inputSize.width, inputSize.height,
1, 1, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
HRESULT hr = device->CreateCommittedResource(
&heapProps, heapFlag, &texDesc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
nullptr, IID_PPV_ARGS(&_ngxInputResource));
if (FAILED(hr)) {
Logger::Get().ComError("CreateCommittedResource 失败", hr);
return hr;
}
}
{
auto& descriptorHeap = graphicsContext.GetDescriptorHeap();
HRESULT hr = descriptorHeap.Alloc(1, _descriptorBaseOffset);
if (FAILED(hr)) {
Logger::Get().ComError("DynamicDescriptorHeap::Alloc 失败", hr);
return hr;
}
CD3DX12_UNORDERED_ACCESS_VIEW_DESC uavDesc =
CD3DX12_UNORDERED_ACCESS_VIEW_DESC::Tex2D(DXGI_FORMAT_R10G10B10A2_UNORM);
device->CreateUnorderedAccessView(_ngxInputResource.get(), nullptr, &uavDesc,
descriptorHeap.GetCpuHandle(_descriptorBaseOffset));
}
HRESULT hr = _InitializePSO();
if (FAILED(hr)) {
Logger::Get().ComError("_InitializePSO 失败", hr);
return hr;
}
return S_OK;
}
HRESULT RtxTrueHdrDrawer::Draw(uint32_t inputSrvOffset, ID3D12Resource* outputTexture) noexcept {
ID3D12GraphicsCommandList* commandList = _graphicsContext->GetCommandList();
auto& descriptorHeap = _graphicsContext->GetDescriptorHeap();
_graphicsContext->SetDescriptorHeap(descriptorHeap.GetHeap());
commandList->SetPipelineState(_prePSO.get());
commandList->SetComputeRootSignature(_preRootSignature.get());
commandList->SetComputeRoot32BitConstants(0, 1, &_colorInfo.sdrWhiteLevel, 0);
commandList->SetComputeRootDescriptorTable(1, descriptorHeap.GetGpuHandle(inputSrvOffset));
commandList->SetComputeRootDescriptorTable(2, descriptorHeap.GetGpuHandle(_descriptorBaseOffset));
constexpr uint32_t BLOCK_SIZE = 16;
commandList->Dispatch(
(_inputSize.width + BLOCK_SIZE - 1) / BLOCK_SIZE,
(_inputSize.height + BLOCK_SIZE - 1) / BLOCK_SIZE,
1
);
if (!_trueHdrFeature) {
NVSDK_NGX_Result status = NVSDK_NGX_D3D12_CreateFeature(
commandList, NVSDK_NGX_Feature_TrueHDR, _ngxParameters, &_trueHdrFeature);
if (NGX_FAILED(status)) {
Logger::Get().Error("NVSDK_NGX_D3D12_CreateFeature 失败");
return E_FAIL;
}
NVSDK_NGX_Parameter_SetD3d12Resource(
_ngxParameters, NVSDK_NGX_Parameter_Input1, _ngxInputResource.get());
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_InLeft, 0);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_InTop, 0);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_InRight, _inputSize.width);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_InBottom, _inputSize.height);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_OutLeft, 0);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_OutTop, 0);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_OutRight, _inputSize.width);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_OutBottom, _inputSize.height);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_Contrast, 30);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_Saturation, 0);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_MiddleGray, 80);
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_MaxLuminance,
std::clamp((uint32_t)std::lroundf(_colorInfo.maxLuminance * SCENE_REFERRED_SDR_WHITE_LEVEL), 400u, 2000u));
}
NVSDK_NGX_Parameter_SetD3d12Resource(
_ngxParameters, NVSDK_NGX_Parameter_Output, outputTexture);
#ifdef _DEBUG
// 忽略 NGX 产生的调试层警告
winrt::com_ptr<ID3D12InfoQueue> infoQueue;
_graphicsContext->GetDevice()->QueryInterface(IID_PPV_ARGS(&infoQueue));
if (infoQueue) {
D3D12_MESSAGE_ID denyList[] = {
D3D12_MESSAGE_ID_CREATERESOURCE_STATE_IGNORED
};
D3D12_INFO_QUEUE_FILTER filter = {
.DenyList = {
.NumIDs = (UINT)std::size(denyList),
.pIDList = denyList
}
};
infoQueue->PushCopyOfStorageFilter();
infoQueue->AddStorageFilterEntries(&filter);
}
#endif
NVSDK_NGX_Result status = NVSDK_NGX_D3D12_EvaluateFeature(
commandList, _trueHdrFeature, _ngxParameters, nullptr);
if (NGX_FAILED(status)) {
Logger::Get().Error("NVSDK_NGX_D3D12_EvaluateFeature 失败");
return E_FAIL;
}
#ifdef _DEBUG
if (infoQueue) {
infoQueue->PopStorageFilter();
}
#endif
_graphicsContext->InvalidateDescriptorHeapCache();
return S_OK;
}
void RtxTrueHdrDrawer::OnColorInfoChanged(const ColorInfo& colorInfo) noexcept {
_colorInfo = colorInfo;
if (_trueHdrFeature) {
NVSDK_NGX_Parameter_SetUI(_ngxParameters, NVSDK_NGX_Parameter_TrueHDR_MaxLuminance,
(uint32_t)std::lroundf(colorInfo.maxLuminance * SCENE_REFERRED_SDR_WHITE_LEVEL));
}
}
HRESULT RtxTrueHdrDrawer::_InitializePSO() noexcept {
ID3D12Device5* device = _graphicsContext->GetDevice();
winrt::com_ptr<ID3DBlob> signature;
{
CD3DX12_DESCRIPTOR_RANGE1 srvRange(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);
CD3DX12_DESCRIPTOR_RANGE1 uavRange(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_VOLATILE);
D3D12_ROOT_PARAMETER1 rootParams[] = {
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS,
.Constants = {
.Num32BitValues = 1
}
},
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &srvRange
}
},
{
.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
.DescriptorTable = {
.NumDescriptorRanges = 1,
.pDescriptorRanges = &uavRange
}
}
};
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc(
(UINT)std::size(rootParams), rootParams, 0, nullptr);
HRESULT hr = D3DX12SerializeVersionedRootSignature(
&rootSignatureDesc,
_graphicsContext->GetRootSignatureVersion(),
signature.put(),
nullptr
);
if (FAILED(hr)) {
Logger::Get().ComError("D3DX12SerializeVersionedRootSignature 失败", hr);
return hr;
}
}
HRESULT hr = device->CreateRootSignature(
0,
signature->GetBufferPointer(),
signature->GetBufferSize(),
IID_PPV_ARGS(&_preRootSignature)
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateRootSignature 失败", hr);
return hr;
}
D3D12_COMPUTE_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = _preRootSignature.get(),
.CS = CD3DX12_SHADER_BYTECODE(RtxTrueHdrPreCS, sizeof(RtxTrueHdrPreCS))
};
hr = _graphicsContext->GetDevice()->CreateComputePipelineState(
&psoDesc, IID_PPV_ARGS(&_prePSO));
if (FAILED(hr)) {
Logger::Get().ComError("CreateComputePipelineState 失败", hr);
return hr;
}
return S_OK;
}
}
#endif

View file

@ -1,53 +0,0 @@
#pragma once
#ifdef MP_ENABLE_RTX_TRUE_HDR
struct NVSDK_NGX_Parameter;
struct NVSDK_NGX_Handle;
namespace Magpie {
class GraphicsContext;
class RtxTrueHdrDrawer {
public:
~RtxTrueHdrDrawer() noexcept;
HRESULT Initialize(
GraphicsContext& graphicsContext,
Size inputSize,
const ColorInfo& colorInfo
) noexcept;
Size GetOutputSize() const noexcept {
return _inputSize;
}
HRESULT Draw(uint32_t inputSrvOffset, ID3D12Resource* outputTexture) noexcept;
void OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
private:
HRESULT _InitializePSO() noexcept;
GraphicsContext* _graphicsContext = nullptr;
Size _inputSize{};
ColorInfo _colorInfo;
winrt::com_ptr<ID3D12Resource> _ngxInputResource;
uint32_t _descriptorBaseOffset = std::numeric_limits<uint32_t>::max();
NVSDK_NGX_Parameter* _ngxParameters = nullptr;
NVSDK_NGX_Handle* _trueHdrFeature = nullptr;
winrt::com_ptr<ID3D12RootSignature> _preRootSignature;
winrt::com_ptr<ID3D12PipelineState> _prePSO;
bool _isNgxInitialized = false;
};
}
#endif

View file

@ -36,32 +36,7 @@ static std::string LogEffects(const std::vector<EffectOption>& effects) noexcept
return result;
}
void ScalingOptions::Prepare() noexcept {
assert(!effects.empty());
assert(cropping.Left >= 0 && cropping.Top >= 0 &&
cropping.Right >= 0 && cropping.Bottom >= 0);
assert(minFrameRate >= 0);
assert(!maxFrameRate.has_value() || *maxFrameRate > 0);
assert(cursorScale >= 0);
assert(!autoHideCursorDelay.has_value() || *autoHideCursorDelay > 0);
assert(initialWindowedScaleFactor >= 0);
assert(!screenshotsDir.empty());
assert(showToast && showError && save);
assert(maxProducerInFlightFrames >= 1 && maxProducerInFlightFrames <= 3);
// GDI 和 DwmSharedSurface 不支持捕获标题栏
IsCaptureTitleBar(IsCaptureTitleBar() &&
captureMethod != CaptureMethod::GDI && captureMethod != CaptureMethod::DwmSharedSurface);
if (IsWindowedMode()) {
IsAllowScalingMaximized(false);
IsSimulateExclusiveFullscreen(false);
}
if (Is3DGameMode()) {
duplicateFrameDetectionMode = DuplicateFrameDetectionMode::Never;
}
void ScalingOptions::Log() const noexcept {
Logger::Get().Info(fmt::format(R"(缩放选项
IsWindowedMode: {}
IsDebugMode: {}
@ -72,6 +47,7 @@ void ScalingOptions::Prepare() noexcept {
IsFontCacheDisabled: {}
IsSaveEffectSources: {}
IsWarningsAreErrors: {}
IsStatisticsForDynamicDetectionEnabled: {}
IsInlineParams: {}
IsTouchSupportEnabled: {}
IsAllowScalingMaximized: {}
@ -87,7 +63,7 @@ void ScalingOptions::Prepare() noexcept {
deviceId: {}
minFrameRate: {}
maxFrameRate: {}
cursorScale: {}
cursorScaling: {}
captureMethod: {}
multiMonitorUsage: {}
cursorInterpolationMode: {}
@ -105,6 +81,7 @@ void ScalingOptions::Prepare() noexcept {
IsFontCacheDisabled(),
IsSaveEffectSources(),
IsWarningsAreErrors(),
IsStatisticsForDynamicDetectionEnabled(),
IsInlineParams(),
IsTouchSupportEnabled(),
IsAllowScalingMaximized(),
@ -119,7 +96,7 @@ void ScalingOptions::Prepare() noexcept {
graphicsCardId.deviceId,
minFrameRate,
maxFrameRate.has_value() ? *maxFrameRate : 0.0f,
cursorScale,
cursorScaling,
(int)captureMethod,
(int)multiMonitorUsage,
(int)cursorInterpolationMode,

View file

@ -155,12 +155,18 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
ScalingWindow& scalingWindow = ScalingWindow::Get();
ScalingWindow::Dispatcher(_dispatcher);
time_point<steady_clock> lastRenderTime;
MSG msg;
while (true) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
scalingWindow.Stop();
return;
} else if (msg.message == CommonSharedConstants::WM_FRONTEND_RENDER &&
msg.hwnd == scalingWindow.Handle()) {
// 缩放窗口收到 WM_FRONTEND_RENDER 将执行渲染
lastRenderTime = steady_clock::now();
}
DispatchMessage(&msg);
@ -172,10 +178,11 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
const auto now = steady_clock::now();
// 限制检测光标移动的频率
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
nanoseconds rest = timeout - (now - scalingWindow.GetLastRenderTime());
if (rest < 1us) {
scalingWindow.Render();
nanoseconds rest = timeout - (now - lastRenderTime);
if (rest.count() <= 0) {
lastRenderTime = now;
rest = timeout;
scalingWindow.Render();
}
// 值为 1000000
@ -185,7 +192,7 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
MsgWaitForMultipleObjectsEx(0, nullptr, restMs, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
} else if (scalingWindow.IsSrcRepositioning()) {
std::optional<bool> repositioning =
IsSrcRepositioning(scalingWindow.SrcHandle());
IsSrcRepositioning(scalingWindow.SrcTracker().Handle());
if (repositioning.has_value()) {
if (*repositioning) {
// 等待调整完成
@ -203,6 +210,7 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
}
} else {
_State(ScalingState::Idle);
lastRenderTime = {};
WaitMessage();
}
}

View file

@ -2,11 +2,9 @@
#include "ScalingWindow.h"
#include "CommonSharedConstants.h"
#include "CursorManager.h"
#include "DebugInfo.h"
#include "ExclModeHelper.h"
#include "Logger.h"
#include "Renderer.h"
#include "Renderer2.h"
#include "Win32Helper.h"
#include "WindowHelper.h"
#include <dwmapi.h>
@ -71,13 +69,11 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
#endif
_runtimeError = ScalingError::NoError;
_lastRenderTime = {};
_isFirstFrame = true;
_isResizing = false;
_isMoving = false;
_isResizingOrMoving = false;
_isPreparingForResizing = false;
_isMovingDueToSrcMoved = false;
_shouldWaitForGpu = false;
_shouldWaitForRender = false;
_areResizeHelperWindowsVisible = false;
_isSrcRepositioning = false;
@ -114,7 +110,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
if (_options.IsWindowedMode()) {
Logger::Get().Info("已最大化的窗口不支持窗口模式缩放");
return ScalingError::BannedInWindowedMode;
} else if (!_options.IsAllowScalingMaximized()) {
} else if (!_options.RealIsAllowScalingMaximized()) {
Logger::Get().Info("源窗口已最大化");
return ScalingError::Maximized;
}
@ -306,7 +302,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
LogRects(_srcTracker.SrcRect(), _rendererRect, _windowRect);
if (!_options.IsAllowScalingMaximized()) {
if (!_options.RealIsAllowScalingMaximized()) {
// 检查源窗口是否是无边框全屏窗口
if (srcWindowKind == SrcWindowKind::NoNativeFrame && _srcTracker.WindowRect() == _rendererRect) {
Logger::Get().Info("源窗口已全屏");
@ -314,24 +310,14 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
}
}
_renderer = std::make_unique<Renderer2>();
RECT destRect;
ScalingError error = _renderer->Initialize(
_hwndRenderer,
_srcTracker.Monitor(),
_srcTracker.SrcRect(),
_rendererRect,
_options.overlayOptions,
destRect
);
_renderer = std::make_unique<class Renderer>();
ScalingError error = _renderer->Initialize(_hwndRenderer, _options.overlayOptions);
if (error != ScalingError::NoError) {
Logger::Get().Error("初始化 Renderer 失败");
return error;
}
_cursorManager = std::make_unique<class CursorManager>();
_cursorManager->Initialize(_srcTracker.SrcRect(), _rendererRect, destRect,
_srcTracker.IsMoving(), _srcTracker.IsFocused());
if (_options.IsTouchSupportEnabled()) {
// 应在 Renderer 初始化后调用。推迟到缩放窗口显示后再显示
@ -344,10 +330,21 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
void ScalingWindow::Start(HWND hwndSrc, ScalingOptions&& options) noexcept {
assert(!Handle());
assert(!options.effects.empty());
assert(options.cropping.Left >= 0 && options.cropping.Top >= 0 &&
options.cropping.Right >= 0 && options.cropping.Bottom >= 0);
assert(options.minFrameRate >= 0);
assert(!options.maxFrameRate.has_value() || *options.maxFrameRate > 0);
assert(options.cursorScaling >= 0);
assert(!options.autoHideCursorDelay.has_value() || *options.autoHideCursorDelay > 0);
assert(options.initialWindowedScaleFactor >= 0);
assert(!options.screenshotsDir.empty());
assert(options.showToast && options.showError && options.save);
options.Log();
// 缩放结束后失效
_options = std::move(options);
_options.Prepare();
ScalingError error = _StartImpl(hwndSrc);
if (error != ScalingError::NoError) {
_options.showError(hwndSrc, error);
@ -381,76 +378,36 @@ void ScalingWindow::ToggleScaling(bool isWindowedMode) noexcept {
}
void ScalingWindow::SwitchToolbarState() noexcept {
// TODO
/*if (_renderer) {
if (_renderer) {
_renderer->SwitchToolbarState();
}*/
}
}
void ScalingWindow::Render(bool onDeviceLost) noexcept {
_lastRenderTime = std::chrono::steady_clock::now();
void ScalingWindow::Render() noexcept {
bool isSrcRepositioning = false;
if (!_UpdateSrcState(isSrcRepositioning)) {
bool srcFocusedChanged = false;
if (!_UpdateSrcState(isSrcRepositioning, srcFocusedChanged)) {
Logger::Get().Info("源窗口状态改变");
_DelayedStop(false, isSrcRepositioning);
return;
}
if (srcFocusedChanged) {
_UpdateFocusStateAsync();
}
// 虽然可以在第一帧渲染完成后再隐藏系统光标,但某些设备上显示窗口时光标状态会变成忙,
// 提前隐藏光标可以提高观感。缩放窗口显示后再隐藏光标还可能造成光标闪烁两次,第一次是
// 创建 D3D 设备后(可能是 OS bug第二次是我们隐藏系统光标。
auto [hCursor, cursorPos] = _cursorManager->Update();
_cursorManager->Update();
bool waitingForFirstFrame = false;
ComponentState state = _renderer->Render(
hCursor, cursorPos, _shouldWaitForGpu || _isFirstFrame, &waitingForFirstFrame);
if (state == ComponentState::NoError) {
if (_renderer->Render(false, _shouldWaitForRender || _isFirstFrame) && _isFirstFrame) {
_isFirstFrame = false;
// 第一帧渲染完成后显示缩放窗口
if (_isFirstFrame && !waitingForFirstFrame) {
_isFirstFrame = false;
_Show();
}
} else {
// 设备再次丢失则不再尝试恢复
if (state == ComponentState::DeviceLost && !onDeviceLost) {
_renderer.reset();
_renderer = std::make_unique<Renderer2>();
RECT destRect;
ScalingError error = _renderer->Initialize(
_hwndRenderer,
_srcTracker.Monitor(),
_srcTracker.SrcRect(),
_rendererRect,
_options.overlayOptions,
destRect
);
if (error != ScalingError::NoError) {
Logger::Get().Error("初始化 Renderer 失败");
_DelayedStop();
return;
}
Render(true);
} else {
_DelayedStop();
}
_Show();
}
}
void ScalingWindow::OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept {
_renderer->OnCursorVisibilityChanged(isVisible, onDestory);
}
void ScalingWindow::OnCursorVirtualizationStarted() noexcept {
_renderer->OnCursorVirtualizationStarted();
}
void ScalingWindow::OnCursorVirtualizationEnded() noexcept {
_renderer->OnCursorVirtualizationEnded();
}
void ScalingWindow::RestartAfterSrcRepositioned() noexcept {
Start(_srcTracker.Handle(), std::move(_options));
}
@ -468,6 +425,10 @@ winrt::hstring ScalingWindow::GetLocalizedString(std::wstring_view resName) cons
}
LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept {
if (_renderer) {
_renderer->MessageHandler(msg, wParam, lParam);
}
switch (msg) {
case WM_CREATE:
{
@ -485,13 +446,8 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
_currentDpi = GetDpiForWindow(Handle());
// 设置窗口不透明度
#ifdef MP_DEBUG_INFO
const BYTE alpha = DEBUG_INFO.scalingWindowOpacity;
#else
constexpr BYTE alpha = 255;
#endif
if (!SetLayeredWindowAttributes(Handle(), 0, alpha, LWA_ALPHA)) {
// 设置窗口不透明。不完全透明时可关闭 DirectFlip
if (!SetLayeredWindowAttributes(Handle(), 0, 255, LWA_ALPHA)) {
Logger::Get().Win32Error("SetLayeredWindowAttributes 失败");
}
@ -547,15 +503,9 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
}
case WM_ENTERSIZEMOVE:
{
_isResizing = _isPreparingForResizing;
_isMoving = !_isPreparingForResizing;
if (_isResizing) {
_cursorManager->OnResizeStarted();
_renderer->OnResizeStarted();
} else {
_cursorManager->OnMoveStarted();
_renderer->OnMoveStarted();
_isResizingOrMoving = true;
if (!_isPreparingForResizing) {
_cursorManager->OnStartMove();
}
if (_options.IsTouchSupportEnabled()) {
@ -568,17 +518,9 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
}
case WM_EXITSIZEMOVE:
{
const bool oldIsResizing = _isResizing;
_isResizing = false;
_isMoving = false;
if (oldIsResizing) {
_cursorManager->OnResizeEnded();
_renderer->OnResizeEnded();
} else {
_cursorManager->OnMoveEnded();
_renderer->OnMoveEnded();
}
_isResizingOrMoving = false;
_renderer->OnEndResize();
_cursorManager->OnEndResizeMove();
if (!_srcTracker.MoveOnEndResizeMove()) {
Logger::Get().Error("SrcTracker::MoveOnEndResizeMove 失败");
@ -586,7 +528,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
return 0;
}
_cursorManager->OnSrcMoved(_srcTracker.SrcRect());
_cursorManager->OnSrcRectChanged();
if (_options.IsTouchSupportEnabled()) {
_UpdateTouchProps(_srcTracker.SrcRect());
@ -642,11 +584,10 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
break;
}
// TODO
// 鼠标在叠加层工具栏上时可以拖动缩放窗口
/*if (_renderer->IsCursorOnOverlayCaptionArea()) {
if (_renderer->IsCursorOnOverlayCaptionArea()) {
return HTCAPTION;
}*/
}
const int16_t srcHitTest = _cursorManager->SrcHitTest();
if (srcHitTest != HTNOWHERE) {
@ -805,7 +746,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
if (!(windowPos.flags & SWP_NOSIZE)) {
if (_options.IsWindowedMode()) {
// 用户调整尺寸时 WM_SIZING 已经确保等比例
if (!_isResizing) {
if (!_isResizingOrMoving) {
// cx 不为 0 时使用 cx 计算,否则使用 cy 计算
if (windowPos.cx == 0) {
if (windowPos.cy == 0) {
@ -889,7 +830,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 里。但它有个问题是要等待源窗口响应消息,因为有的窗口响应速度很慢,我们一直避免同步
// 等待。目前我把它禁用了,希望能找到更好的解决方案。因为缩放窗口调整大小或移动的过程
// 中不会捕获光标,因此即使触发了也不会造成严重后果。
// if ((IsResizingOrMoving() && Win32Helper::IsWindowHung(_srcTracker.Handle())) {
// if (_isResizingOrMoving && Win32Helper::IsWindowHung(_srcTracker.Handle())) {
// Logger::Get().Error("源窗口已挂起");
// _DelayedStop(true);
// }
@ -908,7 +849,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
_UpdateWindowProps();
// 拖拽缩放窗口时不广播
if (!IsResizingOrMoving() && !_srcTracker.IsMoving()) {
if (!_isResizingOrMoving && !_srcTracker.IsMoving()) {
// 广播缩放窗口位置或大小改变
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 2, (LPARAM)Handle());
}
@ -925,13 +866,6 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
}
break;
}
case WM_DISPLAYCHANGE:
{
if (_renderer) {
_renderer->OnMsgDisplayChanged();
}
return 0;
}
case WM_DESTROY:
{
Logger::Get().Info("缩放结束");
@ -944,8 +878,9 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
_exclModeMutex.reset();
}
std::fill(_hwndResizeHelpers.begin(), _hwndResizeHelpers.end(), nullptr);
std::fill(_hwndTouchHoles.begin(), _hwndTouchHoles.end(), nullptr);
for (wil::unique_hwnd& hWnd : _hwndTouchHoles) {
hWnd.reset();
}
_cursorManager.reset();
Logger::Get().Info("CursorManager 已析构");
@ -966,24 +901,6 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 广播停止缩放
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 0, 0);
#ifdef MP_DEBUG_INFO
{
auto lk = DEBUG_INFO.lock.lock_exclusive();
DEBUG_INFO.producerFrameNumber = 1;
DEBUG_INFO.consumerFrameNumber = 0;
DEBUG_INFO.consumerLatency = DEBUG_INFO.producerFrameNumber - DEBUG_INFO.consumerFrameNumber;
DEBUG_INFO.dtmDwmQPC = 0;
DEBUG_INFO.dtmFrameNumer = 0;
DEBUG_INFO.dtmSwapChainRefreshCount = 0;
DEBUG_INFO.dwmToMagpieLatency = 0;
DEBUG_INFO.ctpCaptureQPC = 0;
DEBUG_INFO.ctpCapturedFrame = nullptr;
DEBUG_INFO.ctpFrameNumer = 0;
DEBUG_INFO.captureToPresentLatency = 0;
}
#endif
break;
}
}
@ -997,9 +914,9 @@ LRESULT ScalingWindow::_RendererWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPAR
if (!(windowPos.flags & SWP_NOSIZE)) {
// 为了平滑调整窗口尺寸,渲染所在窗口需要在 WM_WINDOWPOSCHANGING 中
// 更新渲染尺寸。
Get()._HandleResize();
Get()._ResizeRenderer();
} else if (!(windowPos.flags & SWP_NOMOVE)) {
Get()._HandleMove();
Get()._MoveRenderer();
}
return 0;
@ -1328,7 +1245,7 @@ void ScalingWindow::_Show() noexcept {
}
// 模拟独占全屏
if (_options.IsSimulateExclusiveFullscreen()) {
if (_options.RealIsSimulateExclusiveFullscreen()) {
// 延迟 1s 以避免干扰游戏的初始化,见 #495
([]()->winrt::fire_and_forget {
const uint32_t runId = RunId();
@ -1347,21 +1264,29 @@ void ScalingWindow::_Show() noexcept {
};
}
void ScalingWindow::_HandleResize() noexcept {
RECT destRect;
_renderer->OnResized(_rendererRect, destRect);
_cursorManager->OnResized(_rendererRect, destRect);
void ScalingWindow::_ResizeRenderer() noexcept {
if (!_renderer->OnResize()) {
Logger::Get().Error("更改 Renderer 尺寸失败");
return;
}
_cursorManager->OnScalingPosChanged();
Render();
}
void ScalingWindow::_HandleMove() noexcept {
RECT destRect;
_renderer->OnMoved(_rendererRect, destRect);
_cursorManager->OnMoved(_rendererRect, destRect);
void ScalingWindow::_MoveRenderer() noexcept {
_renderer->OnMove();
if (!_isMovingDueToSrcMoved) {
_cursorManager->OnScalingPosChanged();
Render();
}
}
bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
bool ScalingWindow::_UpdateSrcState(
bool& isSrcRepositioning,
bool& srcFocusedChanged
) noexcept {
HWND hwndFore = GetForegroundWindow();
if (hwndFore == Handle()) {
@ -1378,13 +1303,11 @@ bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
}
bool isSrcInvisibleOrMinimized = false;
bool srcFocusedChanged = false;
bool srcRectChanged = false;
bool srcSizeChanged = false;
bool srcMovingChanged = false;
bool srcMonitorChanged = false;
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), IsResizingOrMoving(),
isSrcInvisibleOrMinimized, srcFocusedChanged, srcRectChanged, srcSizeChanged, srcMovingChanged, srcMonitorChanged)) {
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), _isResizingOrMoving,
isSrcInvisibleOrMinimized, srcFocusedChanged, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
return false;
}
@ -1408,12 +1331,9 @@ bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
assert(_options.IsWindowedMode());
if (_srcTracker.IsMoving()) {
_cursorManager->OnSrcMoveStarted();
_renderer->OnSrcMoveStarted();
_cursorManager->OnSrcStartMove();
} else {
_cursorManager->OnSrcMoveEnded();
_renderer->OnSrcMoveEnded();
_cursorManager->OnSrcEndMove();
_EnsureCaptionVisibleOnScreen();
}
@ -1429,7 +1349,7 @@ bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
if (srcRectChanged) {
assert(_options.IsWindowedMode());
_cursorManager->OnSrcMoved(_srcTracker.SrcRect());
_cursorManager->OnSrcRectChanged();
// 窗口模式缩放时允许源窗口移动
const RECT& srcRect = _srcTracker.WindowRect();
@ -1441,16 +1361,6 @@ bool ScalingWindow::_UpdateSrcState(bool& isSrcRepositioning) noexcept {
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE | SWP_NOSENDCHANGING);
_isMovingDueToSrcMoved = false;
}
if (srcFocusedChanged) {
_cursorManager->OnSrcFocusChanged(_srcTracker.IsFocused());
_UpdateFocusStateAsync();
}
if (srcMonitorChanged) {
_renderer->OnMonitorChanged(_srcTracker.Monitor());
}
return true;
}
@ -1493,17 +1403,17 @@ void ScalingWindow::_SetWindowProps() const noexcept {
void ScalingWindow::_UpdateWindowProps() const noexcept {
const HWND hWnd = Handle();
const RECT& srcRect = _srcTracker.SrcRect();
const RECT& srcRect = _renderer->SrcRect();
SetProp(hWnd, L"Magpie.SrcLeft", (HANDLE)(INT_PTR)srcRect.left);
SetProp(hWnd, L"Magpie.SrcTop", (HANDLE)(INT_PTR)srcRect.top);
SetProp(hWnd, L"Magpie.SrcRight", (HANDLE)(INT_PTR)srcRect.right);
SetProp(hWnd, L"Magpie.SrcBottom", (HANDLE)(INT_PTR)srcRect.bottom);
RECT outputRect = (RECT)_renderer->GetOutputRect();
SetProp(hWnd, L"Magpie.DestLeft", (HANDLE)(INT_PTR)(_rendererRect.left + outputRect.left));
SetProp(hWnd, L"Magpie.DestTop", (HANDLE)(INT_PTR)(_rendererRect.top + outputRect.top));
SetProp(hWnd, L"Magpie.DestRight", (HANDLE)(INT_PTR)(_rendererRect.left + outputRect.right));
SetProp(hWnd, L"Magpie.DestBottom", (HANDLE)(INT_PTR)(_rendererRect.top + outputRect.bottom));
const RECT& destRect = _renderer->DestRect();
SetProp(hWnd, L"Magpie.DestLeft", (HANDLE)(INT_PTR)destRect.left);
SetProp(hWnd, L"Magpie.DestTop", (HANDLE)(INT_PTR)destRect.top);
SetProp(hWnd, L"Magpie.DestRight", (HANDLE)(INT_PTR)destRect.right);
SetProp(hWnd, L"Magpie.DestBottom", (HANDLE)(INT_PTR)destRect.bottom);
}
// 供 TouchHelper.exe 使用
@ -1512,7 +1422,7 @@ void ScalingWindow::_UpdateTouchProps(const RECT& srcRect) const noexcept {
const HWND hWnd = Handle();
if (_isResizing) {
if (_isResizingOrMoving) {
// 调整大小时应禁用触控变换
SetProp(hWnd, L"Magpie.SrcTouchLeft",
(HANDLE)(INT_PTR)std::numeric_limits<LONG>::min());
@ -1594,21 +1504,17 @@ LRESULT ScalingWindow::_BorderHelperWndProc(HWND hWnd, UINT msg, WPARAM wParam,
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)((CREATESTRUCT*)lParam)->lpCreateParams);
} else {
switch (msg) {
#ifdef MP_DEBUG_INFO
#ifdef MP_DEBUG_BORDER
case WM_ERASEBKGND:
{
if (DEBUG_INFO.highlightBorder) {
// 用颜色标示辅助窗口
int side = (int)GetWindowLongPtr(hWnd, GWLP_USERDATA);
HBRUSH hBrush = CreateSolidBrush(side % 2 == 0 ? RGB(255, 0, 0) : RGB(0, 0, 255));
RECT clientRect;
GetClientRect(hWnd, &clientRect);
FillRect((HDC)wParam, &clientRect, hBrush);
DeleteBrush(hBrush);
return TRUE;
} else {
break;
}
// 用颜色标示辅助窗口
int side = (int)GetWindowLongPtr(hWnd, GWLP_USERDATA);
HBRUSH hBrush = CreateSolidBrush(side % 2 == 0 ? RGB(255, 0, 0) : RGB(0, 0, 255));
RECT clientRect;
GetClientRect(hWnd, &clientRect);
FillRect((HDC)wParam, &clientRect, hBrush);
DeleteBrush(hBrush);
return TRUE;
}
#endif
case WM_NCHITTEST:
@ -1714,10 +1620,11 @@ void ScalingWindow::_CreateBorderHelperWindows() noexcept {
}
_hwndResizeHelpers[i].reset(CreateWindowEx(
#ifdef MP_DEBUG_INFO
DEBUG_INFO.highlightBorder ? WS_EX_NOACTIVATE :
#endif
#ifdef MP_DEBUG_BORDER
WS_EX_NOACTIVATE,
#else
WS_EX_NOACTIVATE | WS_EX_NOREDIRECTIONBITMAP,
#endif
CommonSharedConstants::SCALING_BORDER_HELPER_WINDOW_CLASS_NAME,
nullptr,
WS_POPUP,
@ -1739,7 +1646,7 @@ void ScalingWindow::_RepostionBorderHelperWindows() noexcept {
resizeHandleLen *= 2;
}
int flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW;
int flags = SWP_NOACTIVATE | SWP_NOCOPYBITS;
if (_areResizeHelperWindowsVisible) {
flags |= SWP_NOZORDER;
@ -1756,10 +1663,8 @@ void ScalingWindow::_RepostionBorderHelperWindows() noexcept {
}
}
#ifdef MP_DEBUG_INFO
if (DEBUG_INFO.highlightBorder) {
flags &= ~SWP_NOREDRAW;
}
#ifndef MP_DEBUG_BORDER
flags |= SWP_NOREDRAW;
#endif
// ┌────────────────────┐
@ -1828,15 +1733,8 @@ static LRESULT CALLBACK BkgWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lP
// 将黑边映射到源窗口
RECT ScalingWindow::_CalcSrcTouchRect() const noexcept {
const RECT& srcRect = _srcTracker.SrcRect();
RECT outputRect = (RECT)_renderer->GetOutputRect();
const RECT destRect = {
_rendererRect.left + outputRect.left,
_rendererRect.top + outputRect.top,
_rendererRect.left + outputRect.right,
_rendererRect.top + outputRect.bottom
};
const RECT& srcRect = _renderer->SrcRect();
const RECT& destRect = _renderer->DestRect();
const double scaleX = double(destRect.right - destRect.left) / (srcRect.right - srcRect.left);
const double scaleY = double(destRect.bottom - destRect.top) / (srcRect.bottom - srcRect.top);
@ -2139,22 +2037,22 @@ void ScalingWindow::_UpdateRendererRect() noexcept {
const RECT& srcRect = _srcTracker.WindowRect();
const int offsetX = (_windowRect.left + _windowRect.right - srcRect.left - srcRect.right) / 2;
const int offsetY = (_windowRect.top + _windowRect.bottom - srcRect.top - srcRect.bottom) / 2;
if (!_srcTracker.Move(offsetX, offsetY, IsResizingOrMoving())) {
if (!_srcTracker.Move(offsetX, offsetY, _isResizingOrMoving)) {
Logger::Get().Error("SrcTracker::Move 失败");
_DelayedStop();
return;
}
_cursorManager->OnSrcMoved(_srcTracker.SrcRect());
_cursorManager->OnSrcRectChanged();
}
if (_hwndRenderer == Handle()) {
if (resized) {
// 为了平滑调整窗口尺寸,渲染所在窗口需要在 WM_NCCALCSIZE 中
// 更新渲染尺寸。
_HandleResize();
_ResizeRenderer();
} else {
_HandleMove();
_MoveRenderer();
}
} else {
// 渲染口过程将在 WM_NCCALCSIZE 中更新渲染尺寸
@ -2210,14 +2108,14 @@ bool ScalingWindow::_EnsureCaptionVisibleOnScreen() noexcept {
}
// 为了避免光标位置跳跃应等待渲染而且不要使用 SWP_NOSENDCHANGING
_shouldWaitForGpu = true;
_shouldWaitForRender = true;
bool result = SetWindowPos(
Handle(),
NULL,
_windowRect.left, mi.rcWork.top, 0, 0,
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOSIZE
);
_shouldWaitForGpu = false;
_shouldWaitForRender = false;
return result;
}

View file

@ -38,25 +38,16 @@ public:
void SwitchToolbarState() noexcept;
void Render(bool onDeviceLost = false) noexcept;
void Render() noexcept;
std::chrono::steady_clock::time_point GetLastRenderTime() const noexcept {
return _lastRenderTime;
const RECT& RendererRect() const noexcept {
return _rendererRect;
}
const ScalingOptions& Options() const noexcept {
return _options;
}
HWND SrcHandle() const noexcept {
return _srcTracker.Handle();
}
#pragma region 弃用
const RECT& RendererRect() const noexcept {
return _rendererRect;
}
class SrcTracker& SrcTracker() noexcept {
return _srcTracker;
}
@ -66,11 +57,11 @@ public:
}
class Renderer& Renderer() noexcept {
return *_oldRenderer;
return *_renderer;
}
const class Renderer& Renderer() const noexcept {
return *_oldRenderer;
return *_renderer;
}
class CursorManager& CursorManager() noexcept {
@ -81,25 +72,6 @@ public:
return *_cursorManager;
}
bool IsResizing() const noexcept {
return _isResizing;
}
bool IsMoving() const noexcept {
return _isMoving;
}
bool IsResizingOrMoving() const noexcept {
return _isResizing || _isMoving;
}
#pragma endregion
void OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept;
void OnCursorVirtualizationStarted() noexcept;
void OnCursorVirtualizationEnded() noexcept;
bool IsSrcRepositioning() const noexcept {
return _isSrcRepositioning;
}
@ -108,6 +80,10 @@ public:
void CleanAfterSrcRepositioned() noexcept;
bool IsResizingOrMoving() const noexcept {
return _isResizingOrMoving;
}
winrt::hstring GetLocalizedString(std::wstring_view resName) const;
void ShowToast(std::wstring_view msg) const noexcept {
@ -142,7 +118,10 @@ private:
void _Show() noexcept;
bool _UpdateSrcState(bool& isSrcRepositioning) noexcept;
bool _UpdateSrcState(
bool& isSrcRepositioning,
bool& srcFocusedChanged
) noexcept;
bool _CheckForegroundFor3DGameMode(HWND hwndFore) const noexcept;
@ -156,9 +135,9 @@ private:
static LRESULT CALLBACK _RendererWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void _HandleResize() noexcept;
void _ResizeRenderer() noexcept;
void _HandleMove() noexcept;
void _MoveRenderer() noexcept;
static LRESULT CALLBACK _BorderHelperWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
@ -199,8 +178,7 @@ private:
uint32_t _nonTopBorderThicknessInClient = 0;
ScalingOptions _options;
std::unique_ptr<class Renderer> _oldRenderer;
std::unique_ptr<class Renderer2> _renderer;
std::unique_ptr<class Renderer> _renderer;
std::unique_ptr<class CursorManager> _cursorManager;
class SrcTracker _srcTracker;
@ -217,16 +195,13 @@ private:
// 窗口缩放时切换到全屏缩放或最小化前保存尺寸供以后恢复
LONG _lastWindowedRendererWidth = 0;
std::chrono::steady_clock::time_point _lastRenderTime;
// 第一帧渲染完成后再显示
bool _isFirstFrame = false;
bool _isResizingOrMoving = false;
// 用于区分调整大小和移动
bool _isPreparingForResizing = false;
bool _isResizing = false;
bool _isMoving = false;
bool _isMovingDueToSrcMoved = false;
bool _shouldWaitForGpu = false;
bool _shouldWaitForRender = false;
bool _areResizeHelperWindowsVisible = false;
bool _isSrcRepositioning = false;
};

View file

@ -1,12 +0,0 @@
#include "pch.h"
#include "ShaderEffectCompilerService.h"
#include "ShaderEffectParser.h"
namespace Magpie {
bool ShaderEffectCompilerService::Submit(std::string_view /*name*/, ShaderEffectDesc& /*desc*/) const noexcept {
return false;
}
}

View file

@ -1,22 +0,0 @@
#pragma once
#include "ShaderEffectDesc.h"
namespace Magpie {
class ShaderEffectCompilerService {
public:
static ShaderEffectCompilerService& Get() noexcept {
static ShaderEffectCompilerService instance;
return instance;
}
ShaderEffectCompilerService(const ShaderEffectCompilerService&) = delete;
ShaderEffectCompilerService(ShaderEffectCompilerService&&) = delete;
bool Submit(std::string_view name, ShaderEffectDesc& desc) const noexcept;
private:
ShaderEffectCompilerService() = default;
};
}

View file

@ -1,91 +0,0 @@
#pragma once
#include "SmallVector.h"
#include <variant>
namespace Magpie {
enum class ShaderEffectTextureFormat {
UNKNOWN,
R8_UNORM,
R16_UNORM,
R16_FLOAT,
R8G8_UNORM,
R32_FLOAT,
R16G16_UNORM,
R16G16_FLOAT,
R11G11B10_FLOAT,
R8G8B8A8_UNORM,
R10G10B10A2_UNORM,
R32G32_FLOAT,
R16G16B16A16_UNORM,
R16G16B16A16_FLOAT,
R32G32B32A32_FLOAT
};
struct ShaderEffectTextureDesc {
ShaderEffectTextureFormat format = ShaderEffectTextureFormat::UNKNOWN;
std::pair<std::string, std::string> sizeExpr;
std::string source;
};
enum class EffectSamplerFilterType {
Point,
Linear
};
enum class EffectSamplerAddressType {
Clamp,
Wrap
};
struct ShaderEffectSamplerDesc {
std::string name;
EffectSamplerFilterType filterType = EffectSamplerFilterType::Point;
EffectSamplerAddressType addressType = EffectSamplerAddressType::Clamp;
};
enum class ShaderEffectPassFlags {
None = 0,
PSStyle = 1
};
DEFINE_ENUM_FLAG_OPERATORS(ShaderEffectPassFlags)
struct ShaderEffectPassDesc {
// 0: INPUT
// 1: OUTPUT
// 2+: 中间纹理
SmallVector<uint32_t> inputs;
SmallVector<uint32_t> outputs;
std::array<uint32_t, 3> numThreads{};
Size blockSize{};
ShaderEffectPassFlags flags = ShaderEffectPassFlags::None;
// 用于在叠加层中显示
std::string desc;
};
enum class ShaderEffectFlags {
None = 0,
UseDynamic = 1,
// 用于在叠加层中显示
UseFP16 = 1 << 1
};
DEFINE_ENUM_FLAG_OPERATORS(ShaderEffectFlags)
struct ShaderEffectDesc {
std::pair<std::string, std::string> outputSizeExprs;
std::vector<std::variant<float, int>> params;
// 不包含 INPUT 和 OUTPUT
std::vector<ShaderEffectTextureDesc> textures;
std::vector<ShaderEffectSamplerDesc> samplers;
std::vector<ShaderEffectPassDesc> passes;
float minFrameRate = 0.0f;
ShaderEffectFlags flags = ShaderEffectFlags::None;
// 用于在叠加层中显示
std::string name;
};
}

View file

@ -1,10 +0,0 @@
#include "pch.h"
#include "ShaderEffectDrawer.h"
namespace Magpie {
uint32_t ShaderEffectDrawer::CalcDescriptorCount(bool /*isFirst*/, bool /*isLast*/) {
return 0;
}
}

View file

@ -1,16 +0,0 @@
#pragma once
#include "EffectDrawerBase.h"
#include "EffectDesc.h"
namespace Magpie {
class ShaderEffectDrawer : public EffectDrawerBase {
public:
virtual ~ShaderEffectDrawer() noexcept = default;
static uint32_t CalcDescriptorCount( bool isFirst, bool isLast);
};
}

View file

@ -1,72 +0,0 @@
#include "pch.h"
#include "ShaderEffectParser.h"
#include "EffectInfo.h"
#include "Win32Helper.h"
#include "ShaderEffectDesc.h"
namespace Magpie {
// 当前 MagpieFX 版本
// static constexpr uint32_t MAGPIE_FX_VERSION = 4;
// static const char* META_INDICATOR = "//!";
class PassInclude : public ID3DInclude {
public:
PassInclude(std::wstring_view localDir) : _localDir(localDir) {}
PassInclude(const PassInclude&) = default;
PassInclude(PassInclude&&) = default;
HRESULT CALLBACK Open(
D3D_INCLUDE_TYPE /*IncludeType*/,
LPCSTR pFileName,
LPCVOID /*pParentData*/,
LPCVOID* ppData,
UINT* pBytes
) noexcept override {
std::wstring relativePath = StrHelper::Concat(_localDir, StrHelper::UTF8ToUTF16(pFileName));
std::string content;
if (!Win32Helper::ReadTextFile(relativePath.c_str(), content)) {
Logger::Get().Win32Error("Win32Helper::ReadTextFile 失败");
return E_FAIL;
}
std::unique_ptr<char[]> data = std::make_unique<char[]>(content.size());
std::memcpy(data.get(), content.data(), content.size());
*ppData = data.release();
*pBytes = (UINT)content.size();
return S_OK;
}
HRESULT CALLBACK Close(LPCVOID pData) noexcept override {
std::unique_ptr<char[]>((char*)pData).reset();
return S_OK;
}
private:
std::wstring _localDir;
};
bool ShaderEffectParser::ParseForInfo(std::string&& name, std::string&& /*source*/, EffectInfo& effectInfo) noexcept {
effectInfo.name = std::move(name);
return false;
}
bool ShaderEffectParser::ParseForDesc(
std::string&& name,
std::string&& /*source*/,
std::string&& workingFolder,
const ShaderEffectParserOptions& /*options*/,
struct ShaderEffectDesc& effectDesc,
ShaderEffectSource& effectSource
) noexcept {
effectDesc.name = std::move(name);
effectSource.workingFolder = std::move(workingFolder);
return false;
}
}

View file

@ -71,8 +71,8 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
return ScalingError::InvalidSourceWindow;
}
_hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
if (!_hMonitor) {
const HMONITOR hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
if (!hMon) {
Logger::Get().Error("源窗口不在任何屏幕上");
return ScalingError::InvalidSourceWindow;
}
@ -146,7 +146,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
{
// 使用屏幕而非窗口的 DPI 来计算边框宽度
UINT dpi = USER_DEFAULT_SCREEN_DPI;
GetDpiForMonitor(_hMonitor, MDT_EFFECTIVE_DPI, &dpi, &dpi);
GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &dpi, &dpi);
borderThicknessInFrame = (LONG)Win32Helper::GetNativeWindowBorderThickness(dpi);
}
@ -168,11 +168,10 @@ bool SrcTracker::UpdateState(
bool& focusedChanged,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged,
bool& monitorChanged
bool& movingChanged
) noexcept {
assert(!isInvisibleOrMinimized && !focusedChanged &&
!rectChanged && !sizeChanged && !movingChanged && !monitorChanged);
!rectChanged && !sizeChanged && !movingChanged);
if (!IsWindow(_hWnd)) {
Logger::Get().Info("源窗口已销毁");
@ -187,7 +186,7 @@ bool SrcTracker::UpdateState(
// 不要使用 IsHungAppWindow它有误报的情况见 GH#1244。这里用了未记录函数
// GhostWindowFromHungWindow它可以准确检查源窗口是否已被替换为幽灵窗口。
static const auto ghostWindowFromHungWindow =
Win32Helper::LoadFunction<HWND WINAPI(HWND)>(
Win32Helper::LoadSystemFunction<HWND WINAPI(HWND)>(
L"user32.dll", "GhostWindowFromHungWindow");
if (ghostWindowFromHungWindow && ghostWindowFromHungWindow(_hWnd)) {
// 检查源窗口是否真的处于无响应状态
@ -224,15 +223,8 @@ bool SrcTracker::UpdateState(
isInvisibleOrMinimized = true;
// rcNormalPosition 使用工作区坐标,应转换为屏幕坐标。
// 标志 MONITOR_DEFAULTTOPRIMARY 和 OS 一致,见:
// https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/winmgr.c#L752
// rcNormalPosition 使用工作区坐标,应转换为屏幕坐标
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTOPRIMARY);
if (!hMon) {
Logger::Get().Win32Error("MonitorFromWindow 失败");
return false;
}
MONITORINFO mi{ sizeof(mi) };
if (!GetMonitorInfo(hMon, &mi)) {
Logger::Get().Win32Error("GetMonitorInfo 失败");
@ -249,25 +241,11 @@ bool SrcTracker::UpdateState(
}
}
// 最大化状态改变视为尺寸发生变化
rectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
if (rectChanged) {
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTONULL);
if (!hMon) {
Logger::Get().Error("源窗口不在任何屏幕上");
return false;
}
if (_hMonitor != hMon) {
monitorChanged = true;
_hMonitor = hMon;
}
sizeChanged = oldMaximized != _isMaximized ||
Win32Helper::GetSizeOfRect(curWindowRect) != Win32Helper::GetSizeOfRect(_windowRect);
if (sizeChanged) {
return true;
}
sizeChanged = oldMaximized != _isMaximized ||
Win32Helper::GetSizeOfRect(curWindowRect) != Win32Helper::GetSizeOfRect(_windowRect);
if (sizeChanged) {
rectChanged = true;
return true;
}
// 缩放窗口正在调整大小或被拖动时源窗口的移动是异步的,暂时不检查源窗口是否移动
@ -275,6 +253,9 @@ bool SrcTracker::UpdateState(
rectChanged = oldMaximized != _isMaximized;
return true;
}
// 最大化状态改变视为尺寸发生变化
rectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
if (isWindowedMode && !sizeChanged) {
if (rectChanged) {
@ -476,7 +457,7 @@ ScalingError SrcTracker::_CalcSrcRect(
) noexcept {
if (_windowKind == SrcWindowKind::NoNativeFrame) {
if (hasCustomNonclient) {
if (options.IsCaptureTitleBar()) {
if (options.RealIsCaptureTitleBar()) {
// 窗口的非客户区是自绘的,无法模拟,因此启用捕获标题栏时捕获整个窗口
_srcRect = _windowRect;
} else {
@ -518,9 +499,10 @@ ScalingError SrcTracker::_CalcSrcRect(
_srcRect = _windowRect;
}
} else {
const bool isCaptureTitleBar = options.RealIsCaptureTitleBar();
// UWP 窗口都是 NoTitleBar 类型,但可能使用子窗口作为“客户区”
if (_windowKind == SrcWindowKind::NoTitleBar &&
!options.IsCaptureTitleBar() && GetClientRectOfUWP(_hWnd, _srcRect)) {
if (_windowKind == SrcWindowKind::NoTitleBar && !isCaptureTitleBar && GetClientRectOfUWP(_hWnd, _srcRect)) {
_srcRect.top = std::max(_srcRect.top, _windowFrameRect.top + borderThicknessInFrame);
} else {
// 不要使用客户区矩形,它不包含滚动条
@ -529,7 +511,7 @@ ScalingError SrcTracker::_CalcSrcRect(
_srcRect.right = _windowFrameRect.right - borderThicknessInFrame;
_srcRect.bottom = _windowFrameRect.bottom - borderThicknessInFrame;
if (!options.IsCaptureTitleBar() || _windowKind == SrcWindowKind::OnlyThickFrame) {
if (!isCaptureTitleBar || _windowKind == SrcWindowKind::OnlyThickFrame) {
RECT clientRect;
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
Logger::Get().Error("GetClientScreenRect 失败");
@ -543,8 +525,9 @@ ScalingError SrcTracker::_CalcSrcRect(
if (_isMaximized) {
// 最大化的窗口只有屏幕内是有效区域
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi{ .cbSize = sizeof(mi) };
if (!GetMonitorInfo(_hMonitor, &mi)) {
if (!GetMonitorInfo(hMon, &mi)) {
Logger::Get().Win32Error("GetMonitorInfo 失败");
return ScalingError::ScalingFailedGeneral;
}

View file

@ -37,8 +37,7 @@ public:
bool& focusedChanged,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged,
bool& monitorChanged
bool& movingChanged
) noexcept;
bool Move(int offsetX, int offsetY, bool async) noexcept;
@ -81,10 +80,6 @@ public:
return _isMoving;
}
HMONITOR Monitor() const noexcept {
return _hMonitor;
}
private:
ScalingError _CalcSrcRect(
const ScalingOptions& options,
@ -96,7 +91,6 @@ private:
RECT _windowRect{};
RECT _windowFrameRect{};
RECT _srcRect{};
HMONITOR _hMonitor = NULL;
SrcWindowKind _windowKind = SrcWindowKind::Native;
bool _isFocused = false;

View file

@ -44,7 +44,7 @@ StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame, bool& fpsUp
WaitMessage();
}
return StepTimerStatus::WaitingForNewFrame;
return StepTimerStatus::WaitForNewFrame;
}
// 包括当前帧的捕获时间和渲染时间以及渲染完成后已经等待的时间
@ -59,7 +59,7 @@ StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame, bool& fpsUp
if (delta < _minInterval) {
_WaitForMsgAndTimer(_minInterval - delta);
return StepTimerStatus::WaitingForFPSLimiter;
return StepTimerStatus::WaitForFPSLimiter;
}
// 有的捕获方法当有新帧时会有消息到达
@ -72,7 +72,7 @@ StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame, bool& fpsUp
}
}
return StepTimerStatus::WaitingForNewFrame;
return StepTimerStatus::WaitForNewFrame;
}
void StepTimer::PrepareForRender() noexcept {

View file

@ -3,8 +3,8 @@
namespace Magpie {
enum class StepTimerStatus {
WaitingForNewFrame,
WaitingForFPSLimiter,
WaitForNewFrame,
WaitForFPSLimiter,
ForceNewFrame
};
@ -42,9 +42,9 @@ private:
std::chrono::nanoseconds _maxInterval{ std::numeric_limits<std::chrono::nanoseconds::rep>::max() };
wil::unique_event_nothrow _hTimer;
std::chrono::steady_clock::time_point _thisFrameStartTime;
std::chrono::steady_clock::time_point _nextFrameStartTime;
std::chrono::steady_clock::time_point _lastSecondTime;
std::chrono::time_point<std::chrono::steady_clock> _thisFrameStartTime;
std::chrono::time_point<std::chrono::steady_clock> _nextFrameStartTime;
std::chrono::time_point<std::chrono::steady_clock> _lastSecondTime;
uint32_t _frameCount = 0;
std::atomic<uint32_t> _framesPerSecond = 0;

View file

@ -1,442 +0,0 @@
#include "pch.h"
#include "SwapChainPresenter.h"
#include "DebugInfo.h"
#include "GraphicsContext.h"
#include "Logger.h"
#include "Win32Helper.h"
#include <dcomp.h>
#include <dwmapi.h>
namespace Magpie {
bool SwapChainPresenter::Initialize(
GraphicsContext& graphicContext,
HWND hwndAttach,
Size size,
const ColorInfo& colorInfo
) noexcept {
_graphicContext = &graphicContext;
_size = size;
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
IDXGIFactory7* dxgiFactory = graphicContext.GetDXGIFactory();
ID3D12Device5* device = graphicContext.GetDevice();
// 检查撕裂支持
{
BOOL supportTearing = FALSE;
HRESULT hr = dxgiFactory->CheckFeatureSupport(
DXGI_FEATURE_PRESENT_ALLOW_TEARING, &supportTearing, sizeof(supportTearing));
if (FAILED(hr)) {
Logger::Get().ComError("IDXGIFactory5::CheckFeatureSupport 失败", hr);
return false;
}
_isTearingSupported = supportTearing;
}
_bufferCount = graphicContext.GetMaxInFlightFrameCount() + 1;
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {
.Width = size.width,
.Height = size.height,
// 默认色域正是我们想要的,无需额外设置
.Format = _isScRGB ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = {
.Count = 1
},
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = _bufferCount,
#ifdef _DEBUG
// 使边缘闪烁更容易观察到
.Scaling = DXGI_SCALING_NONE,
#else
// 使视觉变化尽可能小
.Scaling = DXGI_SCALING_STRETCH,
#endif
// 渲染每帧之前都会清空后缓冲区,因此无需 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = DXGI_ALPHA_MODE_IGNORE,
// 支持时始终启用 DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING
.Flags = UINT((_isTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0)
| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)
};
winrt::com_ptr<IDXGISwapChain1> dxgiSwapChain;
HRESULT hr = dxgiFactory->CreateSwapChainForHwnd(
graphicContext.GetCommandQueue(),
hwndAttach,
&swapChainDesc,
nullptr,
nullptr,
dxgiSwapChain.put()
);
if (FAILED(hr)) {
Logger::Get().ComError("IDXGIFactory2::CreateSwapChainForHwnd 失败", hr);
return false;
}
_dxgiSwapChain = dxgiSwapChain.try_as<IDXGISwapChain4>();
if (!_dxgiSwapChain) {
Logger::Get().Error("检索 IDXGISwapChain4 失败");
return false;
}
hr = _dxgiSwapChain->SetMaximumFrameLatency(_bufferCount - 1);
if (FAILED(hr)) {
Logger::Get().ComError("IDXGISwapChain2::SetMaximumFrameLatency 失败", hr);
return false;
}
_frameLatencyWaitableObject.reset(_dxgiSwapChain->GetFrameLatencyWaitableObject());
if (!_frameLatencyWaitableObject) {
Logger::Get().Error("IDXGISwapChain2::GetFrameLatencyWaitableObject 失败");
return false;
}
dxgiFactory->MakeWindowAssociation(hwndAttach, DXGI_MWA_NO_ALT_ENTER);
hr = _CreateRtvHeap();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateRtvHeap 失败", hr);
return false;
}
_rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
_frameBuffers.resize(_bufferCount);
hr = _CreateDisplayDependentResources();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateDisplayDependentResources 失败", hr);
return false;
}
return true;
}
void SwapChainPresenter::BeginFrame(
ID3D12Resource** frameTex,
D3D12_CPU_DESCRIPTOR_HANDLE& rtvHandle,
D3D12_CPU_DESCRIPTOR_HANDLE& rawRtvHandle
) noexcept {
_frameLatencyWaitableObject.wait(1000);
const uint32_t curBufferIndex = _dxgiSwapChain->GetCurrentBackBufferIndex();
*frameTex = _frameBuffers[curBufferIndex].get();
rtvHandle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
_rtvHeap->GetCPUDescriptorHandleForHeapStart(), curBufferIndex, _rtvDescriptorSize);
if (!_isScRGB) {
rawRtvHandle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
rtvHandle, _bufferCount, _rtvDescriptorSize);
}
}
// 和 DwmFlush 效果相同但更准确
static void WaitForDwmComposition() noexcept {
// Win11 可以使用准确的 DCompositionWaitForCompositorClock
if (Win32Helper::GetOSVersion().IsWin11()) {
static const auto dCompositionWaitForCompositorClock =
Win32Helper::LoadFunction<decltype(DCompositionWaitForCompositorClock)>(
L"dcomp.dll", "DCompositionWaitForCompositorClock");
if (dCompositionWaitForCompositorClock) {
dCompositionWaitForCompositorClock(0, nullptr, INFINITE);
return;
}
}
LARGE_INTEGER qpf;
QueryPerformanceFrequency(&qpf);
qpf.QuadPart /= 10000000;
DWM_TIMING_INFO info{};
info.cbSize = sizeof(info);
DwmGetCompositionTimingInfo(NULL, &info);
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
if (time.QuadPart >= (LONGLONG)info.qpcCompose) {
return;
}
// 提前 1ms 结束然后忙等待
time.QuadPart += 10000;
if (time.QuadPart < (LONGLONG)info.qpcCompose) {
LARGE_INTEGER liDueTime{
.QuadPart = -((LONGLONG)info.qpcCompose - time.QuadPart) / qpf.QuadPart
};
static HANDLE timer = CreateWaitableTimerEx(nullptr, nullptr,
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
SetWaitableTimerEx(timer, &liDueTime, 0, NULL, NULL, 0, 0);
WaitForSingleObject(timer, INFINITE);
} else {
Sleep(0);
}
while (true) {
QueryPerformanceCounter(&time);
if (time.QuadPart >= (LONGLONG)info.qpcCompose) {
return;
}
Sleep(0);
}
}
HRESULT SwapChainPresenter::EndFrame(bool waitForGpu) noexcept {
#ifdef MP_DEBUG_INFO
{
auto lk = DEBUG_INFO.lock.lock_exclusive();
if (DEBUG_INFO.dtmFrameNumer != 0) {
bool restartMeasure = false;
if (DEBUG_INFO.dtmSwapChainRefreshCount == 0) {
if (DEBUG_INFO.dtmFrameNumer == DEBUG_INFO.consumerFrameNumber) {
// 追踪的帧将被呈现,记录当前交换链 VSync 计数
DXGI_FRAME_STATISTICS statistics;
HRESULT hr = _dxgiSwapChain->GetFrameStatistics(&statistics);
if (SUCCEEDED(hr)) {
DEBUG_INFO.dtmSwapChainRefreshCount = statistics.SyncRefreshCount;
} else {
restartMeasure = true;
}
} else if (DEBUG_INFO.dtmFrameNumer < DEBUG_INFO.consumerFrameNumber) {
// 追踪的帧被错过,应重新测量
restartMeasure = true;
}
} else {
DXGI_FRAME_STATISTICS statistics;
HRESULT hr = _dxgiSwapChain->GetFrameStatistics(&statistics);
if (SUCCEEDED(hr)) {
if (statistics.SyncRefreshCount != DEBUG_INFO.dtmSwapChainRefreshCount) {
// 追踪的帧已被呈现
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
DEBUG_INFO.dwmToMagpieLatency =
int32_t((statistics.SyncQPCTime.QuadPart - DEBUG_INFO.dtmDwmQPC) * 1000000LL / frequency.QuadPart);
restartMeasure = true;
}
} else {
restartMeasure = true;
}
}
if (restartMeasure) {
DEBUG_INFO.dtmFrameNumer = 0;
DEBUG_INFO.dtmSwapChainRefreshCount = 0;
}
}
}
#endif
const bool isRecreated = std::exchange(_isRecreated, false);
if (isRecreated || waitForGpu) {
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁。
//
// 我们希望 DWM 绘制新的窗口框架时刚好合成新帧,但这不是我们能控制的,尤其是混合架构
// 下需要在显卡间传输帧数据,无法预测 Present/Commit 后多久 DWM 能收到。我们只能尽
// 可能为 DWM 合成新帧预留时间,这包括两个步骤:
//
// 1. 首先等待渲染完成,确保新帧对 DWM 随时可用。
// 2. 然后在新一轮合成开始时提交,这让 DWM 有更多时间合成新帧。
//
// 目前看来除非像 UWP 一般有 DWM 协助,否则彻底摆脱闪烁是不可能的。
//
// https://github.com/Blinue/Magpie/pull/1071#issuecomment-2718314731 讨论了 UWP
// 调整尺寸的方法,测试表明可以彻底解决闪烁问题。不过它使用了很不稳定的私有接口,没有
// 实用价值。
// 等待渲染完成
HRESULT hr = _graphicContext->WaitForGpu();
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::WaitForGPU", hr);
return hr;
}
// 等待 DWM 开始合成新一帧
WaitForDwmComposition();
}
HRESULT hr = _dxgiSwapChain->Present(0, 0);
if (FAILED(hr)) {
Logger::Get().ComError("IDXGISwapChain::Present", hr);
return hr;
}
#ifdef MP_DEBUG_INFO
{
auto lk = DEBUG_INFO.lock.lock_exclusive();
if (DEBUG_INFO.ctpFrameNumer != 0 && DEBUG_INFO.ctpFrameNumer <= DEBUG_INFO.consumerFrameNumber) {
// 如果 ctpFrameNumer < consumerFrameNumber 那么追踪的帧被错过,应重新测量
if (DEBUG_INFO.ctpFrameNumer == DEBUG_INFO.consumerFrameNumber) {
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
DEBUG_INFO.captureToPresentLatency =
uint32_t((counter.QuadPart - DEBUG_INFO.ctpCaptureQPC) * 1000000LL / frequency.QuadPart);
}
DEBUG_INFO.ctpCapturedFrame = 0;
DEBUG_INFO.ctpFrameNumer = 0;
}
}
#endif
return S_OK;
}
HRESULT SwapChainPresenter::OnResized(Size size) noexcept {
assert(size.width > 0 && size.height > 0 && size != _size);
_size = size;
// 调整大小期间只用两个后备缓冲以提高流畅度并减少边缘闪烁
_bufferCount = _isResizing ? 2 : _graphicContext->GetMaxInFlightFrameCount() + 1;
HRESULT hr = _RecreateBuffers();
if (FAILED(hr)) {
Logger::Get().ComError("_RecreateBuffers 失败", hr);
}
return hr;
}
void SwapChainPresenter::OnResizeStarted() noexcept {
// 尺寸变化时再重建交换链
_isResizing = true;
}
HRESULT SwapChainPresenter::OnResizeEnded() noexcept {
_isResizing = false;
// 恢复后备缓冲数量
const uint32_t oldBufferCount = _bufferCount;
_bufferCount = _graphicContext->GetMaxInFlightFrameCount() + 1;
if (_bufferCount != oldBufferCount) {
// 调用此方法前没等待 GPU
HRESULT hr = _graphicContext->WaitForGpu();
if (FAILED(hr)) {
Logger::Get().ComError("GraphicsContext::WaitForGPU", hr);
return hr;
}
hr = _RecreateBuffers();
if (FAILED(hr)) {
Logger::Get().ComError("_RecreateBuffers 失败", hr);
return hr;
}
}
return S_OK;
}
HRESULT SwapChainPresenter::OnColorInfoChanged(const ColorInfo& colorInfo) noexcept {
const bool wasScRGB = _isScRGB;
_isScRGB = colorInfo.kind != winrt::AdvancedColorKind::StandardDynamicRange;
if (_isScRGB == wasScRGB) {
return S_OK;
}
HRESULT hr = _CreateRtvHeap();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateRtvHeap 失败", hr);
return hr;
}
hr = _RecreateBuffers();
if (FAILED(hr)) {
Logger::Get().ComError("_RecreateBuffers 失败", hr);
return hr;
}
hr = _dxgiSwapChain->SetColorSpace1(
_isScRGB ? DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709 : DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
if (FAILED(hr)) {
Logger::Get().ComError("IDXGISwapChain4::SetColorSpace1 失败", hr);
}
return hr;
}
HRESULT SwapChainPresenter::_CreateRtvHeap() noexcept {
ID3D12Device5* device = _graphicContext->GetDevice();
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {
.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
.NumDescriptors = _isScRGB ? _bufferCount : _bufferCount * 2
};
HRESULT hr = device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&_rtvHeap));
if (FAILED(hr)) {
Logger::Get().ComError("CreateDescriptorHeap 失败", hr);
return hr;
}
return S_OK;
}
HRESULT SwapChainPresenter::_RecreateBuffers() noexcept {
std::fill(_frameBuffers.begin(), _frameBuffers.end(), nullptr);
// 不要更改最大帧延迟,一来调整大小期间不会有帧排队,二来交换链不大支持中途改变
// 最大帧延迟,需要额外等待 FrameLatencyWaitableObject 来修正内部状态。
HRESULT hr = _dxgiSwapChain->ResizeBuffers(
_bufferCount, _size.width, _size.height,
_isScRGB ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
UINT((_isTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0)
| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)
);
if (FAILED(hr)) {
Logger::Get().ComError("IDXGISwapChain::ResizeBuffers", hr);
return hr;
}
_isRecreated = true;
hr = _CreateDisplayDependentResources();
if (FAILED(hr)) {
Logger::Get().ComError("_CreateDisplayDependentResources 失败", hr);
}
return hr;
}
HRESULT SwapChainPresenter::_CreateDisplayDependentResources() noexcept {
ID3D12Device5* device = _graphicContext->GetDevice();
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(_rtvHeap->GetCPUDescriptorHandleForHeapStart());
for (uint32_t i = 0; i < _bufferCount; ++i) {
HRESULT hr = _dxgiSwapChain->GetBuffer(i, IID_PPV_ARGS(&_frameBuffers[i]));
if (FAILED(hr)) {
Logger::Get().ComError("IDXGISwapChain::GetBuffer 失败", hr);
return hr;
}
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc = {
.Format = _isScRGB ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D
};
device->CreateRenderTargetView(_frameBuffers[i].get(), &rtvDesc, rtvHandle);
// 处于 sRGB 空间时创建两个 RTV
if (!_isScRGB) {
rtvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
device->CreateRenderTargetView(_frameBuffers[i].get(), &rtvDesc,
CD3DX12_CPU_DESCRIPTOR_HANDLE(rtvHandle, _bufferCount, _rtvDescriptorSize));
}
rtvHandle.Offset(1, _rtvDescriptorSize);
}
return S_OK;
}
}

View file

@ -1,64 +0,0 @@
#pragma once
namespace Magpie {
class GraphicsContext;
class SwapChainPresenter {
public:
SwapChainPresenter() = default;
SwapChainPresenter(const SwapChainPresenter&) = delete;
SwapChainPresenter(SwapChainPresenter&&) = delete;
bool Initialize(
GraphicsContext& graphicContext,
HWND hwndAttach,
Size size,
const ColorInfo& colorInfo
) noexcept;
// SDR 下 rawRtvHandle 不做伽马校正,用于渲染光标
void BeginFrame(
ID3D12Resource** frameTex,
D3D12_CPU_DESCRIPTOR_HANDLE& rtvHandle,
D3D12_CPU_DESCRIPTOR_HANDLE& rawRtvHandle
) noexcept;
HRESULT EndFrame(bool waitForGpu = false) noexcept;
Size GetSize() const noexcept { return _size; }
HRESULT OnResized(Size size) noexcept;
void OnResizeStarted() noexcept;
HRESULT OnResizeEnded() noexcept;
HRESULT OnColorInfoChanged(const ColorInfo& colorInfo) noexcept;
private:
HRESULT _CreateRtvHeap() noexcept;
HRESULT _RecreateBuffers() noexcept;
HRESULT _CreateDisplayDependentResources() noexcept;
GraphicsContext* _graphicContext = nullptr;
winrt::com_ptr<IDXGISwapChain4> _dxgiSwapChain;
wil::unique_event_nothrow _frameLatencyWaitableObject;
std::vector<winrt::com_ptr<ID3D12Resource>> _frameBuffers;
winrt::com_ptr<ID3D12DescriptorHeap> _rtvHeap;
uint32_t _rtvDescriptorSize = 0;
Size _size{};
uint32_t _bufferCount = 0;
bool _isScRGB = false;
bool _isTearingSupported = false;
bool _isRecreated = true;
bool _isResizing = false;
};
}

View file

@ -54,7 +54,7 @@ wil::unique_process_handle Win32Helper::GetWindowProcessHandle(HWND hWnd) noexce
// 在某些窗口上 OpenProcess 会失败(如暗黑 2尝试使用 GetProcessHandleFromHwnd
static const auto getProcessHandleFromHwnd =
Win32Helper::LoadFunction<HANDLE WINAPI(HWND)>(L"Oleacc.dll", "GetProcessHandleFromHwnd");
Win32Helper::LoadSystemFunction<HANDLE WINAPI(HWND)>(L"Oleacc.dll", "GetProcessHandleFromHwnd");
if (!getProcessHandleFromHwnd) {
return result;
}
@ -231,7 +231,7 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
// 检查是否在窗口内
RECT windowRect;
if (!GetWindowRect(hWnd, &windowRect) || !PtInRect(windowRect, data->ptScreen)) {
if (!GetWindowRect(hWnd, &windowRect) || !PtInRect(&windowRect, data->ptScreen)) {
return TRUE;
}
@ -314,8 +314,8 @@ bool Win32Helper::ReadFile(const wchar_t* fileName, std::vector<uint8_t>& result
DWORD size = GetFileSize(hFile.get(), nullptr);
result.resize(size);
DWORD bytesRead;
if (!::ReadFile(hFile.get(), result.data(), size, &bytesRead, nullptr)) {
DWORD readed;
if (!::ReadFile(hFile.get(), result.data(), size, &readed, nullptr)) {
Logger::Get().Error("读取文件失败");
return false;
}
@ -426,7 +426,7 @@ bool Win32Helper::CreateDir(const std::wstring& path, bool recursive) noexcept {
const Win32Helper::OSVersion& Win32Helper::GetOSVersion() noexcept {
static OSVersion version = [] {
const auto rtlGetVersion =
LoadFunction<LONG WINAPI(PRTL_OSVERSIONINFOW)>(L"ntdll.dll", "RtlGetVersion");
LoadSystemFunction<LONG WINAPI(PRTL_OSVERSIONINFOW)>(L"ntdll.dll", "RtlGetVersion");
if (!rtlGetVersion) {
return OSVersion();
}
@ -885,7 +885,7 @@ void Win32Helper::WaitForDwmComposition() noexcept {
// Win11 可以使用准确的 DCompositionWaitForCompositorClock
if (Win32Helper::GetOSVersion().IsWin11()) {
static const auto dCompositionWaitForCompositorClock =
Win32Helper::LoadFunction<decltype(DCompositionWaitForCompositorClock)>(
Win32Helper::LoadSystemFunction<decltype(DCompositionWaitForCompositorClock)>(
L"dcomp.dll", "DCompositionWaitForCompositorClock");
if (dCompositionWaitForCompositorClock) {
dCompositionWaitForCompositorClock(0, nullptr, INFINITE);

View file

@ -1,16 +1,9 @@
#pragma once
#include <d3d11_4.h>
#include <d3d12.h>
namespace Magpie {
struct DirectXHelper {
union Constant32 {
float floatVal;
uint32_t uintVal;
int intVal;
};
static bool IsWARP(const DXGI_ADAPTER_DESC1& desc) noexcept {
// 不要检查 DXGI_ADAPTER_FLAG_SOFTWARE 标志如果系统没有安装任何显卡WARP 没有这个标志。
// 这两个值来自 https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/d3d10-graphics-programming-guide-dxgi#new-info-about-enumerating-adapters-for-windows-8
@ -41,9 +34,4 @@ struct DirectXHelper {
) noexcept;
};
static inline bool operator==(LUID l, LUID r) noexcept {
// LowPart 不同的概率更高,因此先检查
return l.LowPart == r.LowPart && l.HighPart == r.HighPart;
}
}

View file

@ -1,34 +0,0 @@
#pragma once
namespace Magpie {
struct EffectInfoParameter {
std::string name;
std::string label;
float defaultValue;
float minValue;
float maxValue;
float step;
};
enum class EffectInfoFlags {
None,
SupportFP16 = 1,
// 必须支持一个或 linear sRGB + scRGB
SupportLinearSRGB = 1 << 1,
SupportScRGB = 1 << 2,
SupportSRGB = 1 << 3
};
DEFINE_ENUM_FLAG_OPERATORS(EffectInfoFlags)
struct EffectInfo {
std::string name;
std::string sortName;
std::vector<EffectInfoParameter> params;
// 可使用 INPUT_WIDTH空字符串表示支持自由缩放
std::pair<std::string, std::string> outputSizeExprs;
EffectInfoFlags flags = EffectInfoFlags::SupportLinearSRGB;
};
}

View file

@ -42,7 +42,7 @@ struct GraphicsCardId {
uint32_t deviceId = 0;
};
enum class OutputAlignment {
enum class DestAlignment {
LeftTop,
Top,
RightTop,
@ -159,6 +159,7 @@ struct ScalingFlags {
static constexpr uint32_t DisableDirectFlip = 1 << 13;
static constexpr uint32_t DisableFontCache = 1 << 14;
static constexpr uint32_t AllowScalingMaximized = 1 << 15;
static constexpr uint32_t EnableStatisticsForDynamicDetection = 1 << 16;
// 只影响缩放行为Magpie.Core 不负责启动 TouchHelper.exe
static constexpr uint32_t TouchSupportEnabled = 1 << 17;
static constexpr uint32_t InlineParams = 1 << 18;
@ -179,6 +180,7 @@ struct ScalingOptions {
DEFINE_FLAG_ACCESSOR(IsFontCacheDisabled, ScalingFlags::DisableFontCache, flags)
DEFINE_FLAG_ACCESSOR(IsSaveEffectSources, ScalingFlags::SaveEffectSources, flags)
DEFINE_FLAG_ACCESSOR(IsWarningsAreErrors, ScalingFlags::WarningsAreErrors, flags)
DEFINE_FLAG_ACCESSOR(IsStatisticsForDynamicDetectionEnabled, ScalingFlags::EnableStatisticsForDynamicDetection, flags)
DEFINE_FLAG_ACCESSOR(IsInlineParams, ScalingFlags::InlineParams, flags)
DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::TouchSupportEnabled, flags)
DEFINE_FLAG_ACCESSOR(IsAllowScalingMaximized, ScalingFlags::AllowScalingMaximized, flags)
@ -194,14 +196,13 @@ struct ScalingOptions {
GraphicsCardId graphicsCardId;
float minFrameRate = 0.0f;
std::optional<float> maxFrameRate;
float cursorScale = 1.0f;
float cursorScaling = 1.0f;
CaptureMethod captureMethod = CaptureMethod::GraphicsCapture;
MultiMonitorUsage multiMonitorUsage = MultiMonitorUsage::Closest;
OutputAlignment outputAlignment = OutputAlignment::Center;
DestAlignment destAlignment = DestAlignment::Center;
CursorInterpolationMode cursorInterpolationMode = CursorInterpolationMode::NearestNeighbor;
std::optional<float> autoHideCursorDelay;
DuplicateFrameDetectionMode duplicateFrameDetectionMode = DuplicateFrameDetectionMode::Dynamic;
uint32_t maxProducerInFlightFrames = 2;
ToolbarState fullscreenInitialToolbarState = ToolbarState::AutoHide;
ToolbarState windowedInitialToolbarState = ToolbarState::AutoHide;
float initialWindowedScaleFactor = 0.0f;
@ -214,7 +215,21 @@ struct ScalingOptions {
void (*showError)(HWND hwndTarget, ScalingError error) noexcept = nullptr;
void (*save)(const ScalingOptions& options, HWND hwndScaling) noexcept = nullptr;
void Prepare() noexcept;
void Log() const noexcept;
bool RealIsCaptureTitleBar() const noexcept {
// GDI 和 DwmSharedSurface 不支持捕获标题栏
return IsCaptureTitleBar() &&
captureMethod != CaptureMethod::GDI && captureMethod != CaptureMethod::DwmSharedSurface;
}
bool RealIsAllowScalingMaximized() const noexcept {
return IsAllowScalingMaximized() && !IsWindowedMode();
}
bool RealIsSimulateExclusiveFullscreen() const noexcept {
return IsSimulateExclusiveFullscreen() && !IsWindowedMode();
}
};
}

View file

@ -1,44 +0,0 @@
#pragma once
#include <parallel_hashmap/phmap.h>
namespace Magpie {
enum class ShaderEffectParserFlags {
None = 0,
// 只在效果支持 FP16 时影响字节码
EnableFP16 = 1,
// 只在效果同时支持 linear sRGB 和 scRGB 时影响字节码
EnableAdvancedColor = 1 << 1
};
DEFINE_ENUM_FLAG_OPERATORS(ShaderEffectParserFlags)
struct ShaderEffectParserOptions {
// 只在效果存在参数时影响字节码。为空表示不内联
const phmap::flat_hash_map<std::string, float>* inlineParams = nullptr;
// 只在效果支持 shader model 时影响字节码
D3D_SHADER_MODEL shaderModel = D3D_SHADER_MODEL_5_1;
ShaderEffectParserFlags flags = ShaderEffectParserFlags::None;
};
struct ShaderEffectSource {
std::vector<std::string> sources;
// 用于解析 #include
std::string workingFolder;
std::vector<std::pair<std::string, std::string>> macros;
D3D_SHADER_MODEL shaderModel = D3D_SHADER_MODEL_5_1;
};
struct ShaderEffectParser {
static bool ParseForInfo(std::string&& name, std::string&& source, struct EffectInfo& effectInfo) noexcept;
static bool ParseForDesc(
std::string&& name,
std::string&& source,
std::string&& workingFolder,
const ShaderEffectParserOptions& options,
struct ShaderEffectDesc& effectDesc,
ShaderEffectSource& effectSource
) noexcept;
};
}

View file

@ -33,10 +33,6 @@ struct Win32Helper {
return result.left < result.right && result.top < result.bottom;
}
static bool PtInRect(const RECT& rect, POINT pt) {
return pt.x >= rect.left && pt.x < rect.right && pt.y >= rect.top && pt.y < rect.bottom;
}
static std::wstring GetWindowClassName(HWND hWnd) noexcept;
static std::wstring GetWindowTitle(HWND hWnd) noexcept;
@ -106,10 +102,6 @@ struct Win32Helper {
bool Is24H2OrNewer() const noexcept {
return *this >= Version(10, 0, 26100);
}
bool Is25H2OrNewer() const noexcept {
return *this >= Version(10, 0, 26200);
}
};
static const OSVersion& GetOSVersion() noexcept;
@ -191,7 +183,7 @@ struct Win32Helper {
static const std::filesystem::path& GetExePath() noexcept;
template<typename T, std::enable_if_t<std::is_function_v<T>, int> = 0>
static T* LoadFunction(const wchar_t* dllName, const char* funcName) noexcept {
static T* LoadSystemFunction(const wchar_t* dllName, const char* funcName) noexcept {
assert(dllName && funcName);
HMODULE hMod = GetModuleHandle(dllName);

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Direct3D.D3D12" version="1.619.0" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
</packages>

View file

@ -2,7 +2,7 @@
// Windows 头文件
#include <SDKDDKVer.h>
#include <Windows.h>
#include <windows.h>
#include <windowsx.h>
// 避免 C++/WinRT 头文件的警告
@ -11,8 +11,6 @@
// DirectX 头文件
#include <d3d11_4.h>
#include <dxgi1_6.h>
#include <d3d12.h>
#include <d3dx12.h>
// C++ 运行时
#include <cstdlib>
@ -46,7 +44,6 @@
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.Graphics.Display.h>
#include <winrt/Windows.System.h>
namespace winrt {
@ -55,7 +52,6 @@ using namespace Windows::ApplicationModel::Resources::Core;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Foundation::Metadata;
using namespace Windows::Graphics::Display;
using namespace Windows::System;
}
@ -64,62 +60,3 @@ using namespace Windows::System;
#include <fmt/xchar.h>
#include "CommonDefines.h"
namespace Magpie {
static constexpr uint32_t MAX_CAPTURE_DIRTY_RECT_COUNT = 4;
static constexpr uint32_t DUP_FRAME_DISPATCH_BLOCK_SIZE = 16;
static constexpr uint32_t SCENE_REFERRED_SDR_WHITE_LEVEL = 80;
enum class ComponentState {
NoError,
DeviceLost,
Error
};
struct Size {
uint32_t width;
uint32_t height;
bool operator==(const Size&) const noexcept = default;
explicit operator SIZE() const noexcept {
return { (LONG)width,(LONG)height };
}
};
struct Point {
uint32_t x;
uint32_t y;
bool operator==(const Point&) const noexcept = default;
explicit operator POINT() const noexcept {
return { (LONG)x,(LONG)y };
}
};
struct Rect {
uint32_t left;
uint32_t top;
uint32_t right;
uint32_t bottom;
bool operator==(const Rect& other) const = default;
explicit operator RECT() const noexcept {
return { (LONG)left,(LONG)top,(LONG)right,(LONG)bottom };
}
};
struct ColorInfo {
winrt::AdvancedColorKind kind = winrt::AdvancedColorKind::StandardDynamicRange;
// HDR 模式下最大亮度1.0 表示 80nit
float maxLuminance = 1.0f;
// HDR 模式下 SDR 内容亮度1.0 表示 80nit
float sdrWhiteLevel = 1.0f;
bool operator==(const ColorInfo& other) const = default;
};
}

View file

@ -1,90 +0,0 @@
// 基于 https://github.com/obsproject/obs-studio/blob/master/libobs/data/bicubic_scale.effect
#include "Common.hlsli"
cbuffer RootConstants : register(b0) {
uint2 inputSize;
float2 inputPt;
float2 outputPt;
};
Texture2D<float4> inputTex : register(t0);
RWTexture2D<float4> outputTex : register(u0);
SamplerState linearSampler : register(s0);
float4 weight4(float x) {
// Sharper version. May look better in some cases. B=0, C=0.75
return float4(
((-0.75 * x + 1.5) * x - 0.75) * x,
(1.25 * x - 2.25) * x * x + 1.0,
((-1.25 * x + 1.5) * x + 0.75) * x,
(0.75 * x - 0.75) * x * x
);
}
float4 CatmullRom(float2 pos) {
pos *= inputSize;
float2 pos1 = floor(pos - 0.5f) + 0.5f;
float2 f = pos - pos1;
float4 rowtaps = weight4(f.x);
float4 coltaps = weight4(f.y);
float2 uv1 = pos1 * inputPt;
float2 uv0 = uv1 - inputPt;
float2 uv2 = uv1 + inputPt;
float2 uv3 = uv2 + inputPt;
float u_weight_sum = rowtaps.y + rowtaps.z;
float u_middle_offset = rowtaps.z * inputPt.x / u_weight_sum;
float u_middle = uv1.x + u_middle_offset;
float v_weight_sum = coltaps.y + coltaps.z;
float v_middle_offset = coltaps.z * inputPt.y / v_weight_sum;
float v_middle = uv1.y + v_middle_offset;
int2 coord_top_left = int2(max(uv0 * inputSize, 0.5f));
int2 coord_bottom_right = int2(min(uv3 * inputSize, inputSize - 0.5f));
float3 top = inputTex.Load(int3(coord_top_left, 0)).rgb * rowtaps.x;
top += inputTex.SampleLevel(linearSampler, float2(u_middle, uv0.y), 0).rgb * u_weight_sum;
top += inputTex.Load(int3(coord_bottom_right.x, coord_top_left.y, 0)).rgb * rowtaps.w;
float3 total = top * coltaps.x;
float3 middle = inputTex.SampleLevel(linearSampler, float2(uv0.x, v_middle), 0).rgb * rowtaps.x;
middle += inputTex.SampleLevel(linearSampler, float2(u_middle, v_middle), 0).rgb * u_weight_sum;
middle += inputTex.SampleLevel(linearSampler, float2(uv3.x, v_middle), 0).rgb * rowtaps.w;
total += middle * v_weight_sum;
float3 bottom = inputTex.Load(int3(coord_top_left.x, coord_bottom_right.y, 0)).rgb * rowtaps.x;
bottom += inputTex.SampleLevel(linearSampler, float2(u_middle, uv3.y), 0).rgb * u_weight_sum;
bottom += inputTex.Load(int3(coord_bottom_right, 0)).rgb * rowtaps.w;
total += bottom * coltaps.w;
#ifdef MP_SRGB
total = EncodeSrgb(saturate(total));
#endif
return float4(total, 1);
}
[numthreads(64, 1, 1)]
void main(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {
uint2 gxy = (gid.xy << 4) + Rmp8x8(tid.x);
float2 pos = (gxy + 0.5f) * outputPt;
const float2 step = 8 * outputPt;
outputTex[gxy] = CatmullRom(pos);
gxy.x += 8u;
pos.x += step.x;
outputTex[gxy] = CatmullRom(pos);
gxy.y += 8u;
pos.y += step.y;
outputTex[gxy] = CatmullRom(pos);
gxy.x -= 8u;
pos.x -= step.x;
outputTex[gxy] = CatmullRom(pos);
}

View file

@ -1,2 +0,0 @@
#define MP_SRGB
#include "CatmullRomCS.hlsl"

View file

@ -1,33 +0,0 @@
uint __Bfe(uint src, uint off, uint bits) {
uint mask = (1u << bits) - 1;
return (src >> off) & mask;
}
uint __BfiM(uint src, uint ins, uint bits) {
uint mask = (1u << bits) - 1;
return (ins & mask) | (src & (~mask));
}
// 来自 https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/8f7498a4b42d06d1daf64b3961ddcaedc07ccaf0/Kits/FidelityFX/api/internal/gpu/ffx_core_gpu_common.h#L2694-L2712
uint2 Rmp8x8(uint a) {
return uint2(__Bfe(a, 1u, 3u), __BfiM(__Bfe(a, 3u, 3u), a, 1u));
}
// 来自 https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/8f7498a4b42d06d1daf64b3961ddcaedc07ccaf0/Kits/FidelityFX/api/internal/gpu/ffx_core_gpu_common.h#L2598-L2611
float3 EncodeSrgb(float3 c) {
float3 j = { 0.0031308 * 12.92, 12.92, 1.0 / 2.4 };
float2 k = { 1.055, -0.055 };
return clamp(j.x, c * j.y, pow(c, j.z) * k.x + k.y);
}
float3 DecodeSrgb(float3 c) {
float3 j = { 0.04045, 1.0 / 12.92, 2.4 };
float2 k = { 1.0 / 1.055, 0.055 / 1.055 };
return lerp(c * j.y, pow(c * k.x + k.y, j.z), step(j.x, c));
}
// 接受线性 RGB 输入
float Luminance(float3 color) {
// 参数来自 https://en.wikipedia.org/wiki/Relative_luminance
return dot(color, float3(0.2126, 0.7152, 0.0722));
}

View file

@ -1,28 +0,0 @@
#include "Common.hlsli"
Texture2D<float4> inputTex : register(t0);
RWTexture2D<float4> outputTex : register(u0);
float4 LoadTexel(uint2 gxy) {
float4 color = inputTex[gxy];
#ifdef MP_SRGB
color.rgb = EncodeSrgb(saturate(color.rgb));
#endif
return color;
}
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {
uint2 gxy = (gid.xy << 4) + tid.xy;
outputTex[gxy] = LoadTexel(gxy);
gxy.x += 8u;
outputTex[gxy] = LoadTexel(gxy);
gxy.y += 8u;
outputTex[gxy] = LoadTexel(gxy);
gxy.x -= 8u;
outputTex[gxy] = LoadTexel(gxy);
}

View file

@ -1,2 +0,0 @@
#define MP_SRGB
#include "CopyCS.hlsl"

View file

@ -1,20 +0,0 @@
cbuffer RootConstants : register(b0) {
float2 scale;
float2 offset;
};
struct PSInput {
noperspective float2 uv : TEXCOORD;
noperspective float4 position : SV_POSITION;
};
PSInput main(uint vID : SV_VertexID) {
PSInput result;
// 全屏三角形 (0,0), (2,0), (0,2)
float2 rawUV = float2((vID << 1) & 2, vID & 2);
// 将输出区域 uv 变换为 0~1
result.uv = rawUV * scale + offset;
// 裁剪空间 (-1,1), (3,1), (-1,-3)
result.position = float4(rawUV * float2(2, -2) + float2(-1, 1), 0, 1);
return result;
}

View file

@ -1,17 +0,0 @@
cbuffer RootConstants : register(b0) {
float2 pos;
float2 size;
};
struct PSInput {
noperspective float2 uv : TEXCOORD;
noperspective float4 position : SV_POSITION;
};
PSInput main(uint vID : SV_VertexID) {
PSInput result;
// (0,0)->(1,0)->(0,1)->(1,1)
result.uv = float2(vID & 1, (vID >> 1) & 1);
result.position = float4(result.uv * size + pos, 0, 1);
return result;
}

View file

@ -1,10 +1,3 @@
cbuffer Constants : register(b0) {
uint4 dirtyRect;
float2 texPt;
uint target;
uint resultOffset;
};
// 无需同步
RWBuffer<uint> result : register(u0);
@ -15,65 +8,28 @@ SamplerState sam : register(s0);
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {
if (result[resultOffset] == target) {
if (result[0]) {
return;
}
const uint2 gxy = dirtyRect.xy + (gid.xy << 4) + (tid.xy << 1);
const int2 gxy = (gid.xy << 4) + (tid.xy << 1);
#ifndef MP_NO_BOUNDS_CHECKING
if (gxy.x >= dirtyRect.z || gxy.y >= dirtyRect.w) {
return;
}
#endif
// 不知为何这比通过 cbuffer 传入更快
uint width, height;
tex1.GetDimensions(width, height);
const float2 pos = (gxy + 1) / float2(width, height);
const float2 pos = (gxy + 1) * texPt;
#ifdef MP_NO_BOUNDS_CHECKING
if (any(tex1.GatherRed(sam, pos) != tex2.GatherRed(sam, pos))) {
result[resultOffset] = target;
result[0] = 1u;
return;
}
if (any(tex1.GatherGreen(sam, pos) != tex2.GatherGreen(sam, pos))) {
result[resultOffset] = target;
result[0] = 1u;
return;
}
if (any(tex1.GatherBlue(sam, pos) != tex2.GatherBlue(sam, pos))) {
result[resultOffset] = target;
result[0] = 1u;
}
#else
// w z
// x y
float4 mask = 1.0f;
if (gxy.x + 1 >= dirtyRect.z) {
mask.yz = 0.0f;
}
if (gxy.y + 1 >= dirtyRect.w) {
mask.xy = 0.0f;
}
float4 c1 = tex1.GatherRed(sam, pos);
float4 c2 = tex2.GatherRed(sam, pos);
// 乘以遮罩过滤掉无效区域
if (any((c1 != c2) * mask)) {
result[resultOffset] = target;
return;
}
c1 = tex1.GatherGreen(sam, pos);
c2 = tex2.GatherGreen(sam, pos);
if (any((c1 != c2) * mask)) {
result[resultOffset] = target;
return;
}
c1 = tex1.GatherBlue(sam, pos);
c2 = tex2.GatherBlue(sam, pos);
if (any((c1 != c2) * mask)) {
result[resultOffset] = target;
return;
}
#endif
}

View file

@ -1,2 +0,0 @@
#define MP_NO_BOUNDS_CHECKING
#include "DuplicateFrameCS.hlsl"

View file

@ -1,46 +1,16 @@
#include "Common.hlsli"
Texture2D originTex : register(t0);
Texture2D cursorTex : register(t1);
cbuffer RootConstants : register(b1) {
uint2 cursorTexSize;
uint2 cursorSize;
uint2 originOffset;
#ifdef MP_SRGB
uint shouldEncodeSrgb;
#else
float sdrWhiteLevel;
#endif
};
SamplerState pointSampler : register(s0);
Texture2D<float4> cursorTex : register(t0);
Texture2D<float4> originTex : register(t1);
float4 main(noperspective float2 uv : TEXCOORD) : SV_TARGET {
// A 为 0 时用 RGB 通道取代屏幕颜色,为 1 时将 RGB 通道和屏幕颜色进行异或操作
float4 mask = cursorTex[uint2(uv * cursorTexSize)];
float4 main(noperspective float2 coord : TEXCOORD) : SV_TARGET {
float4 mask = cursorTex.Sample(pointSampler, coord);
if (mask.a < 0.5f) {
return float4(mask.rgb, 1);
} else {
float3 origin = originTex.Sample(pointSampler, coord).rgb;
// 255.001953 的由来见 https://stackoverflow.com/questions/52103720/why-does-d3dcolortoubyte4-multiplies-components-by-255-001953f
return float4((uint3(origin * 255.001953f) ^ uint3(mask.rgb * 255.001953f)) / 255.0f, 1);
}
float3 origin = originTex[uint2(uv * cursorSize) + originOffset].rgb;
#ifdef MP_SRGB
[branch]
if (shouldEncodeSrgb) {
origin = EncodeSrgb(saturate(origin));
}
#else
float white = max(max(origin.r, origin.g), max(origin.b, sdrWhiteLevel));
origin = saturate(origin / white);
#endif
// 255.001953 来自
// https://stackoverflow.com/questions/52103720/why-does-d3dcolortoubyte4-multiplies-components-by-255-001953f
origin = (uint3(origin * 255.001953f) ^ uint3(mask.rgb * 255.001953f)) / 255.0f;
#ifndef MP_SRGB
origin *= white;
#endif
return float4(origin, 1);
}

View file

@ -1,2 +0,0 @@
#define MP_SRGB
#include "MaskedCursorPS.hlsl"

View file

@ -1,54 +1,24 @@
#include "Common.hlsli"
Texture2D originTex : register(t0);
Texture2D<float2> cursorTex : register(t1);
cbuffer RootConstants : register(b1) {
uint2 cursorTexSize;
uint2 cursorSize;
uint2 originOffset;
#ifdef MP_SRGB
uint shouldEncodeSrgb;
#else
float sdrWhiteLevel;
#endif
};
SamplerState pointSampler : register(s0);
Texture2D<uint> cursorTex : register(t0);
Texture2D<float4> originTex : register(t1);
float4 main(noperspective float2 coord : TEXCOORD) : SV_TARGET {
float2 mask = cursorTex.Sample(pointSampler, coord);
if (mask.x > 0.5f) {
float3 origin = originTex.Sample(pointSampler, coord).rgb;
float4 main(noperspective float2 uv : TEXCOORD) : SV_TARGET {
// 高四位是 AND 掩码, 低四位是 XOR 掩码
uint mask = cursorTex[uint2(uv * cursorTexSize)];
bool andMask = (mask & 0xF0u) != 0u;
bool xorMask = (mask & 0x0Fu) != 0u;
if (!andMask) {
// 黑色或白色
#ifdef MP_SRGB
float c = xorMask ? 1.0f : 0.0f;
#else
float c = xorMask ? sdrWhiteLevel : 0.0f;
#endif
return float4(c, c, c, 1.0f);
if (mask.y > 0.5f) {
return float4(1 - origin, 1);
} else {
return float4(origin, 1);
}
} else {
if (mask.y > 0.5f) {
return float4(1, 1, 1, 1);
} else {
return float4(0, 0, 0, 1);
}
}
float3 origin = originTex[uint2(uv * cursorSize) + originOffset].rgb;
#ifdef MP_SRGB
[branch]
if (shouldEncodeSrgb) {
origin = EncodeSrgb(saturate(origin));
}
if (xorMask) {
// 反色
origin = 1.0f - origin;
}
#else
if (xorMask) {
// 反色
float white = max(max(origin.r, origin.g), max(origin.b, sdrWhiteLevel));
origin = white - origin;
}
#endif
return float4(origin, 1.0f);
}

View file

@ -1,2 +0,0 @@
#define MP_SRGB
#include "MonochromeCursorPS.hlsl"

View file

@ -1,34 +0,0 @@
cbuffer RootConstants : register(b0) {
float sdrWhiteLevel;
};
Texture2D<float4> inputTex : register(t0);
RWTexture2D<unorm float4> outputTex : register(u0);
float4 LoadTexel(uint2 gxy) {
// 文档没记录但测试表明TrueHDR 只接受 BT.2020 色域输入,若是输入 sRGB 会严重偏色。
// 不支持 HDR 输入,超出 sdrWhiteLevel 将被截断。
static const float3x3 mat709to2020 = float3x3(
0.6274040f, 0.3292854f, 0.0433106f,
0.0690973f, 0.9195410f, 0.0113617f,
0.0163914f, 0.0880133f, 0.8955953f
);
// 超出 BT.2020 的颜色将被截断
return float4(mul(mat709to2020, inputTex[gxy].rgb / sdrWhiteLevel), 1);
}
[numthreads(8, 8, 1)]
void main(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {
uint2 gxy = (gid.xy << 4) + tid.xy;
outputTex[gxy] = LoadTexel(gxy);
gxy.x += 8u;
outputTex[gxy] = LoadTexel(gxy);
gxy.y += 8u;
outputTex[gxy] = LoadTexel(gxy);
gxy.x -= 8u;
outputTex[gxy] = LoadTexel(gxy);
}

View file

@ -1,11 +1,9 @@
struct PSInput {
noperspective float2 uv : TEXCOORD;
noperspective float4 position : SV_POSITION;
};
PSInput main(float2 position : POSITION, float2 uv : TEXCOORD) {
PSInput result;
result.position = float4(position, 0, 1);
result.uv = uv;
return result;
void main(
float2 pos : POSITION,
float2 coord : TEXCOORD,
out noperspective float2 outCoord : TEXCOORD,
out noperspective float4 outPos : SV_POSITION
) {
outPos = float4(pos, 0, 1);
outCoord = coord;
}

View file

@ -4,6 +4,7 @@
#include "DirectXHelper.h"
#include "Logger.h"
#include "Win32Helper.h"
#include <d3d11_4.h>
using namespace winrt::Magpie::implementation;
using namespace winrt;
@ -30,7 +31,6 @@ bool AdaptersService::Initialize() noexcept {
continue;
}
// TODO: 检查是否适用于 D3D12
// 初始化时不检查是否支持 FL11有些设备上 D3D11CreateDevice 相当慢
_adapterInfos.push_back({
.idx = adapterIdx,
@ -112,12 +112,12 @@ bool AdaptersService::_GatherAdapterInfos(
adapters.push_back(std::move(curAdapter));
}
// 删除不支持 D3D12 的显卡
// 删除不支持功能级别 11 的显卡
wil::srwlock writeLock;
Win32Helper::RunParallel([&](uint32_t i) {
if (FAILED(D3D12CreateDevice(
adapters[i].get(), D3D_FEATURE_LEVEL_11_0, winrt::guid_of<ID3D12Device>(), nullptr)))
{
D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0;
if (FAILED(D3D11CreateDevice(adapters[i].get(), D3D_DRIVER_TYPE_UNKNOWN,
NULL, 0, &fl, 1, D3D11_SDK_VERSION, nullptr, nullptr, nullptr))) {
auto lock = writeLock.lock_exclusive();
adapterInfos[i].idx = std::numeric_limits<uint32_t>::max();
}

View file

@ -120,10 +120,10 @@ bool App::Initialize(const wchar_t* arguments) {
return false;
}
EffectsService::Get().Initialize();
_mainWindow = std::make_unique<class MainWindow>();
EffectsService::Get().Initialize();
// 初始化 XAML 框架。退出时也不要关闭,如果正在播放动画会崩溃。文档中的清空消息队列的做法无用。
_windowsXamlManager = Hosting::WindowsXamlManager::InitializeForCurrentThread();

View file

@ -140,8 +140,8 @@ static void WriteProfile(rapidjson::PrettyWriter<rapidjson::StringBuffer>& write
writer.Double(profile.cropping.Bottom);
writer.EndObject();
writer.Key("outputAlignment");
writer.Uint((uint32_t)profile.outputAlignment);
writer.Key("destAlignment");
writer.Uint((uint32_t)profile.destAlignment);
writer.EndObject();
}
@ -374,6 +374,7 @@ void AppSettings::IsDeveloperMode(bool value) noexcept {
_isSaveEffectSources = false;
_isWarningsAreErrors = false;
_duplicateFrameDetectionMode = DuplicateFrameDetectionMode::Dynamic;
_isStatisticsForDynamicDetectionEnabled = false;
_isFP16Disabled = false;
}
@ -620,6 +621,8 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
writer.Int64(data._updateCheckDate.time_since_epoch().count());
writer.Key("duplicateFrameDetectionMode");
writer.Uint((uint32_t)data._duplicateFrameDetectionMode);
writer.Key("enableStatisticsForDynamicDetection");
writer.Bool(data._isStatisticsForDynamicDetectionEnabled);
writer.Key("minFrameRate");
writer.Double(data._minFrameRate);
writer.Key("disableFP16");
@ -822,6 +825,7 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
}
_duplicateFrameDetectionMode = (::Magpie::DuplicateFrameDetectionMode)duplicateFrameDetectionMode;
}
JsonHelper::ReadBool(root, "enableStatisticsForDynamicDetection", _isStatisticsForDynamicDetectionEnabled);
JsonHelper::ReadFloat(root, "minFrameRate", _minFrameRate);
JsonHelper::ReadBool(root, "disableFP16", _isFP16Disabled);
@ -1131,12 +1135,12 @@ bool AppSettings::_LoadProfile(
}
{
uint32_t outputAlignment = (uint32_t)OutputAlignment::Center;
JsonHelper::ReadUInt(profileObj, "outputAlignment", outputAlignment);
if (outputAlignment >= (uint32_t)OutputAlignment::COUNT) {
outputAlignment = (uint32_t)OutputAlignment::Center;
uint32_t destAlignment = (uint32_t)DestAlignment::Center;
JsonHelper::ReadUInt(profileObj, "destAlignment", destAlignment);
if (destAlignment >= (uint32_t)DestAlignment::COUNT) {
destAlignment = (uint32_t)DestAlignment::Center;
}
profile.outputAlignment = (OutputAlignment)outputAlignment;
profile.destAlignment = (DestAlignment)destAlignment;
}
return true;

Some files were not shown because too many files have changed in this diff Show more