Compare commits
No commits in common. "dev" and "v0.12.0-preview1" have entirely different histories.
dev
...
v0.12.0-pr
|
|
@ -12,7 +12,11 @@
|
|||
"avatar_url": "https://avatars.githubusercontent.com/u/34770031?v=4",
|
||||
"profile": "https://github.com/Blinue",
|
||||
"contributions": [
|
||||
"maintenance"
|
||||
"maintenance",
|
||||
"code",
|
||||
"review",
|
||||
"doc",
|
||||
"question"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -297,51 +301,6 @@
|
|||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kangurek-kao",
|
||||
"name": "Krzysztof",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/116571935?v=4",
|
||||
"profile": "https://github.com/kangurek-kao",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Howard20181",
|
||||
"name": "Howard Wu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40033067?v=4",
|
||||
"profile": "https://github.com/Howard20181",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "arifpedia",
|
||||
"name": "Arif Budiman",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4081293?v=4",
|
||||
"profile": "https://github.com/arifpedia",
|
||||
"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,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,8 @@ cpp_generate_documentation_comments = xml
|
|||
|
||||
# Visual C++ 格式设置
|
||||
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width= 4
|
||||
cpp_indent_braces = false
|
||||
cpp_indent_multi_line_relative_to = innermost_parenthesis
|
||||
cpp_indent_multi_line_relative_to = statement_begin
|
||||
cpp_indent_within_parentheses = indent
|
||||
cpp_indent_preserve_within_parentheses = false
|
||||
cpp_indent_case_contents = true
|
||||
|
|
@ -24,7 +21,7 @@ cpp_indent_goto_labels = one_left
|
|||
cpp_indent_preprocessor = leftmost_column
|
||||
cpp_indent_access_specifiers = false
|
||||
cpp_indent_namespace_contents = false
|
||||
cpp_indent_preserve_comments = false
|
||||
cpp_indent_preserve_comments = true
|
||||
cpp_new_line_before_open_brace_namespace = same_line
|
||||
cpp_new_line_before_open_brace_type = same_line
|
||||
cpp_new_line_before_open_brace_function = same_line
|
||||
|
|
@ -69,17 +66,4 @@ cpp_space_around_binary_operator = insert
|
|||
cpp_space_around_assignment_operator = insert
|
||||
cpp_space_pointer_reference_alignment = left
|
||||
cpp_space_around_ternary_operator = insert
|
||||
cpp_use_unreal_engine_macro_formatting = true
|
||||
cpp_wrap_preserve_blocks = one_liners
|
||||
|
||||
# Visual C++ 包含清理设置
|
||||
|
||||
cpp_include_cleanup_add_missing_error_tag_type = none
|
||||
cpp_include_cleanup_remove_unused_error_tag_type = dimmed
|
||||
cpp_include_cleanup_optimize_unused_error_tag_type = suggestion
|
||||
cpp_include_cleanup_sort_after_edits = false
|
||||
cpp_sort_includes_error_tag_type = suggestion
|
||||
cpp_sort_includes_priority_case_sensitive = false
|
||||
cpp_sort_includes_priority_style = quoted
|
||||
cpp_includes_style = default
|
||||
cpp_includes_use_forward_slash = true
|
||||
|
|
|
|||
30
.github/workflows/build.yml
vendored
|
|
@ -2,48 +2,42 @@ name: build
|
|||
|
||||
on:
|
||||
push:
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.slnx', '*.props', 'scripts/publish.py', 'src/**' ]
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.sln', '*.props', 'publish.py', 'src/**' ]
|
||||
pull_request:
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.slnx', '*.props', 'scripts/publish.py', 'src/**' ]
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.sln', '*.props', 'publish.py', 'src/**' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2025-vs2026
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ["MSVC", "ClangCL"]
|
||||
platform: ["x64", "ARM64"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Conan
|
||||
run: pip install conan
|
||||
|
||||
- name: Load Conan cache
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.conan2/p
|
||||
key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.compiler }}-${{ matrix.platform }}
|
||||
|
||||
key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.platform }}
|
||||
|
||||
- name: Build
|
||||
if: github.ref != 'refs/heads/dev' && github.ref != 'refs/heads/main'
|
||||
run: python scripts/publish.py --compiler=${{ matrix.compiler }} --platform=${{ matrix.platform }}
|
||||
|
||||
- name: Build and sign
|
||||
if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main'
|
||||
run: python scripts/publish.py --compiler=${{ matrix.compiler }} --platform=${{ matrix.platform }} --pfx-path=certs\Magpie.pfx --pfx-password="${{ secrets.MAGPIE_PFX_PASSWORD }}"
|
||||
run: python publish.py ${{ matrix.platform }} unpackaged certs\Magpie.pfx "${{ secrets.MAGPIE_PFX_PASSWORD }}"
|
||||
|
||||
- name: Save hash
|
||||
id: hash
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Store build
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Magpie-dev-${{ steps.hash.outputs.sha_short }}-${{ matrix.compiler }}-${{ matrix.platform }}
|
||||
name: Magpie-dev-${{ steps.hash.outputs.sha_short }}-${{ matrix.platform }}
|
||||
path: ./publish/${{ matrix.platform }}
|
||||
|
|
|
|||
42
.github/workflows/release.yml
vendored
|
|
@ -25,21 +25,27 @@ on:
|
|||
type: boolean
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2025-vs2026
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform: ["x64", "ARM64"]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Conan
|
||||
run: pip install conan
|
||||
|
||||
- name: Load Conan cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.conan2/p
|
||||
key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.platform }}
|
||||
|
||||
- name: Generate tag
|
||||
id: tag
|
||||
|
|
@ -48,12 +54,15 @@ jobs:
|
|||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
$versionString = "${{ steps.tag.outputs.tag }}" -replace "^v(?=\d)", ""
|
||||
python scripts/publish.py --compiler=ClangCL --platform=${{ matrix.platform }} --version-major=${{ inputs.major }} --version-minor=${{ inputs.minor }} --version-patch=${{ inputs.patch }} --version-string=$versionString --pfx-path=certs\Magpie.pfx --pfx-password="${{ secrets.MAGPIE_PFX_PASSWORD }}"
|
||||
run: python publish.py ${{ matrix.platform }} unpackaged certs\Magpie.pfx "${{ secrets.MAGPIE_PFX_PASSWORD }}"
|
||||
env:
|
||||
MAJOR: ${{ inputs.major }}
|
||||
MINOR: ${{ inputs.minor }}
|
||||
PATCH: ${{ inputs.patch }}
|
||||
TAG: ${{ steps.tag.outputs.tag }}
|
||||
|
||||
- name: Store artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Magpie-${{ steps.tag.outputs.tag }}-${{ matrix.platform }}
|
||||
path: publish/${{ matrix.platform }}
|
||||
|
|
@ -61,19 +70,26 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Requests
|
||||
run: pip install requests
|
||||
|
||||
- name: Restore artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: publish
|
||||
|
||||
- name: Publish release
|
||||
run: python scripts/release.py ${{ inputs.major }} ${{ inputs.minor }} ${{ inputs.patch }} ${{ needs.build.outputs.tag }} ${{ inputs.prerelease }} ${{ secrets.CONTENTS_ACCESS_TOKEN }}
|
||||
run: python ci/release.py
|
||||
env:
|
||||
MAJOR: ${{ inputs.major }}
|
||||
MINOR: ${{ inputs.minor }}
|
||||
PATCH: ${{ inputs.patch }}
|
||||
TAG: ${{ needs.build.outputs.tag }}
|
||||
PRERELEASE: ${{ inputs.prerelease }}
|
||||
ACCESS_TOKEN: ${{ secrets.CONTENTS_ACCESS_TOKEN }}
|
||||
|
|
|
|||
12
.github/workflows/wiki.yml
vendored
|
|
@ -3,18 +3,20 @@ name: Publish wiki
|
|||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths: [ '.github/workflows/wiki.yml', 'docs/**', 'scripts/wiki.py' ]
|
||||
paths: [ '.github/workflows/wiki.yml', 'docs/**', 'ci/wiki.py' ]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Upload documentations to wiki
|
||||
run: python scripts/wiki.py ${{ secrets.CONTENTS_ACCESS_TOKEN }}
|
||||
run: python ci/wiki.py
|
||||
env:
|
||||
ACCESS_TOKEN: ${{ secrets.CONTENTS_ACCESS_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PreferredToolArchitecture Condition="'$(PreferredToolArchitecture)' == ''">x64</PreferredToolArchitecture>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
|
|
@ -13,10 +13,20 @@
|
|||
<SDLCheck>true</SDLCheck>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<!-- /await:strict: 禁用协程的非标准语言扩展 -->
|
||||
<!-- /utf-8: 源代码使用 UTF-8 格式 -->
|
||||
<!-- /Zc:__cplusplus: 更新 __cplusplus 宏 -->
|
||||
<!-- /volatile:iso: 禁用 volatile 的语义扩展 -->
|
||||
<AdditionalOptions>/utf-8 /Zc:__cplusplus /volatile:iso %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- /fp:contract: 生成浮点收缩指令。浮点收缩指令是将两个浮点操作合并为一个指令的机器指令,例如 Fused-Multiply-Add (FMA) -->
|
||||
<!-- fp:contract 可以和其他 fp 选项同时存在,因此每个项目可以分别指定自己的浮点模型 -->
|
||||
<AdditionalOptions>%(AdditionalOptions) /await:strict /utf-8 /Zc:__cplusplus /volatile:iso /fp:contract</AdditionalOptions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<ClCompile>
|
||||
<!-- /Gw: 链接时删除未使用和重复的数据,可以减小二进制文件体积 -->
|
||||
<!-- /Zc:checkGwOdr: 防止 /Gw 导致某些 ODR 违规被忽略 -->
|
||||
<AdditionalOptions>%(AdditionalOptions) /Gw /Zc:checkGwOdr</AdditionalOptions>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
</Project>
|
||||
|
|
|
|||
119
Magpie.sln
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32228.430
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Magpie", "src\Magpie\Magpie.vcxproj", "{1239537C-E5B8-427A-9E7F-EA443D1F3529}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052} = {05B51BB8-08CB-4907-884F-8E2AD6BF6052}
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D} = {456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7} = {62503530-B84B-4CC2-80B6-3F89618172B7}
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4} = {E82B7A20-0557-4DC1-B418-87977D7450A4}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{00AB63C3-0CD3-4944-B8E6-58C86138618D}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
src\BuildOptions.props = src\BuildOptions.props
|
||||
src\Common.Post.props = src\Common.Post.props
|
||||
src\Common.Pre.props = src\Common.Pre.props
|
||||
Directory.Build.props = Directory.Build.props
|
||||
src\HybridCRT.props = src\HybridCRT.props
|
||||
src\WinUI.targets = src\WinUI.targets
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_ConanDeps", "src\_ConanDeps\_ConanDeps.vcxproj", "{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Effects", "src\Effects\Effects.vcxproj", "{62503530-B84B-4CC2-80B6-3F89618172B7}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Magpie.Core", "src\Magpie.Core\Magpie.Core.vcxproj", "{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D} = {456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Natvis", "Natvis", "{9808D34F-5715-4D02-B216-4CB80F46BBC0}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
natvis\magpie.natvis = natvis\magpie.natvis
|
||||
natvis\phmap.natvis = natvis\phmap.natvis
|
||||
natvis\rapidjson.natvis = natvis\rapidjson.natvis
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Updater", "src\Updater\Updater.vcxproj", "{E82B7A20-0557-4DC1-B418-87977D7450A4}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D} = {456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TouchHelper", "src\TouchHelper\TouchHelper.vcxproj", "{05B51BB8-08CB-4907-884F-8E2AD6BF6052}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D} = {456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "src\Shared\Shared.vcxitems", "{AABDA3A3-7B23-4189-895B-F68A4C6B14C2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Debug|x64.Build.0 = Debug|x64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Release|x64.ActiveCfg = Release|x64
|
||||
{1239537C-E5B8-427A-9E7F-EA443D1F3529}.Release|x64.Build.0 = Release|x64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Debug|x64.Build.0 = Debug|x64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Release|x64.ActiveCfg = Release|x64
|
||||
{456CCAE4-2C51-4CF2-8D3A-1EFCE8C41A2D}.Release|x64.Build.0 = Release|x64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Debug|x64.Build.0 = Debug|x64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Release|x64.ActiveCfg = Release|x64
|
||||
{62503530-B84B-4CC2-80B6-3F89618172B7}.Release|x64.Build.0 = Release|x64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Debug|x64.Build.0 = Debug|x64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Release|x64.ActiveCfg = Release|x64
|
||||
{0E5205AE-DFA9-4CB8-B662-E43CD6512E2A}.Release|x64.Build.0 = Release|x64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Debug|x64.Build.0 = Debug|x64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|x64.ActiveCfg = Release|x64
|
||||
{E82B7A20-0557-4DC1-B418-87977D7450A4}.Release|x64.Build.0 = Release|x64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Debug|x64.Build.0 = Debug|x64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|x64.ActiveCfg = Release|x64
|
||||
{05B51BB8-08CB-4907-884F-8E2AD6BF6052}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{9808D34F-5715-4D02-B216-4CB80F46BBC0} = {00AB63C3-0CD3-4944-B8E6-58C86138618D}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {0114F74A-3B0C-43A4-AA0E-AB36FD4935F8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
37
Magpie.slnx
|
|
@ -1,37 +0,0 @@
|
|||
<Solution>
|
||||
<Configurations>
|
||||
<Platform Name="ARM64" />
|
||||
<Platform Name="x64" />
|
||||
</Configurations>
|
||||
<Folder Name="/Solution Items/">
|
||||
<File Path="Directory.Build.props" />
|
||||
<File Path="src/BuildOptions.props" />
|
||||
<File Path="src/Common.Post.props" />
|
||||
<File Path="src/Common.Pre.props" />
|
||||
<File Path="src/HybridCRT.props" />
|
||||
<File Path="src/WinUI.targets" />
|
||||
</Folder>
|
||||
<Folder Name="/Solution Items/Natvis/">
|
||||
<File Path="natvis/magpie.natvis" />
|
||||
<File Path="natvis/phmap.natvis" />
|
||||
<File Path="natvis/rapidjson.natvis" />
|
||||
</Folder>
|
||||
<Project Path="src/Effects/Effects.vcxproj" Id="62503530-b84b-4cc2-80b6-3f89618172b7" />
|
||||
<Project Path="src/Magpie.Core/Magpie.Core.vcxproj" Id="0e5205ae-dfa9-4cb8-b662-e43cd6512e2a">
|
||||
<BuildDependency Project="src/_ConanDeps/_ConanDeps.vcxproj" />
|
||||
</Project>
|
||||
<Project Path="src/Magpie/Magpie.vcxproj" Id="1239537c-e5b8-427a-9e7f-ea443d1f3529" DefaultStartup="true">
|
||||
<BuildDependency Project="src/Effects/Effects.vcxproj" />
|
||||
<BuildDependency Project="src/TouchHelper/TouchHelper.vcxproj" />
|
||||
<BuildDependency Project="src/Updater/Updater.vcxproj" />
|
||||
<BuildDependency Project="src/_ConanDeps/_ConanDeps.vcxproj" />
|
||||
</Project>
|
||||
<Project Path="src/Shared/Shared.vcxitems" Id="aabda3a3-7b23-4189-895b-f68a4c6b14c2" />
|
||||
<Project Path="src/TouchHelper/TouchHelper.vcxproj" Id="05b51bb8-08cb-4907-884f-8e2ad6bf6052">
|
||||
<BuildDependency Project="src/_ConanDeps/_ConanDeps.vcxproj" />
|
||||
</Project>
|
||||
<Project Path="src/Updater/Updater.vcxproj" Id="e82b7a20-0557-4dc1-b418-87977d7450a4">
|
||||
<BuildDependency Project="src/_ConanDeps/_ConanDeps.vcxproj" />
|
||||
</Project>
|
||||
<Project Path="src/_ConanDeps/_ConanDeps.vcxproj" Id="456ccae4-2c51-4cf2-8d3a-1efce8c41a2d" />
|
||||
</Solution>
|
||||
69
README.md
|
|
@ -9,13 +9,17 @@
|
|||
[](./LICENSE)
|
||||
[](https://github.com/Blinue/Magpie/actions/workflows/build.yml)
|
||||
[](#acknowledgement-)
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
[](https://github.com/Blinue/Magpie/releases)
|
||||
|
||||
</div>
|
||||
|
||||
🌍 **English** | [简体中文](./README_ZH.md)
|
||||
|
||||
Magpie is a lightweight window upscaling tool that comes equipped with a variety of efficient scaling algorithms and filters.
|
||||
Magpie is a lightweight window scaling tool that comes equipped with various efficient scaling algorithms and filters. Its primary purpose is to enhance game graphics and enable non-fullscreen games to display in fullscreen mode.
|
||||
|
||||
We are using [Weblate](https://weblate.org/) for localization work and would appreciate your help in translating Magpie into more languages.
|
||||
|
||||
[](https://hosted.weblate.org/engage/magpie/)
|
||||
|
||||
👉 [Download](https://github.com/Blinue/Magpie/releases)
|
||||
|
||||
|
|
@ -25,19 +29,42 @@ Magpie is a lightweight window upscaling tool that comes equipped with a variety
|
|||
|
||||
👉 [Compilation guide](https://github.com/Blinue/Magpie/wiki/Compilation%20guide)
|
||||
|
||||
👉 [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## Features
|
||||
|
||||
* Supports both fullscreen and windowed scaling
|
||||
* Includes a variety of built-in algorithms and filters, including [Anime4K](https://github.com/bloc97/Anime4K), [FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR), CRT shaders, and more
|
||||
* Scale any window to fullscreen
|
||||
* Numerous built-in algorithms, including Lanczos, [Anime4K](https://github.com/bloc97/Anime4K), [FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR), Adaptive Sharpen, various CRT shaders, and more
|
||||
* WinUI-based user interface with support for light and dark themes
|
||||
* Create configuration profiles for specific windows
|
||||
* Multi-monitor support
|
||||
|
||||
## How to use
|
||||
|
||||
1. Configuring scaling modes
|
||||
|
||||
Magpie provides some simple scaling modes by default, but it is recommended to configure them according to your specific use case. Then, change the global scaling mode on the "Profiles"-"Defaults" page.
|
||||
|
||||
2. Scaling a window
|
||||
|
||||
To scale a window, bring the desired window to the foreground and press the shortcut key (default is Win+Shift+A) to display it in fullscreen mode. Note that the window to be scaled must be in windowed mode, not maximized or fullscreen mode. You can also use the "Scale after xs" button on the "Home" page, and Magpie will automatically scale the foreground window after a few seconds.
|
||||
|
||||
3. Creating profiles for windows
|
||||
|
||||
This allows you to save configurations specific to a particular window. Magpie also supports automatically activate scaling when that window is brought to the foreground.
|
||||
|
||||
4. Customizing effects
|
||||
|
||||
Magpie uses Direct3D compute shader to implement effects, but the syntax has been extended to define resources and organize multiple passes. For more information, please refer to [MagpieFX](https://github.com/Blinue/Magpie/wiki/MagpieFX%20(EN)). Those with experience in shader writing can easily create custom effects.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<div style="display:flex; gap:10px;">
|
||||
<img src="img/main-window.png" alt= "Main window" height="300">
|
||||
<img src="img/screenshot.png" alt= "Main window" height="300">
|
||||
</div>
|
||||
<img src="img/Main window.png" alt= "Main window" height="300">
|
||||
|
||||
## System requirements
|
||||
|
||||
1. Windows 10 v1903+ or Windows 11
|
||||
2. DirectX feature level 11
|
||||
|
||||
## Hints
|
||||
|
||||
|
|
@ -45,18 +72,7 @@ Magpie is a lightweight window upscaling tool that comes equipped with a variety
|
|||
|
||||
2. Some games support zooming the window, but with extremely naive algorithms. Please set the resolution to the built-in (best) option.
|
||||
|
||||
## System requirements
|
||||
|
||||
1. Windows 10 v1903+ or Windows 11
|
||||
2. DirectX feature level 11
|
||||
|
||||
## Localization
|
||||
|
||||
Thanks to [Weblate](https://weblate.org) for hosting! Click the image below to visit the translation page.
|
||||
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
|
||||
## Acknowledgement
|
||||
## Acknowledgement ✨
|
||||
|
||||
Thanks go to these wonderful people:
|
||||
|
||||
|
|
@ -66,7 +82,7 @@ Thanks go to these wonderful people:
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Blinue"><img src="https://avatars.githubusercontent.com/u/34770031?v=4?s=100" width="100px;" alt="Xu"/><br /><sub><b>Xu</b></sub></a><br /><a href="#maintenance-Blinue" title="Maintenance">🚧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Blinue"><img src="https://avatars.githubusercontent.com/u/34770031?v=4?s=100" width="100px;" alt="Xu"/><br /><sub><b>Xu</b></sub></a><br /><a href="#maintenance-Blinue" title="Maintenance">🚧</a> <a href="https://github.com/Blinue/Magpie/commits?author=Blinue" title="Code">💻</a> <a href="https://github.com/Blinue/Magpie/pulls?q=is%3Apr+reviewed-by%3ABlinue" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/Blinue/Magpie/commits?author=Blinue" title="Documentation">📖</a> <a href="#question-Blinue" title="Answering Questions">💬</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hooke007"><img src="https://avatars.githubusercontent.com/u/41094733?v=4?s=100" width="100px;" alt="hooke007"/><br /><sub><b>hooke007</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=hooke007" title="Documentation">📖</a> <a href="#question-hooke007" title="Answering Questions">💬</a> <a href="#userTesting-hooke007" title="User Testing">📓</a> <a href="https://github.com/Blinue/Magpie/commits?author=hooke007" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://palxex.ys168.com"><img src="https://avatars.githubusercontent.com/u/58222?v=4?s=100" width="100px;" alt="Pal Lockheart"/><br /><sub><b>Pal Lockheart</b></sub></a><br /><a href="#userTesting-palxex" title="User Testing">📓</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.stevedonaghy.com/"><img src="https://avatars.githubusercontent.com/u/1029699?v=4?s=100" width="100px;" alt="Steve Donaghy"/><br /><sub><b>Steve Donaghy</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=neoKushan" title="Code">💻</a> <a href="#translation-neoKushan" title="Translation">🌍</a></td>
|
||||
|
|
@ -106,13 +122,6 @@ Thanks go to these wonderful people:
|
|||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/eriforce"><img src="https://avatars.githubusercontent.com/u/8393109?v=4?s=100" width="100px;" alt="Erich Yu"/><br /><sub><b>Erich Yu</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=eriforce" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TamilNeram"><img src="https://avatars.githubusercontent.com/u/67970539?v=4?s=100" width="100px;" alt="தமிழ் நேரம்"/><br /><sub><b>தமிழ் நேரம்</b></sub></a><br /><a href="#translation-TamilNeram" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mhtvsSFrpHdE"><img src="https://avatars.githubusercontent.com/u/10773245?v=4?s=100" width="100px;" alt="mhtvsSFrpHdE"/><br /><sub><b>mhtvsSFrpHdE</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=mhtvsSFrpHdE" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kangurek-kao"><img src="https://avatars.githubusercontent.com/u/116571935?v=4?s=100" width="100px;" alt="Krzysztof"/><br /><sub><b>Krzysztof</b></sub></a><br /><a href="#translation-kangurek-kao" title="Translation">🌍</a></td>
|
||||
<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>
|
||||
|
|
@ -123,7 +132,3 @@ Thanks go to these wonderful people:
|
|||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://allcontributors.org/) specification. Contributions of any kind are welcome!
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under GPLv3.
|
||||
|
|
|
|||
71
README_ZH.md
|
|
@ -9,13 +9,17 @@
|
|||
[](./LICENSE)
|
||||
[](https://github.com/Blinue/Magpie/actions/workflows/build.yml)
|
||||
[](#%E8%B4%A1%E7%8C%AE%E8%80%85-)
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
[](https://github.com/Blinue/Magpie/releases)
|
||||
|
||||
</div>
|
||||
|
||||
🌍 [English](./README.md) | **简体中文**
|
||||
|
||||
Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算法和滤镜。
|
||||
Magpie 是一个轻量级的窗口缩放工具,内置了多种高效的缩放算法和滤镜。它主要用于提升游戏画质和让不支持全屏化的游戏也能全屏显示等。
|
||||
|
||||
我们使用 [Weblate](https://weblate.org) 进行本地化工作,请帮助我们把 Magpie 翻译成更多语言。
|
||||
|
||||
[](https://hosted.weblate.org/engage/magpie/)
|
||||
|
||||
👉 [下载](https://github.com/Blinue/Magpie/releases)
|
||||
|
||||
|
|
@ -25,39 +29,51 @@ Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算
|
|||
|
||||
👉 [编译指南](https://github.com/Blinue/Magpie/wiki/编译指南)
|
||||
|
||||
👉 [贡献指南](./CONTRIBUTING_ZH.md)
|
||||
|
||||
## 功能
|
||||
|
||||
* 支持全屏和窗口模式缩放
|
||||
* 众多内置算法和滤镜,如 [Anime4K](https://github.com/bloc97/Anime4K)、[FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR)、CRT 着色器等
|
||||
* 将任何窗口放大至全屏
|
||||
* 众多内置算法,包括 Lanczos、[Anime4K](https://github.com/bloc97/Anime4K)、[FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR)、Adaptive Sharpen、多种 CRT 着色器等
|
||||
* 基于 WinUI 的用户界面,支持浅色和深色主题
|
||||
* 支持多屏幕
|
||||
* 为特定窗口创建配置文件
|
||||
* 多屏幕支持
|
||||
|
||||
## 如何使用
|
||||
|
||||
1. 配置缩放模式
|
||||
|
||||
Magpie 预设了一些简单的缩放模式,但建议根据使用场景自行配置。然后在“配置文件”-“默认”页面更改全局缩放模式。
|
||||
|
||||
2. 缩放窗口
|
||||
|
||||
把要缩放的窗口置于前台,按下快捷键(默认为 Win+Shift+A)即可全屏显示。请注意,要缩放的窗口必须处于窗口化状态,而不是最大化或全屏化。也可以使用“主页”上的“x 秒后缩放”按钮,Magpie 将在数秒后自动缩放前台窗口。
|
||||
|
||||
3. 为窗口创建配置文件
|
||||
|
||||
这使你可以保存针对某个窗口的配置,也支持在该窗口位于前台时自动执行缩放。
|
||||
|
||||
4. 自定义效果
|
||||
|
||||
Magpie 使用 Direct3D 计算着色器实现效果,但扩展了语法来定义资源、组织多个通道等,详见 [MagpieFX](https://github.com/Blinue/Magpie/wiki/MagpieFX) 。有着色器编写经验者可以轻松创建自定义效果。
|
||||
|
||||
## 截图
|
||||
|
||||
<div style="display:flex; gap:10px;">
|
||||
<img src="img/main-window-zh.png" alt= "Main window" height="300">
|
||||
<img src="img/screenshot.png" alt= "Main window" height="300">
|
||||
</div>
|
||||
|
||||
## 使用提示
|
||||
|
||||
1. 如果你设置了 DPI 缩放,而要放大的窗口没有高 DPI 支持(这在老游戏中很常见),推荐首先进入该程序的兼容性设置,将“高 DPI 缩放替代”设置为“应用程序”。
|
||||
2. 一些游戏支持调整窗口的大小,但只使用简单的缩放算法,这时请先将其设为原始(最佳)分辨率。
|
||||
<img src="img/主窗口.png" alt= "主窗口" height="300">
|
||||
|
||||
## 系统需求
|
||||
|
||||
1. Windows 10 v1903+ 或 Windows 11
|
||||
2. DirectX 功能级别 11
|
||||
|
||||
## 本地化
|
||||
## 使用提示
|
||||
|
||||
感谢 [Weblate](https://weblate.org) 提供托管服务!点击下面的图片可以进入翻译页面。
|
||||
1. 如果你设置了 DPI 缩放,而要放大的窗口没有高 DPI 支持(这在老游戏中很常见),推荐首先进入该程序的兼容性设置,将“高 DPI 缩放替代”设置为“应用程序”。
|
||||
2. 一些游戏支持调整窗口的大小,但只使用简单的缩放算法,这时请先将其设为原始(最佳)分辨率。
|
||||
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
## 贡献者 ✨
|
||||
|
||||
## 贡献者
|
||||
|
||||
衷心感谢所有为本项目做出贡献的人:
|
||||
感谢每一位参与贡献的人:
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
|
|
@ -65,7 +81,7 @@ Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Blinue"><img src="https://avatars.githubusercontent.com/u/34770031?v=4?s=100" width="100px;" alt="Xu"/><br /><sub><b>Xu</b></sub></a><br /><a href="#maintenance-Blinue" title="Maintenance">🚧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Blinue"><img src="https://avatars.githubusercontent.com/u/34770031?v=4?s=100" width="100px;" alt="Xu"/><br /><sub><b>Xu</b></sub></a><br /><a href="#maintenance-Blinue" title="Maintenance">🚧</a> <a href="https://github.com/Blinue/Magpie/commits?author=Blinue" title="Code">💻</a> <a href="https://github.com/Blinue/Magpie/pulls?q=is%3Apr+reviewed-by%3ABlinue" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/Blinue/Magpie/commits?author=Blinue" title="Documentation">📖</a> <a href="#question-Blinue" title="Answering Questions">💬</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hooke007"><img src="https://avatars.githubusercontent.com/u/41094733?v=4?s=100" width="100px;" alt="hooke007"/><br /><sub><b>hooke007</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=hooke007" title="Documentation">📖</a> <a href="#question-hooke007" title="Answering Questions">💬</a> <a href="#userTesting-hooke007" title="User Testing">📓</a> <a href="https://github.com/Blinue/Magpie/commits?author=hooke007" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://palxex.ys168.com"><img src="https://avatars.githubusercontent.com/u/58222?v=4?s=100" width="100px;" alt="Pal Lockheart"/><br /><sub><b>Pal Lockheart</b></sub></a><br /><a href="#userTesting-palxex" title="User Testing">📓</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.stevedonaghy.com/"><img src="https://avatars.githubusercontent.com/u/1029699?v=4?s=100" width="100px;" alt="Steve Donaghy"/><br /><sub><b>Steve Donaghy</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=neoKushan" title="Code">💻</a> <a href="#translation-neoKushan" title="Translation">🌍</a></td>
|
||||
|
|
@ -105,13 +121,6 @@ Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算
|
|||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/eriforce"><img src="https://avatars.githubusercontent.com/u/8393109?v=4?s=100" width="100px;" alt="Erich Yu"/><br /><sub><b>Erich Yu</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=eriforce" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/TamilNeram"><img src="https://avatars.githubusercontent.com/u/67970539?v=4?s=100" width="100px;" alt="தமிழ் நேரம்"/><br /><sub><b>தமிழ் நேரம்</b></sub></a><br /><a href="#translation-TamilNeram" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mhtvsSFrpHdE"><img src="https://avatars.githubusercontent.com/u/10773245?v=4?s=100" width="100px;" alt="mhtvsSFrpHdE"/><br /><sub><b>mhtvsSFrpHdE</b></sub></a><br /><a href="https://github.com/Blinue/Magpie/commits?author=mhtvsSFrpHdE" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kangurek-kao"><img src="https://avatars.githubusercontent.com/u/116571935?v=4?s=100" width="100px;" alt="Krzysztof"/><br /><sub><b>Krzysztof</b></sub></a><br /><a href="#translation-kangurek-kao" title="Translation">🌍</a></td>
|
||||
<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>
|
||||
|
|
@ -121,8 +130,4 @@ Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算
|
|||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
本项目遵循 [all-contributors](https://allcontributors.org/) 规范,欢迎任何形式的贡献!
|
||||
|
||||
## 许可协议
|
||||
|
||||
本项目采用 GPLv3 许可协议。
|
||||
本项目遵循 [all-contributors](https://allcontributors.org/) 规范。欢迎任何形式的贡献!
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import shutil
|
|||
import requests
|
||||
import hashlib
|
||||
import json
|
||||
import argparse
|
||||
|
||||
try:
|
||||
# https://docs.github.com/en/actions/learn-github-actions/variables
|
||||
|
|
@ -16,17 +15,12 @@ try:
|
|||
except:
|
||||
pass
|
||||
|
||||
argParser = argparse.ArgumentParser()
|
||||
argParser.add_argument("version_major", type=int)
|
||||
argParser.add_argument("version_minor", type=int)
|
||||
argParser.add_argument("version_patch", type=int)
|
||||
argParser.add_argument("tag")
|
||||
argParser.add_argument("prerelease")
|
||||
argParser.add_argument("access_token")
|
||||
args = argParser.parse_args()
|
||||
|
||||
isPrerelease = args.prerelease.lower() == "true"
|
||||
|
||||
majorVersion = os.environ["MAJOR"]
|
||||
minorVersion = os.environ["MINOR"]
|
||||
patchVersion = os.environ["PATCH"]
|
||||
tag = os.environ["TAG"]
|
||||
isPrerelease = os.environ["PRERELEASE"].lower() == "true"
|
||||
githubAccessToken = os.environ["ACCESS_TOKEN"]
|
||||
repo = os.environ["GITHUB_REPOSITORY"]
|
||||
actor = os.environ["GITHUB_ACTOR"]
|
||||
|
||||
|
|
@ -34,21 +28,21 @@ subprocess.run("git config user.name " + actor)
|
|||
subprocess.run(f"git config user.email {actor}@users.noreply.github.com")
|
||||
|
||||
subprocess.run(
|
||||
f"git remote set-url origin https://{args.access_token}@github.com/{repo}.git"
|
||||
f"git remote set-url origin https://{githubAccessToken}@github.com/{repo}.git"
|
||||
)
|
||||
|
||||
# 打标签
|
||||
if subprocess.run(f"git tag -a {args.tag} -m {args.tag}").returncode != 0:
|
||||
if subprocess.run(f"git tag -a {tag} -m {tag}").returncode != 0:
|
||||
raise Exception("打标签失败")
|
||||
|
||||
if subprocess.run("git push origin " + args.tag).returncode != 0:
|
||||
if subprocess.run("git push origin " + tag).returncode != 0:
|
||||
raise Exception("推送标签失败")
|
||||
|
||||
print("已创建标签 " + args.tag, flush=True)
|
||||
print("已创建标签 " + tag, flush=True)
|
||||
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": "Bearer " + args.access_token,
|
||||
"Authorization": "Bearer " + githubAccessToken,
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
|
||||
|
|
@ -59,8 +53,8 @@ try:
|
|||
# 发布预发行版与最新的版本(无论是正式版还是预发行版)对比
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/{repo}/releases",
|
||||
json={"per_page": 1},
|
||||
headers=headers,
|
||||
params={"per_page": 1}
|
||||
)
|
||||
if response.ok:
|
||||
prevReleaseTag = response.json()[0]["tag_name"]
|
||||
|
|
@ -81,13 +75,13 @@ if prevReleaseTag == None:
|
|||
body = ""
|
||||
else:
|
||||
# 默认发行说明为比较两个 tag
|
||||
body = f"https://github.com/{repo}/compare/{prevReleaseTag}...{args.tag}"
|
||||
body = f"https://github.com/{repo}/compare/{prevReleaseTag}...{tag}"
|
||||
|
||||
response = requests.post(
|
||||
f"https://api.github.com/repos/{repo}/releases",
|
||||
json={
|
||||
"tag_name": args.tag,
|
||||
"name": args.tag,
|
||||
"tag_name": tag,
|
||||
"name": tag,
|
||||
"prerelease": isPrerelease,
|
||||
"body": body,
|
||||
"discussion_category_name": "Announcements",
|
||||
|
|
@ -105,7 +99,7 @@ os.chdir(os.path.dirname(__file__) + "\\..\\publish")
|
|||
pkgInfos = {}
|
||||
for platform in ["x64", "ARM64"]:
|
||||
# 打包成 zip
|
||||
pkgName = "Magpie-" + args.tag + "-" + platform
|
||||
pkgName = "Magpie-" + tag + "-" + platform
|
||||
shutil.make_archive(pkgName, "zip", pkgName)
|
||||
pkgName += ".zip"
|
||||
|
||||
|
|
@ -128,7 +122,7 @@ for platform in ["x64", "ARM64"]:
|
|||
|
||||
pkgInfos[platform] = (pkgName, md5)
|
||||
|
||||
print("已发布 " + args.tag, flush=True)
|
||||
print("已发布 " + tag, flush=True)
|
||||
|
||||
# 更新 version.json
|
||||
# 此步应在发布版本之后,因为程序使用 version.json 检查更新
|
||||
|
|
@ -136,15 +130,15 @@ os.chdir("..")
|
|||
with open("version.json", "w", encoding="utf-8") as f:
|
||||
json.dump(
|
||||
{
|
||||
"version": f"{args.version_major}.{args.version_minor}.{args.version_patch}",
|
||||
"tag": args.tag,
|
||||
"version": f"{majorVersion}.{minorVersion}.{patchVersion}",
|
||||
"tag": tag,
|
||||
"binary": {
|
||||
"x64": {
|
||||
"url": f"https://github.com/{repo}/releases/download/{args.tag}/{pkgInfos['x64'][0]}",
|
||||
"url": f"https://github.com/{repo}/releases/download/{tag}/{pkgInfos['x64'][0]}",
|
||||
"hash": pkgInfos["x64"][1],
|
||||
},
|
||||
"ARM64": {
|
||||
"url": f"https://github.com/{repo}/releases/download/{args.tag}/{pkgInfos['ARM64'][0]}",
|
||||
"url": f"https://github.com/{repo}/releases/download/{tag}/{pkgInfos['ARM64'][0]}",
|
||||
"hash": pkgInfos["ARM64"][1],
|
||||
},
|
||||
},
|
||||
|
|
@ -4,7 +4,6 @@ import tempfile
|
|||
import glob
|
||||
import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
try:
|
||||
# https://docs.github.com/en/actions/learn-github-actions/variables
|
||||
|
|
@ -15,11 +14,12 @@ try:
|
|||
except:
|
||||
pass
|
||||
|
||||
argParser = argparse.ArgumentParser()
|
||||
argParser.add_argument("access_token")
|
||||
args = argParser.parse_args()
|
||||
if not "ACCESS_TOKEN" in os.environ:
|
||||
raise Exception("未找到环境变量 ACCESS_TOKEN")
|
||||
|
||||
wikiRepoUrl = f'https://{args.access_token}@github.com/{os.environ["GITHUB_REPOSITORY"]}.wiki.git'
|
||||
wikiRepoUrl = os.path.expandvars(
|
||||
"https://${ACCESS_TOKEN}@github.com/${GITHUB_REPOSITORY}.wiki.git"
|
||||
)
|
||||
|
||||
# 创建临时目录
|
||||
wikiRepoDir = tempfile.mkdtemp()
|
||||
|
|
@ -126,9 +126,6 @@ Magpie ships with a handful of effects that can be used in combinations. Most of
|
|||
* CuNNy family:Suitable for visual novel-style images. The DS variants offer a subtle denoise effect. Provided by [CuNNy](https://github.com/funnyplanter/CuNNy)
|
||||
* Output size: twice that of the input
|
||||
|
||||
* CuNNy2 family: An enhanced version of CuNNy
|
||||
* Output size: twice that of the input
|
||||
|
||||
* Deband
|
||||
* Output size: the same as the input
|
||||
* Parameters
|
||||
|
|
@ -196,9 +193,6 @@ 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
|
||||
|
|
@ -250,12 +244,6 @@ Magpie ships with a handful of effects that can be used in combinations. Most of
|
|||
* SharpBilinear: Scale with the Sharp-Bilinear algorithm. Suitable for upscaling pixel arts.
|
||||
* Output size: determined by scale configuration
|
||||
|
||||
* SGSR: Port of [Snapdragon Game Super Resolution v1](https://github.com/SnapdragonStudios/snapdragon-gsr/tree/main/sgsr/v1).
|
||||
* Output size: determined by scale configuration
|
||||
* Parameter
|
||||
* Edge Sharpness: Edge sharpening intensity (The larger the value, the sharper the image.)
|
||||
* Edge Threshold: Edge detection threshold
|
||||
|
||||
* SMAA_Low, SMAA_Medium, SMAA_High, and SMAA_Ultra: SMAA anti-aliasing. In increasing order of demand for computing power.
|
||||
* Output size: the same as the input
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Magpie provides several capture methods. They have their pros and cons in differ
|
|||
| Supports recording/streaming | No under extreme conditions<sup>[1]</sup> | No | Yes | Yes |
|
||||
| Support the source window to span multiple screens | No under extreme conditions<sup>[1]</sup> | No | Yes | Yes |
|
||||
| Ignores DPI virtualization<sup>[2]</sup> | No | No | Yes| Yes |
|
||||
| Notes | The most recommended capture method | Requires Win10 v2004 | | Not recommended due to unstable performance |
|
||||
| Notes | The most recommended capture method | Requires Win10 v2004 | | Low VRAM usage |
|
||||
|
||||
|
||||
[1]: (1) The source window does not support regular window capture. (2) The operating system is Windows 11.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
In order to compile Magpie, you need to first install:
|
||||
|
||||
1. The latest version of Visual Studio 2022 or 2026. You need to install both "Desktop development with C++" and "Universal Windows Platform development" workloads and Windows SDK build 26100 or newer.
|
||||
1. The latest version of Visual Studio 2022. You need to install both "Desktop development with C++" and "Universal Windows Platform development" workloads and Windows SDK build 26100 or newer.
|
||||
2. [CMake](https://cmake.org/)
|
||||
|
||||
You can also use the built-in CMake of Visual Studio, which is located at `%ProgramFiles%\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin`.
|
||||
|
|
@ -28,7 +28,7 @@ In order to compile Magpie, you need to first install:
|
|||
git clone https://github.com/Blinue/Magpie
|
||||
```
|
||||
|
||||
2. Open the Magpie.slnx in the root directory and build the solution.
|
||||
2. Open the Magpie.sln in the root directory and build the solution.
|
||||
|
||||
### Enabling Touch Support
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ To enable touch input support, TouchHelper.exe needs to be signed. While signing
|
|||
3. Run the following command in the root directory of the repository:
|
||||
|
||||
```bash
|
||||
python scripts/publish.py --pfx-path=<pfx path> --pfx-password=<pfx password>
|
||||
python publish.py x64 unpackaged <pfx path> <pfx password>
|
||||
```
|
||||
|
||||
This will compile Magpie and sign TouchHelper.exe. The compiled files will be located in `publish\x64`.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
When displaying performance monitor like RTSS (Rivatuner Statistics Server), there might be 2 OSD layers displayed with Magpie scaling. This is caused by d3d, the screen capture method since v0.7.0. It will be captured by RTSS as well. You can fix this issue by adding it to the blacklist.
|
||||
|
||||
## The hot keys don't work, but "Scale after `x`s" works.
|
||||
## The hot keys don't work, but "Scale after x s" works.
|
||||
|
||||
1. Try changing the hot keys.
|
||||
2. Try running Magpie as Administrator.
|
||||
|
||||
## Does Magpie support multiple monitors?
|
||||
|
||||
Supported from v0.8.
|
||||
|
||||
## Lagging/latency
|
||||
|
||||
Please check the [Performance optimization](https://github.com/Blinue/Magpie/wiki/Performance%20optimization) page.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
使用性能计数器屏显时,例如 RTSS (Rivatuner Statistics Server),你可能会在缩放时看到两个叠加层。这是由于 Magpie 使用 Direct3D 呈现画面,它也会被 RTSS 捕捉。如果你不关心 Magpie 的性能,请将 Magpie 添加到黑名单。
|
||||
|
||||
### 快捷键不起作用,但可以使用 "`x` 秒后缩放"
|
||||
### 快捷键不起作用,但可以使用 "x秒后缩放"
|
||||
|
||||
1. 尝试更换快捷键
|
||||
2. 尝试以管理员身份运行 Magpie
|
||||
|
||||
### 是否支持多屏?
|
||||
|
||||
从 v0.8 开始支持。
|
||||
|
||||
### 卡顿/延迟
|
||||
|
||||
请查看[性能优化建议](https://github.com/Blinue/Magpie/wiki/性能优化建议)。
|
||||
|
|
|
|||
|
|
@ -126,9 +126,6 @@ Magpie 内置了大量效果供组合使用,大部分提供了参数选项以
|
|||
* CuNNy 族:适合视觉小说风格图像的缩放,由 [CuNNy](https://github.com/funnyplanter/CuNNy) 提供。DS 变体有轻微降噪效果
|
||||
* 输出尺寸:输入的两倍
|
||||
|
||||
* CuNNy2 族:CuNNy 的改进版
|
||||
* 输出尺寸:输入的两倍
|
||||
|
||||
* Deband:去除色带
|
||||
* 输出尺寸:和输入相同
|
||||
* 参数
|
||||
|
|
@ -196,9 +193,6 @@ Magpie 内置了大量效果供组合使用,大部分提供了参数选项以
|
|||
* Sinc Param:值越大图像越锐利
|
||||
* Anti-ringing Strength:抗振铃强度
|
||||
|
||||
* k7_modernAnime:适合动漫类风格的超分算法
|
||||
* 输出尺寸:输入的两倍
|
||||
|
||||
* Lanczos:使用 Lanczos 算法缩放输入。
|
||||
* 输出尺寸:取决于缩放选项
|
||||
* 参数
|
||||
|
|
@ -247,12 +241,6 @@ Magpie 内置了大量效果供组合使用,大部分提供了参数选项以
|
|||
* 输出尺寸:取决于缩放选项
|
||||
* 备注:只支持放大
|
||||
|
||||
* SGSR:[Snapdragon Game Super Resolution v1](https://github.com/SnapdragonStudios/snapdragon-gsr/tree/main/sgsr/v1) 的移植
|
||||
* 输出尺寸:取决于缩放选项
|
||||
* 参数
|
||||
* Edge Sharpness:边缘锐化强度(值越大图像越锐利)
|
||||
* Edge Threshold:边缘检测阈值
|
||||
|
||||
* SharpBilinear:使用 Sharp-Bilinear 算法缩放输入。适合放大像素画
|
||||
* 输出尺寸:取决于缩放选项
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ Magpie 提供数种捕获方式,根据使用场景,它们各有优劣。无
|
|||
| 支持录制/串流 | 特殊情况下不支持<sup>[1]</sup> | 否 | 是 | 是 |
|
||||
| 支持源窗口跨越多个屏幕 | 特殊情况下不支持<sup>[1]</sup> | 否 | 是 | 是 |
|
||||
| 无视 DPI 虚拟化<sup>[2]</sup> | 否 | 否 | 是| 是 |
|
||||
| 备注 | 首选捕获方式 | 要求 Win10 v2004 | | 性能不稳定,不建议使用 |
|
||||
| 备注 | 首选捕获方式 | 要求 Win10 v2004 | | 占用的显存较少 |
|
||||
|
||||
|
||||
[1]: (1) 源窗口不支持常规的窗口捕获 (2) 操作系统为 Windows 11
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
为了编译 Magpie,你首先需要安装:
|
||||
|
||||
1. Visual Studio 2022 或 2026 的最新版本,需要安装“使用 C++ 的桌面开发”和“通用 Windows 平台开发”两个工作负荷以及 Windows SDK build 26100 或更高版本。
|
||||
1. Visual Studio 2022 的最新版本,需要安装“使用 C++ 的桌面开发”和“通用 Windows 平台开发”两个工作负荷以及 Windows SDK build 26100 或更高版本。
|
||||
2. [CMake](https://cmake.org/)
|
||||
|
||||
你也可以使用 Visual Studio 内置的 CMake,它位于 `%ProgramFiles%\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin`。
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
git clone https://github.com/Blinue/Magpie
|
||||
```
|
||||
|
||||
2. 打开根目录的 Magpie.slnx 然后生成解决方案。
|
||||
2. 打开根目录的 Magpie.sln 然后生成解决方案。
|
||||
|
||||
## 启用触控支持
|
||||
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
3. 在存储库根目录下执行以下命令:
|
||||
|
||||
```bash
|
||||
python scripts/publish.py --pfx-path=<pfx 路径> --pfx-password=<pfx 密码>
|
||||
python publish.py x64 unpackaged <pfx 路径> <pfx 密码>
|
||||
```
|
||||
|
||||
这将编译 Magpie 并为 TouchHelper.exe 签名。编译出的程序位于 `publish\x64`。
|
||||
|
|
|
|||
BIN
img/Main window.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
img/Repo card.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
BIN
img/主窗口.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
|
|
@ -2,28 +2,34 @@ import sys
|
|||
import os
|
||||
import subprocess
|
||||
import glob
|
||||
import argparse
|
||||
import shutil
|
||||
from xml.etree import ElementTree
|
||||
|
||||
majorVersion = None
|
||||
try:
|
||||
# https://docs.github.com/en/actions/learn-github-actions/variables
|
||||
if os.environ["GITHUB_ACTIONS"].lower() == "true":
|
||||
# 不知为何在 Github Actions 中运行时默认编码为 ANSI,并且 print 需刷新流才能正常显示
|
||||
for stream in [sys.stdout, sys.stderr]:
|
||||
stream.reconfigure(encoding="utf-8")
|
||||
|
||||
# 存在 MAJOR 环境变量则发布新版本
|
||||
majorVersion = os.environ["MAJOR"]
|
||||
except:
|
||||
pass
|
||||
|
||||
argParser = argparse.ArgumentParser()
|
||||
argParser.add_argument("--compiler", choices=["MSVC", "ClangCL"], default="MSVC")
|
||||
argParser.add_argument("--platform", choices=["x64", "ARM64"], default="x64")
|
||||
argParser.add_argument("--use-native-march", action="store_true")
|
||||
argParser.add_argument("--version-major", type=int, default=0)
|
||||
argParser.add_argument("--version-minor", type=int, default=0)
|
||||
argParser.add_argument("--version-patch", type=int, default=0)
|
||||
argParser.add_argument("--version-string", default="")
|
||||
argParser.add_argument("--pfx-path", default="")
|
||||
argParser.add_argument("--pfx-password", default="")
|
||||
args = argParser.parse_args()
|
||||
platform = "x64"
|
||||
if len(sys.argv) >= 2:
|
||||
platform = sys.argv[1]
|
||||
if not platform in ["x64", "ARM64"]:
|
||||
raise Exception("非法参数")
|
||||
|
||||
if majorVersion != None:
|
||||
import re
|
||||
|
||||
minorVersion = os.environ["MINOR"]
|
||||
patchVersion = os.environ["PATCH"]
|
||||
tag = os.environ["TAG"]
|
||||
|
||||
#####################################################################
|
||||
#
|
||||
|
|
@ -51,16 +57,48 @@ if not os.access(msbuildPath, os.X_OK):
|
|||
#
|
||||
#####################################################################
|
||||
|
||||
os.chdir(os.path.dirname(__file__) + "\\..")
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
|
||||
p = subprocess.run("git rev-parse --short HEAD", capture_output=True)
|
||||
commitId = str(p.stdout, encoding="utf-8")[0:-1]
|
||||
|
||||
versionNumProps = f";MajorVersion={args.version_major};MinorVersion={args.version_minor};PatchVersion={args.version_patch}"
|
||||
versionStrProp = "" if args.version_string == "" else f";VersionString={args.version_string}"
|
||||
if majorVersion != None:
|
||||
version_props = f";MajorVersion={majorVersion};MinorVersion={minorVersion};PatchVersion={patchVersion};VersionTag={tag}"
|
||||
|
||||
# 更新 RC 文件中的版本号
|
||||
version = f"{majorVersion}.{minorVersion}.{patchVersion}.0"
|
||||
version_comma = version.replace(".", ",")
|
||||
for project in os.listdir("src"):
|
||||
rcPath = f"src\\{project}\\{project}.rc"
|
||||
if not os.access(rcPath, os.R_OK | os.W_OK):
|
||||
continue
|
||||
|
||||
with open(rcPath, mode="r+", encoding="utf-8") as f:
|
||||
src = f.read()
|
||||
|
||||
src = re.sub(
|
||||
r"FILEVERSION .*?\n", "FILEVERSION " + version_comma + "\n", src
|
||||
)
|
||||
src = re.sub(
|
||||
r"PRODUCTVERSION .*?\n", "PRODUCTVERSION " + version_comma + "\n", src
|
||||
)
|
||||
src = re.sub(
|
||||
r'"FileVersion", *?".*?"\n', '"FileVersion", "' + version + '"\n', src
|
||||
)
|
||||
src = re.sub(
|
||||
r'"ProductVersion", *?".*?"\n',
|
||||
'"ProductVersion", "' + version + '"\n',
|
||||
src,
|
||||
)
|
||||
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(src)
|
||||
else:
|
||||
version_props = ""
|
||||
|
||||
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};OutDir={os.getcwd()}\\publish\\{args.platform}\\;CommitId={commitId}{versionNumProps}{versionStrProp}'
|
||||
f'"{msbuildPath}" -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={platform};OutDir={os.getcwd()}\\publish\\{platform}\\;CommitId={commitId}{version_props} Magpie.sln'
|
||||
)
|
||||
if p.returncode != 0:
|
||||
raise Exception("编译失败")
|
||||
|
|
@ -71,7 +109,7 @@ if p.returncode != 0:
|
|||
#
|
||||
#####################################################################
|
||||
|
||||
os.chdir("publish\\" + args.platform)
|
||||
os.chdir("publish\\" + platform)
|
||||
|
||||
|
||||
# 删除文件,忽略错误
|
||||
|
|
@ -82,8 +120,9 @@ def remove_file(file):
|
|||
pass
|
||||
|
||||
|
||||
for file in glob.glob("*.lib"):
|
||||
remove_file(file)
|
||||
for pattern in ["*.pdb", "*.lib", "*.exp"]:
|
||||
for file in glob.glob(pattern):
|
||||
remove_file(file)
|
||||
|
||||
print("清理完毕", flush=True)
|
||||
|
||||
|
|
@ -93,16 +132,17 @@ print("清理完毕", flush=True)
|
|||
#
|
||||
#####################################################################
|
||||
|
||||
if args.pfx_path != "":
|
||||
pfxPath = os.path.join("..\\..", args.pfx_path)
|
||||
if len(sys.argv) >= 5 and sys.argv[4] != "":
|
||||
# sys.argv[2] 保留为打包选项
|
||||
pfxPath = os.path.join("..\\..", sys.argv[3])
|
||||
pfxPassword = sys.argv[4]
|
||||
|
||||
# 取最新的 Windows SDK
|
||||
windowsSdkDir = max(
|
||||
glob.glob(programFilesX86Path + "\\Windows Kits\\10\\bin\\10.*")
|
||||
)
|
||||
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} TouchHelper.exe'
|
||||
f'"{windowsSdkDir}\\x64\\signtool.exe" sign /fd SHA256 /a /f "{pfxPath}" /p "{pfxPassword}" TouchHelper.exe'
|
||||
)
|
||||
if p.returncode != 0:
|
||||
raise Exception("签名失败")
|
||||
|
|
@ -2,26 +2,21 @@
|
|||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- 不要直接修改这些选项,应通过 BuildOptions.props.user 或命令行参数覆盖 -->
|
||||
<PropertyGroup>
|
||||
<!-- 使用 clang-cl 编译 -->
|
||||
<UseClangCL>false</UseClangCL>
|
||||
<!-- 针对当前硬件生成优化代码,只在使用 clang-cl 编译时有效 -->
|
||||
<UseNativeMicroArch>false</UseNativeMicroArch>
|
||||
<!-- 编译为打包应用 (暂不支持) -->
|
||||
<IsPackaged>false</IsPackaged>
|
||||
<!-- 窗口模式缩放时把用于调整窗口尺寸的辅助窗口标示出来 -->
|
||||
<DebugBorder>false</DebugBorder>
|
||||
<!-- 在性能分析器上显示调试信息 -->
|
||||
<DebugInfoOnOverlay>false</DebugInfoOnOverlay>
|
||||
<!-- 使用 composition swapchain 呈现 -->
|
||||
<UseCompSwapchain>false</UseCompSwapchain>
|
||||
|
||||
<MajorVersion></MajorVersion>
|
||||
<MinorVersion></MinorVersion>
|
||||
<PatchVersion></PatchVersion>
|
||||
<VersionString></VersionString>
|
||||
<CommitId></CommitId>
|
||||
<CommitId></CommitId>
|
||||
|
||||
<MajorVersion></MajorVersion>
|
||||
<MinorVersion></MinorVersion>
|
||||
<PatchVersion></PatchVersion>
|
||||
<VersionTag></VersionTag>
|
||||
|
||||
<!-- 窗口模式缩放时把用于调整窗口尺寸的辅助窗口标示出来 -->
|
||||
<DebugBorder>false</DebugBorder>
|
||||
<!-- 在性能分析器上显示调试信息 -->
|
||||
<DebugInfoOnOverlay>false</DebugInfoOnOverlay>
|
||||
<!--使用 composition swapchain 呈现-->
|
||||
<UseCompSwapchain>false</UseCompSwapchain>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- 用户自定义编译选项 -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)\BuildOptions.props.user" Condition="Exists('$(MSBuildThisFileDirectory)\BuildOptions.props.user')" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)BuildOptions.props.user" Condition="Exists('$(MSBuildThisFileDirectory)BuildOptions.props.user')" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,94 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Microsoft.Cpp.props 之后导入 -->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
|
||||
<PreprocessorDefinitions>_WINDOWS;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;WINRT_NO_MODULE_LOCK;WIL_SUPPRESS_EXCEPTIONS;WIL_USE_STL=1;NOGDICAPMASKS;NOICONS;NOATOM;NOCLIPBOARD;NODRAWTEXT;NOMEMMGR;NOMETAFILE;NOMINMAX;NOOPENFILE;NOSCROLL;NOSERVICE;NOSOUND;NOTEXTMETRIC;NOCOMM;NOKANJI;NOHELP;NOPROFILER;NOMCX;NO_SHLWAPI_PATH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<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="$(DebugBorder)">MP_DEBUG_BORDER;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="$(DebugInfoOnOverlay)">MP_DEBUG_INFO_ON_OVERLAY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="$(UseCompSwapchain)">MP_USE_COMPSWAPCHAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<DebugInformationFormat Condition="'$(DisablePDB)' == 'true'">None</DebugInformationFormat>
|
||||
<PreprocessorDefinitions>_WINDOWS;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;WINRT_NO_MODULE_LOCK;WIL_SUPPRESS_EXCEPTIONS;WIL_USE_STL=1;NOGDICAPMASKS;NOICONS;NOATOM;NOCLIPBOARD;NODRAWTEXT;NOMEMMGR;NOMETAFILE;NOMINMAX;NOOPENFILE;NOSCROLL;NOSERVICE;NOSOUND;NOTEXTMETRIC;NOCOMM;NOKANJI;NOHELP;NOPROFILER;NODEFERWINDOWPOS;NOMCX;NO_SHLWAPI_PATH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(CommitId)'!=''">MP_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(MajorVersion)'!='' And '$(MinorVersion)'!='' And '$(PatchVersion)'!='' And '$(VersionTag)'!=''">MP_MAJOR_VERSION=$(MajorVersion);MP_MINOR_VERSION=$(MinorVersion);MP_PATCH_VERSION=$(PatchVersion);MP_VERSION_TAG=$(VersionTag);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(DebugBorder)'=='true'">MP_DEBUG_BORDER;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(DebugInfoOnOverlay)'=='true'">MP_DEBUG_INFO_ON_OVERLAY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(UseCompSwapchain)'=='true'">MP_USE_COMPSWAPCHAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- /await:strict: 禁用协程的非标准语言扩展 -->
|
||||
<AdditionalOptions Condition="!$(UseClangCL)">/await:strict %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- -fstrict-vtable-pointers: 缓存虚表指针,这要求不能通过非法手段改变对象的动态类型 -->
|
||||
<!-- -funsafe-math-optimizations: 以降低精度为代价提高浮点运算速度 -->
|
||||
<!-- -fno-math-errno: 浮点操作不设置 errno,这使一些数学函数可以内联 -->
|
||||
<AdditionalOptions Condition="$(UseClangCL)">/clang:-Wno-missing-designated-field-initializers /clang:-Wno-missing-field-initializers /clang:-fstrict-vtable-pointers /clang:-funsafe-math-optimizations /clang:-fno-math-errno %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- -mcx16: 启用 CX16 指令,Windows 从 8.1 开始要求 CPU 支持它 -->
|
||||
<AdditionalOptions Condition="$(UseClangCL) And '$(Platform)' == 'x64'">/clang:-mcx16 %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- -march=native: 针对当前硬件生成优化代码 -->
|
||||
<AdditionalOptions Condition="$(UseClangCL) And $(UseNativeMicroArch)">/clang:-march=native %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- 使用 clang-cl 编译时禁用 cppwinrt 头文件中的编译警告 -->
|
||||
<AdditionalOptions Condition="$(UseClangCL) And '$(GeneratedFilesDir)' != ''">/clang:-isystem /clang:"$(GeneratedFilesDir)\" %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- 修复编译 Shared 中源文件找不到 pch.h 的问题 -->
|
||||
<AdditionalIncludeDirectories Condition="$(UseClangCL)">.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<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>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation Condition="'$(DisablePDB)' == 'true'">false</GenerateDebugInformation>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
|
||||
<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>
|
||||
<!-- /Gw: 链接时删除未使用和重复的数据,可以减小二进制文件体积 -->
|
||||
<!-- /Zc:checkGwOdr: 防止 /Gw 导致某些 ODR 违规被忽略 -->
|
||||
<AdditionalOptions>/Gw %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalOptions Condition="!$(UseClangCL)">/Zc:checkGwOdr %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- clang-cl 不支持 /LTCG,应使用 LTO -->
|
||||
<AdditionalOptions Condition="$(UseClangCL)">/clang:-flto %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
<Lib>
|
||||
<LinkTimeCodeGeneration>true</LinkTimeCodeGeneration>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- 所有项目共享的头文件 -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)\Shared\Shared.vcxitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)Shared\Shared.vcxitems" Label="Shared" />
|
||||
|
||||
<!-- Conan 依赖 -->
|
||||
<Import Project="$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props" Condition="Exists('$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props')" />
|
||||
<Import Project="$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props" Condition="Exists('$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props')" />
|
||||
|
||||
<!-- HybridCRT -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)\HybridCRT.props" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)HybridCRT.props" />
|
||||
|
||||
<!-- _CopyFilesMarkedCopyLocal 有一个 bug:即使没有复制任何文件也会更改 @(FileWrites),经常导致 -->
|
||||
<!-- up-to-date 检查失败。这个任务用于在 _CopyFilesMarkedCopyLocal 执行后修正 @(FileWrites)。 -->
|
||||
|
|
|
|||
|
|
@ -1,45 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Microsoft.Cpp.Default.props 之后导入 -->
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- 编译选项 -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)\BuildOptions.props" />
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<VS17 Condition="$([System.String]::new('$(MSBuildVersion)').StartsWith('17'))">true</VS17>
|
||||
<VS17 Condition="'$(VS17)' != 'true'">false</VS17>
|
||||
<VCProjectVersion Condition="$(VS17)">17.0</VCProjectVersion>
|
||||
<VCProjectVersion Condition="!$(VS17)">18.0</VCProjectVersion>
|
||||
<PropertyGroup>
|
||||
<IsDebug>$(Configuration.StartsWith('Debug'))</IsDebug>
|
||||
<IsPackaged>$(Configuration.EndsWith('Packaged'))</IsPackaged>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<MajorVersion Condition="'$(MajorVersion)' == ''">0</MajorVersion>
|
||||
<MinorVersion Condition="'$(MinorVersion)' == ''">0</MinorVersion>
|
||||
<PatchVersion Condition="'$(PatchVersion)' == ''">0</PatchVersion>
|
||||
<!-- 可通过 VersionString 区分开发版本和发布版本 -->
|
||||
<VersionString Condition="'$(VersionString)' == '' And ('$(MajorVersion)' != '0' Or '$(MinorVersion)' != '0' Or '$(PatchVersion)' != '0')">$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionString>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PlatformToolset Condition="$(UseClangCL)">ClangCL</PlatformToolset>
|
||||
<PlatformToolset Condition="!$(UseClangCL) And $(VS17)">v143</PlatformToolset>
|
||||
<PlatformToolset Condition="!$(UseClangCL) And !$(VS17)">v145</PlatformToolset>
|
||||
<UseDebugLibraries Condition="'$(Configuration)' == 'Debug'">true</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<!-- 编译选项 -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)BuildOptions.props" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -106,9 +106,6 @@ float4 Pass1(float2 pos)
|
|||
// Sample the source pixel
|
||||
float3 col = INPUT.SampleLevel(sam1, pos, 0).rgb;
|
||||
|
||||
#ifdef MP_INLINE_PARAMS
|
||||
[unroll]
|
||||
#endif
|
||||
for (int i = 1; i <= iterations; i++) {
|
||||
// Use the average instead if the difference is below the threshold
|
||||
float3 avg = average(pos, i*range, h);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{62503530-b84b-4cc2-80b6-3f89618172b7}</ProjectGuid>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
|
||||
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Utility</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="PropertySheets">
|
||||
|
|
@ -18,7 +20,7 @@
|
|||
</ImportGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<CopyFileToFolders>
|
||||
<DestinationFolders>$(OutDir)\effects</DestinationFolders>
|
||||
<DestinationFolders>$(OutDir)effects</DestinationFolders>
|
||||
<DestinationFileName>%(RelativeDir)%(Filename)%(Extension)</DestinationFileName>
|
||||
</CopyFileToFolders>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
@ -427,10 +429,14 @@
|
|||
<CopyFileToFolders Include="CuNNy\CuNNy-16x16C-NVL-DN.hlsl">
|
||||
<FileType>Document</FileType>
|
||||
</CopyFileToFolders>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CopyFileToFolders Include="NIS\NIS_Scaler.hlsli">
|
||||
<FileType>Document</FileType>
|
||||
</CopyFileToFolders>
|
||||
<None Include="StubDefs.hlsli" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CopyFileToFolders Include="CuNNy2\CuNNy-3x12-NVL.hlsl">
|
||||
<FileType>Document</FileType>
|
||||
</CopyFileToFolders>
|
||||
|
|
@ -458,12 +464,6 @@
|
|||
<CopyFileToFolders Include="CuNNy2\CuNNy-veryfast-NVL.hlsl">
|
||||
<FileType>Document</FileType>
|
||||
</CopyFileToFolders>
|
||||
<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>
|
||||
|
|
@ -451,8 +451,6 @@
|
|||
<CopyFileToFolders Include="CuNNy2\CuNNy-veryfast-NVL.hlsl">
|
||||
<Filter>CuNNy2</Filter>
|
||||
</CopyFileToFolders>
|
||||
<CopyFileToFolders Include="SGSR.hlsl" />
|
||||
<CopyFileToFolders Include="k7_modernAnime_FHD_x2.hlsl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Anime4K">
|
||||
|
|
|
|||
|
|
@ -1,170 +0,0 @@
|
|||
// Snapdragon™ Game Super Resolution
|
||||
// 移植自 https://github.com/SnapdragonStudios/snapdragon-gsr/blob/main/sgsr/v1/include/hlsl/sgsr1_shader_mobile.hlsl
|
||||
|
||||
//!MAGPIE EFFECT
|
||||
//!VERSION 4
|
||||
|
||||
//!PARAMETER
|
||||
//!LABEL Edge Sharpness
|
||||
//!DEFAULT 2.0
|
||||
//!MIN 0.0
|
||||
//!MAX 10.0
|
||||
//!STEP 0.1
|
||||
float EdgeSharpness;
|
||||
|
||||
//!PARAMETER
|
||||
//!LABEL Edge Threshold
|
||||
//!DEFAULT 8.0
|
||||
//!MIN 0.0
|
||||
//!MAX 16.0
|
||||
//!STEP 0.1
|
||||
float EdgeThreshold;
|
||||
|
||||
//!TEXTURE
|
||||
Texture2D INPUT;
|
||||
|
||||
//!TEXTURE
|
||||
Texture2D OUTPUT;
|
||||
|
||||
//!SAMPLER
|
||||
//!FILTER POINT
|
||||
SamplerState sam;
|
||||
|
||||
//!PASS 1
|
||||
//!STYLE PS
|
||||
//!IN INPUT
|
||||
//!OUT OUTPUT
|
||||
|
||||
#define UseEdgeDirection
|
||||
|
||||
float fastLanczos2(float x)
|
||||
{
|
||||
float wA = x- float(4.0);
|
||||
float wB = x*wA-wA;
|
||||
wA *= wA;
|
||||
return wB*wA;
|
||||
}
|
||||
|
||||
#if defined(UseEdgeDirection)
|
||||
float2 weightY(float dx, float dy, float c, float3 data)
|
||||
#else
|
||||
float2 weightY(float dx, float dy, float c, float data)
|
||||
#endif
|
||||
{
|
||||
#if defined(UseEdgeDirection)
|
||||
float std = data.x;
|
||||
float2 dir = data.yz;
|
||||
|
||||
float edgeDis = ((dx*dir.y)+(dy*dir.x));
|
||||
float x = (((dx*dx)+(dy*dy))+((edgeDis*edgeDis)*((clamp(((c*c)*std),0.0,1.0)*0.7)+-1.0)));
|
||||
#else
|
||||
float std = data;
|
||||
float x = ((dx*dx)+(dy* dy))* float(0.5) + clamp(abs(c)*std, 0.0, 1.0);
|
||||
#endif
|
||||
|
||||
float w = fastLanczos2(x);
|
||||
return float2(w, w * c);
|
||||
}
|
||||
|
||||
float2 edgeDirection(float4 left, float4 right)
|
||||
{
|
||||
float2 dir;
|
||||
float RxLz = (right.x + (-left.z));
|
||||
float RwLy = (right.w + (-left.y));
|
||||
float2 delta;
|
||||
delta.x = (RxLz + RwLy);
|
||||
delta.y = (RxLz + (-RwLy));
|
||||
float lengthInv = rsqrt((delta.x * delta.x+ 3.075740e-05) + (delta.y * delta.y));
|
||||
dir.x = (delta.x * lengthInv);
|
||||
dir.y = (delta.y * lengthInv);
|
||||
return dir;
|
||||
}
|
||||
|
||||
float4 SGSRH(float2 p)
|
||||
{
|
||||
return INPUT.GatherGreen(sam, p);
|
||||
}
|
||||
|
||||
float4 SGSRRGBH(float2 p)
|
||||
{
|
||||
return INPUT.SampleLevel(sam, p, 0);
|
||||
}
|
||||
|
||||
float3 SgsrYuvH(float2 uv, float4 con1)
|
||||
{
|
||||
float3 pix;
|
||||
float edgeThreshold = EdgeThreshold / 255.0;
|
||||
float edgeSharpness = EdgeSharpness;
|
||||
pix = SGSRRGBH(uv).xyz;
|
||||
float xCenter;
|
||||
xCenter = abs(uv.x+-0.5);
|
||||
float yCenter;
|
||||
yCenter = abs(uv.y+-0.5);
|
||||
|
||||
float2 imgCoord = ((uv.xy*con1.zw)+ float2(-0.5,0.5));
|
||||
float2 imgCoordPixel = floor(imgCoord);
|
||||
float2 coord = (imgCoordPixel*con1.xy);
|
||||
float2 pl = (imgCoord+(-imgCoordPixel));
|
||||
float4 left = SGSRH(coord);
|
||||
|
||||
float edgeVote = abs(left.z - left.y) + abs(pix[1] - left.y) + abs(pix[1] - left.z) ;
|
||||
if (edgeVote > edgeThreshold)
|
||||
{
|
||||
coord.x += con1.x;
|
||||
|
||||
float4 right = SGSRH(coord + float2(con1.x, 0.0));
|
||||
float4 upDown;
|
||||
upDown.xy = SGSRH(coord + float2(0.0, -con1.y)).wz;
|
||||
upDown.zw = SGSRH(coord + float2(0.0, con1.y)).yx;
|
||||
|
||||
float mean = (left.y+left.z+right.x+right.w)* float(0.25);
|
||||
left = left - float4(mean,mean,mean,mean);
|
||||
right = right - float4(mean, mean, mean, mean);
|
||||
upDown = upDown - float4(mean, mean, mean, mean);
|
||||
float pix_G = pix[1] - mean;
|
||||
|
||||
float sum = (((((abs(left.x)+abs(left.y))+abs(left.z))+abs(left.w))+(((abs(right.x)+abs(right.y))+abs(right.z))+abs(right.w)))+(((abs(upDown.x)+abs(upDown.y))+abs(upDown.z))+abs(upDown.w)));
|
||||
float sumMean = 1.014185e+01/sum;
|
||||
float std = (sumMean*sumMean);
|
||||
|
||||
#if defined(UseEdgeDirection)
|
||||
float3 data = float3(std, edgeDirection(left, right));
|
||||
#else
|
||||
float data = std;
|
||||
#endif
|
||||
|
||||
float2 aWY = weightY(pl.x, pl.y+1.0, upDown.x,data);
|
||||
aWY += weightY(pl.x-1.0, pl.y+1.0, upDown.y,data);
|
||||
aWY += weightY(pl.x-1.0, pl.y-2.0, upDown.z,data);
|
||||
aWY += weightY(pl.x, pl.y-2.0, upDown.w,data);
|
||||
aWY += weightY(pl.x+1.0, pl.y-1.0, left.x,data);
|
||||
aWY += weightY(pl.x, pl.y-1.0, left.y,data);
|
||||
aWY += weightY(pl.x, pl.y, left.z,data);
|
||||
aWY += weightY(pl.x+1.0, pl.y, left.w,data);
|
||||
aWY += weightY(pl.x-1.0, pl.y-1.0, right.x,data);
|
||||
aWY += weightY(pl.x-2.0, pl.y-1.0, right.y,data);
|
||||
aWY += weightY(pl.x-2.0, pl.y, right.z,data);
|
||||
aWY += weightY(pl.x-1.0, pl.y, right.w,data);
|
||||
|
||||
float finalY = aWY.y/aWY.x;
|
||||
|
||||
float max4 = max(max(left.y,left.z),max(right.x,right.w));
|
||||
float min4 = min(min(left.y,left.z),min(right.x,right.w));
|
||||
finalY = clamp(edgeSharpness*finalY, min4, max4);
|
||||
|
||||
float deltaY = finalY - pix_G;
|
||||
|
||||
pix = saturate(pix+deltaY);
|
||||
}
|
||||
return pix;
|
||||
}
|
||||
|
||||
MF4 Pass1(float2 texCoord)
|
||||
{
|
||||
float2 inputSize = GetInputSize();
|
||||
float2 inputPt = GetInputPt();
|
||||
float4 viewportInfo = float4(inputPt.x, inputPt.y, inputSize.x, inputSize.y);
|
||||
|
||||
return float4(SgsrYuvH(texCoord, viewportInfo), 1);
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#include "pch.h"
|
||||
#include "AdaptivePresenter.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Win32Helper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
|
@ -15,7 +15,6 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
_isDCompPresenting = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +46,7 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
};
|
||||
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
winrt::com_ptr<IDXGISwapChain1> dxgiSwapChain;
|
||||
winrt::com_ptr<IDXGISwapChain1> dxgiSwapChain = nullptr;
|
||||
HRESULT hr = _deviceResources->GetDXGIFactory()->CreateSwapChainForHwnd(
|
||||
d3dDevice,
|
||||
hwndAttach,
|
||||
|
|
@ -102,7 +101,7 @@ bool AdaptivePresenter::BeginFrame(
|
|||
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
|
||||
POINT& drawOffset
|
||||
) noexcept {
|
||||
if (_isDCompPresenting) {
|
||||
if (_dcompSurface) {
|
||||
HRESULT hr = _dcompSurface->BeginDraw(nullptr, IID_PPV_ARGS(&frameTex), &drawOffset);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("BeginDraw 失败", hr);
|
||||
|
|
@ -130,14 +129,12 @@ bool AdaptivePresenter::BeginFrame(
|
|||
return true;
|
||||
}
|
||||
|
||||
void AdaptivePresenter::EndFrame(bool waitForGpu) noexcept {
|
||||
if (_isDCompPresenting) {
|
||||
void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
||||
if (_dcompSurface) {
|
||||
_dcompSurface->EndDraw();
|
||||
}
|
||||
|
||||
if (waitForGpu || _isResized) {
|
||||
_isResized = false;
|
||||
|
||||
if (waitForRenderComplete || _isResized) {
|
||||
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁。
|
||||
//
|
||||
// 我们希望 DWM 绘制新的窗口框架时刚好合成新帧,但这不是我们能控制的,尤其是混合架构
|
||||
|
|
@ -154,13 +151,13 @@ void AdaptivePresenter::EndFrame(bool waitForGpu) noexcept {
|
|||
// 实用价值。
|
||||
|
||||
// 等待渲染完成
|
||||
_WaitForGpu();
|
||||
_WaitForRenderComplete();
|
||||
|
||||
// 等待 DWM 开始合成新一帧
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
_WaitForDwmComposition();
|
||||
}
|
||||
|
||||
if (_isDCompPresenting) {
|
||||
if (_dcompSurface) {
|
||||
_dcompDevice->Commit();
|
||||
} else {
|
||||
// 两个垂直同步之间允许渲染数帧,SyncInterval = 0 只呈现最新的一帧,旧帧被丢弃
|
||||
|
|
@ -174,14 +171,22 @@ void AdaptivePresenter::EndFrame(bool waitForGpu) noexcept {
|
|||
_isSwitchingToSwapChain = false;
|
||||
|
||||
// 等待交换链呈现新帧
|
||||
_WaitForGpu();
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
_WaitForRenderComplete();
|
||||
_WaitForDwmComposition();
|
||||
|
||||
// 清除 DirectCompostion 内容
|
||||
_dcompVisual->SetContent(nullptr);
|
||||
_dcompSurface = nullptr;
|
||||
_dcompDevice->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
if (_isResized) {
|
||||
_isResized = false;
|
||||
} else {
|
||||
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
|
||||
_WaitForRenderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
bool AdaptivePresenter::OnResize() noexcept {
|
||||
|
|
@ -189,8 +194,7 @@ bool AdaptivePresenter::OnResize() noexcept {
|
|||
|
||||
if (ScalingWindow::Get().IsResizingOrMoving() || !_dxgiSwapChain) {
|
||||
// 切换到 DirectComposition 呈现,失败则回落到交换链
|
||||
_isDCompPresenting = _ResizeDCompVisual();
|
||||
if (_isDCompPresenting) {
|
||||
if (_ResizeDCompVisual()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +215,7 @@ bool AdaptivePresenter::OnResize() noexcept {
|
|||
}
|
||||
|
||||
void AdaptivePresenter::OnEndResize(bool& shouldRedraw) noexcept {
|
||||
if (!_isDCompPresenting || !_dxgiSwapChain) {
|
||||
if (!_dcompSurface || !_dxgiSwapChain) {
|
||||
shouldRedraw = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -219,7 +223,7 @@ void AdaptivePresenter::OnEndResize(bool& shouldRedraw) noexcept {
|
|||
shouldRedraw = true;
|
||||
|
||||
_ResizeSwapChain();
|
||||
_isDCompPresenting = false;
|
||||
_dcompSurface = nullptr;
|
||||
// 交换链呈现新帧后再清除 DirectCompostion 内容,确保无缝切换
|
||||
_isSwitchingToSwapChain = true;
|
||||
}
|
||||
|
|
@ -267,16 +271,10 @@ bool AdaptivePresenter::_ResizeSwapChain() noexcept {
|
|||
}
|
||||
|
||||
bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept {
|
||||
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
|
||||
|
||||
if (_dcompSurface) {
|
||||
// 使用 IDCompositionVirtualSurface 而不是 IDCompositionSurface 的原因是
|
||||
// IDCompositionDevice2::CreateSurface 有时相当慢,最坏情况下要几十毫秒。
|
||||
HRESULT hr = _dcompSurface->Resize((UINT)rendererSize.cx, (UINT)rendererSize.cy);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("Resize 失败", hr);
|
||||
return false;
|
||||
}
|
||||
if (_dcompVisual) {
|
||||
// 先释放旧表面
|
||||
_dcompVisual->SetContent(nullptr);
|
||||
_dcompSurface = nullptr;
|
||||
} else {
|
||||
// 初始化 DirectComposition
|
||||
HRESULT hr = DCompositionCreateDevice3(
|
||||
|
|
@ -312,23 +310,26 @@ bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept {
|
|||
Logger::Get().ComError("SetRoot 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompDevice->CreateVirtualSurface(
|
||||
(UINT)rendererSize.cx,
|
||||
(UINT)rendererSize.cy,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
DXGI_ALPHA_MODE_IGNORE,
|
||||
_dcompSurface.put()
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateVirtualSurface 失败", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT hr = _dcompVisual->SetContent(_dcompSurface.get());
|
||||
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
|
||||
HRESULT hr = _dcompDevice->CreateSurface(
|
||||
(UINT)rendererSize.cx,
|
||||
(UINT)rendererSize.cy,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
DXGI_ALPHA_MODE_IGNORE,
|
||||
_dcompSurface.put()
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateSurface 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompVisual->SetContent(_dcompSurface.get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetContent 失败", hr);
|
||||
// 失败时确保 _dcompSurface 为空
|
||||
_dcompSurface = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Magpie {
|
|||
// 根据需要在交换链和 DirectComposition 两种呈现方式间切换。交换链可以触发
|
||||
// DirectFlip/IndependentFlip 以最小化延迟,DirectComposition 在调整尺寸
|
||||
// 时闪烁更少,这个呈现器旨在结合两者的优势。
|
||||
class AdaptivePresenter final : public PresenterBase {
|
||||
class AdaptivePresenter : public PresenterBase {
|
||||
protected:
|
||||
bool _Initialize(HWND hwndAttach) noexcept override;
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ public:
|
|||
POINT& drawOffset
|
||||
) noexcept override;
|
||||
|
||||
void EndFrame(bool waitForGpu = false) noexcept override;
|
||||
void EndFrame(bool waitForRenderComplete = false) noexcept override;
|
||||
|
||||
bool OnResize() noexcept override;
|
||||
|
||||
|
|
@ -38,9 +38,8 @@ private:
|
|||
winrt::com_ptr<IDCompositionDesktopDevice> _dcompDevice;
|
||||
winrt::com_ptr<IDCompositionTarget> _dcompTarget;
|
||||
winrt::com_ptr<IDCompositionVisual2> _dcompVisual;
|
||||
winrt::com_ptr<IDCompositionVirtualSurface> _dcompSurface;
|
||||
|
||||
bool _isDCompPresenting = false;
|
||||
winrt::com_ptr<IDCompositionSurface> _dcompSurface;
|
||||
|
||||
bool _isResized = false;
|
||||
bool _isframeLatencyWaited = false;
|
||||
bool _isSwitchingToSwapChain = false;
|
||||
|
|
|
|||
|
|
@ -2,21 +2,20 @@
|
|||
#include "CompSwapchainPresenter.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static winrt::com_ptr<IPresentationFactory> CreatePresentationFactory(ID3D11Device* d3dDevice) noexcept {
|
||||
winrt::com_ptr<IPresentationFactory> result;
|
||||
static const auto createPresentationFactory = []() {
|
||||
HMODULE hDcomp = GetModuleHandle(L"dcomp.dll");
|
||||
assert(hDcomp);
|
||||
return (decltype(::CreatePresentationFactory)*)GetProcAddress(
|
||||
hDcomp, "CreatePresentationFactory");
|
||||
}();
|
||||
|
||||
static const auto createPresentationFactory =
|
||||
Win32Helper::LoadSystemFunction<decltype(::CreatePresentationFactory)>(
|
||||
L"dcomp.dll", "CreatePresentationFactory");
|
||||
if (!createPresentationFactory) {
|
||||
return result;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IPresentationFactory> result;
|
||||
HRESULT hr = createPresentationFactory(d3dDevice, IID_PPV_ARGS(&result));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreatePresentationFactory 失败", hr);
|
||||
|
|
@ -31,7 +30,9 @@ bool CompSwapchainPresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
HRESULT hr = DCompositionCreateDevice3(nullptr, IID_PPV_ARGS(&_dcompDevice));
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
|
||||
HRESULT hr = DCompositionCreateDevice3(d3dDevice, IID_PPV_ARGS(&_dcompDevice));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DCompositionCreateDevice3 失败", hr);
|
||||
return false;
|
||||
|
|
@ -56,7 +57,7 @@ bool CompSwapchainPresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
}
|
||||
|
||||
winrt::com_ptr<IPresentationFactory> presentationFactory =
|
||||
CreatePresentationFactory(_deviceResources->GetD3DDevice());
|
||||
CreatePresentationFactory(d3dDevice);
|
||||
if (!presentationFactory) {
|
||||
Logger::Get().Error("CreatePresentationFactory 失败");
|
||||
return false;
|
||||
|
|
@ -226,15 +227,15 @@ bool CompSwapchainPresenter::BeginFrame(
|
|||
return true;
|
||||
}
|
||||
|
||||
void CompSwapchainPresenter::EndFrame(bool waitForGpu) noexcept {
|
||||
if (waitForGpu || _isResized) {
|
||||
void CompSwapchainPresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
||||
if (waitForRenderComplete || _isResized) {
|
||||
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁,参见 AdaptivePresenter::EndFrame
|
||||
|
||||
// 等待渲染完成
|
||||
_WaitForGpu();
|
||||
_WaitForRenderComplete();
|
||||
|
||||
// 等待 DWM 开始合成新一帧
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
_WaitForDwmComposition();
|
||||
}
|
||||
|
||||
_presentationManager->Present();
|
||||
|
|
@ -243,7 +244,7 @@ void CompSwapchainPresenter::EndFrame(bool waitForGpu) noexcept {
|
|||
_isResized = false;
|
||||
} else {
|
||||
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
|
||||
_WaitForGpu();
|
||||
_WaitForRenderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
class CompSwapchainPresenter final : public PresenterBase {
|
||||
class CompSwapchainPresenter : public PresenterBase {
|
||||
protected:
|
||||
bool _Initialize(HWND hwndAttach) noexcept override;
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ public:
|
|||
POINT& drawOffset
|
||||
) noexcept override;
|
||||
|
||||
void EndFrame(bool waitForGpu = false) noexcept override;
|
||||
void EndFrame(bool waitForRenderComplete = false) noexcept override;
|
||||
|
||||
bool OnResize() noexcept override;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
#include "pch.h"
|
||||
#include "CursorDrawer.h"
|
||||
#include "CursorManager.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "Renderer.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "shaders/SimpleVS.h"
|
||||
#include "shaders/SimplePS.h"
|
||||
#include "shaders/MaskedCursorPS.h"
|
||||
#include "shaders/MonochromeCursorPS.h"
|
||||
#include "shaders/SimplePS.h"
|
||||
#include "shaders/SimpleVS.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <DirectXMath.h>
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Renderer.h"
|
||||
#include "CursorManager.h"
|
||||
#include "StrHelper.h"
|
||||
|
||||
using namespace DirectX;
|
||||
|
||||
|
|
@ -39,8 +40,9 @@ struct VertexPositionTexture {
|
|||
XMFLOAT2 position;
|
||||
XMFLOAT2 textureCoordinate;
|
||||
|
||||
static constexpr D3D11_INPUT_ELEMENT_DESC InputElements[] = {
|
||||
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
static constexpr D3D11_INPUT_ELEMENT_DESC InputElements[] =
|
||||
{
|
||||
{ "SV_POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
};
|
||||
};
|
||||
|
|
@ -85,64 +87,63 @@ bool CursorDrawer::Initialize(DeviceResources& deviceResources) noexcept {
|
|||
}
|
||||
|
||||
void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept {
|
||||
const ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
|
||||
bool isCursorActive = false;
|
||||
const auto [cursorHandle, cursorPos] = _GetCursorState(isCursorActive);
|
||||
|
||||
if (isCursorActive) {
|
||||
// 启用自动隐藏时光标形状或位置变化后应记录新的形状、位置和变化时间。位置由
|
||||
// _lastCursorPos 记录。
|
||||
_lastRawCursorHandle = scalingWindow.CursorManager().CursorHandle();
|
||||
_lastCursorActiveTime = std::chrono::steady_clock::now();
|
||||
if (!_isCursorVisible) {
|
||||
// 截屏时暂时不渲染光标
|
||||
return;
|
||||
}
|
||||
|
||||
_lastCursorHandle = cursorHandle;
|
||||
const CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
const HCURSOR hCursor = cursorManager.CursorHandle();
|
||||
POINT cursorPos = cursorManager.CursorPos();
|
||||
|
||||
_lastCursorHandle = NULL;
|
||||
|
||||
if (!hCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _CursorInfo* ci = _ResolveCursor(hCursor);
|
||||
if (!ci) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 转换为渲染矩形局部坐标
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
cursorPos.x -= rendererRect.left;
|
||||
cursorPos.y -= rendererRect.top;
|
||||
|
||||
_lastCursorHandle = hCursor;
|
||||
_lastCursorPos = cursorPos;
|
||||
|
||||
if (!cursorHandle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _CursorInfo* cursorInfo = _ResolveCursor(cursorHandle);
|
||||
if (!cursorInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ScalingOptions& options = scalingWindow.Options();
|
||||
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
float cursorScaling = options.cursorScaling;
|
||||
if (cursorScaling < FLOAT_EPSILON<float>) {
|
||||
// 光标缩放和源窗口相同
|
||||
const Renderer& renderer = scalingWindow.Renderer();
|
||||
const Renderer& renderer = ScalingWindow::Get().Renderer();
|
||||
const SIZE srcSize = Win32Helper::GetSizeOfRect(renderer.SrcRect());
|
||||
const SIZE destSize = Win32Helper::GetSizeOfRect(renderer.DestRect());
|
||||
cursorScaling = (((float)destSize.cx / srcSize.cx) + ((float)destSize.cy / srcSize.cy)) / 2;
|
||||
}
|
||||
|
||||
const SIZE cursorSize{
|
||||
lroundf(cursorInfo->size.cx * cursorScaling),
|
||||
lroundf(cursorInfo->size.cy * cursorScaling)
|
||||
lroundf(ci->size.cx * cursorScaling),
|
||||
lroundf(ci->size.cy * cursorScaling)
|
||||
};
|
||||
RECT cursorRect{
|
||||
.left = lroundf(cursorPos.x - cursorInfo->hotSpot.x * cursorScaling),
|
||||
.top = lroundf(cursorPos.y - cursorInfo->hotSpot.y * cursorScaling),
|
||||
.left = lroundf(cursorPos.x - ci->hotSpot.x * cursorScaling),
|
||||
.top = lroundf(cursorPos.y - ci->hotSpot.y * cursorScaling),
|
||||
.right = cursorRect.left + cursorSize.cx,
|
||||
.bottom = cursorRect.top + cursorSize.cy
|
||||
};
|
||||
|
||||
RECT viewportRect;
|
||||
{
|
||||
const RECT& rendererRect = scalingWindow.RendererRect();
|
||||
const RECT& destRect = scalingWindow.Renderer().DestRect();
|
||||
|
||||
viewportRect = {
|
||||
destRect.left - rendererRect.left,
|
||||
destRect.top - rendererRect.top,
|
||||
destRect.right - rendererRect.left,
|
||||
destRect.bottom - rendererRect.top
|
||||
};
|
||||
}
|
||||
const bool isSrcFocused = ScalingWindow::Get().SrcTracker().IsFocused();
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
const RECT viewportRect{
|
||||
isSrcFocused ? destRect.left - rendererRect.left : 0,
|
||||
isSrcFocused ? destRect.top - rendererRect.top : 0,
|
||||
(isSrcFocused ? destRect.right : rendererRect.right) - rendererRect.left,
|
||||
(isSrcFocused ? destRect.bottom : rendererRect.bottom) - rendererRect.top
|
||||
};
|
||||
|
||||
if (cursorRect.left >= viewportRect.right ||
|
||||
cursorRect.top >= viewportRect.bottom ||
|
||||
|
|
@ -202,7 +203,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
d3dDC->RSSetState(nullptr);
|
||||
}
|
||||
|
||||
if (cursorInfo->type == _CursorType::Color) {
|
||||
if (ci->type == _CursorType::Color) {
|
||||
// 配置像素着色器
|
||||
if (!_simplePS) {
|
||||
HRESULT hr = _deviceResources->GetD3DDevice()->CreatePixelShader(
|
||||
|
|
@ -215,7 +216,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
|
||||
d3dDC->PSSetShader(_simplePS.get(), nullptr, 0);
|
||||
d3dDC->PSSetConstantBuffers(0, 0, nullptr);
|
||||
ID3D11ShaderResourceView* cursorSrv = cursorInfo->textureSrv.get();
|
||||
ID3D11ShaderResourceView* cursorSrv = ci->textureSrv.get();
|
||||
d3dDC->PSSetShaderResources(0, 1, &cursorSrv);
|
||||
|
||||
const bool useBilinear = options.cursorInterpolationMode == CursorInterpolationMode::Bilinear &&
|
||||
|
|
@ -259,26 +260,26 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
_tempCursorTextureSize = cursorSize;
|
||||
}
|
||||
|
||||
{
|
||||
D3D11_BOX srcBox{
|
||||
UINT(std::max(cursorRect.left, viewportRect.left) + drawOffset.x),
|
||||
UINT(std::max(cursorRect.top, viewportRect.top) + drawOffset.y),
|
||||
0,
|
||||
UINT(std::min(cursorRect.right, viewportRect.right) + drawOffset.x),
|
||||
UINT(std::min(cursorRect.bottom, viewportRect.bottom) + drawOffset.y),
|
||||
1
|
||||
};
|
||||
UINT destLeft = UINT(std::max(0l, viewportRect.left - cursorRect.left));
|
||||
UINT destTop = UINT(std::max(0l, viewportRect.top - cursorRect.top));
|
||||
D3D11_BOX srcBox{
|
||||
UINT(std::max(cursorRect.left, viewportRect.left) + drawOffset.x),
|
||||
UINT(std::max(cursorRect.top, viewportRect.top) + drawOffset.y),
|
||||
0,
|
||||
UINT(std::min(cursorRect.right, viewportRect.right) + drawOffset.x),
|
||||
UINT(std::min(cursorRect.bottom, viewportRect.bottom) + drawOffset.y),
|
||||
1
|
||||
};
|
||||
d3dDC->CopySubresourceRegion(
|
||||
_tempCursorTexture.get(),
|
||||
0,
|
||||
UINT(std::max(0l, viewportRect.left - cursorRect.left)) ,
|
||||
UINT(std::max(0l, viewportRect.top - cursorRect.left)),
|
||||
0,
|
||||
backBuffer,
|
||||
0,
|
||||
&srcBox
|
||||
);
|
||||
|
||||
assert(LONG(destLeft + srcBox.right - srcBox.left) <= cursorSize.cx);
|
||||
assert(LONG(destTop + srcBox.bottom - srcBox.top) <= cursorSize.cy);
|
||||
|
||||
d3dDC->CopySubresourceRegion(_tempCursorTexture.get(),
|
||||
0, destLeft, destTop, 0, backBuffer, 0, &srcBox);
|
||||
}
|
||||
|
||||
if (cursorInfo->type == _CursorType::MaskedColor) {
|
||||
if (ci->type == _CursorType::MaskedColor) {
|
||||
if (!_maskedCursorPS) {
|
||||
HRESULT hr = _deviceResources->GetD3DDevice()->CreatePixelShader(
|
||||
MaskedCursorPS, sizeof(MaskedCursorPS), nullptr, _maskedCursorPS.put());
|
||||
|
|
@ -303,7 +304,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
d3dDC->PSSetConstantBuffers(0, 0, nullptr);
|
||||
|
||||
{
|
||||
ID3D11ShaderResourceView* srvs[2]{ _tempCursorTextureRtv.get(), cursorInfo->textureSrv.get() };
|
||||
ID3D11ShaderResourceView* srvs[2]{ _tempCursorTextureRtv.get(), ci->textureSrv.get() };
|
||||
d3dDC->PSSetShaderResources(0, 2, srvs);
|
||||
}
|
||||
|
||||
|
|
@ -319,53 +320,25 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
}
|
||||
|
||||
bool CursorDrawer::NeedRedraw() const noexcept {
|
||||
bool isCursorActive = false;
|
||||
const auto [cursorHandle, cursorPos] = _GetCursorState(isCursorActive);
|
||||
// 光标形状或位置变化时需要重新绘制
|
||||
return cursorHandle != _lastCursorHandle || (cursorHandle && cursorPos != _lastCursorPos);
|
||||
}
|
||||
|
||||
std::pair<HCURSOR, POINT> CursorDrawer::_GetCursorState(bool& isActive) const noexcept {
|
||||
assert(!isActive);
|
||||
using namespace std::chrono;
|
||||
|
||||
const ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
const ScalingOptions& options = scalingWindow.Options();
|
||||
|
||||
const CursorManager& cursorManager = scalingWindow.CursorManager();
|
||||
HCURSOR cursorHandle = cursorManager.CursorHandle();
|
||||
const CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
const HCURSOR hCursor = cursorManager.CursorHandle();
|
||||
POINT cursorPos = cursorManager.CursorPos();
|
||||
// 转换为渲染矩形局部坐标
|
||||
const RECT& rendererRect = scalingWindow.RendererRect();
|
||||
|
||||
// 检查光标形状是否变化
|
||||
if (hCursor != _lastCursorHandle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hCursor) {
|
||||
// 一直不可见
|
||||
return false;
|
||||
}
|
||||
|
||||
// 光标可见则检查位置是否变化。为了适配缩放窗口位置变化,比较在缩放窗口中的相对位置
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
cursorPos.x -= rendererRect.left;
|
||||
cursorPos.y -= rendererRect.top;
|
||||
|
||||
// 检查自动隐藏光标
|
||||
if (options.autoHideCursorDelay.has_value()) {
|
||||
// 光标在叠加层上或拖动窗口时禁用自动隐藏。光标处于隐藏状态视为形状不变,考虑形状
|
||||
// 变化:箭头->隐藏->箭头,只要位置不变,自动隐藏功能应让光标始终隐藏;反之如果光
|
||||
// 标隐藏时移动了或显示时形状变化了应正常显示。
|
||||
if (cursorManager.IsCursorCaptured() &&
|
||||
!scalingWindow.IsResizingOrMoving() &&
|
||||
!scalingWindow.SrcTracker().IsMoving() &&
|
||||
_lastCursorPos == cursorPos &&
|
||||
(_lastRawCursorHandle == cursorHandle || !cursorHandle))
|
||||
{
|
||||
const duration<float> hideDelay(*options.autoHideCursorDelay);
|
||||
if (steady_clock::now() - _lastCursorActiveTime > hideDelay) {
|
||||
cursorHandle = NULL;
|
||||
}
|
||||
} else {
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 截屏时暂时不渲染光标
|
||||
if (!_isCursorVisible) {
|
||||
cursorHandle = NULL;
|
||||
}
|
||||
|
||||
return { cursorHandle, cursorPos };
|
||||
return cursorPos != _lastCursorPos;
|
||||
}
|
||||
|
||||
const CursorDrawer::_CursorInfo* CursorDrawer::_ResolveCursor(HCURSOR hCursor) noexcept {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#include "ScalingOptions.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -26,8 +27,6 @@ public:
|
|||
bool NeedRedraw() const noexcept;
|
||||
|
||||
private:
|
||||
std::pair<HCURSOR, POINT> _GetCursorState(bool& isActive) const noexcept;
|
||||
|
||||
enum class _CursorType {
|
||||
// 彩色光标,此时纹理中 RGB 通道已预乘 A 通道(premultiplied alpha),A 通道已预先取反
|
||||
// 这是为了减少着色器的计算量以及确保(可能进行的)双线性差值的准确性
|
||||
|
|
@ -72,10 +71,6 @@ private:
|
|||
winrt::com_ptr<ID3D11ShaderResourceView> _tempCursorTextureRtv;
|
||||
SIZE _tempCursorTextureSize{};
|
||||
|
||||
// 这两个成员用于检查自动隐藏光标
|
||||
HCURSOR _lastRawCursorHandle = NULL;
|
||||
std::chrono::steady_clock::time_point _lastCursorActiveTime;
|
||||
// 上次绘制的光标形状和位置
|
||||
HCURSOR _lastCursorHandle = NULL;
|
||||
POINT _lastCursorPos{ std::numeric_limits<LONG>::max(), std::numeric_limits<LONG>::max() };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#include "pch.h"
|
||||
#include "CursorManager.h"
|
||||
#include "Logger.h"
|
||||
#include "Renderer.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <dwmapi.h>
|
||||
#include "Renderer.h"
|
||||
#include <magnification.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -101,13 +101,12 @@ CursorManager::~CursorManager() noexcept {
|
|||
|
||||
if (_isUnderCapture) {
|
||||
POINT cursorPos;
|
||||
if (GetCursorPos(&cursorPos)) {
|
||||
_StopCapture(cursorPos, true);
|
||||
_ReliableSetCursorPos(cursorPos);
|
||||
} else {
|
||||
if (!GetCursorPos(&cursorPos)) {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
_RestoreClipCursor();
|
||||
}
|
||||
|
||||
_StopCapture(cursorPos, true);
|
||||
_ReliableSetCursorPos(cursorPos);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,8 +200,11 @@ void CursorManager::_ShowSystemCursor(bool show, bool onDestory) {
|
|||
return;
|
||||
}
|
||||
|
||||
static const auto showSystemCursor =
|
||||
Win32Helper::LoadSystemFunction<void WINAPI(BOOL)>(L"user32.dll", "ShowSystemCursor");
|
||||
static void (WINAPI* const showSystemCursor)(BOOL bShow) = []()->void(WINAPI*)(BOOL) {
|
||||
HMODULE hUser32 = GetModuleHandle(L"user32.dll");
|
||||
assert(hUser32);
|
||||
return (void(WINAPI*)(BOOL))GetProcAddress(hUser32, "ShowSystemCursor");
|
||||
}();
|
||||
|
||||
if (showSystemCursor) {
|
||||
showSystemCursor((BOOL)show);
|
||||
|
|
@ -396,7 +398,7 @@ static bool PtInWindow(HWND hWnd, POINT pt) noexcept {
|
|||
// 也会考虑自定义形状的窗口。反之如果位于非客户区,我们需手动处理后者。
|
||||
//
|
||||
// 可以参考 ChildWindowFromPointEx 的实现:
|
||||
// https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/winwhere.c#L47
|
||||
// https://github.com/tongzx/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/winwhere.c#L47
|
||||
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(hWnd, clientRect)) {
|
||||
|
|
@ -580,6 +582,7 @@ void CursorManager::_UpdateCursorState() noexcept {
|
|||
|
||||
POINT cursorPos;
|
||||
if (!GetCursorPos(&cursorPos)) {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
_RestoreClipCursor();
|
||||
return;
|
||||
}
|
||||
|
|
@ -966,7 +969,7 @@ void CursorManager::_ClipCursorOnSrcMoving() noexcept {
|
|||
if (!monitorRects.empty()) {
|
||||
// 移动源窗口时,如果只有一个显示器,应将光标限制在工作矩形内。一旦超出工作矩形,
|
||||
// 源窗口将无法继续移动。还需检查窗口样式,以和 OS 保持一致,见
|
||||
// https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L1142
|
||||
// https://github.com/tongzx/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L1142
|
||||
if (monitorRects.size() == 1) {
|
||||
const DWORD exStyle = GetWindowExStyle(ScalingWindow::Get().SrcTracker().Handle());
|
||||
if ((exStyle & (WS_EX_TOPMOST | WS_EX_TOOLWINDOW)) == 0) {
|
||||
|
|
@ -1111,6 +1114,7 @@ void CursorManager::_StartCapture(POINT& cursorPos) noexcept {
|
|||
}
|
||||
|
||||
const Renderer& renderer = ScalingWindow::Get().Renderer();
|
||||
const RECT& srcRect = renderer.SrcRect();
|
||||
const RECT& destRect = renderer.DestRect();
|
||||
|
||||
// 在以下情况下进入捕获状态:
|
||||
|
|
@ -1123,6 +1127,9 @@ void CursorManager::_StartCapture(POINT& cursorPos) noexcept {
|
|||
//
|
||||
// 在有黑边的情况下自动将光标调整到画面内
|
||||
|
||||
SIZE srcFrameSize = Win32Helper::GetSizeOfRect(srcRect);
|
||||
SIZE outputSize = Win32Helper::GetSizeOfRect(destRect);
|
||||
|
||||
_AdjustCursorSpeed();
|
||||
|
||||
// 移动光标位置,应跳过黑边
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public:
|
|||
}
|
||||
void IsCursorCapturedOnOverlay(bool value) noexcept;
|
||||
|
||||
int16_t SrcHitTest() const noexcept {
|
||||
const int16_t SrcHitTest() const noexcept {
|
||||
return _lastCompletedHitTestResult;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include "pch.h"
|
||||
#include "DDSHelper.h"
|
||||
#include "DDShelper.h"
|
||||
#include "DDS.h"
|
||||
#include "Logger.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#include "pch.h"
|
||||
#include "DesktopDuplicationFrameSource.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "SmallVector.h"
|
||||
#include "Win32Helper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "FrameSourceBase.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "SmallVector.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
#include "pch.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "ScalingWindow.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include "ScalingOptions.h"
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#include "ScalingOptions.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include "pch.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include <d3dcompiler.h>
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include <d3dcompiler.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "DwmSharedSurfaceFrameSource.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "DeviceResources.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -17,18 +16,18 @@ using DwmGetDxSharedSurfaceFunc = BOOL(
|
|||
ULONGLONG* pWin32KUpdateId
|
||||
);
|
||||
|
||||
static DwmGetDxSharedSurfaceFunc* DwmGetDxSharedSurface = nullptr;
|
||||
static DwmGetDxSharedSurfaceFunc* dwmGetDxSharedSurface = nullptr;
|
||||
|
||||
bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
|
||||
[[maybe_unused]] static Ignore _ = [] {
|
||||
DwmGetDxSharedSurface = Win32Helper::LoadSystemFunction<DwmGetDxSharedSurfaceFunc>(
|
||||
L"user32.dll", "DwmGetDxSharedSurface");
|
||||
return Ignore();
|
||||
}();
|
||||
if (!dwmGetDxSharedSurface) {
|
||||
HMODULE hUser32 = GetModuleHandle(L"user32.dll");
|
||||
assert(hUser32);
|
||||
dwmGetDxSharedSurface = (DwmGetDxSharedSurfaceFunc*)GetProcAddress(hUser32, "DwmGetDxSharedSurface");
|
||||
|
||||
if (!DwmGetDxSharedSurface) {
|
||||
Logger::Get().Win32Error("获取函数 DwmGetDxSharedSurface 地址失败");
|
||||
return false;
|
||||
if (!dwmGetDxSharedSurface) {
|
||||
Logger::Get().Win32Error("获取函数 DwmGetDxSharedSurface 地址失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const SrcTracker& srcTracker = ScalingWindow::Get().SrcTracker();
|
||||
|
|
@ -86,7 +85,7 @@ bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
|
|||
|
||||
FrameSourceState DwmSharedSurfaceFrameSource::_Update() noexcept {
|
||||
HANDLE sharedTextureHandle = NULL;
|
||||
if (!DwmGetDxSharedSurface(ScalingWindow::Get().SrcTracker().Handle(),
|
||||
if (!dwmGetDxSharedSurface(ScalingWindow::Get().SrcTracker().Handle(),
|
||||
&sharedTextureHandle, nullptr, nullptr, nullptr, nullptr)
|
||||
|| !sharedTextureHandle
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
#include "pch.h"
|
||||
#include "EffectCacheManager.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "YasHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include <d3dcompiler.h>
|
||||
#include <rapidhash.h>
|
||||
#include "YasHelper.h"
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
|
|
@ -256,7 +255,7 @@ void EffectCacheManager::Save(
|
|||
int i = 6;
|
||||
for (; i < 22; ++i) {
|
||||
const wchar_t c = fileName[effectNameLen + i];
|
||||
if (!((c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f'))) {
|
||||
if (!(c >= L'0' && c <= L'9' || c >= L'a' && c <= L'f')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +271,7 @@ void EffectCacheManager::Save(
|
|||
int i = 1;
|
||||
for (; i < 18; ++i) {
|
||||
const wchar_t c = fileName[effectNameLen + i];
|
||||
if (!((c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f'))) {
|
||||
if (!(c >= L'0' && c <= L'9' || c >= L'a' && c <= L'f')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include "Win32Helper.h"
|
||||
#include "EffectDesc.h"
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "EffectCompiler.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "EffectCacheManager.h"
|
||||
#include "EffectDesc.h"
|
||||
#include "EffectHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <bit> // std::has_single_bit
|
||||
#include <bitset>
|
||||
#include <charconv>
|
||||
#include "EffectCacheManager.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include <bit> // std::has_single_bit
|
||||
#include "DirectXHelper.h"
|
||||
#include "EffectHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "EffectDesc.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -1159,23 +1160,21 @@ static uint32_t GeneratePassSource(
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// SRV
|
||||
for (uint32_t i = 0, end = (uint32_t)passDesc.inputs.size(); i < end; ++i) {
|
||||
for (int i = 0; i < passDesc.inputs.size(); ++i) {
|
||||
auto& texDesc = desc.textures[passDesc.inputs[i]];
|
||||
result.append(fmt::format("Texture2D<{}> {} : register(t{});\n",
|
||||
EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].srvTexelType, texDesc.name, i));
|
||||
result.append(fmt::format("Texture2D<{}> {} : register(t{});\n", EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].srvTexelType, texDesc.name, i));
|
||||
}
|
||||
|
||||
// UAV
|
||||
for (uint32_t i = 0, end = (uint32_t)passDesc.outputs.size(); i < end; ++i) {
|
||||
for (int i = 0; i < passDesc.outputs.size(); ++i) {
|
||||
auto& texDesc = desc.textures[passDesc.outputs[i]];
|
||||
result.append(fmt::format("RWTexture2D<{}> {} : register(u{});\n",
|
||||
EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].uavTexelType, texDesc.name, i));
|
||||
result.append(fmt::format("RWTexture2D<{}> {} : register(u{});\n", EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].uavTexelType, texDesc.name, i));
|
||||
}
|
||||
|
||||
|
||||
if (!desc.samplers.empty()) {
|
||||
// 采样器
|
||||
for (uint32_t i = 0, end = (uint32_t)desc.samplers.size(); i < end; ++i) {
|
||||
for (int i = 0; i < desc.samplers.size(); ++i) {
|
||||
result.append(fmt::format("SamplerState {} : register(s{});\n", desc.samplers[i].name, i));
|
||||
}
|
||||
}
|
||||
|
|
@ -1345,9 +1344,7 @@ MF4 MulAdd(MF4 x, MF4x4 y, MF4 a) {
|
|||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if (passDesc.flags & EffectPassFlags::PSStyle) {
|
||||
const uint32_t outputCount = (uint32_t)passDesc.outputs.size();
|
||||
|
||||
if (outputCount <= 1u) {
|
||||
if (passDesc.outputs.size() <= 1) {
|
||||
std::string outputSize;
|
||||
std::string outputPt;
|
||||
if (passIdx == desc.passes.size()) {
|
||||
|
|
@ -1400,7 +1397,7 @@ void __M(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {{
|
|||
float2 pos = (gxy + 0.5f) * __pass{0}OutputPt;
|
||||
float2 step = 8 * __pass{0}OutputPt;
|
||||
)", passIdx));
|
||||
for (uint32_t i = 0; i < outputCount; ++i) {
|
||||
for (int i = 0; i < passDesc.outputs.size(); ++i) {
|
||||
auto& texDesc = desc.textures[passDesc.outputs[i]];
|
||||
result.append(fmt::format("\t{} c{};\n",
|
||||
EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].srvTexelType, i));
|
||||
|
|
@ -1408,13 +1405,12 @@ void __M(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {{
|
|||
|
||||
std::string callPass = fmt::format("\tPass{}(pos, ", passIdx);
|
||||
|
||||
for (uint32_t i = 0, end = outputCount - 1; i < end; ++i) {
|
||||
for (int i = 0; i < passDesc.outputs.size() - 1; ++i) {
|
||||
callPass.append(fmt::format("c{}, ", i));
|
||||
}
|
||||
callPass.append(fmt::format("c{});\n", outputCount - 1));
|
||||
for (uint32_t i = 0; i < outputCount; ++i) {
|
||||
callPass.append(fmt::format("\t\t\t{}[gxy] = c{};\n",
|
||||
desc.textures[passDesc.outputs[i]].name, i));
|
||||
callPass.append(fmt::format("c{});\n", passDesc.outputs.size() - 1));
|
||||
for (int i = 0; i < passDesc.outputs.size(); ++i) {
|
||||
callPass.append(fmt::format("\t\t\t{}[gxy] = c{};\n", desc.textures[passDesc.outputs[i]].name, i));
|
||||
}
|
||||
|
||||
result.append(fmt::format(R"({0}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "EffectDrawer.h"
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "EffectHelper.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "Logger.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "StrHelper.h"
|
||||
#include "TextureHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "EffectHelper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include "EffectsProfiler.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#pragma once
|
||||
#include "EffectDesc.h"
|
||||
#include "SmallVector.h"
|
||||
// Conan 的 muparser 不含 UNICODE 支持
|
||||
#include "EffectHelper.h"
|
||||
|
||||
#pragma push_macro("_UNICODE")
|
||||
// Conan 的 muparser 不含 UNICODE 支持
|
||||
#undef _UNICODE
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4310) // 类型强制转换截断常量值
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <dxgi.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "pch.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "DeviceResources.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "SmallVector.h"
|
||||
#include "Win32Helper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include "Win32Helper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#include "pch.h"
|
||||
#include "FrameSourceBase.h"
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "shaders/DuplicateFrameCS.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "SmallVector.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "shaders/DuplicateFrameCS.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include <dwmapi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -72,24 +72,21 @@ protected:
|
|||
winrt::com_ptr<ID3D11ComputeShader> _dupFrameCS;
|
||||
std::pair<uint32_t, uint32_t> _dispatchCount;
|
||||
|
||||
bool _roundCornerDisabled = false;
|
||||
|
||||
private:
|
||||
bool _InitCheckingForDuplicateFrame();
|
||||
|
||||
bool _IsDuplicateFrame();
|
||||
|
||||
// (预测错误帧数, 总计跳过帧数)
|
||||
std::atomic<std::pair<uint32_t, uint32_t>> _statistics;
|
||||
|
||||
// 用于检查重复帧
|
||||
winrt::com_ptr<ID3D11Texture2D> _prevFrame;
|
||||
winrt::com_ptr<ID3D11ShaderResourceView> _prevFrameSrv;
|
||||
uint16_t _nextSkipCount;
|
||||
uint16_t _framesLeft;
|
||||
|
||||
// (预测错误帧数, 总计跳过帧数)
|
||||
std::atomic<std::pair<uint32_t, uint32_t>> _statistics;
|
||||
bool _isCheckingForDuplicateFrame = true;
|
||||
|
||||
protected:
|
||||
bool _roundCornerDisabled = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "GDIFrameSource.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "ScalingWindow.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#include "pch.h"
|
||||
#include "GraphicsCaptureFrameSource.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <dwmapi.h>
|
||||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
|
||||
#include "Win32Helper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include <dwmapi.h>
|
||||
|
||||
namespace winrt {
|
||||
using namespace Windows::Graphics;
|
||||
|
|
@ -90,20 +91,11 @@ FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept {
|
|||
return FrameSourceState::Waiting;
|
||||
}
|
||||
|
||||
// 取最新帧,帧率较低时可以有效降低延迟
|
||||
while (true) {
|
||||
if (winrt::Direct3D11CaptureFrame nextFrame = _captureFramePool.TryGetNextFrame()) {
|
||||
frame = std::move(nextFrame);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 从帧获取 IDXGISurface
|
||||
winrt::IDirect3DSurface d3dSurface = frame.Surface();
|
||||
|
||||
winrt::com_ptr<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess> dxgiInterfaceAccess(
|
||||
d3dSurface.try_as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()
|
||||
d3dSurface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()
|
||||
);
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> withFrame;
|
||||
|
|
@ -184,26 +176,6 @@ static bool CalcWindowCapturedFrameBounds(HWND hWnd, RECT& rect) noexcept {
|
|||
return true;
|
||||
}
|
||||
|
||||
// 部分使用 Kirikiri 引擎的游戏有着这样的架构: 游戏窗口并非顶级窗口,而是被一个零尺寸
|
||||
// 的窗口所有。此时 Alt+Tab 列表中的窗口和任务栏图标实际上是所有者窗口,这会导致 WGC
|
||||
// 捕获失败。我们特殊处理这类窗口。
|
||||
static bool IsKirikiriWindow(HWND hwndSrc) noexcept {
|
||||
const HWND hwndOwner = GetWindowOwner(hwndSrc);
|
||||
if (!hwndOwner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT ownerRect;
|
||||
if (!GetWindowRect(hwndOwner, &ownerRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 所有者窗口尺寸为零,而且是顶级窗口
|
||||
return ownerRect.left == ownerRect.right && ownerRect.top == ownerRect.bottom &&
|
||||
!GetWindowOwner(hwndOwner);
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* interop) noexcept {
|
||||
const SrcTracker& srcTracker = ScalingWindow::Get().SrcTracker();
|
||||
const HWND hwndSrc = srcTracker.Handle();
|
||||
|
|
@ -231,67 +203,64 @@ bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* int
|
|||
1
|
||||
};
|
||||
|
||||
const DWORD srcExStyle = GetWindowExStyle(hwndSrc);
|
||||
// WS_EX_APPWINDOW 样式使窗口始终在 Alt+Tab 列表中显示
|
||||
if (srcExStyle & WS_EX_APPWINDOW) {
|
||||
return _TryCreateGraphicsCaptureItem(interop);
|
||||
}
|
||||
|
||||
const bool isSrcKirikiri = IsKirikiriWindow(hwndSrc);
|
||||
if (isSrcKirikiri) {
|
||||
Logger::Get().Info("源窗口有零尺寸的所有者窗口");
|
||||
} else {
|
||||
// 第一次尝试捕获。Kirikiri 窗口必定失败,无需尝试
|
||||
if (_TryCreateGraphicsCaptureItem(interop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 WS_EX_APPWINDOW 样式
|
||||
if (!SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, srcExStyle | WS_EX_APPWINDOW)) {
|
||||
Logger::Get().Win32Error("SetWindowLongPtr 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger::Get().Info("已改变源窗口样式");
|
||||
_isSrcStyleChanged = true;
|
||||
|
||||
// Kirikiri 窗口改变样式后所有者窗口和游戏窗口将同时出现在 Alt+Tab 列表和任务栏中。
|
||||
// 虽然所有窗口都会如此,但 Kirikiri 的特殊之处在于两个窗口的图标和标题相同,为了不
|
||||
// 引起困惑应隐藏所有者窗口的图标。
|
||||
if (isSrcKirikiri) {
|
||||
_taskbarList = winrt::try_create_instance<ITaskbarList>(CLSID_TaskbarList);
|
||||
if (_taskbarList) {
|
||||
HRESULT hr = _taskbarList->HrInit();
|
||||
if (SUCCEEDED(hr)) {
|
||||
// 修正任务栏图标
|
||||
_taskbarList->DeleteTab(GetWindowOwner(hwndSrc));
|
||||
_taskbarList->AddTab(hwndSrc);
|
||||
|
||||
// 修正 Alt+Tab 切换顺序
|
||||
if (GetForegroundWindow() == hwndSrc) {
|
||||
SetForegroundWindow(GetDesktopWindow());
|
||||
SetForegroundWindow(hwndSrc);
|
||||
}
|
||||
} else {
|
||||
Logger::Get().ComError("ITaskbarList::HrInit 失败", hr);
|
||||
_taskbarList = nullptr;
|
||||
}
|
||||
} else {
|
||||
Logger::Get().Error("创建 ITaskbarList 失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 再次尝试捕获
|
||||
if (_TryCreateGraphicsCaptureItem(interop)) {
|
||||
return true;
|
||||
} else {
|
||||
if (_isSrcStyleChanged) {
|
||||
// 恢复源窗口样式
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, srcExStyle);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试设置源窗口样式,因为 WGC 只能捕获位于 Alt+Tab 列表中的窗口
|
||||
LONG_PTR srcExStyle = GetWindowLongPtr(hwndSrc, GWL_EXSTYLE);
|
||||
if ((srcExStyle & WS_EX_APPWINDOW) == 0) {
|
||||
// 添加 WS_EX_APPWINDOW 样式,确保源窗口可被 Alt+Tab 选中
|
||||
if (SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, srcExStyle | WS_EX_APPWINDOW)) {
|
||||
Logger::Get().Info("已改变源窗口样式");
|
||||
_originalSrcExStyle = srcExStyle;
|
||||
|
||||
if (_TryCreateGraphicsCaptureItem(interop)) {
|
||||
_RemoveOwnerFromAltTabList(hwndSrc);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
Logger::Get().Win32Error("SetWindowLongPtr 失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果窗口使用 ITaskbarList 隐藏了任务栏图标也不会出现在 Alt+Tab 列表。这种情况很罕见
|
||||
_taskbarList = winrt::try_create_instance<ITaskbarList>(CLSID_TaskbarList);
|
||||
if (_taskbarList && SUCCEEDED(_taskbarList->HrInit())) {
|
||||
HRESULT hr = _taskbarList->AddTab(hwndSrc);
|
||||
if (SUCCEEDED(hr)) {
|
||||
Logger::Get().Info("已添加任务栏图标");
|
||||
|
||||
if (_TryCreateGraphicsCaptureItem(interop)) {
|
||||
_RemoveOwnerFromAltTabList(hwndSrc);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
_taskbarList = nullptr;
|
||||
Logger::Get().Error("ITaskbarList::AddTab 失败");
|
||||
}
|
||||
} else {
|
||||
_taskbarList = nullptr;
|
||||
Logger::Get().Error("创建 ITaskbarList 失败");
|
||||
}
|
||||
|
||||
// 上面的尝试失败了则还原更改
|
||||
if (_taskbarList) {
|
||||
_taskbarList->DeleteTab(hwndSrc);
|
||||
_taskbarList = nullptr;
|
||||
}
|
||||
if (_originalSrcExStyle) {
|
||||
// 首先还原所有者窗口的样式以压制任务栏的动画
|
||||
if (_originalOwnerExStyle) {
|
||||
SetWindowLongPtr(GetWindowOwner(hwndSrc), GWL_EXSTYLE, _originalOwnerExStyle);
|
||||
_originalOwnerExStyle = 0;
|
||||
}
|
||||
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, _originalSrcExStyle);
|
||||
_originalSrcExStyle = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_TryCreateGraphicsCaptureItem(IGraphicsCaptureItemInterop* interop) noexcept {
|
||||
|
|
@ -313,6 +282,43 @@ bool GraphicsCaptureFrameSource::_TryCreateGraphicsCaptureItem(IGraphicsCaptureI
|
|||
return true;
|
||||
}
|
||||
|
||||
// 部分使用 Kirikiri 引擎的游戏有着这样的架构: 游戏窗口并非根窗口,它被一个尺寸为 0 的窗口
|
||||
// 所有。此时 Alt+Tab 列表中的窗口和任务栏图标实际上是所有者窗口,这会导致 WGC 捕获游戏窗
|
||||
// 口时失败。_CaptureWindow 在初次捕获失败后会将 WS_EX_APPWINDOW 样式添加到游戏窗口,这
|
||||
// 可以工作,但也导致所有者窗口和游戏窗口同时出现在 Alt+Tab 列表中,引起用户的困惑。
|
||||
//
|
||||
// 此函数检测这种情况并改变所有者窗口的样式将它从 Alt+Tab 列表中移除。
|
||||
void GraphicsCaptureFrameSource::_RemoveOwnerFromAltTabList(HWND hwndSrc) noexcept {
|
||||
HWND hwndOwner = GetWindowOwner(hwndSrc);
|
||||
if (!hwndOwner) {
|
||||
return;
|
||||
}
|
||||
|
||||
RECT ownerRect{};
|
||||
if (!GetWindowRect(hwndOwner, &ownerRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查所有者窗口尺寸
|
||||
if (ownerRect.right != ownerRect.left || ownerRect.bottom != ownerRect.top) {
|
||||
return;
|
||||
}
|
||||
|
||||
LONG_PTR ownerExStyle = GetWindowLongPtr(hwndOwner, GWL_EXSTYLE);
|
||||
if (ownerExStyle == 0) {
|
||||
Logger::Get().Win32Error("GetWindowLongPtr 失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SetWindowLongPtr(hwndOwner, GWL_EXSTYLE, ownerExStyle | WS_EX_TOOLWINDOW)) {
|
||||
Logger::Get().Win32Error("SetWindowLongPtr 失败");
|
||||
return;
|
||||
}
|
||||
|
||||
_originalOwnerExStyle = ownerExStyle;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_StartCapture() noexcept {
|
||||
if (_captureSession) {
|
||||
return true;
|
||||
|
|
@ -323,7 +329,7 @@ bool GraphicsCaptureFrameSource::_StartCapture() noexcept {
|
|||
_captureFramePool = winrt::Direct3D11CaptureFramePool::Create(
|
||||
_wrappedD3DDevice,
|
||||
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
4, // 帧的缓存数量,更大的值有利于在低帧率下降低延迟
|
||||
1, // 帧的缓存数量
|
||||
{ (int)_frameBox.right, (int)_frameBox.bottom } // 帧的尺寸为包含源窗口的最小尺寸
|
||||
);
|
||||
|
||||
|
|
@ -381,22 +387,18 @@ GraphicsCaptureFrameSource::~GraphicsCaptureFrameSource() {
|
|||
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
|
||||
// 还原源窗口样式
|
||||
if (_isSrcStyleChanged) {
|
||||
const DWORD srcExStyle = GetWindowExStyle(hwndSrc);
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, srcExStyle & ~WS_EX_APPWINDOW);
|
||||
}
|
||||
|
||||
// 还原 Kirikiri 窗口
|
||||
if (_taskbarList) {
|
||||
_taskbarList->DeleteTab(hwndSrc);
|
||||
_taskbarList->AddTab(GetWindowOwner(hwndSrc));
|
||||
}
|
||||
|
||||
// 修正任务栏焦点窗口和 Alt+Tab 切换顺序
|
||||
if (GetForegroundWindow() == hwndSrc) {
|
||||
SetForegroundWindow(GetDesktopWindow());
|
||||
SetForegroundWindow(hwndSrc);
|
||||
// 还原源窗口样式
|
||||
if (_originalSrcExStyle) {
|
||||
// 首先还原所有者窗口的样式以压制任务栏的动画
|
||||
if (_originalOwnerExStyle) {
|
||||
SetWindowLongPtr(GetWindowOwner(hwndSrc), GWL_EXSTYLE, _originalOwnerExStyle);
|
||||
}
|
||||
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, _originalSrcExStyle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
#include "FrameSourceBase.h"
|
||||
#include <ShlObj.h>
|
||||
#include <Windows.Graphics.Capture.Interop.h>
|
||||
#include <winrt/Windows.Graphics.Capture.h>
|
||||
#include <Windows.Graphics.Capture.Interop.h>
|
||||
#include <ShlObj.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -38,15 +38,18 @@ private:
|
|||
|
||||
bool _TryCreateGraphicsCaptureItem(IGraphicsCaptureItemInterop* interop) noexcept;
|
||||
|
||||
void _RemoveOwnerFromAltTabList(HWND hwndSrc) noexcept;
|
||||
|
||||
LONG_PTR _originalSrcExStyle = 0;
|
||||
LONG_PTR _originalOwnerExStyle = 0;
|
||||
winrt::com_ptr<ITaskbarList> _taskbarList;
|
||||
|
||||
D3D11_BOX _frameBox{};
|
||||
|
||||
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;
|
||||
bool _isSrcStyleChanged = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
#include "pch.h"
|
||||
#include "ImGuiBackend.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "shaders/ImGuiImplPS.h"
|
||||
#include "shaders/ImGuiImplVS.h"
|
||||
#include <d3dcompiler.h>
|
||||
#include <imgui.h>
|
||||
#include "DeviceResources.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "shaders/ImGuiImplVS.h"
|
||||
#include "shaders/ImGuiImplPS.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -82,7 +84,7 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData, POINT viewportOffs
|
|||
_vertexBufferSize = drawData.TotalVtxCount + 5000;
|
||||
|
||||
D3D11_BUFFER_DESC desc{
|
||||
.ByteWidth = UINT(_vertexBufferSize * sizeof(ImDrawVert)),
|
||||
.ByteWidth = _vertexBufferSize * sizeof(ImDrawVert),
|
||||
.Usage = D3D11_USAGE_DYNAMIC,
|
||||
.BindFlags = D3D11_BIND_VERTEX_BUFFER,
|
||||
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE
|
||||
|
|
@ -97,7 +99,7 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData, POINT viewportOffs
|
|||
_indexBufferSize = drawData.TotalIdxCount + 10000;
|
||||
|
||||
D3D11_BUFFER_DESC desc{
|
||||
.ByteWidth = UINT(_indexBufferSize * sizeof(ImDrawIdx)),
|
||||
.ByteWidth = _indexBufferSize * sizeof(ImDrawIdx),
|
||||
.Usage = D3D11_USAGE_DYNAMIC,
|
||||
.BindFlags = D3D11_BIND_INDEX_BUFFER,
|
||||
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE
|
||||
|
|
@ -226,7 +228,7 @@ bool ImGuiBackend::_CreateDeviceObjects() noexcept {
|
|||
}
|
||||
|
||||
static constexpr D3D11_INPUT_ELEMENT_DESC LOCAL_LAYOUT[] = {
|
||||
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
{ "SV_POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#include "pch.h"
|
||||
#include "ImGuiFontsCacheManager.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include <imgui.h>
|
||||
#include "YasHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "YasHelper.h"
|
||||
#include <imgui.h>
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "StrHelper.h"
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
|
|
@ -139,7 +140,7 @@ struct serializer<
|
|||
namespace Magpie {
|
||||
|
||||
// 缓存版本号。当缓存文件结构有更改时更新它,使旧缓存失效
|
||||
static constexpr uint32_t FONTS_CACHE_VERSION = 7;
|
||||
static constexpr uint32_t FONTS_CACHE_VERSION = 3;
|
||||
|
||||
static std::wstring GetCacheFileName(const std::wstring_view& language, uint32_t dpi) noexcept {
|
||||
return fmt::format(L"{}\\fonts_{}_{}", CommonSharedConstants::CACHE_DIR, language, dpi);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "ImGuiImpl.h"
|
||||
#include "CursorManager.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "ImGuiBackend.h"
|
||||
#include "Logger.h"
|
||||
#include "Renderer.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include "ImGuiBackend.h"
|
||||
#include "CursorManager.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Renderer.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include <ranges>
|
||||
#include "StrHelper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -53,10 +53,6 @@ bool ImGuiImpl::Initialize(DeviceResources& deviceResources) noexcept {
|
|||
io.ConfigFlags |= ImGuiConfigFlags_NavNoCaptureKeyboard | ImGuiConfigFlags_NoMouseCursorChange;
|
||||
// 禁用 ini 配置文件
|
||||
io.IniFilename = nullptr;
|
||||
#ifndef _DEBUG
|
||||
// Release 配置下禁用重复 ID 检查
|
||||
io.ConfigDebugHighlightIdConflicts = false;
|
||||
#endif
|
||||
|
||||
if (!_backend.Initialize(deviceResources)) {
|
||||
Logger::Get().Error("初始化 ImGuiBackend 失败");
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ private:
|
|||
ImGuiBackend _backend;
|
||||
|
||||
phmap::flat_hash_map<std::string, ImVec4> _windowRects;
|
||||
|
||||
uint32_t _handlerId = 0;
|
||||
|
||||
HANDLE _hHookThread = NULL;
|
||||
DWORD _hookThreadId = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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')" />
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<CppWinRTFastAbi>true</CppWinRTFastAbi>
|
||||
<CppWinRTOptimized>true</CppWinRTOptimized>
|
||||
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
|
||||
<CppWinRTGenerateWindowsMetadata>false</CppWinRTGenerateWindowsMetadata>
|
||||
<CppWinRTVerbosity>low</CppWinRTVerbosity>
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{0e5205ae-dfa9-4cb8-b662-e43cd6512e2a}</ProjectGuid>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
|
||||
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<GeneratedFilesDir>$(IntDir)\Generated Files\</GeneratedFilesDir>
|
||||
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
|
||||
<GeneratedFilesDir>$(IntDir)Generated Files\</GeneratedFilesDir>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
|
|
@ -27,11 +29,12 @@
|
|||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\Common.Post.props" />
|
||||
<Import Project="$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props" Condition="Exists('$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props')" />
|
||||
<Import Project="$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props" Condition="Exists('$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
<AdditionalIncludeDirectories>include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<FxCompile>
|
||||
|
|
@ -39,7 +42,7 @@
|
|||
<AllResourcesBound>true</AllResourcesBound>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<VariableName>%(Filename)</VariableName>
|
||||
<HeaderFileOutput>$(GeneratedFilesDir)\shaders\%(Filename).h</HeaderFileOutput>
|
||||
<HeaderFileOutput>$(GeneratedFilesDir)shaders\%(Filename).h</HeaderFileOutput>
|
||||
<ObjectFileOutput />
|
||||
</FxCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
|
@ -68,6 +71,7 @@
|
|||
<ClInclude Include="include\DirectXHelper.h" />
|
||||
<ClInclude Include="include\EffectCompiler.h" />
|
||||
<ClInclude Include="include\EffectDesc.h" />
|
||||
<ClInclude Include="include\ScalingError.h" />
|
||||
<ClInclude Include="include\ScalingOptions.h" />
|
||||
<ClInclude Include="include\ScalingRuntime.h" />
|
||||
<ClInclude Include="include\Win32Helper.h" />
|
||||
|
|
@ -153,15 +157,15 @@
|
|||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.250325.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.250325.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<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')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.250325.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.250325.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<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'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -64,6 +64,12 @@
|
|||
<ClInclude Include="include\WindowHelper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\StrHelper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\Version.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\Win32Helper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -73,6 +79,9 @@
|
|||
<ClInclude Include="include\WindowBase.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\ScalingError.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\DirectXHelper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -167,9 +176,13 @@
|
|||
<Filter>Capture</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ScalingOptions.cpp" />
|
||||
<ClCompile Include="Version.cpp" />
|
||||
<ClCompile Include="Win32Helper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StrHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="BackendDescriptorStore.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
#include "pch.h"
|
||||
#include "OverlayDrawer.h"
|
||||
#include "CursorManager.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "EffectDesc.h"
|
||||
#include "FrameSourceBase.h"
|
||||
#include "ImGuiFontsCacheManager.h"
|
||||
#include "Logger.h"
|
||||
#include "OverlayHelper.h"
|
||||
#include "Renderer.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "StepTimer.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "FrameSourceBase.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "EffectDesc.h"
|
||||
#include "OverlayHelper.h"
|
||||
#include "ImGuiFontsCacheManager.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include <ShlObj.h>
|
||||
#include "CursorManager.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
|
|
@ -110,14 +112,12 @@ void OverlayDrawer::Draw(
|
|||
_imguiImpl.NewFrame(_overlayOptions->windows, fittsLawAdjustment, _dpiScale);
|
||||
|
||||
bool needRedraw = false;
|
||||
// 防止 ID 冲突
|
||||
int itemId = 0;
|
||||
|
||||
if (_isToolbarVisible && _DrawToolbar(fps, itemId)) {
|
||||
if (_isToolbarVisible && _DrawToolbar(fps)) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
if (_isProfilerVisible && _DrawProfiler(effectTimings, fps, itemId)) {
|
||||
if (_isProfilerVisible && _DrawProfiler(effectTimings, fps)) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
|
|
@ -340,12 +340,10 @@ 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" || language == L"fi") {
|
||||
} else if (language == L"tr" || language == L"pl") {
|
||||
SetGlyphRanges(ranges, OverlayHelper::EXTENDED_LATIN_RANGES);
|
||||
} else if (language == L"vi") {
|
||||
SetGlyphRanges(ranges, fontAtlas.GetGlyphRangesVietnamese());
|
||||
} else if (language == L"fr") {
|
||||
SetGlyphRanges(ranges, OverlayHelper::FRENCH_RANGES);
|
||||
} else {
|
||||
// Basic Latin 使用默认字体
|
||||
SetGlyphRanges(ranges, OverlayHelper::BASIC_LATIN_RANGES);
|
||||
|
|
@ -646,7 +644,7 @@ static std::string IconLabel(ImWchar iconChar) noexcept {
|
|||
return StrHelper::UTF16ToUTF8(text);
|
||||
}
|
||||
|
||||
bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
||||
bool OverlayDrawer::_DrawToolbar(uint32_t fps) noexcept {
|
||||
bool needRedraw = false;
|
||||
|
||||
const float windowWidth = 360 * _dpiScale;
|
||||
|
|
@ -657,8 +655,6 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
_lastToolbarAlpha = _CalcToolbarAlpha();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, _lastToolbarAlpha);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, (ImU32)ImColor(15, 15, 15, 180));
|
||||
const ImVec2 originalWindowPadding = ImGui::GetStyle().WindowPadding;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 6 * _dpiScale,0.0f });
|
||||
|
||||
_isToolbarItemActive = false;
|
||||
|
||||
|
|
@ -683,7 +679,6 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
|
||||
ImGui::SetCursorPosY((CORNER_ROUNDING + 3) * _dpiScale);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, originalWindowPadding);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 4 * _dpiScale,4 * _dpiScale });
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4 * _dpiScale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0);
|
||||
|
|
@ -727,7 +722,7 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
_isToolbarItemActive = true;
|
||||
}
|
||||
ImGui::PopFont();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
if (ImGui::IsItemHovered() || ImGui::IsItemClicked()) {
|
||||
_imguiImpl.Tooltip(tooltip, _dpiScale, description);
|
||||
}
|
||||
return clicked;
|
||||
|
|
@ -774,7 +769,6 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
|
||||
if (isDeveloperMode && effectDesc.passes.size() > 1) {
|
||||
// 开发者模式允许保存任意通道的输出
|
||||
ImGui::PushID(itemId++);
|
||||
if (ImGui::BeginMenu(effectName.data())) {
|
||||
const uint32_t passCount = (uint32_t)effectDesc.passes.size();
|
||||
for (uint32_t j = 0; j < passCount; ++j) {
|
||||
|
|
@ -782,37 +776,28 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
const uint32_t outputCount = (uint32_t)passDesc.outputs.size();
|
||||
|
||||
if (outputCount == 1) {
|
||||
ImGui::PushID(itemId++);
|
||||
if (ImGui::MenuItem(passDesc.desc.c_str())) {
|
||||
ScalingWindow::Get().Renderer().TakeScreenshot(i, j);
|
||||
}
|
||||
ImGui::PopID();
|
||||
} else {
|
||||
ImGui::PushID(itemId++);
|
||||
if (ImGui::BeginMenu(passDesc.desc.c_str())) {
|
||||
for (uint32_t k = 0; k < outputCount; ++k) {
|
||||
ImGui::PushID(itemId++);
|
||||
if (ImGui::MenuItem(effectDesc.textures[passDesc.outputs[k]].name.c_str())) {
|
||||
ScalingWindow::Get().Renderer().TakeScreenshot(i, j, k);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::PopID();
|
||||
} else {
|
||||
ImGui::PushID(itemId++);
|
||||
if (ImGui::MenuItem(effectName.data())) {
|
||||
ScalingWindow::Get().Renderer().TakeScreenshot(i);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -831,52 +816,23 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosY((CORNER_ROUNDING + 3) * _dpiScale);
|
||||
|
||||
// 源窗口支持最小化时才显示最小化按钮
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
const bool canSrcMinimized = GetWindowStyle(hwndSrc) & WS_MINIMIZEBOX;
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x -
|
||||
((canSrcMinimized ? 3 : 2) * 28 - 4) * _dpiScale);
|
||||
|
||||
if (canSrcMinimized) {
|
||||
const std::string& minimizeStr = _GetResourceString(L"Overlay_Toolbar_Minimize");
|
||||
const ImWchar icon = Win32Helper::GetOSVersion().IsWin11() ?
|
||||
OverlayHelper::SegoeIcons::CheckboxIndeterminate : OverlayHelper::SegoeIcons::Remove;
|
||||
if (drawButton(icon, minimizeStr.c_str())) {
|
||||
// 模拟通过标题栏最小化,失败则回落到 ShowWindow
|
||||
if (!PostMessage(hwndSrc, WM_SYSCOMMAND, SC_MINIMIZE, 0)) {
|
||||
ShowWindowAsync(hwndSrc, SW_SHOWMINIMIZED);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
{
|
||||
const bool isWindowedMode = ScalingWindow::Get().Options().IsWindowedMode();
|
||||
const ImWchar icon = isWindowedMode ?
|
||||
OverlayHelper::SegoeIcons::FullScreen : OverlayHelper::SegoeIcons::Favicon;
|
||||
const std::string& switchScalingStr = _GetResourceString(
|
||||
isWindowedMode ? L"Overlay_Toolbar_SwitchToFullscreen" : L"Overlay_Toolbar_SwitchToWindowed");
|
||||
if (drawButton(icon, switchScalingStr.c_str())) {
|
||||
ScalingWindow::Dispatcher().TryEnqueue([]() {
|
||||
ScalingWindow::Get().ToggleScaling(!ScalingWindow::Get().Options().IsWindowedMode());
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - 50 * _dpiScale);
|
||||
|
||||
// 和主窗口保持一致 (#C42B1C)
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.769f, 0.169f, 0.11f, 1.0f });
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.769f, 0.169f, 0.11f, 0.8f });
|
||||
|
||||
const std::string& closeStr = _GetResourceString(L"Overlay_Toolbar_Close");
|
||||
const std::string& closeDescStr = _GetResourceString(L"Overlay_Toolbar_Close_Description");
|
||||
if (drawButton(OverlayHelper::SegoeIcons::Cancel, closeStr.c_str(), closeDescStr.c_str())) {
|
||||
const std::string& stopScalingStr = _GetResourceString(L"Overlay_Toolbar_StopScaling");
|
||||
if (drawButton(OverlayHelper::SegoeIcons::BackToWindow, stopScalingStr.c_str())) {
|
||||
ScalingWindow::Dispatcher().TryEnqueue([]() {
|
||||
ScalingWindow::Get().Stop();
|
||||
ScalingWindow::Get().Destroy();
|
||||
});
|
||||
}
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
const std::string& closeStr = _GetResourceString(L"Overlay_Toolbar_Close");
|
||||
if (drawButton(OverlayHelper::SegoeIcons::Cancel, closeStr.c_str())) {
|
||||
ScalingWindow::Dispatcher().TryEnqueue([this, runId(ScalingWindow::RunId())]() {
|
||||
if (runId == ScalingWindow::RunId()) {
|
||||
ToolbarState(ToolbarState::Off);
|
||||
|
|
@ -888,14 +844,14 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
|||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::PopStyleColor(5);
|
||||
ImGui::PopStyleVar(6);
|
||||
ImGui::PopStyleVar(5);
|
||||
} else {
|
||||
_isCursorOnCaptionArea = false;
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return needRedraw;
|
||||
}
|
||||
|
|
@ -909,7 +865,7 @@ static std::string RectToStr(const RECT& rect) noexcept {
|
|||
#endif
|
||||
|
||||
// 返回 true 表示应再渲染一次
|
||||
bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps, int& itemId) noexcept {
|
||||
bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps) noexcept {
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
const Renderer& renderer = ScalingWindow::Get().Renderer();
|
||||
|
||||
|
|
@ -1111,6 +1067,8 @@ bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint3
|
|||
}
|
||||
|
||||
static int selectedIdx = -1;
|
||||
// 防止 ID 冲突
|
||||
int itemId = 0;
|
||||
|
||||
if (nEffect > 1 || showPasses) {
|
||||
ImGui::Spacing();
|
||||
|
|
|
|||
|
|
@ -77,9 +77,9 @@ private:
|
|||
bool selected = false
|
||||
);
|
||||
|
||||
bool _DrawToolbar(uint32_t fps, int& itemId) noexcept;
|
||||
bool _DrawToolbar(uint32_t fps) noexcept;
|
||||
|
||||
bool _DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps, int& itemId) noexcept;
|
||||
bool _DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps) noexcept;
|
||||
|
||||
const std::string& _GetResourceString(const std::wstring_view& key) noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
#include "SmallVector.h"
|
||||
#include <imgui.h>
|
||||
#include "SmallVector.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -18,8 +18,6 @@ struct OverlayHelper {
|
|||
// Basic Latin + Latin-1 Supplement + Latin Extended-A,用于土耳其语、波兰语等。
|
||||
// 参见 https://en.wikipedia.org/wiki/Latin_Extended-A
|
||||
static constexpr ImWchar EXTENDED_LATIN_RANGES[] = { 0x20, 0x17F };
|
||||
// 法语字符,包含 EXTENDED_LATIN_RANGES + General Punctuation
|
||||
static constexpr ImWchar FRENCH_RANGES[] = { 0x20, 0x17F, 0x2000, 0x206F };
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -51,14 +49,10 @@ struct OverlayHelper {
|
|||
//
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
// 更改图标后记得更新 FONTS_CACHE_VERSION
|
||||
struct SegoeIcons {
|
||||
static const ImWchar Cancel = 0xE711;
|
||||
static const ImWchar Camera = 0xE722;
|
||||
static const ImWchar Favicon = 0xE737;
|
||||
static const ImWchar Remove = 0xE738;
|
||||
static const ImWchar CheckboxIndeterminate = 0xE73C;
|
||||
static const ImWchar FullScreen = 0xE740;
|
||||
static const ImWchar BackToWindow = 0xE73F;
|
||||
static const ImWchar Pinned = 0xE840;
|
||||
static const ImWchar Diagnostic = 0xE9D9;
|
||||
#ifdef _DEBUG
|
||||
|
|
@ -69,10 +63,7 @@ struct OverlayHelper {
|
|||
static constexpr ImWchar ICON_RANGES[] = {
|
||||
SegoeIcons::Cancel, SegoeIcons::Cancel,
|
||||
SegoeIcons::Camera, SegoeIcons::Camera,
|
||||
SegoeIcons::Favicon, SegoeIcons::Favicon,
|
||||
SegoeIcons::Remove, SegoeIcons::Remove,
|
||||
SegoeIcons::CheckboxIndeterminate, SegoeIcons::CheckboxIndeterminate,
|
||||
SegoeIcons::FullScreen, SegoeIcons::FullScreen,
|
||||
SegoeIcons::BackToWindow, SegoeIcons::BackToWindow,
|
||||
SegoeIcons::Pinned, SegoeIcons::Pinned,
|
||||
SegoeIcons::Diagnostic, SegoeIcons::Diagnostic,
|
||||
#ifdef _DEBUG
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
#include "PresenterBase.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include <dwmapi.h>
|
||||
#include <dcomp.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -27,12 +30,66 @@ bool PresenterBase::Initialize(HWND hwndAttach, const DeviceResources& deviceRes
|
|||
return _Initialize(hwndAttach);
|
||||
}
|
||||
|
||||
void PresenterBase::_WaitForDwmComposition() noexcept {
|
||||
// Win11 可以使用准确的 DCompositionWaitForCompositorClock
|
||||
if (Win32Helper::GetOSVersion().IsWin11()) {
|
||||
static const auto dCompositionWaitForCompositorClock = []() {
|
||||
HMODULE hDcomp = GetModuleHandle(L"dcomp.dll");
|
||||
assert(hDcomp);
|
||||
return (decltype(::DCompositionWaitForCompositorClock)*)GetProcAddress(
|
||||
hDcomp, "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);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PresenterBase::_CalcBufferCount() noexcept {
|
||||
// 缓冲区数量取决于 ScalingRuntime::_ScalingThreadProc 中检查光标移动的频率
|
||||
return ScalingWindow::Get().Options().Is3DGameMode() ? 4 : 8;
|
||||
}
|
||||
|
||||
void PresenterBase::_WaitForGpu() noexcept {
|
||||
void PresenterBase::_WaitForRenderComplete() noexcept {
|
||||
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
|
||||
|
||||
// 等待渲染完成
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public:
|
|||
POINT& drawOffset
|
||||
) noexcept = 0;
|
||||
|
||||
virtual void EndFrame(bool waitForGpu = false) noexcept = 0;
|
||||
virtual void EndFrame(bool waitForRenderComplete = false) noexcept = 0;
|
||||
|
||||
virtual bool OnResize() noexcept = 0;
|
||||
|
||||
|
|
@ -27,7 +27,10 @@ public:
|
|||
protected:
|
||||
virtual bool _Initialize(HWND hwndAttach) noexcept = 0;
|
||||
|
||||
void _WaitForGpu() noexcept;
|
||||
void _WaitForRenderComplete() noexcept;
|
||||
|
||||
// 和 DwmFlush 效果相同但更准确
|
||||
static void _WaitForDwmComposition() noexcept;
|
||||
|
||||
static uint32_t _CalcBufferCount() noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
#include "pch.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "DesktopDuplicationFrameSource.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "DwmSharedSurfaceFrameSource.h"
|
||||
#include "EffectCompiler.h"
|
||||
#include "EffectDrawer.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "GDIFrameSource.h"
|
||||
#include "GraphicsCaptureFrameSource.h"
|
||||
#include "Logger.h"
|
||||
#include "OverlayDrawer.h"
|
||||
#include "Renderer.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "ScreenshotHelper.h"
|
||||
#include "StrHelper.h"
|
||||
#include "TextureHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "EffectDrawer.h"
|
||||
#include "StrHelper.h"
|
||||
#include "EffectCompiler.h"
|
||||
#include "GraphicsCaptureFrameSource.h"
|
||||
#include "DesktopDuplicationFrameSource.h"
|
||||
#include "GDIFrameSource.h"
|
||||
#include "DwmSharedSurfaceFrameSource.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "OverlayDrawer.h"
|
||||
#include "CursorManager.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#ifdef MP_USE_COMPSWAPCHAIN
|
||||
#include "CompSwapchainPresenter.h"
|
||||
#else
|
||||
#include "AdaptivePresenter.h"
|
||||
#endif
|
||||
#include "ScreenshotHelper.h"
|
||||
#include "TextureHelper.h"
|
||||
#include <dispatcherqueue.h>
|
||||
#include <d3dkmthk.h>
|
||||
|
||||
|
|
@ -133,10 +134,8 @@ ScalingError Renderer::Initialize(HWND hwndAttach, OverlayOptions& overlayOption
|
|||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
if (!options.Is3DGameMode()) {
|
||||
_overlayDrawer.ToolbarState(options.IsWindowedMode() ?
|
||||
options.windowedInitialToolbarState : options.fullscreenInitialToolbarState);
|
||||
if (!ScalingWindow::Get().Options().Is3DGameMode()) {
|
||||
_overlayDrawer.ToolbarState(ScalingWindow::Get().Options().initialToolbarState);
|
||||
}
|
||||
|
||||
_hKeyboardHook.reset(SetWindowsHookEx(WH_KEYBOARD_LL, _LowLevelKeyboardHook, NULL, 0));
|
||||
|
|
@ -199,7 +198,7 @@ winrt::fire_and_forget Renderer::TakeScreenshot(
|
|||
}
|
||||
}
|
||||
|
||||
void Renderer::_FrontendRender(bool waitForGpu) noexcept {
|
||||
void Renderer::_FrontendRender(bool waitForRenderComplete) noexcept {
|
||||
winrt::com_ptr<ID3D11Texture2D> frameTex;
|
||||
winrt::com_ptr<ID3D11RenderTargetView> frameRtv;
|
||||
POINT drawOffset;
|
||||
|
|
@ -267,10 +266,10 @@ void Renderer::_FrontendRender(bool waitForGpu) noexcept {
|
|||
// 绘制光标
|
||||
_cursorDrawer.Draw(frameTex.get(), drawOffset);
|
||||
|
||||
_presenter->EndFrame(waitForGpu);
|
||||
_presenter->EndFrame(waitForRenderComplete);
|
||||
}
|
||||
|
||||
bool Renderer::Render(bool force, bool waitForGpu) noexcept {
|
||||
bool Renderer::Render(bool force, bool waitForRenderComplete) noexcept {
|
||||
if (!force && _lastAccessMutexKey == _sharedTextureMutexKey.load(std::memory_order_relaxed)) {
|
||||
if (_lastAccessMutexKey == 0) {
|
||||
// 第一帧尚未完成
|
||||
|
|
@ -282,7 +281,7 @@ bool Renderer::Render(bool force, bool waitForGpu) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
_FrontendRender(waitForGpu);
|
||||
_FrontendRender(waitForRenderComplete);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -357,7 +356,7 @@ void Renderer::OnMove() noexcept {
|
|||
_UpdateDestRect();
|
||||
}
|
||||
|
||||
void Renderer::SwitchToolbarState() noexcept {
|
||||
void Renderer::ToggleToolbarState() noexcept {
|
||||
const ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
|
||||
if (scalingWindow.Options().Is3DGameMode()) {
|
||||
|
|
@ -699,43 +698,14 @@ 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;
|
||||
}
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
_frontendSharedTexture->GetDesc(&desc);
|
||||
|
||||
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);
|
||||
_destRect.left = (rendererRect.left + rendererRect.right - (LONG)desc.Width) / 2;
|
||||
_destRect.top = (rendererRect.top + rendererRect.bottom - (LONG)desc.Height) / 2;
|
||||
_destRect.right = _destRect.left + (LONG)desc.Width;
|
||||
_destRect.bottom = _destRect.top + (LONG)desc.Height;
|
||||
}
|
||||
|
||||
HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept {
|
||||
|
|
@ -845,9 +815,8 @@ void Renderer::_BackendThreadProc() noexcept {
|
|||
case FrameSourceState::Error:
|
||||
// 捕获出错,退出缩放
|
||||
ScalingWindow::Dispatcher().TryEnqueue([]() {
|
||||
ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
scalingWindow.ShowError(ScalingError::CaptureFailed);
|
||||
scalingWindow.Stop();
|
||||
ScalingWindow::Get().RuntimeError(ScalingError::CaptureFailed);
|
||||
ScalingWindow::Get().Destroy();
|
||||
});
|
||||
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
|
|
@ -896,7 +865,7 @@ HANDLE Renderer::_InitBackend() noexcept {
|
|||
// 某些捕获方式不会限制捕获帧率,因此将捕获帧率限制为屏幕刷新率
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
if (HMONITOR hMon = MonitorFromWindow(hwndSrc, MONITOR_DEFAULTTONEAREST)) {
|
||||
MONITORINFOEX mi{ { sizeof(MONITORINFOEX) } };
|
||||
MONITORINFOEX mi{ sizeof(MONITORINFOEX) };
|
||||
GetMonitorInfo(hMon, &mi);
|
||||
|
||||
DEVMODE dm{ .dmSize = sizeof(DEVMODE) };
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
#pragma once
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include "CursorDrawer.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include "EffectDrawer.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "OverlayDrawer.h"
|
||||
#include "PresenterBase.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "CursorDrawer.h"
|
||||
#include "StepTimer.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "ScalingError.h"
|
||||
#include "PresenterBase.h"
|
||||
#include "OverlayDrawer.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ public:
|
|||
|
||||
ScalingError Initialize(HWND hwndAttach, OverlayOptions& overlayOptions) noexcept;
|
||||
|
||||
bool Render(bool force = false, bool waitForGpu = false) noexcept;
|
||||
bool Render(bool force = false, bool waitForRenderComplete = false) noexcept;
|
||||
|
||||
bool OnResize() noexcept;
|
||||
|
||||
|
|
@ -30,7 +32,7 @@ public:
|
|||
|
||||
void OnMove() noexcept;
|
||||
|
||||
void SwitchToolbarState() noexcept;
|
||||
void ToggleToolbarState() noexcept;
|
||||
|
||||
const RECT& SrcRect() const noexcept;
|
||||
|
||||
|
|
@ -66,7 +68,7 @@ public:
|
|||
) noexcept;
|
||||
|
||||
private:
|
||||
void _FrontendRender(bool waitForGpu = false) noexcept;
|
||||
void _FrontendRender(bool waitForRenderComplete = false) noexcept;
|
||||
|
||||
void _BackendThreadProc() noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -36,12 +36,46 @@ static std::string LogEffects(const std::vector<EffectOption>& effects) noexcept
|
|||
return result;
|
||||
}
|
||||
|
||||
bool ScalingOptions::Prepare() noexcept {
|
||||
if (screenshotsDir.empty()) {
|
||||
Logger::Get().Error("screenshotsDir 为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!showToast) {
|
||||
Logger::Get().Error("showToast 为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!save) {
|
||||
Logger::Get().Error("save 为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsWindowedMode()) {
|
||||
if (Is3DGameMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Desktop Duplication 不支持窗口模式缩放
|
||||
if (captureMethod == CaptureMethod::DesktopDuplication) {
|
||||
captureMethod = CaptureMethod::GraphicsCapture;
|
||||
}
|
||||
}
|
||||
|
||||
// GDI 和 DwmSharedSurface 不支持捕获标题栏
|
||||
if (captureMethod == CaptureMethod::GDI || captureMethod == CaptureMethod::DwmSharedSurface) {
|
||||
IsCaptureTitleBar(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScalingOptions::Log() const noexcept {
|
||||
Logger::Get().Info(fmt::format(R"(缩放选项
|
||||
IsWindowedMode: {}
|
||||
IsDebugMode: {}
|
||||
IsBenchmarkMode: {}
|
||||
IsTopmostDisabled: {}
|
||||
IsFP16Disabled: {}
|
||||
IsEffectCacheDisabled: {}
|
||||
IsFontCacheDisabled: {}
|
||||
|
|
@ -68,14 +102,12 @@ void ScalingOptions::Log() const noexcept {
|
|||
multiMonitorUsage: {}
|
||||
cursorInterpolationMode: {}
|
||||
duplicateFrameDetectionMode: {}
|
||||
fullscreenInitialToolbarState: {}
|
||||
windowedInitialToolbarState: {}
|
||||
initialToolbarState: {}
|
||||
screenshotsDir: {}
|
||||
effects: {})",
|
||||
IsWindowedMode(),
|
||||
IsDebugMode(),
|
||||
IsBenchmarkMode(),
|
||||
IsTopmostDisabled(),
|
||||
IsFP16Disabled(),
|
||||
IsEffectCacheDisabled(),
|
||||
IsFontCacheDisabled(),
|
||||
|
|
@ -101,8 +133,7 @@ void ScalingOptions::Log() const noexcept {
|
|||
(int)multiMonitorUsage,
|
||||
(int)cursorInterpolationMode,
|
||||
(int)duplicateFrameDetectionMode,
|
||||
(int)fullscreenInitialToolbarState,
|
||||
(int)windowedInitialToolbarState,
|
||||
(int)initialToolbarState,
|
||||
StrHelper::UTF16ToUTF8(screenshotsDir.native()),
|
||||
LogEffects(effects)
|
||||
));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#include "pch.h"
|
||||
#include "ScalingRuntime.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <dispatcherqueue.h>
|
||||
|
||||
|
|
@ -14,6 +14,8 @@ ScalingRuntime::ScalingRuntime() : _scalingThread(&ScalingRuntime::_ScalingThrea
|
|||
}
|
||||
|
||||
ScalingRuntime::~ScalingRuntime() {
|
||||
Stop();
|
||||
|
||||
if (_scalingThread.joinable()) {
|
||||
const HANDLE hScalingThread = _scalingThread.native_handle();
|
||||
|
||||
|
|
@ -48,81 +50,86 @@ ScalingRuntime::~ScalingRuntime() {
|
|||
}
|
||||
}
|
||||
|
||||
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options, bool force) {
|
||||
assert(!options.screenshotsDir.empty() && options.showToast && options.showError && options.save);
|
||||
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
|
||||
if (!options.Prepare()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options)), force]() mutable {
|
||||
ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
// 如果正在缩放且 force 为假则忽略
|
||||
if (scalingWindow && !force) {
|
||||
return;
|
||||
_State expected = _State::Idle;
|
||||
if (!_state.compare_exchange_strong(
|
||||
expected, _State::Initializing, std::memory_order_relaxed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IsRunningChanged.Invoke(true, ScalingError::NoError);
|
||||
|
||||
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options))]() mutable {
|
||||
options.Log();
|
||||
|
||||
ScalingError error = ScalingWindow::Get().Create(hwndSrc, std::move(options));
|
||||
if (error == ScalingError::NoError) {
|
||||
_state.store(_State::Scaling, std::memory_order_relaxed);
|
||||
} else {
|
||||
// 缩放失败
|
||||
_state.store(_State::Idle, std::memory_order_relaxed);
|
||||
IsRunningChanged.Invoke(false, error);
|
||||
}
|
||||
|
||||
scalingWindow.Stop();
|
||||
|
||||
// 初始化时视为处于缩放状态
|
||||
_State(ScalingState::Scaling);
|
||||
scalingWindow.Start(hwndSrc, std::move(options));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScalingRuntime::ToggleScaling(bool isWindowedMode) {
|
||||
_Dispatcher().TryEnqueue([isWindowedMode]() {
|
||||
if (ScalingWindow& scalingWindow = ScalingWindow::Get()) {
|
||||
scalingWindow.ToggleScaling(isWindowedMode);
|
||||
};
|
||||
});
|
||||
}
|
||||
void ScalingRuntime::ToggleToolbarState() {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void ScalingRuntime::SwitchToolbarState() {
|
||||
_Dispatcher().TryEnqueue([]() {
|
||||
if (ScalingWindow& scalingWindow = ScalingWindow::Get()) {
|
||||
scalingWindow.SwitchToolbarState();
|
||||
scalingWindow.ToggleToolbarState();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
void ScalingRuntime::Stop() {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_Dispatcher().TryEnqueue([]() {
|
||||
ScalingWindow::Get().Stop();
|
||||
// 消息循环会更改 _state
|
||||
ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
if (scalingWindow.IsSrcRepositioning()) {
|
||||
scalingWindow.CleanAfterSrcRepositioned();
|
||||
} else {
|
||||
scalingWindow.Destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::optional<bool> IsSrcRepositioning(HWND hwndSrc) noexcept {
|
||||
if (!IsWindow(hwndSrc)) {
|
||||
Logger::Get().Info("源窗口已销毁");
|
||||
return std::nullopt;
|
||||
// 返回值:
|
||||
// -1: 应取消缩放
|
||||
// 0: 仍在调整中
|
||||
// 1: 调整完毕
|
||||
static int GetSrcRepositionState(HWND hwndSrc, bool allowScalingMaximized) noexcept {
|
||||
if (!IsWindow(hwndSrc) || GetForegroundWindow() != hwndSrc) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 窗口不可见或最小化则继续等待。注意 showCmd 不能准确判断窗口可见性,
|
||||
// 应使用 IsWindowVisible。
|
||||
if (!IsWindowVisible(hwndSrc)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Win32Helper::IsWindowHung(hwndSrc)) {
|
||||
Logger::Get().Info("源窗口已挂起");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const UINT showCmd = Win32Helper::GetWindowShowCmd(hwndSrc);
|
||||
if (showCmd == SW_SHOWMAXIMIZED) {
|
||||
// 窗口最大化则尝试缩放,失败会显示错误消息
|
||||
return false;
|
||||
} else if (showCmd == SW_SHOWMINIMIZED) {
|
||||
return true;
|
||||
if (UINT showCmd = Win32Helper::GetWindowShowCmd(hwndSrc); showCmd != SW_NORMAL) {
|
||||
if (showCmd != SW_SHOWMAXIMIZED || !allowScalingMaximized) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查源窗口是否正在调整大小或移动
|
||||
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
|
||||
if (!GetGUIThreadInfo(GetWindowThreadProcessId(hwndSrc, nullptr), &guiThreadInfo)) {
|
||||
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
|
||||
return std::nullopt;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bool(guiThreadInfo.flags & GUI_INMOVESIZE);
|
||||
return (guiThreadInfo.flags & GUI_INMOVESIZE) ? 0 : 1;
|
||||
}
|
||||
|
||||
void ScalingRuntime::_ScalingThreadProc() noexcept {
|
||||
|
|
@ -156,15 +163,20 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
ScalingWindow::Dispatcher(_dispatcher);
|
||||
|
||||
time_point<steady_clock> lastRenderTime;
|
||||
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
|
||||
|
||||
MSG msg;
|
||||
while (true) {
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) {
|
||||
scalingWindow.Stop();
|
||||
scalingWindow.Destroy();
|
||||
|
||||
if (_state.exchange(_State::Idle, std::memory_order_relaxed) != _State::Idle) {
|
||||
IsRunningChanged.Invoke(false, ScalingError::NoError);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (msg.message == CommonSharedConstants::WM_FRONTEND_RENDER &&
|
||||
msg.hwnd == scalingWindow.Handle()) {
|
||||
} else if (msg.message == CommonSharedConstants::WM_FRONTEND_RENDER && msg.hwnd == scalingWindow.Handle()) {
|
||||
// 缩放窗口收到 WM_FRONTEND_RENDER 将执行渲染
|
||||
lastRenderTime = steady_clock::now();
|
||||
}
|
||||
|
|
@ -172,12 +184,14 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
if (scalingWindow) {
|
||||
_State(ScalingState::Scaling);
|
||||
if (_state.load(std::memory_order_relaxed) != _State::Scaling) {
|
||||
WaitMessage();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scalingWindow) {
|
||||
const auto now = steady_clock::now();
|
||||
// 限制检测光标移动的频率
|
||||
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
|
||||
nanoseconds rest = timeout - (now - lastRenderTime);
|
||||
if (rest.count() <= 0) {
|
||||
lastRenderTime = now;
|
||||
|
|
@ -191,27 +205,24 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
const DWORD restMs = DWORD((rest.count() + ratio - 1) / ratio);
|
||||
MsgWaitForMultipleObjectsEx(0, nullptr, restMs, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||
} else if (scalingWindow.IsSrcRepositioning()) {
|
||||
std::optional<bool> repositioning =
|
||||
IsSrcRepositioning(scalingWindow.SrcTracker().Handle());
|
||||
if (repositioning.has_value()) {
|
||||
if (*repositioning) {
|
||||
// 等待调整完成
|
||||
_State(ScalingState::Waiting);
|
||||
MsgWaitForMultipleObjectsEx(0, nullptr, 10, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||
} else {
|
||||
// 重新缩放。初始化时视为处于缩放状态
|
||||
_State(ScalingState::Scaling);
|
||||
ScalingWindow::Get().RestartAfterSrcRepositioned();
|
||||
}
|
||||
const int state = GetSrcRepositionState(
|
||||
scalingWindow.SrcTracker().Handle(),
|
||||
scalingWindow.Options().IsAllowScalingMaximized()
|
||||
);
|
||||
if (state == 0) {
|
||||
// 等待调整完成
|
||||
MsgWaitForMultipleObjectsEx(0, nullptr, 10, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||
} else if (state == 1) {
|
||||
// 重新缩放
|
||||
ScalingWindow::Get().RecreateAfterSrcRepositioned();
|
||||
} else {
|
||||
// 取消缩放
|
||||
ScalingWindow::Get().CleanAfterSrcRepositioned();
|
||||
_State(ScalingState::Idle);
|
||||
}
|
||||
} else {
|
||||
_State(ScalingState::Idle);
|
||||
lastRenderTime = {};
|
||||
WaitMessage();
|
||||
// 缩放结束
|
||||
_state.store(_State::Idle, std::memory_order_relaxed);
|
||||
IsRunningChanged.Invoke(false, scalingWindow.RuntimeError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -225,10 +236,4 @@ const winrt::DispatcherQueue& ScalingRuntime::_Dispatcher() noexcept {
|
|||
return _dispatcher;
|
||||
}
|
||||
|
||||
void ScalingRuntime::_State(ScalingState value) {
|
||||
if (_state.exchange(value, std::memory_order_relaxed) != value) {
|
||||
StateChanged.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
#include "pch.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "CursorManager.h"
|
||||
#include "ExclModeHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "Renderer.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "WindowHelper.h"
|
||||
#include "CursorManager.h"
|
||||
#include "FrameSourceBase.h"
|
||||
#include "ExclModeHelper.h"
|
||||
#include "StrHelper.h"
|
||||
#include <timeapi.h>
|
||||
#include <dwmapi.h>
|
||||
#include <ShellScalingApi.h>
|
||||
#include <timeapi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ static UINT WM_MAGPIE_SCALINGCHANGED;
|
|||
static constexpr int WINDOWED_MODE_MIN_SPACE_AROUND = 2 * 50;
|
||||
|
||||
static void InitMessage() noexcept {
|
||||
[[maybe_unused]] static Ignore _ = []() {
|
||||
static Ignore _ = []() {
|
||||
WM_MAGPIE_SCALINGCHANGED =
|
||||
RegisterWindowMessage(CommonSharedConstants::WM_MAGPIE_SCALINGCHANGED);
|
||||
|
||||
|
|
@ -27,10 +29,6 @@ static void InitMessage() noexcept {
|
|||
}();
|
||||
}
|
||||
|
||||
static bool IsTopmostWindow(HWND hWnd) noexcept {
|
||||
return GetWindowExStyle(hWnd) & WS_EX_TOPMOST;
|
||||
}
|
||||
|
||||
ScalingWindow::ScalingWindow() noexcept :
|
||||
_resourceLoader(winrt::ResourceLoader::GetForViewIndependentUse(CommonSharedConstants::APP_RESOURCE_MAP_ID)) {}
|
||||
|
||||
|
|
@ -50,24 +48,15 @@ static void LogRects(const RECT& srcRect, const RECT& rendererRect, const RECT&
|
|||
windowRect.right - windowRect.left, windowRect.bottom - windowRect.top));
|
||||
}
|
||||
|
||||
ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
||||
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
|
||||
#ifdef MP_VERSION_STRING
|
||||
STRINGIFY(MP_VERSION_STRING),
|
||||
#elif defined(MP_COMMIT_ID)
|
||||
"dev (" STRINGIFY(MP_COMMIT_ID) ")",
|
||||
#else
|
||||
"dev",
|
||||
#endif
|
||||
Win32Helper::GetOSVersion().ToString<char>(),
|
||||
Win32Helper::IsProcessElevated() ? "是" : "否"
|
||||
));
|
||||
ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcept {
|
||||
if (Handle()) {
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
#if _DEBUG
|
||||
OutputDebugString(fmt::format(L"可执行文件路径: {}\n窗口类: {}\n",
|
||||
Win32Helper::GetWindowPath(hwndSrc), Win32Helper::GetWindowClassName(hwndSrc)).c_str());
|
||||
#endif
|
||||
InitMessage();
|
||||
|
||||
// 缩放结束后失效
|
||||
_options = std::move(options);
|
||||
_runtimeError = ScalingError::NoError;
|
||||
_isFirstFrame = true;
|
||||
_isResizingOrMoving = false;
|
||||
|
|
@ -77,46 +66,42 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
_areResizeHelperWindowsVisible = false;
|
||||
_isSrcRepositioning = false;
|
||||
|
||||
if (_options.IsWindowedMode()) {
|
||||
if (_options.Is3DGameMode()) {
|
||||
return ScalingError::Windowed3DGameMode;
|
||||
} else if (_options.captureMethod == CaptureMethod::DesktopDuplication) {
|
||||
return ScalingError::WindowedDesktopDuplication;
|
||||
}
|
||||
}
|
||||
|
||||
if (FindWindow(CommonSharedConstants::SCALING_WINDOW_CLASS_NAME, nullptr)) {
|
||||
Logger::Get().Error("已存在缩放窗口");
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
InitMessage();
|
||||
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
|
||||
#ifdef MP_VERSION_TAG
|
||||
STRING(MP_VERSION_TAG),
|
||||
#else
|
||||
"dev",
|
||||
#endif
|
||||
Win32Helper::GetOSVersion().ToString<char>(),
|
||||
Win32Helper::IsProcessElevated() ? "是" : "否"
|
||||
));
|
||||
|
||||
bool isSrcInvisibleOrMinimized = false;
|
||||
if (ScalingError error = _srcTracker.Set(hwndSrc, _options, isSrcInvisibleOrMinimized);
|
||||
error != ScalingError::NoError
|
||||
) {
|
||||
if (ScalingError error = _srcTracker.Set(hwndSrc, _options); error != ScalingError::NoError) {
|
||||
Logger::Get().Error("初始化 SrcTracker 失败");
|
||||
return error;
|
||||
}
|
||||
|
||||
if (isSrcInvisibleOrMinimized || (_srcTracker.IsMoving() && !_options.IsWindowedMode())) {
|
||||
// 等待源窗口状态改变
|
||||
_isSrcRepositioning = true;
|
||||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
if (_srcTracker.IsZoomed()) {
|
||||
if (_options.IsWindowedMode()) {
|
||||
Logger::Get().Info("已最大化的窗口不支持窗口模式缩放");
|
||||
return ScalingError::BannedInWindowedMode;
|
||||
} else if (!_options.RealIsAllowScalingMaximized()) {
|
||||
} else if (!_options.IsAllowScalingMaximized()) {
|
||||
Logger::Get().Info("源窗口已最大化");
|
||||
return ScalingError::Maximized;
|
||||
}
|
||||
}
|
||||
|
||||
[[maybe_unused]] static Ignore _ = []() {
|
||||
#if _DEBUG
|
||||
OutputDebugString(fmt::format(L"可执行文件路径: {}\n窗口类: {}\n",
|
||||
Win32Helper::GetWindowPath(hwndSrc), Win32Helper::GetWindowClassName(hwndSrc)).c_str());
|
||||
#endif
|
||||
|
||||
static Ignore _ = []() {
|
||||
WNDCLASSEXW wcex{
|
||||
.cbSize = sizeof(wcex),
|
||||
.lpfnWndProc = _WndProc,
|
||||
|
|
@ -137,7 +122,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
const bool isWin11 = Win32Helper::GetOSVersion().IsWin11();
|
||||
// 不存在非客户区,渲染无需创建在子窗口里
|
||||
const bool isAllClient = !isWin11 &&
|
||||
(srcWindowKind == SrcWindowKind::NoBorder || srcWindowKind == SrcWindowKind::NoNativeFrame);
|
||||
(srcWindowKind == SrcWindowKind::NoBorder || srcWindowKind == SrcWindowKind::NoDecoration);
|
||||
if (_options.IsWindowedMode()) {
|
||||
const RECT& srcWindowRect = _srcTracker.WindowRect();
|
||||
|
||||
|
|
@ -146,15 +131,15 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
(srcWindowRect.top + srcWindowRect.bottom) / 2
|
||||
};
|
||||
HMONITOR hMon = MonitorFromPoint(windowCenter, MONITOR_DEFAULTTONEAREST);
|
||||
|
||||
|
||||
if (isAllClient) {
|
||||
_topBorderThicknessInClient = 0;
|
||||
_nonTopBorderThicknessInClient = 0;
|
||||
} else {
|
||||
GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &_currentDpi, &_currentDpi);
|
||||
|
||||
if (isWin11 && srcWindowKind == SrcWindowKind::NoNativeFrame) {
|
||||
// NoNativeFrame 在 Win11 中和 NoTitleBar 一样处理并禁用边框,优点是只需一个辅助窗口
|
||||
if (isWin11 && srcWindowKind == SrcWindowKind::NoDecoration) {
|
||||
// NoDecoration 在 Win11 中和 NoTitleBar 一样处理并禁用边框,优点是只需一个辅助窗口
|
||||
_topBorderThicknessInClient = 0;
|
||||
} else {
|
||||
_topBorderThicknessInClient = Win32Helper::GetNativeWindowBorderThickness(_currentDpi);
|
||||
|
|
@ -173,32 +158,27 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
// 填入渲染矩形尺寸
|
||||
int windowWidth = 0;
|
||||
int windowHeight = 0;
|
||||
if (_lastWindowedRendererWidth == 0) {
|
||||
if (_options.initialWindowedScaleFactor < 1.0f) {
|
||||
// 根据屏幕的工作区尺寸计算
|
||||
MONITORINFO mi{ .cbSize = sizeof(mi) };
|
||||
if (GetMonitorInfo(hMon, &mi)) {
|
||||
const SIZE monitorSize = Win32Helper::GetSizeOfRect(mi.rcWork);
|
||||
const float srcAspectRatio = (float)srcSize.cy / srcSize.cx;
|
||||
if (_options.initialWindowedScaleFactor < 1.0f) {
|
||||
// 根据屏幕的工作区尺寸计算
|
||||
MONITORINFO mi{ .cbSize = sizeof(mi) };
|
||||
if (GetMonitorInfo(hMon, &mi)) {
|
||||
const SIZE monitorSize = Win32Helper::GetSizeOfRect(mi.rcWork);
|
||||
const float srcAspectRatio = (float)srcSize.cy / srcSize.cx;
|
||||
|
||||
// 放大到显示器的 3/4,且最少放大 1/3 倍
|
||||
if ((float)monitorSize.cy / monitorSize.cx > srcAspectRatio) {
|
||||
windowWidth = std::max(monitorSize.cx * 3 / 4, srcSize.cx * 4 / 3);
|
||||
} else {
|
||||
windowHeight = std::max(monitorSize.cy * 3 / 4, srcSize.cy * 4 / 3);
|
||||
}
|
||||
// 放大到显示器的 3/4,且最少放大 1/3 倍
|
||||
if ((float)monitorSize.cy / monitorSize.cx > srcAspectRatio) {
|
||||
windowWidth = std::max(monitorSize.cx * 3 / 4, srcSize.cx * 4 / 3);
|
||||
} else {
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
windowWidth = srcSize.cx;
|
||||
windowHeight = std::max(monitorSize.cy * 3 / 4, srcSize.cy * 4 / 3);
|
||||
}
|
||||
} else {
|
||||
windowWidth = (LONG)std::lroundf(srcSize.cx * _options.initialWindowedScaleFactor);
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
windowWidth = srcSize.cx;
|
||||
}
|
||||
} else {
|
||||
// 恢复上次窗口模式缩放尺寸
|
||||
windowWidth = _lastWindowedRendererWidth;
|
||||
windowWidth = (LONG)std::lroundf(srcSize.cx * _options.initialWindowedScaleFactor);
|
||||
}
|
||||
|
||||
|
||||
if (!_CalcWindowedScalingWindowSize(windowWidth, windowHeight, true)) {
|
||||
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
|
|
@ -301,11 +281,12 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
}
|
||||
|
||||
LogRects(_srcTracker.SrcRect(), _rendererRect, _windowRect);
|
||||
|
||||
if (!_options.RealIsAllowScalingMaximized()) {
|
||||
|
||||
if (!_options.IsWindowedMode() && !_options.IsAllowScalingMaximized()) {
|
||||
// 检查源窗口是否是无边框全屏窗口
|
||||
if (srcWindowKind == SrcWindowKind::NoNativeFrame && _srcTracker.WindowRect() == _rendererRect) {
|
||||
if (srcWindowKind == SrcWindowKind::NoDecoration && _srcTracker.WindowRect() == _rendererRect) {
|
||||
Logger::Get().Info("源窗口已全屏");
|
||||
Destroy();
|
||||
return ScalingError::Maximized;
|
||||
}
|
||||
}
|
||||
|
|
@ -314,6 +295,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
ScalingError error = _renderer->Initialize(_hwndRenderer, _options.overlayOptions);
|
||||
if (error != ScalingError::NoError) {
|
||||
Logger::Get().Error("初始化 Renderer 失败");
|
||||
Destroy();
|
||||
return error;
|
||||
}
|
||||
|
||||
|
|
@ -327,72 +309,16 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ScalingError error = _StartImpl(hwndSrc);
|
||||
if (error != ScalingError::NoError) {
|
||||
_options.showError(hwndSrc, error);
|
||||
// 清理
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ScalingWindow::Stop() noexcept {
|
||||
Destroy();
|
||||
// 为了简化逻辑和确保可靠清理,这里始终调用 CleanAfterSrcRepositioned
|
||||
CleanAfterSrcRepositioned();
|
||||
}
|
||||
|
||||
void ScalingWindow::ToggleScaling(bool isWindowedMode) noexcept {
|
||||
assert(Handle());
|
||||
|
||||
if (_options.IsWindowedMode() == isWindowedMode || !_srcTracker.IsFocused()) {
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// 源窗口在前台时按快捷键可以切换全屏/窗口模式缩放
|
||||
_isSrcRepositioning = true;
|
||||
if (_options.IsWindowedMode()) {
|
||||
_lastWindowedRendererWidth = _rendererRect.right - _rendererRect.left;
|
||||
}
|
||||
Destroy();
|
||||
_options.IsWindowedMode(isWindowedMode);
|
||||
RestartAfterSrcRepositioned();
|
||||
}
|
||||
|
||||
void ScalingWindow::SwitchToolbarState() noexcept {
|
||||
if (_renderer) {
|
||||
_renderer->SwitchToolbarState();
|
||||
}
|
||||
}
|
||||
|
||||
void ScalingWindow::Render() noexcept {
|
||||
bool isSrcRepositioning = false;
|
||||
bool srcFocusedChanged = false;
|
||||
if (!_UpdateSrcState(isSrcRepositioning, srcFocusedChanged)) {
|
||||
const bool originIsSrcFocused = _srcTracker.IsFocused();
|
||||
|
||||
if (!_UpdateSrcState()) {
|
||||
Logger::Get().Info("源窗口状态改变");
|
||||
_DelayedStop(false, isSrcRepositioning);
|
||||
_DelayedDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (srcFocusedChanged) {
|
||||
if (_srcTracker.IsFocused() != originIsSrcFocused) {
|
||||
_UpdateFocusStateAsync();
|
||||
}
|
||||
|
||||
|
|
@ -408,15 +334,18 @@ void ScalingWindow::Render() noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
void ScalingWindow::RestartAfterSrcRepositioned() noexcept {
|
||||
Start(_srcTracker.Handle(), std::move(_options));
|
||||
void ScalingWindow::ToggleToolbarState() noexcept {
|
||||
if (_renderer) {
|
||||
_renderer->ToggleToolbarState();
|
||||
}
|
||||
}
|
||||
|
||||
void ScalingWindow::RecreateAfterSrcRepositioned() noexcept {
|
||||
Create(_srcTracker.Handle(), std::move(_options));
|
||||
}
|
||||
|
||||
void ScalingWindow::CleanAfterSrcRepositioned() noexcept {
|
||||
if (_options.save) {
|
||||
_options = {};
|
||||
}
|
||||
_lastWindowedRendererWidth = 0;
|
||||
_options = {};
|
||||
_isSrcRepositioning = false;
|
||||
}
|
||||
|
||||
|
|
@ -461,8 +390,8 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
DwmExtendFrameIntoClientArea(Handle(), &margins);
|
||||
}
|
||||
|
||||
if (_srcTracker.WindowKind() == SrcWindowKind::NoNativeFrame && Win32Helper::GetOSVersion().IsWin11()) {
|
||||
// Win11 中禁用边框和圆角以模仿 NoNativeFrame 的样式
|
||||
if (_srcTracker.WindowKind() == SrcWindowKind::NoDecoration && Win32Helper::GetOSVersion().IsWin11()) {
|
||||
// Win11 中禁用边框和圆角以模仿 NoDecoration 的样式
|
||||
COLORREF color = DWMWA_COLOR_NONE;
|
||||
DwmSetWindowAttribute(Handle(), DWMWA_BORDER_COLOR, &color, sizeof(color));
|
||||
|
||||
|
|
@ -484,20 +413,6 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
{
|
||||
// 调整窗口大小时会进入 OS 的内部循环,我们的消息循环没有机会调用 Render。幸运的是
|
||||
// 内部循环会正常分发消息,因此有必要在窗口过程中执行渲染以避免调整大小时渲染暂停。
|
||||
if (!_renderer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 删除消息队列中的其他 WM_FRONTEND_RENDER 以避免重复渲染
|
||||
{
|
||||
MSG msg1;
|
||||
while (PeekMessage(&msg1, Handle(), CommonSharedConstants::WM_FRONTEND_RENDER,
|
||||
CommonSharedConstants::WM_FRONTEND_RENDER, PM_REMOVE)
|
||||
) {
|
||||
// 不做处理
|
||||
}
|
||||
}
|
||||
|
||||
Render();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -524,7 +439,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
|
||||
if (!_srcTracker.MoveOnEndResizeMove()) {
|
||||
Logger::Get().Error("SrcTracker::MoveOnEndResizeMove 失败");
|
||||
_DelayedStop();
|
||||
_DelayedDestroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -552,7 +467,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
newSize = SIZE{ width, height };
|
||||
} else {
|
||||
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
|
||||
_DelayedStop();
|
||||
_DelayedDestroy();
|
||||
}
|
||||
} else {
|
||||
newSize = _AdjustFullscreenWindowSize(
|
||||
|
|
@ -607,7 +522,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
// 这时鼠标点击将激活源窗口
|
||||
const HWND hwndForground = GetForegroundWindow();
|
||||
if (hwndForground != _srcTracker.Handle()) {
|
||||
if (!_srcTracker.SetFocus()) {
|
||||
if (!SetForegroundWindow(_srcTracker.Handle())) {
|
||||
// 设置前台窗口失败,可能是因为前台窗口是开始菜单
|
||||
if (WindowHelper::IsStartMenu(hwndForground)) {
|
||||
using namespace std::chrono;
|
||||
|
|
@ -640,7 +555,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
Sleep(1);
|
||||
}
|
||||
|
||||
_srcTracker.SetFocus();
|
||||
SetForegroundWindow(_srcTracker.Handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -650,7 +565,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
{
|
||||
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
_DelayedStop(true);
|
||||
_DelayedDestroy(true);
|
||||
// 阻止拖拽和调整大小
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -703,7 +618,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
|
||||
if (!_CalcWindowedScalingWindowSize(windowWidth, windowHeight, false)) {
|
||||
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
|
||||
_DelayedStop();
|
||||
_DelayedDestroy();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -725,18 +640,6 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
{
|
||||
WINDOWPOS& windowPos = *(WINDOWPOS*)lParam;
|
||||
|
||||
// 阻止 OS 修改置顶状态。当源窗口中途置顶/取消置顶时,OS 会试图修改缩放窗口的置顶
|
||||
// 状态,这不是我们想要的。
|
||||
if (!(windowPos.flags & SWP_NOZORDER) && !_options.IsDebugMode()) {
|
||||
if (_CalcTopmostState()) {
|
||||
if (windowPos.hwndInsertAfter != HWND_TOP) {
|
||||
windowPos.hwndInsertAfter = HWND_TOPMOST;
|
||||
}
|
||||
} else if (windowPos.hwndInsertAfter == HWND_TOPMOST) {
|
||||
windowPos.hwndInsertAfter = HWND_NOTOPMOST;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果全屏模式缩放包含 WS_MAXIMIZE 样式,创建窗口时将收到 WM_WINDOWPOSCHANGING,
|
||||
// 应该忽略。
|
||||
if (!_renderer || (windowPos.flags & (SWP_NOSIZE | SWP_NOMOVE)) == (SWP_NOSIZE | SWP_NOMOVE)) {
|
||||
|
|
@ -759,7 +662,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
|
||||
if (!_CalcWindowedScalingWindowSize(windowPos.cx, windowPos.cy, false)) {
|
||||
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
|
||||
_DelayedStop();
|
||||
_DelayedDestroy();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -818,7 +721,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
// 焦点。进行下面的操作:调整缩放窗口尺寸,打开开始菜单然后关闭,缩放窗口便
|
||||
// 得到焦点了。这应该是 OS 的 bug,下面的代码用于规避它。
|
||||
if (!(windowPos.flags & SWP_NOACTIVATE)) {
|
||||
_srcTracker.SetFocus();
|
||||
SetForegroundWindow(_srcTracker.Handle());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -832,7 +735,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
// 中不会捕获光标,因此即使触发了也不会造成严重后果。
|
||||
// if (_isResizingOrMoving && Win32Helper::IsWindowHung(_srcTracker.Handle())) {
|
||||
// Logger::Get().Error("源窗口已挂起");
|
||||
// _DelayedStop(true);
|
||||
// _DelayedDestroy(true);
|
||||
// }
|
||||
|
||||
// 更新窗口矩形和渲染器矩形,因为 WM_NCCALCSIZE 和 WM_WINDOWPOSCHANGING 都是可选的
|
||||
|
|
@ -862,7 +765,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
// 使用 WM_SYSCOMMAND 区分接下来的 WM_ENTERSIZEMOVE 是调整大小还是移动
|
||||
_isPreparingForResizing = (wParam & 0xFFF0) == SC_SIZE;
|
||||
if (_isPreparingForResizing) {
|
||||
_srcTracker.SetFocus();
|
||||
SetForegroundWindow(_srcTracker.Handle());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -888,10 +791,11 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
_renderer.reset();
|
||||
Logger::Get().Info("Renderer 已析构");
|
||||
|
||||
// 如果正在源窗口正在调整,暂时不清理这些成员
|
||||
if (!_isSrcRepositioning) {
|
||||
// 缩放结束时保存配置
|
||||
_options.save(_options, NULL);
|
||||
CleanAfterSrcRepositioned();
|
||||
_options = {};
|
||||
}
|
||||
|
||||
// 还原时钟精度
|
||||
|
|
@ -1125,15 +1029,24 @@ ScalingError ScalingWindow::_CalcFullscreenRendererRect(uint32_t& monitorCount)
|
|||
}
|
||||
}
|
||||
|
||||
// 全屏模式缩放无需保持比例,但要限制最大尺寸
|
||||
// 全屏模式缩放无需保持比例,但要限制最小和最大尺寸
|
||||
SIZE ScalingWindow::_AdjustFullscreenWindowSize(SIZE size, uint32_t dpi) const noexcept {
|
||||
if (dpi == 0) {
|
||||
dpi = _currentDpi;
|
||||
}
|
||||
|
||||
const RECT& srcFrameRect = _srcTracker.WindowFrameRect();
|
||||
const LONG spaceAround = lroundf(WINDOWED_MODE_MIN_SPACE_AROUND *
|
||||
dpi / float(USER_DEFAULT_SCREEN_DPI));
|
||||
const LONG minWidth = srcFrameRect.right - srcFrameRect.left + spaceAround;
|
||||
const LONG minHeight = srcFrameRect.bottom - srcFrameRect.top + spaceAround;
|
||||
const LONG maxWidth = GetSystemMetricsForDpi(SM_CXMAXTRACK, dpi);
|
||||
const LONG maxHeight = GetSystemMetricsForDpi(SM_CYMAXTRACK, dpi);
|
||||
return SIZE{ std::clamp(size.cx, 1l, maxWidth), std::clamp(size.cy, 1l, maxHeight) };
|
||||
|
||||
return SIZE{
|
||||
std::clamp(size.cx, minWidth, maxWidth),
|
||||
std::clamp(size.cy, minHeight, maxHeight)
|
||||
};
|
||||
}
|
||||
|
||||
ScalingError ScalingWindow::_InitialMoveSrcWindowInFullscreen() noexcept {
|
||||
|
|
@ -1202,7 +1115,7 @@ void ScalingWindow::_Show() noexcept {
|
|||
|
||||
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
_DelayedStop(true);
|
||||
_DelayedDestroy(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1215,7 +1128,7 @@ void ScalingWindow::_Show() noexcept {
|
|||
Handle(),
|
||||
NULL,
|
||||
0, 0, 0, 0,
|
||||
SWP_SHOWWINDOW | SWP_NO_ACTIVATE_MOVE_SIZE
|
||||
SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE
|
||||
);
|
||||
|
||||
// 广播开始缩放
|
||||
|
|
@ -1229,7 +1142,7 @@ void ScalingWindow::_Show() noexcept {
|
|||
|
||||
// 如果源窗口位于前台则将缩放窗口置顶
|
||||
if (_srcTracker.IsFocused()) {
|
||||
_UpdateFocusStateAsync();
|
||||
_UpdateFocusStateAsync(true);
|
||||
}
|
||||
|
||||
if (_options.IsTouchSupportEnabled()) {
|
||||
|
|
@ -1245,7 +1158,7 @@ void ScalingWindow::_Show() noexcept {
|
|||
}
|
||||
|
||||
// 模拟独占全屏
|
||||
if (_options.RealIsSimulateExclusiveFullscreen()) {
|
||||
if (_options.IsSimulateExclusiveFullscreen()) {
|
||||
// 延迟 1s 以避免干扰游戏的初始化,见 #495
|
||||
([]()->winrt::fire_and_forget {
|
||||
const uint32_t runId = RunId();
|
||||
|
|
@ -1283,17 +1196,14 @@ void ScalingWindow::_MoveRenderer() noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
bool ScalingWindow::_UpdateSrcState(
|
||||
bool& isSrcRepositioning,
|
||||
bool& srcFocusedChanged
|
||||
) noexcept {
|
||||
bool ScalingWindow::_UpdateSrcState() noexcept {
|
||||
HWND hwndFore = GetForegroundWindow();
|
||||
|
||||
if (hwndFore == Handle()) {
|
||||
// 缩放窗口不应该得到焦点,我们通过 WS_EX_NOACTIVATE 样式和处理 WM_MOUSEACTIVATE
|
||||
// 等消息来做到这一点。但如果由于某种我们尚未了解的机制这些手段都失败了,这里
|
||||
// 进行纠正。
|
||||
_srcTracker.SetFocus();
|
||||
SetForegroundWindow(_srcTracker.Handle());
|
||||
hwndFore = GetForegroundWindow();
|
||||
}
|
||||
|
||||
|
|
@ -1302,31 +1212,21 @@ bool ScalingWindow::_UpdateSrcState(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool isSrcInvisibleOrMinimized = false;
|
||||
bool srcRectChanged = false;
|
||||
bool srcSizeChanged = false;
|
||||
bool srcMovingChanged = false;
|
||||
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), _isResizingOrMoving,
|
||||
isSrcInvisibleOrMinimized, srcFocusedChanged, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
|
||||
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(),
|
||||
_isResizingOrMoving, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isSrcInvisibleOrMinimized || srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
|
||||
// 不要立刻设置 _isSrcSizing,销毁窗口是异步的
|
||||
isSrcRepositioning = true;
|
||||
|
||||
if (srcSizeChanged) {
|
||||
// 源窗口大小改变则清除记忆
|
||||
_lastWindowedRendererWidth = 0;
|
||||
} else if (isSrcInvisibleOrMinimized) {
|
||||
if (_options.IsWindowedMode()) {
|
||||
_lastWindowedRendererWidth = _rendererRect.right - _rendererRect.left;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
|
||||
_isSrcRepositioning = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// DirectFlip 可能使窗口移动很卡,目前发现缩放 Magpie 主窗口有这个
|
||||
// 问题。因此源窗口移动过程中临时禁用 DirectFlip。
|
||||
if (srcMovingChanged) {
|
||||
assert(_options.IsWindowedMode());
|
||||
|
||||
|
|
@ -1594,7 +1494,7 @@ LRESULT ScalingWindow::_BorderHelperWndProc(HWND hWnd, UINT msg, WPARAM wParam,
|
|||
}
|
||||
|
||||
void ScalingWindow::_CreateBorderHelperWindows() noexcept {
|
||||
[[maybe_unused]] static Ignore _ = [] {
|
||||
static Ignore _ = [] {
|
||||
WNDCLASSEXW wcex{
|
||||
.cbSize = sizeof(wcex),
|
||||
.lpfnWndProc = _BorderHelperWndProc,
|
||||
|
|
@ -1608,7 +1508,7 @@ void ScalingWindow::_CreateBorderHelperWindows() noexcept {
|
|||
|
||||
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
_DelayedStop(true);
|
||||
_DelayedDestroy(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1658,7 +1558,7 @@ void ScalingWindow::_RepostionBorderHelperWindows() noexcept {
|
|||
// 可能卡死。
|
||||
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
_DelayedStop(true);
|
||||
_DelayedDestroy(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1777,7 +1677,7 @@ void ScalingWindow::_UpdateTouchHoleWindows(bool onInit) noexcept {
|
|||
const RECT srcTouchRect = _CalcSrcTouchRect();
|
||||
_UpdateTouchProps(srcTouchRect);
|
||||
|
||||
[[maybe_unused]] static Ignore _ = [] {
|
||||
static Ignore _ = [] {
|
||||
WNDCLASSEXW wcex{
|
||||
.cbSize = sizeof(wcex),
|
||||
.lpfnWndProc = BkgWndProc,
|
||||
|
|
@ -1870,115 +1770,40 @@ void ScalingWindow::_UpdateFrameMargins() const noexcept {
|
|||
DwmExtendFrameIntoClientArea(Handle(), &margins);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync() const noexcept {
|
||||
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(bool onShow) const noexcept {
|
||||
if (_options.IsWindowedMode()) {
|
||||
// 根据源窗口状态绘制非客户区,我们必须自己控制非客户区是绘制成焦点状态还是非焦点
|
||||
// 状态,因为缩放窗口实际上永远不会得到焦点。
|
||||
DefWindowProc(Handle(), WM_NCACTIVATE, _srcTracker.IsFocused(), 0);
|
||||
}
|
||||
} else if (!_options.IsDebugMode()) {
|
||||
if (!onShow) {
|
||||
const uint32_t runId = RunId();
|
||||
|
||||
// 前台窗口变化后立刻调用 SetWindowPos 有时会导致 Z 顺序混乱,因此等待一段时间
|
||||
co_await 20ms;
|
||||
co_await _dispatcher;
|
||||
|
||||
if (runId != RunId()) {
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_options.IsDebugMode()) {
|
||||
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
_DelayedStop();
|
||||
Destroy();
|
||||
co_return;
|
||||
}
|
||||
|
||||
// 这里搞得很复杂,是我反复实验得到的,若要修改应测试下列情形:
|
||||
// 1. 缩放 WindowCase 中的 TopmostWindow 和 PopupHostWindow
|
||||
// 2. 缩放常规窗口然后切换到管理员身份的窗口。测试这一条时应直接运行,不要调试,因
|
||||
// 为调试状态下 SetWindowPos 的行为有变化
|
||||
// 3. 缩放时将任意窗口最小化然后还原
|
||||
// 4. 缩放时拖动任意窗口
|
||||
|
||||
const bool oldTopmost = IsTopmostWindow(Handle());
|
||||
const bool newTopmost = _CalcTopmostState();
|
||||
if (oldTopmost != newTopmost) {
|
||||
if (newTopmost) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
SetWindowPos(Handle(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
|
||||
|
||||
if (IsTopmostWindow(Handle()) == newTopmost) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏模式缩放时确保缩放窗口在所有置顶窗口之上,这使不支持 MPO 的显卡更容易激
|
||||
// 活 DirectFlip。
|
||||
if (!_options.IsWindowedMode()) {
|
||||
SetWindowPos(Handle(), HWND_TOP, 0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
|
||||
}
|
||||
} else {
|
||||
const uint32_t runId = ScalingWindow::RunId();
|
||||
bool isInBackground = false;
|
||||
|
||||
HWND hwndFore = GetForegroundWindow();
|
||||
if (!hwndFore) {
|
||||
// 切换窗口时有一个瞬间无前台窗口,这里等待切换完成
|
||||
co_await winrt::resume_after(1ms);
|
||||
isInBackground = true;
|
||||
hwndFore = GetForegroundWindow();
|
||||
}
|
||||
|
||||
bool isForeMovable = true;
|
||||
if (hwndFore) {
|
||||
DWORD windowIL;
|
||||
isForeMovable = Win32Helper::GetWindowIntegrityLevel(hwndFore, windowIL) &&
|
||||
windowIL <= Win32Helper::GetCurrentProcessIntegrityLevel();
|
||||
}
|
||||
|
||||
if (!isForeMovable) {
|
||||
if (!isInBackground) {
|
||||
co_await winrt::resume_background();
|
||||
isInBackground = true;
|
||||
}
|
||||
|
||||
// 等待 DWM 开始合成新帧以避免显示中间状态
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
}
|
||||
|
||||
if (isInBackground) {
|
||||
co_await ScalingWindow::Get().Dispatcher();
|
||||
|
||||
// 等待时源窗口重新回到前台了应放弃后续操作
|
||||
if (runId != ScalingWindow::RunId() || _srcTracker.IsFocused()) {
|
||||
co_return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
HDWP hDwp = BeginDeferWindowPos(isForeMovable ? 2 : 3);
|
||||
|
||||
// 改变置顶状态
|
||||
hDwp = DeferWindowPos(hDwp, Handle(), HWND_NOTOPMOST,
|
||||
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
|
||||
|
||||
// 将缩放窗口恰好置于源窗口前
|
||||
if (isForeMovable) {
|
||||
// 这个方式没有中间状态,但会导致源窗口遮挡前台窗口,之后会手动将前台窗口移到顶部
|
||||
hDwp = DeferWindowPos(hDwp, Handle(), _srcTracker.Handle(),
|
||||
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
|
||||
} else {
|
||||
// 这个方式不会移动源窗口,但有中间状态。如果前台窗口 IL 更高,这是唯一的办法
|
||||
hDwp = DeferWindowPos(hDwp, Handle(), _srcTracker.Handle(),
|
||||
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE | SWP_NOOWNERZORDER);
|
||||
hDwp = DeferWindowPos(hDwp, _srcTracker.Handle(), Handle(),
|
||||
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE | SWP_NOOWNERZORDER);
|
||||
}
|
||||
|
||||
EndDeferWindowPos(hDwp);
|
||||
|
||||
// 确保缩放窗口刚好在源窗口前
|
||||
if (IsTopmostWindow(Handle()) == newTopmost &&
|
||||
GetWindow(_srcTracker.Handle(), GW_HWNDPREV) == Handle()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isForeMovable && hwndFore && GetForegroundWindow() == hwndFore) {
|
||||
SetWindowPos(hwndFore, HWND_TOP, 0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
|
||||
}
|
||||
}
|
||||
// 源窗口位于前台时将缩放窗口置顶,这使不支持 MPO 的显卡更容易激活 DirectFlip
|
||||
if (_srcTracker.IsFocused()) {
|
||||
SetWindowPos(Handle(), HWND_TOPMOST,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
// 再次调用 SetWindowPos 确保缩放窗口在所有置顶窗口之上
|
||||
SetWindowPos(Handle(), HWND_TOP,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
} else {
|
||||
SetWindowPos(Handle(), HWND_NOTOPMOST,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1990,29 +1815,14 @@ winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync() const noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
bool ScalingWindow::_CalcTopmostState() const noexcept {
|
||||
// 源窗口置顶时缩放窗口必须置顶
|
||||
if (IsTopmostWindow(_srcTracker.Handle())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 源窗口位于前台时一般将缩放窗口置顶,这是为了防止有些窗口突破 OS 维护的所有者关系
|
||||
// 顺序,如 GH#1232。一个例外是源窗口有弹窗时缩放窗口应在弹窗下方,除了常规弹窗,还
|
||||
// 应检查模拟模态弹窗(见 ScalingService.cpp 的 IsPopupWindow)。
|
||||
return !_options.IsTopmostDisabled() &&
|
||||
_srcTracker.IsFocused() &&
|
||||
!GetWindow(_srcTracker.Handle(), GW_ENABLEDPOPUP) &&
|
||||
IsWindowEnabled(_srcTracker.Handle());
|
||||
}
|
||||
|
||||
bool ScalingWindow::_IsBorderless() const noexcept {
|
||||
assert(_options.IsWindowedMode());
|
||||
|
||||
const SrcWindowKind srcWindowKind = _srcTracker.WindowKind();
|
||||
// NoBorder: Win11 中这类窗口有着特殊的边框,因此和 Win10 的处理方式相同。
|
||||
// NoNativeFrame: Win11 中实现为无标题栏并隐藏边框。
|
||||
// NoDecoration: Win11 中实现为无标题栏并隐藏边框。
|
||||
return srcWindowKind == SrcWindowKind::NoBorder ||
|
||||
(srcWindowKind == SrcWindowKind::NoNativeFrame && Win32Helper::GetOSVersion().IsWin10());
|
||||
(srcWindowKind == SrcWindowKind::NoDecoration && Win32Helper::GetOSVersion().IsWin10());
|
||||
}
|
||||
|
||||
void ScalingWindow::_UpdateRendererRect() noexcept {
|
||||
|
|
@ -2031,15 +1841,14 @@ void ScalingWindow::_UpdateRendererRect() noexcept {
|
|||
const bool resized = Win32Helper::GetSizeOfRect(_rendererRect) !=
|
||||
Win32Helper::GetSizeOfRect(oldRendererRect);
|
||||
|
||||
// 全屏模式缩放时不移动源窗口,因为我们不限制最小尺寸,而且源窗口可能处于最大化或全屏状态
|
||||
if (_options.IsWindowedMode() && !_isMovingDueToSrcMoved && !_srcTracker.IsMoving()) {
|
||||
if (!_isMovingDueToSrcMoved && !_srcTracker.IsMoving()) {
|
||||
// 确保源窗口中心点和缩放窗口中心点相同。应先移动源窗口,因为之后需要调整光标位置
|
||||
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)) {
|
||||
Logger::Get().Error("SrcTracker::Move 失败");
|
||||
_DelayedStop();
|
||||
_DelayedDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2069,7 +1878,7 @@ void ScalingWindow::_UpdateRendererRect() noexcept {
|
|||
}
|
||||
|
||||
// OS 有类似的机制,但我们很少能触发,只能自己处理。参考自
|
||||
// https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L592
|
||||
// https://github.com/tongzx/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L592
|
||||
bool ScalingWindow::_EnsureCaptionVisibleOnScreen() noexcept {
|
||||
struct EnumData {
|
||||
RECT windowRect;
|
||||
|
|
@ -2140,25 +1949,20 @@ void ScalingWindow::_UpdateWindowRectFromWindowPos(const WINDOWPOS& windowPos) n
|
|||
}
|
||||
}
|
||||
|
||||
void ScalingWindow::_DelayedStop(bool onSrcHung, bool onSrcRepositioning) const noexcept {
|
||||
void ScalingWindow::_DelayedDestroy(bool onSrcHung) const noexcept {
|
||||
if (!onSrcHung) {
|
||||
const HWND hwndSrc = _srcTracker.Handle();
|
||||
if (IsTopmostWindow(Handle()) && !(IsWindow(hwndSrc) && Win32Helper::IsWindowHung(hwndSrc))) {
|
||||
if (!(IsWindow(hwndSrc) && Win32Helper::IsWindowHung(hwndSrc))) {
|
||||
// 提前取消置顶,这样销毁时出现问题不会影响和桌面环境交互
|
||||
SetWindowPos(Handle(), HWND_NOTOPMOST, 0, 0, 0, 0,
|
||||
SWP_NO_ACTIVATE_MOVE_SIZE | SWP_NOOWNERZORDER);
|
||||
SetWindowPos(Handle(), HWND_NOTOPMOST,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
}
|
||||
}
|
||||
|
||||
// 延迟销毁可以避免中间状态
|
||||
_dispatcher.TryEnqueue([runId(RunId()), onSrcRepositioning]() {
|
||||
_dispatcher.TryEnqueue([runId(RunId())]() {
|
||||
if (runId == RunId()) {
|
||||
if (onSrcRepositioning) {
|
||||
ScalingWindow::Get()._isSrcRepositioning = true;
|
||||
ScalingWindow::Get().Destroy();
|
||||
} else {
|
||||
ScalingWindow::Get().Stop();
|
||||
}
|
||||
ScalingWindow::Get().Destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
#pragma once
|
||||
#include "ScalingOptions.h"
|
||||
#include "SrcTracker.h"
|
||||
#include "WindowBase.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingError.h"
|
||||
#include "SrcTracker.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
class CursorManager;
|
||||
|
||||
class ScalingWindow final : public WindowBaseT<ScalingWindow> {
|
||||
class ScalingWindow : public WindowBaseT<ScalingWindow> {
|
||||
using base_type = WindowBaseT<ScalingWindow>;
|
||||
friend base_type;
|
||||
friend class base_type;
|
||||
|
||||
public:
|
||||
static ScalingWindow& Get() noexcept {
|
||||
|
|
@ -30,16 +31,12 @@ public:
|
|||
return _dispatcher;
|
||||
}
|
||||
|
||||
void Start(HWND hwndSrc, ScalingOptions&& options) noexcept;
|
||||
|
||||
void Stop() noexcept;
|
||||
|
||||
void ToggleScaling(bool isWindowedMode) noexcept;
|
||||
|
||||
void SwitchToolbarState() noexcept;
|
||||
ScalingError Create(HWND hwndSrc, ScalingOptions options) noexcept;
|
||||
|
||||
void Render() noexcept;
|
||||
|
||||
void ToggleToolbarState() noexcept;
|
||||
|
||||
const RECT& RendererRect() const noexcept {
|
||||
return _rendererRect;
|
||||
}
|
||||
|
|
@ -48,11 +45,7 @@ public:
|
|||
return _options;
|
||||
}
|
||||
|
||||
class SrcTracker& SrcTracker() noexcept {
|
||||
return _srcTracker;
|
||||
}
|
||||
|
||||
const class SrcTracker& SrcTracker() const noexcept {
|
||||
SrcTracker& SrcTracker() noexcept {
|
||||
return _srcTracker;
|
||||
}
|
||||
|
||||
|
|
@ -60,15 +53,7 @@ public:
|
|||
return *_renderer;
|
||||
}
|
||||
|
||||
const class Renderer& Renderer() const noexcept {
|
||||
return *_renderer;
|
||||
}
|
||||
|
||||
class CursorManager& CursorManager() noexcept {
|
||||
return *_cursorManager;
|
||||
}
|
||||
|
||||
const class CursorManager& CursorManager() const noexcept {
|
||||
CursorManager& CursorManager() noexcept {
|
||||
return *_cursorManager;
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +61,7 @@ public:
|
|||
return _isSrcRepositioning;
|
||||
}
|
||||
|
||||
void RestartAfterSrcRepositioned() noexcept;
|
||||
void RecreateAfterSrcRepositioned() noexcept;
|
||||
|
||||
void CleanAfterSrcRepositioned() noexcept;
|
||||
|
||||
|
|
@ -86,12 +71,17 @@ public:
|
|||
|
||||
winrt::hstring GetLocalizedString(std::wstring_view resName) const;
|
||||
|
||||
void ShowToast(std::wstring_view msg) const noexcept {
|
||||
_options.showToast(Handle(), msg);
|
||||
// 缩放过程中出现的错误
|
||||
ScalingError RuntimeError() const noexcept {
|
||||
return _runtimeError;
|
||||
}
|
||||
|
||||
void ShowError(ScalingError error) const noexcept {
|
||||
_options.showError(_srcTracker.Handle(), error);
|
||||
void RuntimeError(ScalingError value) noexcept {
|
||||
_runtimeError = value;
|
||||
}
|
||||
|
||||
void ShowToast(std::wstring_view msg) const noexcept {
|
||||
_options.showToast(Handle(), msg);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
|
@ -101,8 +91,6 @@ private:
|
|||
ScalingWindow() noexcept;
|
||||
~ScalingWindow() noexcept;
|
||||
|
||||
ScalingError _StartImpl(HWND hwndSrc) noexcept;
|
||||
|
||||
// 确保渲染窗口长宽比不变,且限制最小和最大尺寸。必须提供 width 和 height 之一,另一个
|
||||
// 应为 0。如果 isRendererSize 为真,传入的 width 和 height 为渲染矩形尺寸,否则为缩
|
||||
// 放窗口尺寸。返回时 width 和 height 是新的缩放窗口尺寸。
|
||||
|
|
@ -118,10 +106,7 @@ private:
|
|||
|
||||
void _Show() noexcept;
|
||||
|
||||
bool _UpdateSrcState(
|
||||
bool& isSrcRepositioning,
|
||||
bool& srcFocusedChanged
|
||||
) noexcept;
|
||||
bool _UpdateSrcState() noexcept;
|
||||
|
||||
bool _CheckForegroundFor3DGameMode(HWND hwndFore) const noexcept;
|
||||
|
||||
|
|
@ -151,9 +136,7 @@ private:
|
|||
|
||||
void _UpdateFrameMargins() const noexcept;
|
||||
|
||||
winrt::fire_and_forget _UpdateFocusStateAsync() const noexcept;
|
||||
|
||||
bool _CalcTopmostState() const noexcept;
|
||||
winrt::fire_and_forget _UpdateFocusStateAsync(bool onShow = false) const noexcept;
|
||||
|
||||
bool _IsBorderless() const noexcept;
|
||||
|
||||
|
|
@ -163,7 +146,7 @@ private:
|
|||
|
||||
void _UpdateWindowRectFromWindowPos(const WINDOWPOS& windowPos) noexcept;
|
||||
|
||||
void _DelayedStop(bool onSrcHung = false, bool onSrcRepositioning = false) const noexcept;
|
||||
void _DelayedDestroy(bool onSrcHung = false) const noexcept;
|
||||
|
||||
static inline std::atomic<uint32_t> _runId = 0;
|
||||
static inline winrt::DispatcherQueue _dispatcher{ nullptr };
|
||||
|
|
@ -192,9 +175,6 @@ private:
|
|||
|
||||
ScalingError _runtimeError = ScalingError::NoError;
|
||||
|
||||
// 窗口缩放时切换到全屏缩放或最小化前保存尺寸供以后恢复
|
||||
LONG _lastWindowedRendererWidth = 0;
|
||||
|
||||
// 第一帧渲染完成后再显示
|
||||
bool _isFirstFrame = false;
|
||||
bool _isResizingOrMoving = false;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "ScreenshotHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include <wincodec.h>
|
||||
#include <charconv>
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct ScreenshotHelper {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,45 @@
|
|||
#include "pch.h"
|
||||
#include "SrcTracker.h"
|
||||
#include "Logger.h"
|
||||
#include "SmallVector.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "Logger.h"
|
||||
#ifdef _DEBUG
|
||||
#include "WindowHelper.h"
|
||||
#endif
|
||||
#include <dwmapi.h>
|
||||
#include "SmallVector.h"
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static bool IsWindowMoving(HWND hWnd) noexcept {
|
||||
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
|
||||
if (GetGUIThreadInfo(GetWindowThreadProcessId(hWnd, nullptr), &guiThreadInfo)) {
|
||||
return guiThreadInfo.flags & GUI_INMOVESIZE;
|
||||
} else {
|
||||
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
|
||||
static bool GetWindowIntegrityLevel(HWND hWnd, DWORD& integrityLevel) noexcept {
|
||||
wil::unique_process_handle hProc = Win32Helper::GetWindowProcessHandle(hWnd);
|
||||
if (!hProc) {
|
||||
Logger::Get().Error("GetWindowProcessHandle 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::unique_handle hQueryToken;
|
||||
if (!OpenProcessToken(hProc.get(), TOKEN_QUERY, hQueryToken.put())) {
|
||||
Logger::Get().Win32Error("OpenProcessToken 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return Win32Helper::GetProcessIntegrityLevel(hQueryToken.get(), integrityLevel);
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept {
|
||||
assert(!isInvisibleOrMinimized);
|
||||
static bool CheckIL(HWND hwndSrc) noexcept {
|
||||
static DWORD thisIL = []() -> DWORD {
|
||||
DWORD il;
|
||||
return Win32Helper::GetProcessIntegrityLevel(NULL, il) ? il : 0;
|
||||
}();
|
||||
|
||||
DWORD windowIL;
|
||||
return GetWindowIntegrityLevel(hwndSrc, windowIL) && windowIL <= thisIL;
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept {
|
||||
_hWnd = hWnd;
|
||||
|
||||
|
||||
// 这里不检查源窗口是否挂起,将在创建缩放窗口前检查
|
||||
|
||||
if (!IsWindow(_hWnd)) {
|
||||
|
|
@ -33,34 +47,19 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
|
|||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
// 不可见和最小化的窗口将等待源窗口状态改变,这里提前返回。注意 showCmd 不能准确
|
||||
// 判断窗口可见性,应使用 IsWindowVisible。
|
||||
if (!IsWindowVisible(_hWnd)) {
|
||||
isInvisibleOrMinimized = true;
|
||||
return ScalingError::NoError;
|
||||
Logger::Get().Error("不支持缩放隐藏的窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
const UINT showCmd = Win32Helper::GetWindowShowCmd(hWnd);
|
||||
if (showCmd == SW_SHOWMINIMIZED) {
|
||||
isInvisibleOrMinimized = true;
|
||||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
if (Win32Helper::GetWindowClassName(hWnd) == L"Ghost") {
|
||||
Logger::Get().Error("不支持缩放幽灵窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
// 检查 integrity level
|
||||
{
|
||||
DWORD windowIL;
|
||||
if (!Win32Helper::GetWindowIntegrityLevel(hWnd, windowIL) ||
|
||||
windowIL > Win32Helper::GetCurrentProcessIntegrityLevel()) {
|
||||
Logger::Get().Error("不支持缩放 IL 更高的窗口");
|
||||
return ScalingError::LowIntegrityLevel;
|
||||
}
|
||||
if (!CheckIL(hWnd)) {
|
||||
Logger::Get().Error("不支持缩放 IL 更高的窗口");
|
||||
return ScalingError::LowIntegrityLevel;
|
||||
}
|
||||
|
||||
// 已在 ScalingService 中阻止
|
||||
|
|
@ -78,7 +77,6 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
|
|||
}
|
||||
|
||||
_isFocused = GetForegroundWindow() == hWnd;
|
||||
_isMoving = IsWindowMoving(_hWnd);
|
||||
|
||||
if (!GetWindowRect(hWnd, &_windowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
|
|
@ -98,15 +96,21 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
|
|||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
const UINT showCmd = Win32Helper::GetWindowShowCmd(hWnd);
|
||||
if (showCmd == SW_SHOWMINIMIZED) {
|
||||
Logger::Get().Error("不支持缩放最小化的窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
// 计算窗口样式
|
||||
BOOL hasBorder = TRUE;
|
||||
bool hasCustomNonclient = false;
|
||||
hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
|
||||
if (FAILED(hr) || !hasBorder) {
|
||||
// 无原生框架要么是因为无 WS_CAPTION 和 WS_THICKFRAME 样式,要么禁用了原生窗口框架
|
||||
// 以自绘标题栏和边框。
|
||||
_windowKind = SrcWindowKind::NoNativeFrame;
|
||||
hasCustomNonclient = GetWindowStyle(hWnd) & (WS_CAPTION | WS_THICKFRAME);
|
||||
// 凡是没有原生框架的窗口都视为 NoDecoration,这类窗口可能没有 WS_CAPTION 和 WS_THICKFRAME 样式,
|
||||
// 或者禁用了原生窗口框架以自绘标题栏和边框。
|
||||
_windowKind = SrcWindowKind::NoDecoration;
|
||||
} else {
|
||||
// 最大化窗口的上边框很可能存在非客户区,这使得 NoTitleBar 类型的窗口最大化时会被归类到 Native。
|
||||
// 技术上说这很合理,上边框处的非客户区当然可以视为标题栏,对后续计算也没有影响。
|
||||
|
|
@ -120,7 +124,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
|
|||
// 一个窗口要么有边框,要么没有。只要左右下三边中有一条边没有边框,我们就将它视为无边框窗口。
|
||||
//
|
||||
// FIXME: 有的窗口(如微信)通过 WM_NCCALCSIZE 移除边框,但不使用 DwmExtendFrameIntoClientArea
|
||||
// 还原阴影,这种窗口实际上是 NoNativeFrame 类型。遗憾的是没有办法获知窗口是否调用了
|
||||
// 还原阴影,这种窗口实际上是 NoDecoration 类型。遗憾的是没有办法获知窗口是否调用了
|
||||
// DwmExtendFrameIntoClientArea,因此我们假设所有使用 WM_NCCALCSIZE 移除边框的窗口都有阴影,
|
||||
// 一方面有阴影的情况更多,比如基于 electron 的窗口,另一方面如果假设没有阴影会使得 Win11 中
|
||||
// 不能正确裁剪边框导致黑边,而如果假设有阴影,猜错的后果相对较轻。
|
||||
|
|
@ -137,11 +141,11 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
|
|||
}
|
||||
}
|
||||
|
||||
// * 最大化窗口、NoNativeFrame 窗口和 Win10 中 NoBorder 窗口不存在边框
|
||||
// * 最大化窗口、NoDecoration 窗口和 Win10 中 NoBorder 窗口不存在边框
|
||||
LONG borderThicknessInFrame = 0;
|
||||
const bool isWin11 = Win32Helper::GetOSVersion().IsWin11();
|
||||
if (!_isMaximized &&
|
||||
_windowKind != SrcWindowKind::NoNativeFrame &&
|
||||
_windowKind != SrcWindowKind::NoDecoration &&
|
||||
(_windowKind != SrcWindowKind::NoBorder || isWin11))
|
||||
{
|
||||
// 使用屏幕而非窗口的 DPI 来计算边框宽度
|
||||
|
|
@ -151,7 +155,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isI
|
|||
borderThicknessInFrame = (LONG)Win32Helper::GetNativeWindowBorderThickness(dpi);
|
||||
}
|
||||
|
||||
return _CalcSrcRect(options, hasCustomNonclient, borderThicknessInFrame);
|
||||
return _CalcSrcRect(options, borderThicknessInFrame);
|
||||
}
|
||||
|
||||
static bool IsPrimaryMouseButtonDown() noexcept {
|
||||
|
|
@ -164,118 +168,88 @@ bool SrcTracker::UpdateState(
|
|||
HWND hwndFore,
|
||||
bool isWindowedMode,
|
||||
bool isResizingOrMoving,
|
||||
bool& isInvisibleOrMinimized,
|
||||
bool& focusedChanged,
|
||||
bool& rectChanged,
|
||||
bool& sizeChanged,
|
||||
bool& movingChanged
|
||||
bool& srcRectChanged,
|
||||
bool& srcSizeChanged,
|
||||
bool& srcMovingChanged
|
||||
) noexcept {
|
||||
assert(!isInvisibleOrMinimized && !focusedChanged &&
|
||||
!rectChanged && !sizeChanged && !movingChanged);
|
||||
assert(!srcRectChanged && !srcSizeChanged && !srcMovingChanged);
|
||||
|
||||
if (!IsWindow(_hWnd)) {
|
||||
Logger::Get().Info("源窗口已销毁");
|
||||
Logger::Get().Error("源窗口已销毁");
|
||||
return false;
|
||||
}
|
||||
|
||||
isInvisibleOrMinimized = !IsWindowVisible(_hWnd);
|
||||
|
||||
// 缩放中途对源窗口响应性的检查更宽松,即使源窗口挂起一段时间,只要用户不做额
|
||||
// 外的操作就不会终止缩放,直到源窗口被替换为幽灵窗口。
|
||||
//
|
||||
// 不要使用 IsHungAppWindow,它有误报的情况,见 GH#1244。这里用了未记录函数
|
||||
// GhostWindowFromHungWindow,它可以准确检查源窗口是否已被替换为幽灵窗口。
|
||||
static const auto ghostWindowFromHungWindow =
|
||||
Win32Helper::LoadSystemFunction<HWND WINAPI(HWND)>(
|
||||
L"user32.dll", "GhostWindowFromHungWindow");
|
||||
if (ghostWindowFromHungWindow && ghostWindowFromHungWindow(_hWnd)) {
|
||||
// 检查源窗口是否真的处于无响应状态
|
||||
if (Win32Helper::IsWindowHung(_hWnd)) {
|
||||
Logger::Get().Info("源窗口已挂起");
|
||||
return false;
|
||||
}
|
||||
if (!IsWindowVisible(_hWnd)) {
|
||||
Logger::Get().Error("源窗口已隐藏");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isFocused != (hwndFore == _hWnd)) {
|
||||
_isFocused = !_isFocused;
|
||||
focusedChanged = true;
|
||||
// Win32Helper::IsWindowHung 更准确,但它会向源窗口发送消息,比较耗时。
|
||||
// IsHungAppWindow 的另一个好处是它不如 Win32Helper::IsWindowHung 严
|
||||
// 格,因此即使源窗口挂起一段时间,只要用户不做额外的操作就不会结束缩放,
|
||||
// 直到源窗口被替换为幽灵窗口。
|
||||
if (IsHungAppWindow(_hWnd)) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
return false;
|
||||
}
|
||||
|
||||
_isFocused = hwndFore == _hWnd;
|
||||
|
||||
const bool oldMaximized = _isMaximized;
|
||||
UINT showCmd = Win32Helper::GetWindowShowCmd(_hWnd);
|
||||
if (showCmd == SW_SHOWMINIMIZED) {
|
||||
Logger::Get().Error("源窗口处于最小化状态");
|
||||
return false;
|
||||
}
|
||||
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
WINDOWPLACEMENT wp{ sizeof(wp) };
|
||||
if (!GetWindowPlacement(_hWnd, &wp)) {
|
||||
Logger::Get().Win32Error("GetWindowPlacement 失败");
|
||||
RECT curWindowRect;
|
||||
if (!GetWindowRect(_hWnd, &curWindowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
_isMaximized = wp.showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
RECT curWindowRect;
|
||||
if (wp.showCmd == SW_SHOWMINIMIZED) {
|
||||
// 窗口最小化有两步:先将窗口状态设为最小化,然后将窗口移出屏幕 (左上角坐标
|
||||
// (-32000,-32000))。如果我们刚好在两步之间停止缩放,第二步将无法执行,这和缩
|
||||
// 放窗口被源窗口所有有关,不确定是否是 OS 的 bug。这个检查确保第二步完成后再
|
||||
// 停止缩放。
|
||||
if (wp.rcNormalPosition.left == wp.ptMinPosition.x) {
|
||||
return true;
|
||||
}
|
||||
|
||||
isInvisibleOrMinimized = true;
|
||||
|
||||
// rcNormalPosition 使用工作区坐标,应转换为屏幕坐标
|
||||
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO mi{ sizeof(mi) };
|
||||
if (!GetMonitorInfo(hMon, &mi)) {
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
curWindowRect = wp.rcNormalPosition;
|
||||
Win32Helper::OffsetRect(
|
||||
curWindowRect, mi.rcWork.left - mi.rcMonitor.left, mi.rcWork.top - mi.rcMonitor.top);
|
||||
} else {
|
||||
if (!GetWindowRect(_hWnd, &curWindowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sizeChanged = oldMaximized != _isMaximized ||
|
||||
srcSizeChanged = oldMaximized != _isMaximized ||
|
||||
Win32Helper::GetSizeOfRect(curWindowRect) != Win32Helper::GetSizeOfRect(_windowRect);
|
||||
if (sizeChanged) {
|
||||
rectChanged = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 缩放窗口正在调整大小或被拖动时源窗口的移动是异步的,暂时不检查源窗口是否移动
|
||||
if (isResizingOrMoving) {
|
||||
rectChanged = oldMaximized != _isMaximized;
|
||||
srcRectChanged = oldMaximized != _isMaximized;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 最大化状态改变视为尺寸发生变化
|
||||
rectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
|
||||
srcRectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
|
||||
|
||||
if (isWindowedMode && !sizeChanged) {
|
||||
if (rectChanged) {
|
||||
if (isWindowedMode && !srcSizeChanged) {
|
||||
bool isMoving = false;
|
||||
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
|
||||
if (GetGUIThreadInfo(GetWindowThreadProcessId(_hWnd, nullptr), &guiThreadInfo)) {
|
||||
isMoving = guiThreadInfo.flags & GUI_INMOVESIZE;
|
||||
} else {
|
||||
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
|
||||
}
|
||||
|
||||
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
|
||||
// 可能会有误判,但幸好后果不太严重。
|
||||
if (_isMoving || (!_isMoving && srcRectChanged)) {
|
||||
isMoving = isMoving || IsPrimaryMouseButtonDown();
|
||||
}
|
||||
|
||||
if (srcRectChanged) {
|
||||
const LONG offsetX = curWindowRect.left - _windowRect.left;
|
||||
const LONG offsetY = curWindowRect.top - _windowRect.top;
|
||||
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_srcRect, offsetX, offsetY);
|
||||
}
|
||||
|
||||
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
|
||||
// 可能会有误判,但幸好后果不太严重。
|
||||
const bool isMoving = !isInvisibleOrMinimized &&
|
||||
(IsWindowMoving(_hWnd) || (rectChanged && IsPrimaryMouseButtonDown()));
|
||||
if (_isMoving != isMoving) {
|
||||
movingChanged = true;
|
||||
srcMovingChanged = true;
|
||||
_isMoving = isMoving;
|
||||
}
|
||||
}
|
||||
|
||||
if (rectChanged) {
|
||||
if (srcRectChanged) {
|
||||
_windowRect = curWindowRect;
|
||||
}
|
||||
|
||||
|
|
@ -397,9 +371,8 @@ static HWND FindClientWindowOfUWP(HWND hwndSrc, const wchar_t* clientWndClassNam
|
|||
}
|
||||
|
||||
// 如果有多个匹配的子窗口,取最大的(一般不会出现)
|
||||
int maxSize = 0;
|
||||
uint32_t maxIdx = 0;
|
||||
for (uint32_t i = 0, end = (uint32_t)data.childWindows.size(); i < end; ++i) {
|
||||
int maxSize = 0, maxIdx = 0;
|
||||
for (int i = 0; i < data.childWindows.size(); ++i) {
|
||||
RECT rect;
|
||||
if (!GetClientRect(data.childWindows[i], &rect)) {
|
||||
continue;
|
||||
|
|
@ -435,83 +408,22 @@ static bool GetClientRectOfUWP(HWND hWnd, RECT& rect) noexcept {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SrcTracker::SetFocus() const noexcept {
|
||||
// 不要把被禁用的窗口设为前台,检查是否存在弹窗
|
||||
if (IsWindowEnabled(_hWnd)) {
|
||||
return SetForegroundWindow(_hWnd);
|
||||
}
|
||||
|
||||
const HWND hwndPopup = GetWindow(_hWnd, GW_ENABLEDPOPUP);
|
||||
if (hwndPopup && IsWindowEnabled(hwndPopup)) {
|
||||
return SetForegroundWindow(hwndPopup);
|
||||
}
|
||||
|
||||
// 源窗口被禁用视为成功
|
||||
return true;
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::_CalcSrcRect(
|
||||
const ScalingOptions& options,
|
||||
bool hasCustomNonclient,
|
||||
LONG borderThicknessInFrame
|
||||
) noexcept {
|
||||
if (_windowKind == SrcWindowKind::NoNativeFrame) {
|
||||
if (hasCustomNonclient) {
|
||||
if (options.RealIsCaptureTitleBar()) {
|
||||
// 窗口的非客户区是自绘的,无法模拟,因此启用捕获标题栏时捕获整个窗口
|
||||
_srcRect = _windowRect;
|
||||
} else {
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
|
||||
Logger::Get().Error("GetClientScreenRect 失败");
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
// 如果有滚动条需特殊处理
|
||||
if (const DWORD srcStyle = GetWindowStyle(_hWnd); srcStyle & (WS_VSCROLL | WS_HSCROLL)) {
|
||||
// 左右两边不可能都有滚动条,据此找到边框宽度
|
||||
const LONG borderThickness = std::min(
|
||||
clientRect.left - _windowRect.left,
|
||||
_windowRect.right - clientRect.right
|
||||
);
|
||||
|
||||
if (srcStyle & WS_VSCROLL) {
|
||||
_srcRect.left = _windowRect.left + borderThickness;
|
||||
_srcRect.right = _windowRect.right - borderThickness;
|
||||
} else {
|
||||
_srcRect.left = clientRect.left;
|
||||
_srcRect.right = clientRect.right;
|
||||
}
|
||||
|
||||
if (srcStyle & WS_HSCROLL) {
|
||||
_srcRect.bottom = _windowRect.bottom - borderThickness;
|
||||
} else {
|
||||
_srcRect.bottom = clientRect.bottom;
|
||||
}
|
||||
|
||||
_srcRect.top = clientRect.top;
|
||||
} else {
|
||||
_srcRect = clientRect;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 不存在非客户区
|
||||
_srcRect = _windowRect;
|
||||
}
|
||||
ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept {
|
||||
if (_windowKind == SrcWindowKind::NoDecoration) {
|
||||
// NoDecoration 类型的窗口不裁剪非客户区。它们要么没有非客户区,要么非客户区不是由
|
||||
// DWM 绘制,前者无需裁剪,后者不能裁剪。
|
||||
_srcRect = _windowRect;
|
||||
} else {
|
||||
const bool isCaptureTitleBar = options.RealIsCaptureTitleBar();
|
||||
|
||||
// UWP 窗口都是 NoTitleBar 类型,但可能使用子窗口作为“客户区”
|
||||
if (_windowKind == SrcWindowKind::NoTitleBar && !isCaptureTitleBar && GetClientRectOfUWP(_hWnd, _srcRect)) {
|
||||
if (_windowKind == SrcWindowKind::NoTitleBar && !options.IsCaptureTitleBar() && GetClientRectOfUWP(_hWnd, _srcRect)) {
|
||||
_srcRect.top = std::max(_srcRect.top, _windowFrameRect.top + borderThicknessInFrame);
|
||||
} else {
|
||||
// 不要使用客户区矩形,它不包含滚动条
|
||||
_srcRect.left = _windowFrameRect.left + borderThicknessInFrame;
|
||||
_srcRect.top = _windowFrameRect.top + borderThicknessInFrame;
|
||||
_srcRect.right = _windowFrameRect.right - borderThicknessInFrame;
|
||||
_srcRect.bottom = _windowFrameRect.bottom - borderThicknessInFrame;
|
||||
|
||||
if (!isCaptureTitleBar || _windowKind == SrcWindowKind::OnlyThickFrame) {
|
||||
if (!options.IsCaptureTitleBar() || _windowKind == SrcWindowKind::OnlyThickFrame) {
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
|
||||
Logger::Get().Error("GetClientScreenRect 失败");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingError.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -13,8 +14,8 @@ enum class SrcWindowKind {
|
|||
// 无标题栏,是否有边框取决于 OS 版本(Win10 中不存在边框,Win11 中边框
|
||||
// 被绘制到客户区内),有阴影,在窗口内调整大小,Win11 中有圆角
|
||||
NoBorder,
|
||||
// 无标题栏、边框和阴影,在窗口内调整大小,Win11 中无圆角。可能自绘非客户区
|
||||
NoNativeFrame,
|
||||
// 无标题栏、边框和阴影,在窗口内调整大小,Win11 中无圆角
|
||||
NoDecoration,
|
||||
// 无标题栏,系统边框但上边框较粗,有阴影,左右下三边在窗口外调整大小,Win11 中有圆角
|
||||
OnlyThickFrame
|
||||
};
|
||||
|
|
@ -27,17 +28,15 @@ public:
|
|||
SrcTracker(const SrcTracker&) = delete;
|
||||
SrcTracker(SrcTracker&&) = delete;
|
||||
|
||||
ScalingError Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept;
|
||||
ScalingError Set(HWND hWnd, const ScalingOptions& options) noexcept;
|
||||
|
||||
bool UpdateState(
|
||||
HWND hwndFore,
|
||||
bool isWindowedMode,
|
||||
bool isResizingOrMoving,
|
||||
bool& isInvisibleOrMinimized,
|
||||
bool& focusedChanged,
|
||||
bool& rectChanged,
|
||||
bool& sizeChanged,
|
||||
bool& movingChanged
|
||||
bool& srcRectChanged,
|
||||
bool& srcSizeChanged,
|
||||
bool& srcMovingChanged
|
||||
) noexcept;
|
||||
|
||||
bool Move(int offsetX, int offsetY, bool async) noexcept;
|
||||
|
|
@ -64,8 +63,6 @@ public:
|
|||
return _isFocused;
|
||||
}
|
||||
|
||||
bool SetFocus() const noexcept;
|
||||
|
||||
// IsMaximized 已定义为宏
|
||||
bool IsZoomed() const noexcept {
|
||||
return _isMaximized;
|
||||
|
|
@ -81,11 +78,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
ScalingError _CalcSrcRect(
|
||||
const ScalingOptions& options,
|
||||
bool hasCustomNonclient,
|
||||
LONG borderThicknessInFrame
|
||||
) noexcept;
|
||||
ScalingError _CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept;
|
||||
|
||||
HWND _hWnd = NULL;
|
||||
RECT _windowRect{};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include "Win32Helper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "TextureHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "DDSHelper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "EffectHelper.h"
|
||||
#include "Logger.h"
|
||||
#include <wincodec.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
#include "pch.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include <dcomp.h>
|
||||
#include <dwmapi.h>
|
||||
#include <io.h>
|
||||
#pragma push_macro("ShellExecute")
|
||||
#undef ShellExecute
|
||||
#include <shellapi.h>
|
||||
#pragma pop_macro("ShellExecute")
|
||||
#include <ShlObj.h>
|
||||
#include <Psapi.h>
|
||||
#include <winternl.h>
|
||||
#include <dwmapi.h>
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#include <wil/token_helpers.h>
|
||||
#include <ShlObj.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -39,33 +39,32 @@ std::wstring Win32Helper::GetWindowTitle(HWND hWnd) noexcept {
|
|||
}
|
||||
|
||||
wil::unique_process_handle Win32Helper::GetWindowProcessHandle(HWND hWnd) noexcept {
|
||||
wil::unique_process_handle result;
|
||||
wil::unique_process_handle hProc;
|
||||
|
||||
if (DWORD dwProcId = 0; GetWindowThreadProcessId(hWnd, &dwProcId)) {
|
||||
result.reset(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId));
|
||||
if (result) {
|
||||
return result;
|
||||
} else {
|
||||
hProc.reset(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId));
|
||||
if (!hProc) {
|
||||
Logger::Get().Win32Error("OpenProcess 失败");
|
||||
}
|
||||
} else {
|
||||
Logger::Get().Win32Error("GetWindowThreadProcessId 失败");
|
||||
}
|
||||
|
||||
// 在某些窗口上 OpenProcess 会失败(如暗黑 2),尝试使用 GetProcessHandleFromHwnd
|
||||
static const auto getProcessHandleFromHwnd =
|
||||
Win32Helper::LoadSystemFunction<HANDLE WINAPI(HWND)>(L"Oleacc.dll", "GetProcessHandleFromHwnd");
|
||||
if (!getProcessHandleFromHwnd) {
|
||||
return result;
|
||||
if (!hProc) {
|
||||
// 在某些窗口上 OpenProcess 会失败(如暗黑 2),尝试使用 GetProcessHandleFromHwnd
|
||||
static const auto getProcessHandleFromHwnd = (HANDLE(WINAPI*)(HWND))GetProcAddress(
|
||||
LoadLibraryEx(L"Oleacc.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32),
|
||||
"GetProcessHandleFromHwnd"
|
||||
);
|
||||
if (getProcessHandleFromHwnd) {
|
||||
hProc.reset(getProcessHandleFromHwnd(hWnd));
|
||||
if (!hProc) {
|
||||
Logger::Get().Win32Error("GetProcessHandleFromHwnd 失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.reset(getProcessHandleFromHwnd(hWnd));
|
||||
if (!result) {
|
||||
Logger::Get().Win32Error("GetProcessHandleFromHwnd 失败");
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
return hProc;
|
||||
}
|
||||
|
||||
std::wstring Win32Helper::GetWindowPath(HWND hWnd) noexcept {
|
||||
|
|
@ -200,7 +199,7 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
|
|||
DWORD_PTR area = HTNOWHERE;
|
||||
SendMessageTimeout(hwndChild, WM_NCHITTEST, 0, MAKELPARAM(ptScreen.x, ptScreen.y),
|
||||
SMTO_NORMAL, timeout, &area);
|
||||
if ((int)area != HTTRANSPARENT) {
|
||||
if (area != HTTRANSPARENT) {
|
||||
hwndCur = hwndChild;
|
||||
hittest = (int16_t)area;
|
||||
continue;
|
||||
|
|
@ -259,7 +258,7 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
|
|||
DWORD_PTR area = HTNOWHERE;
|
||||
SendMessageTimeout(hWnd, WM_NCHITTEST, 0,
|
||||
MAKELPARAM(data->ptScreen.x, data->ptScreen.y), SMTO_NORMAL, data->timeout, &area);
|
||||
if ((int)area == HTTRANSPARENT) {
|
||||
if (area == HTTRANSPARENT) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -291,9 +290,8 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
|
|||
}
|
||||
|
||||
bool Win32Helper::IsWindowHung(HWND hWnd) noexcept {
|
||||
// 保险起见不使用 SMTO_ABORTIFHUNG。我不知道 OS 怎么判断线程是否处于无响应
|
||||
// 状态,考虑到 IsHungAppWindow 有误报的情况 (GH#1244),最好不要依赖。
|
||||
return 0 == SendMessageTimeout(hWnd, WM_NULL, 0, 0, SMTO_ERRORONEXIT, 500, nullptr);
|
||||
return 0 == SendMessageTimeout(hWnd, WM_NULL, 0, 0,
|
||||
SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT, 500, nullptr);
|
||||
}
|
||||
|
||||
bool Win32Helper::ReadFile(const wchar_t* fileName, std::vector<uint8_t>& result) noexcept {
|
||||
|
|
@ -424,17 +422,21 @@ bool Win32Helper::CreateDir(const std::wstring& path, bool recursive) noexcept {
|
|||
}
|
||||
|
||||
const Win32Helper::OSVersion& Win32Helper::GetOSVersion() noexcept {
|
||||
static OSVersion version = [] {
|
||||
const auto rtlGetVersion =
|
||||
LoadSystemFunction<LONG WINAPI(PRTL_OSVERSIONINFOW)>(L"ntdll.dll", "RtlGetVersion");
|
||||
static OSVersion version = []() -> OSVersion {
|
||||
HMODULE hNtDll = GetModuleHandle(L"ntdll.dll");
|
||||
assert(hNtDll);
|
||||
|
||||
auto rtlGetVersion = (LONG(WINAPI*)(PRTL_OSVERSIONINFOW))GetProcAddress(hNtDll, "RtlGetVersion");
|
||||
if (!rtlGetVersion) {
|
||||
return OSVersion();
|
||||
Logger::Get().Win32Error("获取 RtlGetVersion 地址失败");
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
RTL_OSVERSIONINFOW versionInfo{ .dwOSVersionInfoSize = sizeof(versionInfo) };
|
||||
rtlGetVersion(&versionInfo);
|
||||
|
||||
return OSVersion(versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, versionInfo.dwBuildNumber);
|
||||
return { versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, versionInfo.dwBuildNumber };
|
||||
}();
|
||||
|
||||
return version;
|
||||
|
|
@ -698,30 +700,6 @@ bool Win32Helper::GetProcessIntegrityLevel(HANDLE hQueryToken, DWORD& integrityL
|
|||
return true;
|
||||
}
|
||||
|
||||
DWORD Win32Helper::GetCurrentProcessIntegrityLevel() noexcept {
|
||||
static DWORD result = []() -> DWORD {
|
||||
DWORD il;
|
||||
return Win32Helper::GetProcessIntegrityLevel(NULL, il) ? il : 0;
|
||||
}();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Win32Helper::GetWindowIntegrityLevel(HWND hWnd, DWORD& integrityLevel) noexcept {
|
||||
wil::unique_process_handle hProc = GetWindowProcessHandle(hWnd);
|
||||
if (!hProc) {
|
||||
Logger::Get().Error("GetWindowProcessHandle 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::unique_handle hQueryToken;
|
||||
if (!OpenProcessToken(hProc.get(), TOKEN_QUERY, hQueryToken.put())) {
|
||||
Logger::Get().Win32Error("OpenProcessToken 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return GetProcessIntegrityLevel(hQueryToken.get(), integrityLevel);
|
||||
}
|
||||
|
||||
static winrt::com_ptr<IShellView> FindDesktopFolderView() noexcept {
|
||||
winrt::com_ptr<IShellWindows> shellWindows =
|
||||
winrt::try_create_instance<IShellWindows>(CLSID_ShellWindows, CLSCTX_LOCAL_SERVER);
|
||||
|
|
@ -742,7 +720,7 @@ static winrt::com_ptr<IShellView> FindDesktopFolderView() noexcept {
|
|||
}
|
||||
|
||||
winrt::com_ptr<IShellBrowser> shellBrowser;
|
||||
hr = dispatch.try_as<IServiceProvider>()->QueryService(
|
||||
hr = dispatch.as<IServiceProvider>()->QueryService(
|
||||
SID_STopLevelBrowser, IID_PPV_ARGS(&shellBrowser));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IServiceProvider::QueryService 失败", hr);
|
||||
|
|
@ -881,56 +859,4 @@ const std::filesystem::path& Win32Helper::GetExePath() noexcept {
|
|||
return result;
|
||||
}
|
||||
|
||||
void Win32Helper::WaitForDwmComposition() noexcept {
|
||||
// Win11 可以使用准确的 DCompositionWaitForCompositorClock
|
||||
if (Win32Helper::GetOSVersion().IsWin11()) {
|
||||
static const auto dCompositionWaitForCompositorClock =
|
||||
Win32Helper::LoadSystemFunction<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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include "pch.h"
|
||||
#include "WindowHelper.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "StrHelper.h"
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#include "SmallVector.h"
|
||||
// YAS 暂不支持 ARM64
|
||||
// https://github.com/niXman/yas/pull/121
|
||||
#ifdef _M_ARM64
|
||||
|
|
@ -19,6 +18,8 @@
|
|||
#include <yas/types/std/variant.hpp>
|
||||
#pragma warning(pop)
|
||||
|
||||
#include "SmallVector.h"
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
// 可平凡复制类型
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include <dxgi1_6.h>
|
||||
#include <d3d11_4.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#pragma once
|
||||
#include "SmallVector.h"
|
||||
#include <d3dcommon.h>
|
||||
#include <variant>
|
||||
#include "SmallVector.h"
|
||||
|
||||
struct ID3D10Blob;
|
||||
typedef ID3D10Blob ID3DBlob;
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
|
|||
41
src/Magpie.Core/include/ScalingError.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
enum class ScalingError {
|
||||
NoError,
|
||||
|
||||
/////////////////////////////////////
|
||||
//
|
||||
// 先决条件错误
|
||||
//
|
||||
/////////////////////////////////////
|
||||
|
||||
// 未配置缩放模式或者缩放模式不合法
|
||||
InvalidScalingMode,
|
||||
// 启用触控支持失败
|
||||
TouchSupport,
|
||||
// 3D 游戏模式下不支持窗口模式缩放
|
||||
Windowed3DGameMode,
|
||||
// 通用的不支持缩放错误
|
||||
InvalidSourceWindow,
|
||||
// 因窗口已最大化或全屏而无法缩放,可通过更改设置强制缩放
|
||||
Maximized,
|
||||
// 因窗口的 IL 更高而无法缩放
|
||||
LowIntegrityLevel,
|
||||
// 应用自定义裁剪后尺寸太小或为负
|
||||
InvalidCropping,
|
||||
// 窗口不符合窗口模式缩放的条件,如已最大化
|
||||
BannedInWindowedMode,
|
||||
|
||||
/////////////////////////////////////
|
||||
//
|
||||
// 初始化和缩放时错误
|
||||
//
|
||||
/////////////////////////////////////
|
||||
|
||||
// 通用的缩放失败错误
|
||||
ScalingFailedGeneral,
|
||||
// FrameSource 初始化失败
|
||||
CaptureFailed,
|
||||
// ID3D11Device5::CreateFence 失败
|
||||
CreateFenceFailed
|
||||
};
|
||||
|
|
@ -21,7 +21,6 @@ enum class MultiMonitorUsage {
|
|||
enum class CursorInterpolationMode {
|
||||
NearestNeighbor,
|
||||
Bilinear,
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct Cropping {
|
||||
|
|
@ -42,17 +41,26 @@ struct GraphicsCardId {
|
|||
uint32_t deviceId = 0;
|
||||
};
|
||||
|
||||
enum class DestAlignment {
|
||||
LeftTop,
|
||||
Top,
|
||||
RightTop,
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
LeftBottom,
|
||||
Bottom,
|
||||
RightBottom,
|
||||
COUNT
|
||||
struct ScalingFlags {
|
||||
static constexpr uint32_t WindowedMode = 1;
|
||||
static constexpr uint32_t DebugMode = 1 << 1;
|
||||
static constexpr uint32_t DisableEffectCache = 1 << 2;
|
||||
static constexpr uint32_t SaveEffectSources = 1 << 3;
|
||||
static constexpr uint32_t WarningsAreErrors = 1 << 4;
|
||||
static constexpr uint32_t SimulateExclusiveFullscreen = 1 << 5;
|
||||
static constexpr uint32_t Is3DGameMode = 1 << 6;
|
||||
static constexpr uint32_t CaptureTitleBar = 1 << 10;
|
||||
static constexpr uint32_t AdjustCursorSpeed = 1 << 11;
|
||||
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 IsTouchSupportEnabled = 1 << 17;
|
||||
static constexpr uint32_t InlineParams = 1 << 18;
|
||||
static constexpr uint32_t IsFP16Disabled = 1 << 19;
|
||||
static constexpr uint32_t BenchmarkMode = 1 << 20;
|
||||
static constexpr uint32_t DeveloperMode = 1 << 21;
|
||||
};
|
||||
|
||||
enum class ScalingType {
|
||||
|
|
@ -104,85 +112,19 @@ struct OverlayOptions {
|
|||
phmap::flat_hash_map<std::string, OverlayWindowOption> windows;
|
||||
};
|
||||
|
||||
enum class ScalingError {
|
||||
NoError,
|
||||
|
||||
/////////////////////////////////////
|
||||
//
|
||||
// 先决条件错误
|
||||
//
|
||||
/////////////////////////////////////
|
||||
|
||||
// 未配置缩放模式或者缩放模式不合法
|
||||
InvalidScalingMode,
|
||||
// 启用触控支持失败
|
||||
TouchSupport,
|
||||
// 3D 游戏模式下不支持窗口模式缩放
|
||||
Windowed3DGameMode,
|
||||
// Desktop Duplication 不支持窗口模式缩放
|
||||
WindowedDesktopDuplication,
|
||||
// 通用的不支持缩放错误
|
||||
InvalidSourceWindow,
|
||||
// 因窗口已最大化或全屏而无法缩放,可通过更改设置强制缩放
|
||||
Maximized,
|
||||
// 因窗口的 IL 更高而无法缩放
|
||||
LowIntegrityLevel,
|
||||
// 应用自定义裁剪后尺寸太小或为负
|
||||
InvalidCropping,
|
||||
// 窗口不符合窗口模式缩放的条件,如已最大化
|
||||
BannedInWindowedMode,
|
||||
|
||||
/////////////////////////////////////
|
||||
//
|
||||
// 初始化和缩放时错误
|
||||
//
|
||||
/////////////////////////////////////
|
||||
|
||||
// 通用的缩放失败错误
|
||||
ScalingFailedGeneral,
|
||||
// FrameSource 初始化失败
|
||||
CaptureFailed,
|
||||
// ID3D11Device5::CreateFence 失败
|
||||
CreateFenceFailed
|
||||
};
|
||||
|
||||
struct ScalingFlags {
|
||||
static constexpr uint32_t WindowedMode = 1;
|
||||
static constexpr uint32_t DebugMode = 1 << 1;
|
||||
static constexpr uint32_t DisableEffectCache = 1 << 2;
|
||||
static constexpr uint32_t SaveEffectSources = 1 << 3;
|
||||
static constexpr uint32_t WarningsAreErrors = 1 << 4;
|
||||
static constexpr uint32_t SimulateExclusiveFullscreen = 1 << 5;
|
||||
static constexpr uint32_t Is3DGameMode = 1 << 6;
|
||||
static constexpr uint32_t CaptureTitleBar = 1 << 10;
|
||||
static constexpr uint32_t AdjustCursorSpeed = 1 << 11;
|
||||
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;
|
||||
static constexpr uint32_t DisableFP16 = 1 << 19;
|
||||
static constexpr uint32_t BenchmarkMode = 1 << 20;
|
||||
static constexpr uint32_t DeveloperMode = 1 << 21;
|
||||
static constexpr uint32_t DisableTopmost = 1 << 22;
|
||||
};
|
||||
|
||||
struct ScalingOptions {
|
||||
DEFINE_FLAG_ACCESSOR(IsWindowedMode, ScalingFlags::WindowedMode, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsDeveloperMode, ScalingFlags::DeveloperMode, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsDebugMode, ScalingFlags::DebugMode, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsBenchmarkMode, ScalingFlags::BenchmarkMode, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsTopmostDisabled, ScalingFlags::DisableTopmost, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsFP16Disabled, ScalingFlags::DisableFP16, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsFP16Disabled, ScalingFlags::IsFP16Disabled, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsEffectCacheDisabled, ScalingFlags::DisableEffectCache, flags)
|
||||
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(IsTouchSupportEnabled, ScalingFlags::IsTouchSupportEnabled, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsAllowScalingMaximized, ScalingFlags::AllowScalingMaximized, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsSimulateExclusiveFullscreen, ScalingFlags::SimulateExclusiveFullscreen, flags)
|
||||
DEFINE_FLAG_ACCESSOR(Is3DGameMode, ScalingFlags::Is3DGameMode, flags)
|
||||
|
|
@ -190,6 +132,9 @@ struct ScalingOptions {
|
|||
DEFINE_FLAG_ACCESSOR(IsAdjustCursorSpeed, ScalingFlags::AdjustCursorSpeed, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsDirectFlipDisabled, ScalingFlags::DisableDirectFlip, flags)
|
||||
|
||||
bool Prepare() noexcept;
|
||||
void Log() const noexcept;
|
||||
|
||||
std::vector<EffectOption> effects;
|
||||
uint32_t flags = ScalingFlags::AdjustCursorSpeed;
|
||||
Cropping cropping{};
|
||||
|
|
@ -199,37 +144,17 @@ struct ScalingOptions {
|
|||
float cursorScaling = 1.0f;
|
||||
CaptureMethod captureMethod = CaptureMethod::GraphicsCapture;
|
||||
MultiMonitorUsage multiMonitorUsage = MultiMonitorUsage::Closest;
|
||||
DestAlignment destAlignment = DestAlignment::Center;
|
||||
CursorInterpolationMode cursorInterpolationMode = CursorInterpolationMode::NearestNeighbor;
|
||||
std::optional<float> autoHideCursorDelay;
|
||||
DuplicateFrameDetectionMode duplicateFrameDetectionMode = DuplicateFrameDetectionMode::Dynamic;
|
||||
ToolbarState fullscreenInitialToolbarState = ToolbarState::AutoHide;
|
||||
ToolbarState windowedInitialToolbarState = ToolbarState::AutoHide;
|
||||
ToolbarState initialToolbarState = ToolbarState::AutoHide;
|
||||
float initialWindowedScaleFactor = 0.0f;
|
||||
std::filesystem::path screenshotsDir;
|
||||
|
||||
// 下面的成员支持在缩放时修改
|
||||
OverlayOptions overlayOptions;
|
||||
|
||||
void (*showToast)(HWND hwndTarget, std::wstring_view msg) noexcept = nullptr;
|
||||
void (*showError)(HWND hwndTarget, ScalingError error) noexcept = nullptr;
|
||||
void (*save)(const ScalingOptions& options, HWND hwndScaling) noexcept = nullptr;
|
||||
|
||||
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();
|
||||
}
|
||||
void (*showToast)(HWND hwndTarget, std::wstring_view msg) = nullptr;
|
||||
void (*save)(const ScalingOptions& options, HWND hwndScaling) = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,29 @@
|
|||
#pragma once
|
||||
#include "Event.h"
|
||||
#include <Windows.h>
|
||||
#include <winrt/base.h>
|
||||
#include <winrt/Windows.System.h>
|
||||
#include "ScalingError.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
enum class ScalingState {
|
||||
Idle,
|
||||
Scaling,
|
||||
Waiting
|
||||
};
|
||||
|
||||
class ScalingRuntime {
|
||||
public:
|
||||
ScalingRuntime();
|
||||
~ScalingRuntime();
|
||||
|
||||
bool Start(HWND hwndSrc, struct ScalingOptions&& options, bool force);
|
||||
bool Start(HWND hwndSrc, struct ScalingOptions&& options);
|
||||
|
||||
void ToggleScaling(bool isWindowedMode);
|
||||
|
||||
void SwitchToolbarState();
|
||||
void ToggleToolbarState();
|
||||
|
||||
void Stop();
|
||||
|
||||
ScalingState State() const noexcept {
|
||||
return _state.load(std::memory_order_relaxed);
|
||||
bool IsRunning() const noexcept {
|
||||
return _state.load(std::memory_order_relaxed) != _State::Idle;
|
||||
}
|
||||
|
||||
// 调用者应处理线程同步
|
||||
MultithreadEvent<ScalingState> StateChanged;
|
||||
MultithreadEvent<bool, ScalingError> IsRunningChanged;
|
||||
|
||||
private:
|
||||
void _ScalingThreadProc() noexcept;
|
||||
|
|
@ -35,16 +31,19 @@ private:
|
|||
// 确保 _dispatcher 完成初始化
|
||||
const winrt::DispatcherQueue& _Dispatcher() noexcept;
|
||||
|
||||
void _State(ScalingState value);
|
||||
|
||||
std::thread _scalingThread;
|
||||
|
||||
enum class _State {
|
||||
Idle,
|
||||
Initializing,
|
||||
Scaling
|
||||
};
|
||||
std::atomic<_State> _state{ _State::Idle };
|
||||
|
||||
winrt::DispatcherQueue _dispatcher{ nullptr };
|
||||
std::atomic<bool> _dispatcherInitialized = false;
|
||||
// 只能在主线程访问,省下检查 _dispatcherInitialized 的开销
|
||||
bool _dispatcherInitializedCache = false;
|
||||
|
||||
std::atomic<ScalingState> _state = ScalingState::Idle;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
#pragma once
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Version.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
|
@ -117,10 +115,6 @@ struct Win32Helper {
|
|||
|
||||
static bool GetProcessIntegrityLevel(HANDLE hQueryToken, DWORD& integrityLevel) noexcept;
|
||||
|
||||
static DWORD GetCurrentProcessIntegrityLevel() noexcept;
|
||||
|
||||
static bool GetWindowIntegrityLevel(HWND hWnd, DWORD& integrityLevel) noexcept;
|
||||
|
||||
// VARIANT 封装,自动管理生命周期,比 WIL 提供更多功能
|
||||
struct Variant : public VARIANT {
|
||||
Variant() noexcept {
|
||||
|
|
@ -137,7 +131,7 @@ struct Win32Helper {
|
|||
}
|
||||
|
||||
Variant(VARIANT&& varSrc) noexcept {
|
||||
std::memcpy((VARIANT*)this, &varSrc, sizeof(varSrc));
|
||||
std::memcpy(this, &varSrc, sizeof(varSrc));
|
||||
varSrc.vt = VT_EMPTY;
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +164,7 @@ struct Win32Helper {
|
|||
}
|
||||
|
||||
Variant& operator=(VARIANT&& other) noexcept {
|
||||
std::memcpy((VARIANT*)this, &other, sizeof(other));
|
||||
std::memcpy(this, &other, sizeof(other));
|
||||
other.vt = VT_EMPTY;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -181,42 +175,6 @@ struct Win32Helper {
|
|||
static bool OpenFolderAndSelectFile(const wchar_t* fileName) noexcept;
|
||||
|
||||
static const std::filesystem::path& GetExePath() noexcept;
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_function_v<T>, int> = 0>
|
||||
static T* LoadSystemFunction(const wchar_t* dllName, const char* funcName) noexcept {
|
||||
assert(dllName && funcName);
|
||||
|
||||
HMODULE hMod = GetModuleHandle(dllName);
|
||||
if (!hMod) {
|
||||
hMod = LoadLibraryEx(dllName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
if (!hMod) {
|
||||
Logger::Get().Win32Error(fmt::format("加载 {} 失败",
|
||||
StrHelper::UTF16ToUTF8(dllName)));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const FARPROC address = GetProcAddress(hMod, funcName);
|
||||
if (!address) {
|
||||
const uintptr_t ordinal = reinterpret_cast<uintptr_t>(funcName);
|
||||
// 小于 0xFFFF 则为序号
|
||||
if (ordinal <= 0xFFFFu) {
|
||||
Logger::Get().Win32Error(fmt::format("加载 {}!{} 失败",
|
||||
StrHelper::UTF16ToUTF8(dllName), ordinal));
|
||||
} else {
|
||||
Logger::Get().Win32Error(fmt::format("加载 {}!{} 失败",
|
||||
StrHelper::UTF16ToUTF8(dllName), funcName));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 先转成 void* 以避免警告
|
||||
return reinterpret_cast<T*>(reinterpret_cast<void*>(address));
|
||||
}
|
||||
|
||||
// 和 DwmFlush 效果相同但更准确
|
||||
static void WaitForDwmComposition() noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.250325.1" targetFramework="native" />
|
||||
</packages>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
SamplerState sam : register(s0);
|
||||
Texture2D tex : register(t0);
|
||||
|
||||
float4 main(noperspective float2 coord : TEXCOORD, noperspective float4 color : COLOR) : SV_Target {
|
||||
float4 main(float2 coord : TEXCOORD, float4 color : COLOR) : SV_Target {
|
||||
return color * float4(1, 1, 1, tex.Sample(sam, coord).r);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ cbuffer vertexBuffer : register(b0) {
|
|||
};
|
||||
|
||||
void main(
|
||||
float2 pos : POSITION,
|
||||
float4 pos : SV_POSITION,
|
||||
float2 coord : TEXCOORD,
|
||||
float4 color : COLOR,
|
||||
out noperspective float2 outCoord : TEXCOORD,
|
||||
out noperspective float4 outColor : COLOR,
|
||||
out noperspective float4 outPos : SV_POSITION
|
||||
out float2 outCoord : TEXCOORD,
|
||||
out float4 outColor : COLOR,
|
||||
out float4 outPos : SV_POSITION
|
||||
) {
|
||||
outPos = mul(projectionMatrix, float4(pos, 0, 1));
|
||||
outPos = mul(projectionMatrix, float4(pos.xy, 0.f, 1.f));
|
||||
outCoord = coord;
|
||||
outColor = color;
|
||||
}
|
||||
|
|
|
|||