Compare commits
116 commits
v0.12.0-pr
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2980a69156 | ||
|
|
1840bded7b |
||
|
|
8daceb51f9 |
||
|
|
e73483a272 |
||
|
|
4a2c3499a3 |
||
|
|
cd2388fbbd |
||
|
|
1f809e80b0 |
||
|
|
626eb5612b |
||
|
|
405fe46f22 |
||
|
|
b57b1aec43 | ||
|
|
d1178d4ac4 |
||
|
|
359c7ac176 | ||
|
|
1ffba2f447 |
||
|
|
e3c29dbdf9 |
||
|
|
000257a25a |
||
|
|
6d50a02af8 |
||
|
|
06c42c0826 |
||
|
|
198de957aa |
||
|
|
65ffd9a1aa | ||
|
|
4e4fde380e | ||
|
|
991695168e | ||
|
|
959f93a816 | ||
|
|
a2c6608d9c |
||
|
|
dc8f59e8e3 |
||
|
|
a5e079b0ee | ||
|
|
c3473a0604 |
||
|
|
463d6f1246 | ||
|
|
22484d88e1 |
||
|
|
9ec6c5dc4a |
||
|
|
de00805204 |
||
|
|
60494174ba |
||
|
|
6c9c47f3fb |
||
|
|
e67ef2b598 |
||
|
|
b38dbf2c9b |
||
|
|
dc6c65fe14 | ||
|
|
6fc3594d9d |
||
|
|
2c4e6aa8ca |
||
|
|
7287e8be40 | ||
|
|
45583f105a |
||
|
|
19bf67e285 | ||
|
|
730d73ecea | ||
|
|
c10034af50 | ||
|
|
bcd5b6fed6 | ||
|
|
046b2868c7 | ||
|
|
15d3e392c4 |
||
|
|
2eb70761c9 | ||
|
|
8fba0716e6 |
||
|
|
9e48ef9ab5 |
||
|
|
1413dc3e69 | ||
|
|
d3fe8e56bb | ||
|
|
1b637c774b | ||
|
|
b187013b29 | ||
|
|
ac758361c4 | ||
|
|
27037c3b45 | ||
|
|
37f38253c7 |
||
|
|
1988cde675 |
||
|
|
88f6ac243f |
||
|
|
1afc257050 |
||
|
|
4bef67d775 |
||
|
|
fb46f324c1 |
||
|
|
81e314d3a6 |
||
|
|
28bbb84cea |
||
|
|
0d7ba8bd37 |
||
|
|
8fc3205844 |
||
|
|
2718439414 | ||
|
|
74b02f46ab |
||
|
|
23b9774716 | ||
|
|
69d6105d56 | ||
|
|
664e0f4c8a | ||
|
|
1e7c571e6e |
||
|
|
74d26e4a0d |
||
|
|
4622274cb3 |
||
|
|
b8c0801f93 |
||
|
|
10176cf4fb |
||
|
|
b4c71bd912 | ||
|
|
0e3a5e8608 | ||
|
|
5e597f8f1d |
||
|
|
33c3ed4c51 |
||
|
|
ab24bd768e |
||
|
|
61765e4344 |
||
|
|
d01956e3a7 |
||
|
|
e3e82b3bd8 | ||
|
|
2c75a8f328 |
||
|
|
6bdafe8106 |
||
|
|
ae0c914c5c |
||
|
|
9467b6aa2f |
||
|
|
6c4d75ee3a |
||
|
|
07a9643b14 |
||
|
|
6ba65bd2ea |
||
|
|
4da359c524 |
||
|
|
6381424727 | ||
|
|
9dea5f5f3e |
||
|
|
8ea304bd54 | ||
|
|
2ec4166905 | ||
|
|
355676dc49 | ||
|
|
19a5032da1 | ||
|
|
a350639eb3 | ||
|
|
f2888d5c81 | ||
|
|
debf547ab8 |
||
|
|
fb2270ec89 |
||
|
|
ffdc3e772b |
||
|
|
dce6adb097 | ||
|
|
07a6dc2c62 | ||
|
|
cdb27a48e6 |
||
|
|
f7f7ebbc56 |
||
|
|
396a1956c2 |
||
|
|
ea107ac9c3 |
||
|
|
96a1c7287a | ||
|
|
7b71454b5d | ||
|
|
151bb3a6ed |
||
|
|
d84ad92b97 | ||
|
|
5db358fdf3 | ||
|
|
16457063fb |
||
|
|
02bf2a1c5f | ||
|
|
19a964195c | ||
|
|
2b5c715d2b |
|
|
@ -12,11 +12,7 @@
|
|||
"avatar_url": "https://avatars.githubusercontent.com/u/34770031?v=4",
|
||||
"profile": "https://github.com/Blinue",
|
||||
"contributions": [
|
||||
"maintenance",
|
||||
"code",
|
||||
"review",
|
||||
"doc",
|
||||
"question"
|
||||
"maintenance"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -301,6 +297,51 @@
|
|||
"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,
|
||||
|
|
|
|||
26
.github/workflows/build.yml
vendored
|
|
@ -2,40 +2,40 @@ name: build
|
|||
|
||||
on:
|
||||
push:
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.sln', '*.props', 'publish.py', 'src/**' ]
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.slnx', '*.props', 'scripts/publish.py', 'src/**' ]
|
||||
pull_request:
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.sln', '*.props', 'publish.py', 'src/**' ]
|
||||
paths: [ '.github/workflows/build.yml', 'Magpie.slnx', '*.props', 'scripts/publish.py', 'src/**' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2025-vs2026
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ["MSVC", "ClangCL"]
|
||||
platform: ["x64", "ARM64"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Setup Conan
|
||||
run: pip install conan
|
||||
|
||||
- name: Load Conan cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.conan2/p
|
||||
key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.compiler }}-${{ matrix.platform }}
|
||||
|
||||
- name: Build
|
||||
if: github.event_name == 'pull_request'
|
||||
run: python scripts/publish.py --compiler=${{ matrix.compiler }} --platform=${{ matrix.platform }}
|
||||
|
||||
- name: Build
|
||||
if: github.event_name != 'pull_request'
|
||||
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 }}"
|
||||
|
||||
- name: Save hash
|
||||
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Store build
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Magpie-dev-${{ steps.hash.outputs.sha_short }}-${{ matrix.compiler }}-${{ matrix.platform }}
|
||||
path: ./publish/${{ matrix.platform }}
|
||||
|
|
|
|||
28
.github/workflows/release.yml
vendored
|
|
@ -25,27 +25,21 @@ on:
|
|||
type: boolean
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2025-vs2026
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform: ["x64", "ARM64"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.13'
|
||||
|
||||
- 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
|
||||
|
|
@ -54,10 +48,12 @@ jobs:
|
|||
echo "tag=$tag" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Build
|
||||
run: python scripts/publish.py --compiler=ClangCL --platform=${{ matrix.platform }} --version-major=${{ inputs.major }} --version-minor=${{ inputs.minor }} --version-patch=${{ inputs.patch }} --version-tag=${{ steps.tag.outputs.tag }} --pfx-path=certs\Magpie.pfx --pfx-password="${{ secrets.MAGPIE_PFX_PASSWORD }}"
|
||||
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 }}"
|
||||
|
||||
- name: Store artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: Magpie-${{ steps.tag.outputs.tag }}-${{ matrix.platform }}
|
||||
path: publish/${{ matrix.platform }}
|
||||
|
|
@ -65,17 +61,17 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Setup Requests
|
||||
run: pip install requests
|
||||
|
||||
- name: Restore artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: publish
|
||||
|
||||
|
|
|
|||
8
.github/workflows/wiki.yml
vendored
|
|
@ -3,18 +3,18 @@ name: Publish wiki
|
|||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths: [ '.github/workflows/wiki.yml', 'docs/**', 'ci/wiki.py' ]
|
||||
paths: [ '.github/workflows/wiki.yml', 'docs/**', 'scripts/wiki.py' ]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Upload documentations to wiki
|
||||
run: python scripts/wiki.py ${{ secrets.CONTENTS_ACCESS_TOKEN }}
|
||||
|
|
|
|||
119
Magpie.sln
|
|
@ -1,119 +0,0 @@
|
|||
|
||||
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
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<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,17 +9,13 @@
|
|||
[](./LICENSE)
|
||||
[](https://github.com/Blinue/Magpie/actions/workflows/build.yml)
|
||||
[](#acknowledgement-)
|
||||
[](https://github.com/Blinue/Magpie/releases)
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
|
||||
</div>
|
||||
|
||||
🌍 **English** | [简体中文](./README_ZH.md)
|
||||
|
||||
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/)
|
||||
Magpie is a lightweight window upscaling tool that comes equipped with a variety of efficient scaling algorithms and filters.
|
||||
|
||||
👉 [Download](https://github.com/Blinue/Magpie/releases)
|
||||
|
||||
|
|
@ -29,42 +25,19 @@ We are using [Weblate](https://weblate.org/) for localization work and would app
|
|||
|
||||
👉 [Compilation guide](https://github.com/Blinue/Magpie/wiki/Compilation%20guide)
|
||||
|
||||
👉 [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## Features
|
||||
|
||||
* 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
|
||||
* 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
|
||||
* 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
|
||||
|
||||
<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
|
||||
<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>
|
||||
|
||||
## Hints
|
||||
|
||||
|
|
@ -72,7 +45,18 @@ We are using [Weblate](https://weblate.org/) for localization work and would app
|
|||
|
||||
2. Some games support zooming the window, but with extremely naive algorithms. Please set the resolution to the built-in (best) option.
|
||||
|
||||
## Acknowledgement ✨
|
||||
## 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
|
||||
|
||||
Thanks go to these wonderful people:
|
||||
|
||||
|
|
@ -82,7 +66,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> <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/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/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>
|
||||
|
|
@ -122,6 +106,13 @@ 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>
|
||||
|
|
@ -132,3 +123,7 @@ 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.
|
||||
|
|
|
|||
75
README_ZH.md
|
|
@ -9,17 +9,13 @@
|
|||
[](./LICENSE)
|
||||
[](https://github.com/Blinue/Magpie/actions/workflows/build.yml)
|
||||
[](#%E8%B4%A1%E7%8C%AE%E8%80%85-)
|
||||
[](https://github.com/Blinue/Magpie/releases)
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
|
||||
</div>
|
||||
|
||||
🌍 [English](./README.md) | **简体中文**
|
||||
|
||||
Magpie 是一个轻量级的窗口缩放工具,内置了多种高效的缩放算法和滤镜。它主要用于提升游戏画质和让不支持全屏化的游戏也能全屏显示等。
|
||||
|
||||
我们使用 [Weblate](https://weblate.org) 进行本地化工作,请帮助我们把 Magpie 翻译成更多语言。
|
||||
|
||||
[](https://hosted.weblate.org/engage/magpie/)
|
||||
Magpie 是一个轻量级的窗口超分辨率工具,内置众多高效的算法和滤镜。
|
||||
|
||||
👉 [下载](https://github.com/Blinue/Magpie/releases)
|
||||
|
||||
|
|
@ -29,51 +25,39 @@ Magpie 是一个轻量级的窗口缩放工具,内置了多种高效的缩放
|
|||
|
||||
👉 [编译指南](https://github.com/Blinue/Magpie/wiki/编译指南)
|
||||
|
||||
👉 [贡献指南](./CONTRIBUTING_ZH.md)
|
||||
|
||||
## 功能
|
||||
|
||||
* 将任何窗口放大至全屏
|
||||
* 众多内置算法,包括 Lanczos、[Anime4K](https://github.com/bloc97/Anime4K)、[FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR)、Adaptive Sharpen、多种 CRT 着色器等
|
||||
* 支持全屏和窗口模式缩放
|
||||
* 众多内置算法和滤镜,如 [Anime4K](https://github.com/bloc97/Anime4K)、[FSR](https://github.com/GPUOpen-Effects/FidelityFX-FSR)、CRT 着色器等
|
||||
* 基于 WinUI 的用户界面,支持浅色和深色主题
|
||||
* 为特定窗口创建配置文件
|
||||
* 多屏幕支持
|
||||
|
||||
## 如何使用
|
||||
|
||||
1. 配置缩放模式
|
||||
|
||||
Magpie 预设了一些简单的缩放模式,但建议根据使用场景自行配置。然后在“配置文件”-“默认”页面更改全局缩放模式。
|
||||
|
||||
2. 缩放窗口
|
||||
|
||||
把要缩放的窗口置于前台,按下快捷键(默认为 Win+Shift+A)即可全屏显示。请注意,要缩放的窗口必须处于窗口化状态,而不是最大化或全屏化。也可以使用“主页”上的“x 秒后缩放”按钮,Magpie 将在数秒后自动缩放前台窗口。
|
||||
|
||||
3. 为窗口创建配置文件
|
||||
|
||||
这使你可以保存针对某个窗口的配置,也支持在该窗口位于前台时自动执行缩放。
|
||||
|
||||
4. 自定义效果
|
||||
|
||||
Magpie 使用 Direct3D 计算着色器实现效果,但扩展了语法来定义资源、组织多个通道等,详见 [MagpieFX](https://github.com/Blinue/Magpie/wiki/MagpieFX) 。有着色器编写经验者可以轻松创建自定义效果。
|
||||
* 支持多屏幕
|
||||
|
||||
## 截图
|
||||
|
||||
<img src="img/主窗口.png" alt= "主窗口" height="300">
|
||||
|
||||
## 系统需求
|
||||
|
||||
1. Windows 10 v1903+ 或 Windows 11
|
||||
2. DirectX 功能级别 11
|
||||
<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. 一些游戏支持调整窗口的大小,但只使用简单的缩放算法,这时请先将其设为原始(最佳)分辨率。
|
||||
|
||||
## 贡献者 ✨
|
||||
## 系统需求
|
||||
|
||||
感谢每一位参与贡献的人:
|
||||
1. Windows 10 v1903+ 或 Windows 11
|
||||
2. DirectX 功能级别 11
|
||||
|
||||
## 本地化
|
||||
|
||||
感谢 [Weblate](https://weblate.org) 提供托管服务!点击下面的图片可以进入翻译页面。
|
||||
|
||||
[](https://hosted.weblate.org/engage/magpie)
|
||||
|
||||
## 贡献者
|
||||
|
||||
衷心感谢所有为本项目做出贡献的人:
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
|
|
@ -81,7 +65,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> <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/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/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>
|
||||
|
|
@ -121,6 +105,13 @@ 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>
|
||||
|
|
@ -130,4 +121,8 @@ Magpie 是一个轻量级的窗口缩放工具,内置了多种高效的缩放
|
|||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
本项目遵循 [all-contributors](https://allcontributors.org/) 规范。欢迎任何形式的贡献!
|
||||
本项目遵循 [all-contributors](https://allcontributors.org/) 规范,欢迎任何形式的贡献!
|
||||
|
||||
## 许可协议
|
||||
|
||||
本项目采用 GPLv3 许可协议。
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ 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
|
||||
|
|
@ -193,6 +196,9 @@ Magpie ships with a handful of effects that can be used in combinations. Most of
|
|||
* Sinc Param: The larger the value is the sharper the images become. Must be greater than 0. Default value: 0.825
|
||||
* Anti-ringing Strength: The greater the value is the better the effect becomes, but the images will be more blurry.
|
||||
|
||||
* k7_modernAnime: anime-targeted super-resolution algorithm
|
||||
* Output size: twice that of the input
|
||||
|
||||
* Lanczos: Scaling with the Lanczos algorithm.
|
||||
* Output size: determined by scale configuration
|
||||
* Parameters
|
||||
|
|
@ -244,6 +250,12 @@ 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 | | Low VRAM usage |
|
||||
| Notes | The most recommended capture method | Requires Win10 v2004 | | Not recommended due to unstable performance |
|
||||
|
||||
|
||||
[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. 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 or 2026. 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.sln in the root directory and build the solution.
|
||||
2. Open the Magpie.slnx 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 publish.py x64 unpackaged <pfx path> <pfx password>
|
||||
python scripts/publish.py --pfx-path=<pfx path> --pfx-password=<pfx password>
|
||||
```
|
||||
|
||||
This will compile Magpie and sign TouchHelper.exe. The compiled files will be located in `publish\x64`.
|
||||
|
|
|
|||
|
|
@ -2,15 +2,11 @@
|
|||
|
||||
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,15 +2,11 @@
|
|||
|
||||
使用性能计数器屏显时,例如 RTSS (Rivatuner Statistics Server),你可能会在缩放时看到两个叠加层。这是由于 Magpie 使用 Direct3D 呈现画面,它也会被 RTSS 捕捉。如果你不关心 Magpie 的性能,请将 Magpie 添加到黑名单。
|
||||
|
||||
### 快捷键不起作用,但可以使用 "x秒后缩放"
|
||||
### 快捷键不起作用,但可以使用 "`x` 秒后缩放"
|
||||
|
||||
1. 尝试更换快捷键
|
||||
2. 尝试以管理员身份运行 Magpie
|
||||
|
||||
### 是否支持多屏?
|
||||
|
||||
从 v0.8 开始支持。
|
||||
|
||||
### 卡顿/延迟
|
||||
|
||||
请查看[性能优化建议](https://github.com/Blinue/Magpie/wiki/性能优化建议)。
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ Magpie 内置了大量效果供组合使用,大部分提供了参数选项以
|
|||
* CuNNy 族:适合视觉小说风格图像的缩放,由 [CuNNy](https://github.com/funnyplanter/CuNNy) 提供。DS 变体有轻微降噪效果
|
||||
* 输出尺寸:输入的两倍
|
||||
|
||||
* CuNNy2 族:CuNNy 的改进版
|
||||
* 输出尺寸:输入的两倍
|
||||
|
||||
* Deband:去除色带
|
||||
* 输出尺寸:和输入相同
|
||||
* 参数
|
||||
|
|
@ -193,6 +196,9 @@ Magpie 内置了大量效果供组合使用,大部分提供了参数选项以
|
|||
* Sinc Param:值越大图像越锐利
|
||||
* Anti-ringing Strength:抗振铃强度
|
||||
|
||||
* k7_modernAnime:适合动漫类风格的超分算法
|
||||
* 输出尺寸:输入的两倍
|
||||
|
||||
* Lanczos:使用 Lanczos 算法缩放输入。
|
||||
* 输出尺寸:取决于缩放选项
|
||||
* 参数
|
||||
|
|
@ -241,6 +247,12 @@ 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,8 +6,7 @@ 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 的最新版本,需要安装“使用 C++ 的桌面开发”和“通用 Windows 平台开发”两个工作负荷以及 Windows SDK build 26100 或更高版本。
|
||||
1. Visual Studio 2022 或 2026 的最新版本,需要安装“使用 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.sln 然后生成解决方案。
|
||||
2. 打开根目录的 Magpie.slnx 然后生成解决方案。
|
||||
|
||||
## 启用触控支持
|
||||
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
3. 在存储库根目录下执行以下命令:
|
||||
|
||||
```bash
|
||||
python publish.py x64 unpackaged <pfx 路径> <pfx 密码>
|
||||
python scripts/publish.py --pfx-path=<pfx 路径> --pfx-password=<pfx 密码>
|
||||
```
|
||||
|
||||
这将编译 Magpie 并为 TouchHelper.exe 签名。编译出的程序位于 `publish\x64`。
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 40 KiB |
BIN
img/main-window-zh.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
img/main-window.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
img/repo-card.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
img/screenshot.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
img/主窗口.png
|
Before Width: | Height: | Size: 128 KiB |
|
|
@ -2,7 +2,6 @@ import sys
|
|||
import os
|
||||
import subprocess
|
||||
import glob
|
||||
import re
|
||||
import argparse
|
||||
|
||||
try:
|
||||
|
|
@ -21,7 +20,7 @@ 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-tag", default="")
|
||||
argParser.add_argument("--version-string", default="")
|
||||
argParser.add_argument("--pfx-path", default="")
|
||||
argParser.add_argument("--pfx-password", default="")
|
||||
args = argParser.parse_args()
|
||||
|
|
@ -57,44 +56,11 @@ 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 = ""
|
||||
if args.version_major != 0 or args.version_minor != 0 or args.version_patch != 0:
|
||||
versionNumProps = f";MajorVersion={args.version_major};MinorVersion={args.version_minor};PatchVersion={args.version_patch}"
|
||||
|
||||
# 更新 RC 文件中的版本号
|
||||
version = f"{args.version_major}.{args.version_minor}.{args.version_patch}.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)
|
||||
|
||||
versionTagProp = "" if args.version_tag == "" else f";VersionTag={args.version_tag}"
|
||||
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}"
|
||||
|
||||
p = subprocess.run(
|
||||
f'"{msbuildPath}" Magpie.sln -m -t:Rebuild -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={args.platform};UseClangCL={args.compiler == "ClangCL"};UseNativeMicroArch={args.use_native_march};OutDir={os.getcwd()}\\publish\\{args.platform}\\;CommitId={commitId}{versionNumProps}{versionTagProp}'
|
||||
f'"{msbuildPath}" Magpie.slnx -m -t:Rebuild -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={args.platform};DisablePDB=true;UseClangCL={args.compiler == "ClangCL"};UseNativeMicroArch={args.use_native_march};OutDir={os.getcwd()}\\publish\\{args.platform}\\;CommitId={commitId}{versionNumProps}{versionStrProp}'
|
||||
)
|
||||
if p.returncode != 0:
|
||||
raise Exception("编译失败")
|
||||
|
|
@ -116,9 +82,8 @@ def remove_file(file):
|
|||
pass
|
||||
|
||||
|
||||
for pattern in ["*.pdb", "*.lib", "*.exp"]:
|
||||
for file in glob.glob(pattern):
|
||||
remove_file(file)
|
||||
for file in glob.glob("*.lib"):
|
||||
remove_file(file)
|
||||
|
||||
print("清理完毕", flush=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,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"]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ argParser = argparse.ArgumentParser()
|
|||
argParser.add_argument("access_token")
|
||||
args = argParser.parse_args()
|
||||
|
||||
wikiRepoUrl = f"https://{args.access_token}@github.com/{os.environ["GITHUB_REPOSITORY"]}.wiki.git"
|
||||
wikiRepoUrl = f'https://{args.access_token}@github.com/{os.environ["GITHUB_REPOSITORY"]}.wiki.git'
|
||||
|
||||
# 创建临时目录
|
||||
wikiRepoDir = tempfile.mkdtemp()
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@
|
|||
<DebugInfoOnOverlay>false</DebugInfoOnOverlay>
|
||||
<!-- 使用 composition swapchain 呈现 -->
|
||||
<UseCompSwapchain>false</UseCompSwapchain>
|
||||
|
||||
<CommitId></CommitId>
|
||||
|
||||
<MajorVersion></MajorVersion>
|
||||
<MinorVersion></MinorVersion>
|
||||
<PatchVersion></PatchVersion>
|
||||
<VersionTag></VersionTag>
|
||||
<VersionString></VersionString>
|
||||
<CommitId></CommitId>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- 用户自定义编译选项 -->
|
||||
|
|
|
|||
|
|
@ -1,26 +1,19 @@
|
|||
<?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;NODEFERWINDOWPOS;NOMCX;NO_SHLWAPI_PATH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<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="'$(MajorVersion)' != '' And '$(MinorVersion)' != '' And '$(PatchVersion)' != ''">MP_MAJOR_VERSION=$(MajorVersion);MP_MINOR_VERSION=$(MinorVersion);MP_PATCH_VERSION=$(PatchVersion);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(VersionTag)' != ''">MP_VERSION_TAG=$(VersionTag);%(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>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- /await:strict: 禁用协程的非标准语言扩展 -->
|
||||
<AdditionalOptions Condition="!$(UseClangCL)">/await:strict %(AdditionalOptions)</AdditionalOptions>
|
||||
|
|
@ -32,20 +25,41 @@
|
|||
<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>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<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>
|
||||
|
|
@ -62,6 +76,9 @@
|
|||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
<Lib>
|
||||
<LinkTimeCodeGeneration>true</LinkTimeCodeGeneration>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- 所有项目共享的头文件 -->
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?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">
|
||||
|
|
@ -22,12 +23,23 @@
|
|||
<!-- 编译选项 -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)\BuildOptions.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
<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)">v143</PlatformToolset>
|
||||
<PlatformToolset Condition="!$(UseClangCL) And $(VS17)">v143</PlatformToolset>
|
||||
<PlatformToolset Condition="!$(UseClangCL) And !$(VS17)">v145</PlatformToolset>
|
||||
<UseDebugLibraries Condition="'$(Configuration)' == 'Debug'">true</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@ 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,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{62503530-b84b-4cc2-80b6-3f89618172b7}</ProjectGuid>
|
||||
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
|
||||
|
|
@ -10,6 +8,7 @@
|
|||
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Utility</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
|
|
@ -428,14 +427,10 @@
|
|||
<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>
|
||||
|
|
@ -463,6 +458,12 @@
|
|||
<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,6 +451,8 @@
|
|||
<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">
|
||||
|
|
|
|||
170
src/Effects/SGSR.hlsl
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// 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);
|
||||
}
|
||||
|
||||
1787
src/Effects/k7_modernAnime_FHD_x2.hlsl
Normal file
|
|
@ -47,7 +47,7 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
};
|
||||
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
winrt::com_ptr<IDXGISwapChain1> dxgiSwapChain = nullptr;
|
||||
winrt::com_ptr<IDXGISwapChain1> dxgiSwapChain;
|
||||
HRESULT hr = _deviceResources->GetDXGIFactory()->CreateSwapChainForHwnd(
|
||||
d3dDevice,
|
||||
hwndAttach,
|
||||
|
|
@ -130,12 +130,14 @@ bool AdaptivePresenter::BeginFrame(
|
|||
return true;
|
||||
}
|
||||
|
||||
void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
||||
void AdaptivePresenter::EndFrame(bool waitForGpu) noexcept {
|
||||
if (_isDCompPresenting) {
|
||||
_dcompSurface->EndDraw();
|
||||
}
|
||||
|
||||
if (waitForRenderComplete || _isResized) {
|
||||
if (waitForGpu || _isResized) {
|
||||
_isResized = false;
|
||||
|
||||
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁。
|
||||
//
|
||||
// 我们希望 DWM 绘制新的窗口框架时刚好合成新帧,但这不是我们能控制的,尤其是混合架构
|
||||
|
|
@ -152,10 +154,10 @@ void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
|||
// 实用价值。
|
||||
|
||||
// 等待渲染完成
|
||||
_WaitForRenderComplete();
|
||||
_WaitForGpu();
|
||||
|
||||
// 等待 DWM 开始合成新一帧
|
||||
_WaitForDwmComposition();
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
}
|
||||
|
||||
if (_isDCompPresenting) {
|
||||
|
|
@ -172,21 +174,14 @@ void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
|||
_isSwitchingToSwapChain = false;
|
||||
|
||||
// 等待交换链呈现新帧
|
||||
_WaitForRenderComplete();
|
||||
_WaitForDwmComposition();
|
||||
_WaitForGpu();
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
|
||||
// 清除 DirectCompostion 内容
|
||||
_dcompVisual->SetContent(nullptr);
|
||||
_dcompDevice->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
if (_isResized) {
|
||||
_isResized = false;
|
||||
} else {
|
||||
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
|
||||
_WaitForRenderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
bool AdaptivePresenter::OnResize() noexcept {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public:
|
|||
POINT& drawOffset
|
||||
) noexcept override;
|
||||
|
||||
void EndFrame(bool waitForRenderComplete = false) noexcept override;
|
||||
void EndFrame(bool waitForGpu = false) noexcept override;
|
||||
|
||||
bool OnResize() noexcept override;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ bool CompSwapchainPresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
|
||||
HRESULT hr = DCompositionCreateDevice3(d3dDevice, IID_PPV_ARGS(&_dcompDevice));
|
||||
HRESULT hr = DCompositionCreateDevice3(nullptr, IID_PPV_ARGS(&_dcompDevice));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DCompositionCreateDevice3 失败", hr);
|
||||
return false;
|
||||
|
|
@ -58,7 +56,7 @@ bool CompSwapchainPresenter::_Initialize(HWND hwndAttach) noexcept {
|
|||
}
|
||||
|
||||
winrt::com_ptr<IPresentationFactory> presentationFactory =
|
||||
CreatePresentationFactory(d3dDevice);
|
||||
CreatePresentationFactory(_deviceResources->GetD3DDevice());
|
||||
if (!presentationFactory) {
|
||||
Logger::Get().Error("CreatePresentationFactory 失败");
|
||||
return false;
|
||||
|
|
@ -228,15 +226,15 @@ bool CompSwapchainPresenter::BeginFrame(
|
|||
return true;
|
||||
}
|
||||
|
||||
void CompSwapchainPresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
||||
if (waitForRenderComplete || _isResized) {
|
||||
void CompSwapchainPresenter::EndFrame(bool waitForGpu) noexcept {
|
||||
if (waitForGpu || _isResized) {
|
||||
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁,参见 AdaptivePresenter::EndFrame
|
||||
|
||||
// 等待渲染完成
|
||||
_WaitForRenderComplete();
|
||||
_WaitForGpu();
|
||||
|
||||
// 等待 DWM 开始合成新一帧
|
||||
_WaitForDwmComposition();
|
||||
Win32Helper::WaitForDwmComposition();
|
||||
}
|
||||
|
||||
_presentationManager->Present();
|
||||
|
|
@ -245,7 +243,7 @@ void CompSwapchainPresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
|||
_isResized = false;
|
||||
} else {
|
||||
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
|
||||
_WaitForRenderComplete();
|
||||
_WaitForGpu();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public:
|
|||
POINT& drawOffset
|
||||
) noexcept override;
|
||||
|
||||
void EndFrame(bool waitForRenderComplete = false) noexcept override;
|
||||
void EndFrame(bool waitForGpu = false) noexcept override;
|
||||
|
||||
bool OnResize() noexcept override;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,9 +39,8 @@ struct VertexPositionTexture {
|
|||
XMFLOAT2 position;
|
||||
XMFLOAT2 textureCoordinate;
|
||||
|
||||
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 },
|
||||
static constexpr D3D11_INPUT_ELEMENT_DESC InputElements[] = {
|
||||
{ "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 },
|
||||
};
|
||||
};
|
||||
|
|
@ -86,63 +85,64 @@ bool CursorDrawer::Initialize(DeviceResources& deviceResources) noexcept {
|
|||
}
|
||||
|
||||
void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept {
|
||||
if (!_isCursorVisible) {
|
||||
// 截屏时暂时不渲染光标
|
||||
return;
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
_lastCursorHandle = cursorHandle;
|
||||
_lastCursorPos = cursorPos;
|
||||
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
if (!cursorHandle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _CursorInfo* cursorInfo = _ResolveCursor(cursorHandle);
|
||||
if (!cursorInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ScalingOptions& options = scalingWindow.Options();
|
||||
|
||||
float cursorScaling = options.cursorScaling;
|
||||
if (cursorScaling < FLOAT_EPSILON<float>) {
|
||||
// 光标缩放和源窗口相同
|
||||
const Renderer& renderer = ScalingWindow::Get().Renderer();
|
||||
const Renderer& renderer = scalingWindow.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(ci->size.cx * cursorScaling),
|
||||
lroundf(ci->size.cy * cursorScaling)
|
||||
lroundf(cursorInfo->size.cx * cursorScaling),
|
||||
lroundf(cursorInfo->size.cy * cursorScaling)
|
||||
};
|
||||
RECT cursorRect{
|
||||
.left = lroundf(cursorPos.x - ci->hotSpot.x * cursorScaling),
|
||||
.top = lroundf(cursorPos.y - ci->hotSpot.y * cursorScaling),
|
||||
.left = lroundf(cursorPos.x - cursorInfo->hotSpot.x * cursorScaling),
|
||||
.top = lroundf(cursorPos.y - cursorInfo->hotSpot.y * cursorScaling),
|
||||
.right = cursorRect.left + cursorSize.cx,
|
||||
.bottom = cursorRect.top + cursorSize.cy
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
if (cursorRect.left >= viewportRect.right ||
|
||||
cursorRect.top >= viewportRect.bottom ||
|
||||
|
|
@ -202,7 +202,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
d3dDC->RSSetState(nullptr);
|
||||
}
|
||||
|
||||
if (ci->type == _CursorType::Color) {
|
||||
if (cursorInfo->type == _CursorType::Color) {
|
||||
// 配置像素着色器
|
||||
if (!_simplePS) {
|
||||
HRESULT hr = _deviceResources->GetD3DDevice()->CreatePixelShader(
|
||||
|
|
@ -215,7 +215,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
|
||||
d3dDC->PSSetShader(_simplePS.get(), nullptr, 0);
|
||||
d3dDC->PSSetConstantBuffers(0, 0, nullptr);
|
||||
ID3D11ShaderResourceView* cursorSrv = ci->textureSrv.get();
|
||||
ID3D11ShaderResourceView* cursorSrv = cursorInfo->textureSrv.get();
|
||||
d3dDC->PSSetShaderResources(0, 1, &cursorSrv);
|
||||
|
||||
const bool useBilinear = options.cursorInterpolationMode == CursorInterpolationMode::Bilinear &&
|
||||
|
|
@ -259,26 +259,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
|
||||
};
|
||||
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
|
||||
);
|
||||
{
|
||||
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));
|
||||
|
||||
if (ci->type == _CursorType::MaskedColor) {
|
||||
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 (!_maskedCursorPS) {
|
||||
HRESULT hr = _deviceResources->GetD3DDevice()->CreatePixelShader(
|
||||
MaskedCursorPS, sizeof(MaskedCursorPS), nullptr, _maskedCursorPS.put());
|
||||
|
|
@ -303,7 +303,7 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
d3dDC->PSSetConstantBuffers(0, 0, nullptr);
|
||||
|
||||
{
|
||||
ID3D11ShaderResourceView* srvs[2]{ _tempCursorTextureRtv.get(), ci->textureSrv.get() };
|
||||
ID3D11ShaderResourceView* srvs[2]{ _tempCursorTextureRtv.get(), cursorInfo->textureSrv.get() };
|
||||
d3dDC->PSSetShaderResources(0, 2, srvs);
|
||||
}
|
||||
|
||||
|
|
@ -319,25 +319,53 @@ void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept
|
|||
}
|
||||
|
||||
bool CursorDrawer::NeedRedraw() const noexcept {
|
||||
const CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
const HCURSOR hCursor = cursorManager.CursorHandle();
|
||||
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();
|
||||
POINT cursorPos = cursorManager.CursorPos();
|
||||
|
||||
// 检查光标形状是否变化
|
||||
if (hCursor != _lastCursorHandle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hCursor) {
|
||||
// 一直不可见
|
||||
return false;
|
||||
}
|
||||
|
||||
// 光标可见则检查位置是否变化。为了适配缩放窗口位置变化,比较在缩放窗口中的相对位置
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
// 转换为渲染矩形局部坐标
|
||||
const RECT& rendererRect = scalingWindow.RendererRect();
|
||||
cursorPos.x -= rendererRect.left;
|
||||
cursorPos.y -= rendererRect.top;
|
||||
return cursorPos != _lastCursorPos;
|
||||
|
||||
// 检查自动隐藏光标
|
||||
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 };
|
||||
}
|
||||
|
||||
const CursorDrawer::_CursorInfo* CursorDrawer::_ResolveCursor(HCURSOR hCursor) noexcept {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ public:
|
|||
bool NeedRedraw() const noexcept;
|
||||
|
||||
private:
|
||||
std::pair<HCURSOR, POINT> _GetCursorState(bool& isActive) const noexcept;
|
||||
|
||||
enum class _CursorType {
|
||||
// 彩色光标,此时纹理中 RGB 通道已预乘 A 通道(premultiplied alpha),A 通道已预先取反
|
||||
// 这是为了减少着色器的计算量以及确保(可能进行的)双线性差值的准确性
|
||||
|
|
@ -70,6 +72,10 @@ 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() };
|
||||
|
||||
|
|
|
|||
|
|
@ -101,12 +101,13 @@ CursorManager::~CursorManager() noexcept {
|
|||
|
||||
if (_isUnderCapture) {
|
||||
POINT cursorPos;
|
||||
if (!GetCursorPos(&cursorPos)) {
|
||||
if (GetCursorPos(&cursorPos)) {
|
||||
_StopCapture(cursorPos, true);
|
||||
_ReliableSetCursorPos(cursorPos);
|
||||
} else {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
_RestoreClipCursor();
|
||||
}
|
||||
|
||||
_StopCapture(cursorPos, true);
|
||||
_ReliableSetCursorPos(cursorPos);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -395,7 +396,7 @@ static bool PtInWindow(HWND hWnd, POINT pt) noexcept {
|
|||
// 也会考虑自定义形状的窗口。反之如果位于非客户区,我们需手动处理后者。
|
||||
//
|
||||
// 可以参考 ChildWindowFromPointEx 的实现:
|
||||
// https://github.com/tongzx/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/winwhere.c#L47
|
||||
// https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/winwhere.c#L47
|
||||
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(hWnd, clientRect)) {
|
||||
|
|
@ -579,7 +580,6 @@ void CursorManager::_UpdateCursorState() noexcept {
|
|||
|
||||
POINT cursorPos;
|
||||
if (!GetCursorPos(&cursorPos)) {
|
||||
Logger::Get().Win32Error("GetCursorPos 失败");
|
||||
_RestoreClipCursor();
|
||||
return;
|
||||
}
|
||||
|
|
@ -966,7 +966,7 @@ void CursorManager::_ClipCursorOnSrcMoving() noexcept {
|
|||
if (!monitorRects.empty()) {
|
||||
// 移动源窗口时,如果只有一个显示器,应将光标限制在工作矩形内。一旦超出工作矩形,
|
||||
// 源窗口将无法继续移动。还需检查窗口样式,以和 OS 保持一致,见
|
||||
// https://github.com/tongzx/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L1142
|
||||
// https://github.com/Blinue/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) {
|
||||
|
|
|
|||
|
|
@ -72,21 +72,24 @@ 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,15 @@ 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();
|
||||
|
||||
|
|
@ -175,6 +184,26 @@ 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();
|
||||
|
|
@ -202,64 +231,67 @@ 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;
|
||||
}
|
||||
|
||||
// 尝试设置源窗口样式,因为 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;
|
||||
if (_isSrcStyleChanged) {
|
||||
// 恢复源窗口样式
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, srcExStyle);
|
||||
}
|
||||
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, _originalSrcExStyle);
|
||||
_originalSrcExStyle = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_TryCreateGraphicsCaptureItem(IGraphicsCaptureItemInterop* interop) noexcept {
|
||||
|
|
@ -281,43 +313,6 @@ 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;
|
||||
|
|
@ -328,7 +323,7 @@ bool GraphicsCaptureFrameSource::_StartCapture() noexcept {
|
|||
_captureFramePool = winrt::Direct3D11CaptureFramePool::Create(
|
||||
_wrappedD3DDevice,
|
||||
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
1, // 帧的缓存数量
|
||||
4, // 帧的缓存数量,更大的值有利于在低帧率下降低延迟
|
||||
{ (int)_frameBox.right, (int)_frameBox.bottom } // 帧的尺寸为包含源窗口的最小尺寸
|
||||
);
|
||||
|
||||
|
|
@ -386,18 +381,22 @@ 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));
|
||||
|
||||
// 还原源窗口样式
|
||||
if (_originalSrcExStyle) {
|
||||
// 首先还原所有者窗口的样式以压制任务栏的动画
|
||||
if (_originalOwnerExStyle) {
|
||||
SetWindowLongPtr(GetWindowOwner(hwndSrc), GWL_EXSTYLE, _originalOwnerExStyle);
|
||||
// 修正任务栏焦点窗口和 Alt+Tab 切换顺序
|
||||
if (GetForegroundWindow() == hwndSrc) {
|
||||
SetForegroundWindow(GetDesktopWindow());
|
||||
SetForegroundWindow(hwndSrc);
|
||||
}
|
||||
|
||||
SetWindowLongPtr(hwndSrc, GWL_EXSTYLE, _originalSrcExStyle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,18 +38,15 @@ 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ bool ImGuiBackend::_CreateDeviceObjects() noexcept {
|
|||
}
|
||||
|
||||
static constexpr D3D11_INPUT_ELEMENT_DESC LOCAL_LAYOUT[] = {
|
||||
{ "SV_POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
||||
{ "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 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ struct serializer<
|
|||
namespace Magpie {
|
||||
|
||||
// 缓存版本号。当缓存文件结构有更改时更新它,使旧缓存失效
|
||||
static constexpr uint32_t FONTS_CACHE_VERSION = 4;
|
||||
static constexpr uint32_t FONTS_CACHE_VERSION = 7;
|
||||
|
||||
static std::wstring GetCacheFileName(const std::wstring_view& language, uint32_t dpi) noexcept {
|
||||
return fmt::format(L"{}\\fonts_{}_{}", CommonSharedConstants::CACHE_DIR, language, dpi);
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ 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,11 +48,6 @@ private:
|
|||
ImGuiBackend _backend;
|
||||
|
||||
phmap::flat_hash_map<std::string, ImVec4> _windowRects;
|
||||
|
||||
uint32_t _handlerId = 0;
|
||||
|
||||
HANDLE _hHookThread = NULL;
|
||||
DWORD _hookThreadId = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
<?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>
|
||||
|
|
@ -17,6 +15,7 @@
|
|||
<GeneratedFilesDir>$(IntDir)\Generated Files\</GeneratedFilesDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
|
|
@ -154,15 +153,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>
|
||||
|
|
@ -110,12 +110,14 @@ void OverlayDrawer::Draw(
|
|||
_imguiImpl.NewFrame(_overlayOptions->windows, fittsLawAdjustment, _dpiScale);
|
||||
|
||||
bool needRedraw = false;
|
||||
// 防止 ID 冲突
|
||||
int itemId = 0;
|
||||
|
||||
if (_isToolbarVisible && _DrawToolbar(fps)) {
|
||||
if (_isToolbarVisible && _DrawToolbar(fps, itemId)) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
if (_isProfilerVisible && _DrawProfiler(effectTimings, fps)) {
|
||||
if (_isProfilerVisible && _DrawProfiler(effectTimings, fps, itemId)) {
|
||||
needRedraw = true;
|
||||
}
|
||||
|
||||
|
|
@ -338,10 +340,12 @@ SmallVector<ImWchar> OverlayDrawer::_BuildFontUI(
|
|||
SetGlyphRanges(ranges, OverlayHelper::BASIC_LATIN_RANGES);
|
||||
} else if (language == L"ru" || language == L"uk") {
|
||||
SetGlyphRanges(ranges, fontAtlas.GetGlyphRangesCyrillic());
|
||||
} else if (language == L"tr" || language == L"pl") {
|
||||
} else if (language == L"tr" || language == L"pl" || language == L"fi") {
|
||||
SetGlyphRanges(ranges, OverlayHelper::EXTENDED_LATIN_RANGES);
|
||||
} else if (language == L"vi") {
|
||||
SetGlyphRanges(ranges, fontAtlas.GetGlyphRangesVietnamese());
|
||||
} else if (language == L"fr") {
|
||||
SetGlyphRanges(ranges, OverlayHelper::FRENCH_RANGES);
|
||||
} else {
|
||||
// Basic Latin 使用默认字体
|
||||
SetGlyphRanges(ranges, OverlayHelper::BASIC_LATIN_RANGES);
|
||||
|
|
@ -642,7 +646,7 @@ static std::string IconLabel(ImWchar iconChar) noexcept {
|
|||
return StrHelper::UTF16ToUTF8(text);
|
||||
}
|
||||
|
||||
bool OverlayDrawer::_DrawToolbar(uint32_t fps) noexcept {
|
||||
bool OverlayDrawer::_DrawToolbar(uint32_t fps, int& itemId) noexcept {
|
||||
bool needRedraw = false;
|
||||
|
||||
const float windowWidth = 360 * _dpiScale;
|
||||
|
|
@ -653,6 +657,8 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) 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;
|
||||
|
||||
|
|
@ -677,6 +683,7 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) 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);
|
||||
|
|
@ -767,6 +774,7 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) 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) {
|
||||
|
|
@ -774,28 +782,37 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -814,7 +831,25 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) noexcept {
|
|||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosY((CORNER_ROUNDING + 3) * _dpiScale);
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - 50 * _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();
|
||||
|
|
@ -824,11 +859,10 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) noexcept {
|
|||
isWindowedMode ? L"Overlay_Toolbar_SwitchToFullscreen" : L"Overlay_Toolbar_SwitchToWindowed");
|
||||
if (drawButton(icon, switchScalingStr.c_str())) {
|
||||
ScalingWindow::Dispatcher().TryEnqueue([]() {
|
||||
ScalingWindow::Get().SwitchScalingState(!ScalingWindow::Get().Options().IsWindowedMode());
|
||||
ScalingWindow::Get().ToggleScaling(!ScalingWindow::Get().Options().IsWindowedMode());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// 和主窗口保持一致 (#C42B1C)
|
||||
|
|
@ -854,14 +888,14 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) noexcept {
|
|||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::PopStyleColor(5);
|
||||
ImGui::PopStyleVar(5);
|
||||
ImGui::PopStyleVar(6);
|
||||
} else {
|
||||
_isCursorOnCaptionArea = false;
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
return needRedraw;
|
||||
}
|
||||
|
|
@ -875,7 +909,7 @@ static std::string RectToStr(const RECT& rect) noexcept {
|
|||
#endif
|
||||
|
||||
// 返回 true 表示应再渲染一次
|
||||
bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps) noexcept {
|
||||
bool OverlayDrawer::_DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps, int& itemId) noexcept {
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
const Renderer& renderer = ScalingWindow::Get().Renderer();
|
||||
|
||||
|
|
@ -1077,8 +1111,6 @@ 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) noexcept;
|
||||
bool _DrawToolbar(uint32_t fps, int& itemId) noexcept;
|
||||
|
||||
bool _DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps) noexcept;
|
||||
bool _DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps, int& itemId) noexcept;
|
||||
|
||||
const std::string& _GetResourceString(const std::wstring_view& key) noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ 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 };
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -49,10 +51,13 @@ 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 Pinned = 0xE840;
|
||||
static const ImWchar Diagnostic = 0xE9D9;
|
||||
|
|
@ -65,6 +70,8 @@ struct OverlayHelper {
|
|||
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::Pinned, SegoeIcons::Pinned,
|
||||
SegoeIcons::Diagnostic, SegoeIcons::Diagnostic,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <dcomp.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -30,64 +27,12 @@ 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 =
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PresenterBase::_CalcBufferCount() noexcept {
|
||||
// 缓冲区数量取决于 ScalingRuntime::_ScalingThreadProc 中检查光标移动的频率
|
||||
return ScalingWindow::Get().Options().Is3DGameMode() ? 4 : 8;
|
||||
}
|
||||
|
||||
void PresenterBase::_WaitForRenderComplete() noexcept {
|
||||
void PresenterBase::_WaitForGpu() noexcept {
|
||||
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
|
||||
|
||||
// 等待渲染完成
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public:
|
|||
POINT& drawOffset
|
||||
) noexcept = 0;
|
||||
|
||||
virtual void EndFrame(bool waitForRenderComplete = false) noexcept = 0;
|
||||
virtual void EndFrame(bool waitForGpu = false) noexcept = 0;
|
||||
|
||||
virtual bool OnResize() noexcept = 0;
|
||||
|
||||
|
|
@ -27,10 +27,7 @@ public:
|
|||
protected:
|
||||
virtual bool _Initialize(HWND hwndAttach) noexcept = 0;
|
||||
|
||||
void _WaitForRenderComplete() noexcept;
|
||||
|
||||
// 和 DwmFlush 效果相同但更准确
|
||||
static void _WaitForDwmComposition() noexcept;
|
||||
void _WaitForGpu() noexcept;
|
||||
|
||||
static uint32_t _CalcBufferCount() noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ winrt::fire_and_forget Renderer::TakeScreenshot(
|
|||
}
|
||||
}
|
||||
|
||||
void Renderer::_FrontendRender(bool waitForRenderComplete) noexcept {
|
||||
void Renderer::_FrontendRender(bool waitForGpu) noexcept {
|
||||
winrt::com_ptr<ID3D11Texture2D> frameTex;
|
||||
winrt::com_ptr<ID3D11RenderTargetView> frameRtv;
|
||||
POINT drawOffset;
|
||||
|
|
@ -267,10 +267,10 @@ void Renderer::_FrontendRender(bool waitForRenderComplete) noexcept {
|
|||
// 绘制光标
|
||||
_cursorDrawer.Draw(frameTex.get(), drawOffset);
|
||||
|
||||
_presenter->EndFrame(waitForRenderComplete);
|
||||
_presenter->EndFrame(waitForGpu);
|
||||
}
|
||||
|
||||
bool Renderer::Render(bool force, bool waitForRenderComplete) noexcept {
|
||||
bool Renderer::Render(bool force, bool waitForGpu) noexcept {
|
||||
if (!force && _lastAccessMutexKey == _sharedTextureMutexKey.load(std::memory_order_relaxed)) {
|
||||
if (_lastAccessMutexKey == 0) {
|
||||
// 第一帧尚未完成
|
||||
|
|
@ -282,7 +282,7 @@ bool Renderer::Render(bool force, bool waitForRenderComplete) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
_FrontendRender(waitForRenderComplete);
|
||||
_FrontendRender(waitForGpu);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -699,14 +699,43 @@ ID3D11Texture2D* Renderer::_ResizeEffects() noexcept {
|
|||
|
||||
void Renderer::_UpdateDestRect() noexcept {
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
DestAlignment alignment = ScalingWindow::Get().Options().destAlignment;
|
||||
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
_frontendSharedTexture->GetDesc(&desc);
|
||||
LONG destWidth;
|
||||
LONG destHeight;
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
_frontendSharedTexture->GetDesc(&desc);
|
||||
destWidth = (LONG)desc.Width;
|
||||
destHeight = (LONG)desc.Height;
|
||||
}
|
||||
|
||||
_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;
|
||||
using enum DestAlignment;
|
||||
|
||||
if (alignment == LeftTop || alignment == Left || alignment == LeftBottom) {
|
||||
_destRect.left = 0;
|
||||
_destRect.right = destWidth;
|
||||
} else if (alignment == Top || alignment == Center || alignment == Bottom) {
|
||||
_destRect.left = (rendererRect.left + rendererRect.right - destWidth) / 2;
|
||||
_destRect.right = _destRect.left + destWidth;
|
||||
} else {
|
||||
_destRect.left = rendererRect.right - destWidth;
|
||||
_destRect.right = rendererRect.right;
|
||||
}
|
||||
|
||||
if (alignment == LeftTop || alignment == Top || alignment == RightTop) {
|
||||
_destRect.top = 0;
|
||||
_destRect.bottom = destHeight;
|
||||
} else if (alignment == Left || alignment == Center || alignment == Right) {
|
||||
_destRect.top = (rendererRect.top + rendererRect.bottom - destHeight) / 2;
|
||||
_destRect.bottom = _destRect.top + destHeight;
|
||||
} else {
|
||||
_destRect.top = rendererRect.bottom - destHeight;
|
||||
_destRect.bottom = rendererRect.bottom;
|
||||
}
|
||||
|
||||
assert(_destRect.left + destWidth == _destRect.right);
|
||||
assert(_destRect.top + destHeight == _destRect.bottom);
|
||||
}
|
||||
|
||||
HANDLE Renderer::_CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public:
|
|||
|
||||
ScalingError Initialize(HWND hwndAttach, OverlayOptions& overlayOptions) noexcept;
|
||||
|
||||
bool Render(bool force = false, bool waitForRenderComplete = false) noexcept;
|
||||
bool Render(bool force = false, bool waitForGpu = false) noexcept;
|
||||
|
||||
bool OnResize() noexcept;
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ public:
|
|||
) noexcept;
|
||||
|
||||
private:
|
||||
void _FrontendRender(bool waitForRenderComplete = false) noexcept;
|
||||
void _FrontendRender(bool waitForGpu = false) noexcept;
|
||||
|
||||
void _BackendThreadProc() noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ void ScalingOptions::Log() const noexcept {
|
|||
IsWindowedMode: {}
|
||||
IsDebugMode: {}
|
||||
IsBenchmarkMode: {}
|
||||
IsTopmostDisabled: {}
|
||||
IsFP16Disabled: {}
|
||||
IsEffectCacheDisabled: {}
|
||||
IsFontCacheDisabled: {}
|
||||
|
|
@ -74,6 +75,7 @@ void ScalingOptions::Log() const noexcept {
|
|||
IsWindowedMode(),
|
||||
IsDebugMode(),
|
||||
IsBenchmarkMode(),
|
||||
IsTopmostDisabled(),
|
||||
IsFP16Disabled(),
|
||||
IsEffectCacheDisabled(),
|
||||
IsFontCacheDisabled(),
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ ScalingRuntime::ScalingRuntime() : _scalingThread(&ScalingRuntime::_ScalingThrea
|
|||
}
|
||||
|
||||
ScalingRuntime::~ScalingRuntime() {
|
||||
Stop();
|
||||
|
||||
if (_scalingThread.joinable()) {
|
||||
const HANDLE hScalingThread = _scalingThread.native_handle();
|
||||
|
||||
|
|
@ -50,27 +48,30 @@ ScalingRuntime::~ScalingRuntime() {
|
|||
}
|
||||
}
|
||||
|
||||
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
|
||||
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options, bool force) {
|
||||
assert(!options.screenshotsDir.empty() && options.showToast && options.showError && options.save);
|
||||
|
||||
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options))]() mutable {
|
||||
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options)), force]() mutable {
|
||||
ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
if (scalingWindow.IsSrcRepositioning()) {
|
||||
scalingWindow.CleanAfterSrcRepositioned();
|
||||
// 如果正在缩放且 force 为假则忽略
|
||||
if (scalingWindow && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
scalingWindow.Stop();
|
||||
|
||||
// 初始化时视为处于缩放状态
|
||||
_IsScaling(true);
|
||||
_State(ScalingState::Scaling);
|
||||
scalingWindow.Start(hwndSrc, std::move(options));
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScalingRuntime::SwitchScalingState(bool isWindowedMode) {
|
||||
void ScalingRuntime::ToggleScaling(bool isWindowedMode) {
|
||||
_Dispatcher().TryEnqueue([isWindowedMode]() {
|
||||
if (ScalingWindow& scalingWindow = ScalingWindow::Get()) {
|
||||
scalingWindow.SwitchScalingState(isWindowedMode);
|
||||
scalingWindow.ToggleScaling(isWindowedMode);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -89,38 +90,39 @@ void ScalingRuntime::Stop() {
|
|||
});
|
||||
}
|
||||
|
||||
// 返回值:
|
||||
// -1: 应取消缩放
|
||||
// 0: 仍在调整中
|
||||
// 1: 调整完毕
|
||||
static int GetSrcRepositionState(HWND hwndSrc) noexcept {
|
||||
static std::optional<bool> IsSrcRepositioning(HWND hwndSrc) noexcept {
|
||||
if (!IsWindow(hwndSrc)) {
|
||||
return -1;
|
||||
Logger::Get().Info("源窗口已销毁");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// 窗口不可见或最小化则继续等待。注意 showCmd 不能准确判断窗口可见性,
|
||||
// 应使用 IsWindowVisible。
|
||||
if (!IsWindowVisible(hwndSrc)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Win32Helper::IsWindowHung(hwndSrc)) {
|
||||
return -1;
|
||||
Logger::Get().Info("源窗口已挂起");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const UINT showCmd = Win32Helper::GetWindowShowCmd(hwndSrc);
|
||||
if (showCmd == SW_HIDE) {
|
||||
return -1;
|
||||
} else if (showCmd == SW_SHOWMAXIMIZED) {
|
||||
if (showCmd == SW_SHOWMAXIMIZED) {
|
||||
// 窗口最大化则尝试缩放,失败会显示错误消息
|
||||
return 1;
|
||||
return false;
|
||||
} else if (showCmd == SW_SHOWMINIMIZED) {
|
||||
// 窗口最小化则继续等待
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查源窗口是否正在调整大小或移动
|
||||
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
|
||||
if (!GetGUIThreadInfo(GetWindowThreadProcessId(hwndSrc, nullptr), &guiThreadInfo)) {
|
||||
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
|
||||
return -1;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return (guiThreadInfo.flags & GUI_INMOVESIZE) ? 0 : 1;
|
||||
return bool(guiThreadInfo.flags & GUI_INMOVESIZE);
|
||||
}
|
||||
|
||||
void ScalingRuntime::_ScalingThreadProc() noexcept {
|
||||
|
|
@ -160,9 +162,9 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT) {
|
||||
scalingWindow.Stop();
|
||||
_IsScaling(false);
|
||||
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();
|
||||
}
|
||||
|
|
@ -170,9 +172,9 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
_IsScaling(scalingWindow);
|
||||
|
||||
if (scalingWindow) {
|
||||
_State(ScalingState::Scaling);
|
||||
|
||||
const auto now = steady_clock::now();
|
||||
// 限制检测光标移动的频率
|
||||
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
|
||||
|
|
@ -189,18 +191,25 @@ 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()) {
|
||||
const int state = GetSrcRepositionState(scalingWindow.SrcTracker().Handle());
|
||||
if (state == 0) {
|
||||
// 等待调整完成
|
||||
MsgWaitForMultipleObjectsEx(0, nullptr, 10, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||
} else if (state == 1) {
|
||||
// 重新缩放
|
||||
ScalingWindow::Get().RestartAfterSrcRepositioned();
|
||||
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();
|
||||
}
|
||||
} else {
|
||||
// 取消缩放
|
||||
ScalingWindow::Get().CleanAfterSrcRepositioned();
|
||||
_State(ScalingState::Idle);
|
||||
}
|
||||
} else {
|
||||
_State(ScalingState::Idle);
|
||||
lastRenderTime = {};
|
||||
WaitMessage();
|
||||
}
|
||||
|
|
@ -216,9 +225,9 @@ const winrt::DispatcherQueue& ScalingRuntime::_Dispatcher() noexcept {
|
|||
return _dispatcher;
|
||||
}
|
||||
|
||||
void ScalingRuntime::_IsScaling(bool value) {
|
||||
if (_isScaling.exchange(value, std::memory_order_relaxed) != value) {
|
||||
IsScalingChanged.Invoke(value);
|
||||
void ScalingRuntime::_State(ScalingState value) {
|
||||
if (_state.exchange(value, std::memory_order_relaxed) != value) {
|
||||
StateChanged.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ 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)) {}
|
||||
|
||||
|
|
@ -48,8 +52,10 @@ static void LogRects(const RECT& srcRect, const RECT& rendererRect, const RECT&
|
|||
|
||||
ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
||||
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
|
||||
#ifdef MP_VERSION_TAG
|
||||
STRING(MP_VERSION_TAG),
|
||||
#ifdef MP_VERSION_STRING
|
||||
STRINGIFY(MP_VERSION_STRING),
|
||||
#elif defined(MP_COMMIT_ID)
|
||||
"dev (" STRINGIFY(MP_COMMIT_ID) ")",
|
||||
#else
|
||||
"dev",
|
||||
#endif
|
||||
|
|
@ -86,11 +92,20 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
|
||||
InitMessage();
|
||||
|
||||
if (ScalingError error = _srcTracker.Set(hwndSrc, _options); error != ScalingError::NoError) {
|
||||
bool isSrcInvisibleOrMinimized = false;
|
||||
if (ScalingError error = _srcTracker.Set(hwndSrc, _options, isSrcInvisibleOrMinimized);
|
||||
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("已最大化的窗口不支持窗口模式缩放");
|
||||
|
|
@ -101,11 +116,6 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
if (_srcTracker.IsMoving() && !_options.IsWindowedMode()) {
|
||||
_isSrcRepositioning = true;
|
||||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static Ignore _ = []() {
|
||||
WNDCLASSEXW wcex{
|
||||
.cbSize = sizeof(wcex),
|
||||
|
|
@ -127,7 +137,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
const bool isWin11 = Win32Helper::GetOSVersion().IsWin11();
|
||||
// 不存在非客户区,渲染无需创建在子窗口里
|
||||
const bool isAllClient = !isWin11 &&
|
||||
(srcWindowKind == SrcWindowKind::NoBorder || srcWindowKind == SrcWindowKind::NoDecoration);
|
||||
(srcWindowKind == SrcWindowKind::NoBorder || srcWindowKind == SrcWindowKind::NoNativeFrame);
|
||||
if (_options.IsWindowedMode()) {
|
||||
const RECT& srcWindowRect = _srcTracker.WindowRect();
|
||||
|
||||
|
|
@ -143,8 +153,8 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
} else {
|
||||
GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &_currentDpi, &_currentDpi);
|
||||
|
||||
if (isWin11 && srcWindowKind == SrcWindowKind::NoDecoration) {
|
||||
// NoDecoration 在 Win11 中和 NoTitleBar 一样处理并禁用边框,优点是只需一个辅助窗口
|
||||
if (isWin11 && srcWindowKind == SrcWindowKind::NoNativeFrame) {
|
||||
// NoNativeFrame 在 Win11 中和 NoTitleBar 一样处理并禁用边框,优点是只需一个辅助窗口
|
||||
_topBorderThicknessInClient = 0;
|
||||
} else {
|
||||
_topBorderThicknessInClient = Win32Helper::GetNativeWindowBorderThickness(_currentDpi);
|
||||
|
|
@ -294,7 +304,7 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
|
||||
if (!_options.RealIsAllowScalingMaximized()) {
|
||||
// 检查源窗口是否是无边框全屏窗口
|
||||
if (srcWindowKind == SrcWindowKind::NoDecoration && _srcTracker.WindowRect() == _rendererRect) {
|
||||
if (srcWindowKind == SrcWindowKind::NoNativeFrame && _srcTracker.WindowRect() == _rendererRect) {
|
||||
Logger::Get().Info("源窗口已全屏");
|
||||
return ScalingError::Maximized;
|
||||
}
|
||||
|
|
@ -318,10 +328,18 @@ ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
|
|||
}
|
||||
|
||||
void ScalingWindow::Start(HWND hwndSrc, ScalingOptions&& options) noexcept {
|
||||
if (Handle()) {
|
||||
options.showError(hwndSrc, ScalingError::ScalingFailedGeneral);
|
||||
return;
|
||||
}
|
||||
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();
|
||||
// 缩放结束后失效
|
||||
|
|
@ -341,7 +359,7 @@ void ScalingWindow::Stop() noexcept {
|
|||
CleanAfterSrcRepositioned();
|
||||
}
|
||||
|
||||
void ScalingWindow::SwitchScalingState(bool isWindowedMode) noexcept {
|
||||
void ScalingWindow::ToggleScaling(bool isWindowedMode) noexcept {
|
||||
assert(Handle());
|
||||
|
||||
if (_options.IsWindowedMode() == isWindowedMode || !_srcTracker.IsFocused()) {
|
||||
|
|
@ -368,16 +386,15 @@ void ScalingWindow::SwitchToolbarState() noexcept {
|
|||
void ScalingWindow::Render() noexcept {
|
||||
bool isSrcRepositioning = false;
|
||||
bool srcFocusedChanged = false;
|
||||
bool srcOwnedWindowFocusedChanged = false;
|
||||
if (!_UpdateSrcState(isSrcRepositioning, srcFocusedChanged, srcOwnedWindowFocusedChanged)) {
|
||||
if (!_UpdateSrcState(isSrcRepositioning, srcFocusedChanged)) {
|
||||
Logger::Get().Info("源窗口状态改变");
|
||||
_DelayedStop(false, isSrcRepositioning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (srcFocusedChanged || srcOwnedWindowFocusedChanged) {
|
||||
_UpdateFocusStateAsync(!srcFocusedChanged && srcOwnedWindowFocusedChanged, false);
|
||||
}
|
||||
if (srcFocusedChanged) {
|
||||
_UpdateFocusStateAsync();
|
||||
}
|
||||
|
||||
// 虽然可以在第一帧渲染完成后再隐藏系统光标,但某些设备上显示窗口时光标状态会变成忙,
|
||||
// 提前隐藏光标可以提高观感。缩放窗口显示后再隐藏光标还可能造成光标闪烁两次,第一次是
|
||||
|
|
@ -444,8 +461,8 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
|
|||
DwmExtendFrameIntoClientArea(Handle(), &margins);
|
||||
}
|
||||
|
||||
if (_srcTracker.WindowKind() == SrcWindowKind::NoDecoration && Win32Helper::GetOSVersion().IsWin11()) {
|
||||
// Win11 中禁用边框和圆角以模仿 NoDecoration 的样式
|
||||
if (_srcTracker.WindowKind() == SrcWindowKind::NoNativeFrame && Win32Helper::GetOSVersion().IsWin11()) {
|
||||
// Win11 中禁用边框和圆角以模仿 NoNativeFrame 的样式
|
||||
COLORREF color = DWMWA_COLOR_NONE;
|
||||
DwmSetWindowAttribute(Handle(), DWMWA_BORDER_COLOR, &color, sizeof(color));
|
||||
|
||||
|
|
@ -467,6 +484,20 @@ 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;
|
||||
}
|
||||
|
|
@ -694,6 +725,18 @@ 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)) {
|
||||
|
|
@ -1082,24 +1125,15 @@ 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, minWidth, maxWidth),
|
||||
std::clamp(size.cy, minHeight, maxHeight)
|
||||
};
|
||||
return SIZE{ std::clamp(size.cx, 1l, maxWidth), std::clamp(size.cy, 1l, maxHeight) };
|
||||
}
|
||||
|
||||
ScalingError ScalingWindow::_InitialMoveSrcWindowInFullscreen() noexcept {
|
||||
|
|
@ -1181,7 +1215,7 @@ void ScalingWindow::_Show() noexcept {
|
|||
Handle(),
|
||||
NULL,
|
||||
0, 0, 0, 0,
|
||||
SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE
|
||||
SWP_SHOWWINDOW | SWP_NO_ACTIVATE_MOVE_SIZE
|
||||
);
|
||||
|
||||
// 广播开始缩放
|
||||
|
|
@ -1195,7 +1229,7 @@ void ScalingWindow::_Show() noexcept {
|
|||
|
||||
// 如果源窗口位于前台则将缩放窗口置顶
|
||||
if (_srcTracker.IsFocused()) {
|
||||
_UpdateFocusStateAsync(false, true);
|
||||
_UpdateFocusStateAsync();
|
||||
}
|
||||
|
||||
if (_options.IsTouchSupportEnabled()) {
|
||||
|
|
@ -1251,8 +1285,7 @@ void ScalingWindow::_MoveRenderer() noexcept {
|
|||
|
||||
bool ScalingWindow::_UpdateSrcState(
|
||||
bool& isSrcRepositioning,
|
||||
bool& srcFocusedChanged,
|
||||
bool& srcOwnedWindowFocusedChanged
|
||||
bool& srcFocusedChanged
|
||||
) noexcept {
|
||||
HWND hwndFore = GetForegroundWindow();
|
||||
|
||||
|
|
@ -1269,24 +1302,23 @@ bool ScalingWindow::_UpdateSrcState(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool srcMinimized = false;
|
||||
bool isSrcInvisibleOrMinimized = false;
|
||||
bool srcRectChanged = false;
|
||||
bool srcSizeChanged = false;
|
||||
bool srcMovingChanged = false;
|
||||
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), _isResizingOrMoving,
|
||||
srcFocusedChanged, srcOwnedWindowFocusedChanged,
|
||||
srcMinimized, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
|
||||
isSrcInvisibleOrMinimized, srcFocusedChanged, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (srcMinimized || srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
|
||||
if (isSrcInvisibleOrMinimized || srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
|
||||
// 不要立刻设置 _isSrcSizing,销毁窗口是异步的
|
||||
isSrcRepositioning = true;
|
||||
|
||||
if (srcSizeChanged) {
|
||||
// 源窗口大小改变则清除记忆
|
||||
_lastWindowedRendererWidth = 0;
|
||||
} else if (srcMinimized) {
|
||||
} else if (isSrcInvisibleOrMinimized) {
|
||||
if (_options.IsWindowedMode()) {
|
||||
_lastWindowedRendererWidth = _rendererRect.right - _rendererRect.left;
|
||||
}
|
||||
|
|
@ -1838,77 +1870,149 @@ void ScalingWindow::_UpdateFrameMargins() const noexcept {
|
|||
DwmExtendFrameIntoClientArea(Handle(), &margins);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(
|
||||
bool onSrcOwnedWindowFocusedChanged,
|
||||
bool onShow
|
||||
) const noexcept {
|
||||
if (!onSrcOwnedWindowFocusedChanged && _options.IsWindowedMode()) {
|
||||
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync() const noexcept {
|
||||
if (_options.IsWindowedMode()) {
|
||||
// 根据源窗口状态绘制非客户区,我们必须自己控制非客户区是绘制成焦点状态还是非焦点
|
||||
// 状态,因为缩放窗口实际上永远不会得到焦点。
|
||||
DefWindowProc(Handle(), WM_NCACTIVATE, _srcTracker.IsFocused(), 0);
|
||||
}
|
||||
|
||||
if (_srcTracker.IsOwnedWindowFocused() ||
|
||||
(_options.RealIsKeepOnTop() && !onSrcOwnedWindowFocusedChanged))
|
||||
{
|
||||
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("源窗口已挂起");
|
||||
Destroy();
|
||||
_DelayedStop();
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (_srcTracker.IsOwnedWindowFocused()) {
|
||||
// 前台窗口是源窗口的弹窗则将缩放窗口置于源窗口之前
|
||||
const HWND hwndPrev = GetWindow(_srcTracker.Handle(), GW_HWNDPREV);
|
||||
SetWindowPos(Handle(), hwndPrev,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
} else if (_options.RealIsKeepOnTop() && !onSrcOwnedWindowFocusedChanged) {
|
||||
// 源窗口位于前台时将缩放窗口置顶
|
||||
if (_srcTracker.IsFocused()) {
|
||||
if (!_options.IsDebugMode()) {
|
||||
SetWindowPos(Handle(), HWND_TOPMOST,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
// 这里搞得很复杂,是我反复实验得到的,若要修改应测试下列情形:
|
||||
// 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);
|
||||
}
|
||||
// 非调试模式时再次调用 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!onSrcOwnedWindowFocusedChanged) {
|
||||
if (_srcTracker.IsFocused()) {
|
||||
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 1, (LPARAM)Handle());
|
||||
} else {
|
||||
// lParam 传 1 表示转到后台而非结束缩放
|
||||
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 0, 1);
|
||||
}
|
||||
if (_srcTracker.IsFocused()) {
|
||||
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 1, (LPARAM)Handle());
|
||||
} else {
|
||||
// lParam 传 1 表示转到后台而非结束缩放
|
||||
PostMessage(HWND_BROADCAST, WM_MAGPIE_SCALINGCHANGED, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
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 的处理方式相同。
|
||||
// NoDecoration: Win11 中实现为无标题栏并隐藏边框。
|
||||
// NoNativeFrame: Win11 中实现为无标题栏并隐藏边框。
|
||||
return srcWindowKind == SrcWindowKind::NoBorder ||
|
||||
(srcWindowKind == SrcWindowKind::NoDecoration && Win32Helper::GetOSVersion().IsWin10());
|
||||
(srcWindowKind == SrcWindowKind::NoNativeFrame && Win32Helper::GetOSVersion().IsWin10());
|
||||
}
|
||||
|
||||
void ScalingWindow::_UpdateRendererRect() noexcept {
|
||||
|
|
@ -1927,7 +2031,8 @@ void ScalingWindow::_UpdateRendererRect() noexcept {
|
|||
const bool resized = Win32Helper::GetSizeOfRect(_rendererRect) !=
|
||||
Win32Helper::GetSizeOfRect(oldRendererRect);
|
||||
|
||||
if (!_isMovingDueToSrcMoved && !_srcTracker.IsMoving()) {
|
||||
// 全屏模式缩放时不移动源窗口,因为我们不限制最小尺寸,而且源窗口可能处于最大化或全屏状态
|
||||
if (_options.IsWindowedMode() && !_isMovingDueToSrcMoved && !_srcTracker.IsMoving()) {
|
||||
// 确保源窗口中心点和缩放窗口中心点相同。应先移动源窗口,因为之后需要调整光标位置
|
||||
const RECT& srcRect = _srcTracker.WindowRect();
|
||||
const int offsetX = (_windowRect.left + _windowRect.right - srcRect.left - srcRect.right) / 2;
|
||||
|
|
@ -1964,7 +2069,7 @@ void ScalingWindow::_UpdateRendererRect() noexcept {
|
|||
}
|
||||
|
||||
// OS 有类似的机制,但我们很少能触发,只能自己处理。参考自
|
||||
// https://github.com/tongzx/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L592
|
||||
// https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/movesize.c#L592
|
||||
bool ScalingWindow::_EnsureCaptionVisibleOnScreen() noexcept {
|
||||
struct EnumData {
|
||||
RECT windowRect;
|
||||
|
|
@ -2038,10 +2143,10 @@ void ScalingWindow::_UpdateWindowRectFromWindowPos(const WINDOWPOS& windowPos) n
|
|||
void ScalingWindow::_DelayedStop(bool onSrcHung, bool onSrcRepositioning) const noexcept {
|
||||
if (!onSrcHung) {
|
||||
const HWND hwndSrc = _srcTracker.Handle();
|
||||
if (!(IsWindow(hwndSrc) && Win32Helper::IsWindowHung(hwndSrc))) {
|
||||
if (IsTopmostWindow(Handle()) && !(IsWindow(hwndSrc) && Win32Helper::IsWindowHung(hwndSrc))) {
|
||||
// 提前取消置顶,这样销毁时出现问题不会影响和桌面环境交互
|
||||
SetWindowPos(Handle(), HWND_NOTOPMOST,
|
||||
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
||||
SetWindowPos(Handle(), HWND_NOTOPMOST, 0, 0, 0, 0,
|
||||
SWP_NO_ACTIVATE_MOVE_SIZE | SWP_NOOWNERZORDER);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public:
|
|||
|
||||
void Stop() noexcept;
|
||||
|
||||
void SwitchScalingState(bool isWindowedMode) noexcept;
|
||||
void ToggleScaling(bool isWindowedMode) noexcept;
|
||||
|
||||
void SwitchToolbarState() noexcept;
|
||||
|
||||
|
|
@ -48,7 +48,11 @@ public:
|
|||
return _options;
|
||||
}
|
||||
|
||||
SrcTracker& SrcTracker() noexcept {
|
||||
class SrcTracker& SrcTracker() noexcept {
|
||||
return _srcTracker;
|
||||
}
|
||||
|
||||
const class SrcTracker& SrcTracker() const noexcept {
|
||||
return _srcTracker;
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +60,15 @@ public:
|
|||
return *_renderer;
|
||||
}
|
||||
|
||||
CursorManager& CursorManager() noexcept {
|
||||
const class Renderer& Renderer() const noexcept {
|
||||
return *_renderer;
|
||||
}
|
||||
|
||||
class CursorManager& CursorManager() noexcept {
|
||||
return *_cursorManager;
|
||||
}
|
||||
|
||||
const class CursorManager& CursorManager() const noexcept {
|
||||
return *_cursorManager;
|
||||
}
|
||||
|
||||
|
|
@ -108,8 +120,7 @@ private:
|
|||
|
||||
bool _UpdateSrcState(
|
||||
bool& isSrcRepositioning,
|
||||
bool& srcFocusedChanged,
|
||||
bool& srcOwnedWindowFocusedChanged
|
||||
bool& srcFocusedChanged
|
||||
) noexcept;
|
||||
|
||||
bool _CheckForegroundFor3DGameMode(HWND hwndFore) const noexcept;
|
||||
|
|
@ -140,10 +151,9 @@ private:
|
|||
|
||||
void _UpdateFrameMargins() const noexcept;
|
||||
|
||||
winrt::fire_and_forget _UpdateFocusStateAsync(
|
||||
bool onSrcOwnedWindowFocusedChanged,
|
||||
bool onShow
|
||||
) const noexcept;
|
||||
winrt::fire_and_forget _UpdateFocusStateAsync() const noexcept;
|
||||
|
||||
bool _CalcTopmostState() const noexcept;
|
||||
|
||||
bool _IsBorderless() const noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,32 +11,6 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static bool IsWindowMoving(HWND hWnd) noexcept {
|
||||
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
|
||||
if (GetGUIThreadInfo(GetWindowThreadProcessId(hWnd, nullptr), &guiThreadInfo)) {
|
||||
|
|
@ -47,7 +21,9 @@ static bool IsWindowMoving(HWND hWnd) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept {
|
||||
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept {
|
||||
assert(!isInvisibleOrMinimized);
|
||||
|
||||
_hWnd = hWnd;
|
||||
|
||||
// 这里不检查源窗口是否挂起,将在创建缩放窗口前检查
|
||||
|
|
@ -57,19 +33,34 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
|
|||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
// 不可见和最小化的窗口将等待源窗口状态改变,这里提前返回。注意 showCmd 不能准确
|
||||
// 判断窗口可见性,应使用 IsWindowVisible。
|
||||
if (!IsWindowVisible(_hWnd)) {
|
||||
Logger::Get().Error("不支持缩放隐藏的窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
isInvisibleOrMinimized = true;
|
||||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (!CheckIL(hWnd)) {
|
||||
Logger::Get().Error("不支持缩放 IL 更高的窗口");
|
||||
return ScalingError::LowIntegrityLevel;
|
||||
// 检查 integrity level
|
||||
{
|
||||
DWORD windowIL;
|
||||
if (!Win32Helper::GetWindowIntegrityLevel(hWnd, windowIL) ||
|
||||
windowIL > Win32Helper::GetCurrentProcessIntegrityLevel()) {
|
||||
Logger::Get().Error("不支持缩放 IL 更高的窗口");
|
||||
return ScalingError::LowIntegrityLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// 已在 ScalingService 中阻止
|
||||
|
|
@ -86,10 +77,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
|
|||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
const HWND hwndFore = GetForegroundWindow();
|
||||
_isFocused = hwndFore == hWnd;
|
||||
_UpdateIsOwnedWindowFocused(hwndFore);
|
||||
|
||||
_isFocused = GetForegroundWindow() == hWnd;
|
||||
_isMoving = IsWindowMoving(_hWnd);
|
||||
|
||||
if (!GetWindowRect(hWnd, &_windowRect)) {
|
||||
|
|
@ -110,21 +98,15 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
|
|||
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) {
|
||||
// 凡是没有原生框架的窗口都视为 NoDecoration,这类窗口可能没有 WS_CAPTION 和 WS_THICKFRAME 样式,
|
||||
// 或者禁用了原生窗口框架以自绘标题栏和边框。
|
||||
_windowKind = SrcWindowKind::NoDecoration;
|
||||
// 无原生框架要么是因为无 WS_CAPTION 和 WS_THICKFRAME 样式,要么禁用了原生窗口框架
|
||||
// 以自绘标题栏和边框。
|
||||
_windowKind = SrcWindowKind::NoNativeFrame;
|
||||
hasCustomNonclient = GetWindowStyle(hWnd) & (WS_CAPTION | WS_THICKFRAME);
|
||||
} else {
|
||||
// 最大化窗口的上边框很可能存在非客户区,这使得 NoTitleBar 类型的窗口最大化时会被归类到 Native。
|
||||
// 技术上说这很合理,上边框处的非客户区当然可以视为标题栏,对后续计算也没有影响。
|
||||
|
|
@ -138,7 +120,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
|
|||
// 一个窗口要么有边框,要么没有。只要左右下三边中有一条边没有边框,我们就将它视为无边框窗口。
|
||||
//
|
||||
// FIXME: 有的窗口(如微信)通过 WM_NCCALCSIZE 移除边框,但不使用 DwmExtendFrameIntoClientArea
|
||||
// 还原阴影,这种窗口实际上是 NoDecoration 类型。遗憾的是没有办法获知窗口是否调用了
|
||||
// 还原阴影,这种窗口实际上是 NoNativeFrame 类型。遗憾的是没有办法获知窗口是否调用了
|
||||
// DwmExtendFrameIntoClientArea,因此我们假设所有使用 WM_NCCALCSIZE 移除边框的窗口都有阴影,
|
||||
// 一方面有阴影的情况更多,比如基于 electron 的窗口,另一方面如果假设没有阴影会使得 Win11 中
|
||||
// 不能正确裁剪边框导致黑边,而如果假设有阴影,猜错的后果相对较轻。
|
||||
|
|
@ -155,11 +137,11 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
|
|||
}
|
||||
}
|
||||
|
||||
// * 最大化窗口、NoDecoration 窗口和 Win10 中 NoBorder 窗口不存在边框
|
||||
// * 最大化窗口、NoNativeFrame 窗口和 Win10 中 NoBorder 窗口不存在边框
|
||||
LONG borderThicknessInFrame = 0;
|
||||
const bool isWin11 = Win32Helper::GetOSVersion().IsWin11();
|
||||
if (!_isMaximized &&
|
||||
_windowKind != SrcWindowKind::NoDecoration &&
|
||||
_windowKind != SrcWindowKind::NoNativeFrame &&
|
||||
(_windowKind != SrcWindowKind::NoBorder || isWin11))
|
||||
{
|
||||
// 使用屏幕而非窗口的 DPI 来计算边框宽度
|
||||
|
|
@ -169,7 +151,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
|
|||
borderThicknessInFrame = (LONG)Win32Helper::GetNativeWindowBorderThickness(dpi);
|
||||
}
|
||||
|
||||
return _CalcSrcRect(options, borderThicknessInFrame);
|
||||
return _CalcSrcRect(options, hasCustomNonclient, borderThicknessInFrame);
|
||||
}
|
||||
|
||||
static bool IsPrimaryMouseButtonDown() noexcept {
|
||||
|
|
@ -182,27 +164,36 @@ bool SrcTracker::UpdateState(
|
|||
HWND hwndFore,
|
||||
bool isWindowedMode,
|
||||
bool isResizingOrMoving,
|
||||
bool& isInvisibleOrMinimized,
|
||||
bool& focusedChanged,
|
||||
bool& ownedWindowFocusedChanged,
|
||||
bool& minimized,
|
||||
bool& rectChanged,
|
||||
bool& sizeChanged,
|
||||
bool& movingChanged
|
||||
) noexcept {
|
||||
assert(!focusedChanged && !ownedWindowFocusedChanged && !rectChanged && !sizeChanged && !movingChanged);
|
||||
assert(!isInvisibleOrMinimized && !focusedChanged &&
|
||||
!rectChanged && !sizeChanged && !movingChanged);
|
||||
|
||||
if (!IsWindow(_hWnd)) {
|
||||
Logger::Get().Info("源窗口已销毁");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Win32Helper::IsWindowHung 更准确,但它会向源窗口发送消息,比较耗时。
|
||||
// IsHungAppWindow 的另一个好处是它不如 Win32Helper::IsWindowHung 严
|
||||
// 格,因此即使源窗口挂起一段时间,只要用户不做额外的操作就不会结束缩放,
|
||||
// 直到源窗口被替换为幽灵窗口。
|
||||
if (IsHungAppWindow(_hWnd)) {
|
||||
Logger::Get().Info("源窗口已挂起");
|
||||
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 (_isFocused != (hwndFore == _hWnd)) {
|
||||
|
|
@ -210,29 +201,39 @@ bool SrcTracker::UpdateState(
|
|||
focusedChanged = true;
|
||||
}
|
||||
|
||||
ownedWindowFocusedChanged = _UpdateIsOwnedWindowFocused(hwndFore);
|
||||
|
||||
const bool oldMaximized = _isMaximized;
|
||||
const UINT showCmd = Win32Helper::GetWindowShowCmd(_hWnd);
|
||||
if (showCmd == SW_HIDE) {
|
||||
Logger::Get().Info("源窗口已隐藏");
|
||||
|
||||
WINDOWPLACEMENT wp{ sizeof(wp) };
|
||||
if (!GetWindowPlacement(_hWnd, &wp)) {
|
||||
Logger::Get().Win32Error("GetWindowPlacement 失败");
|
||||
return false;
|
||||
} else if (showCmd == SW_SHOWMINIMIZED) {
|
||||
Logger::Get().Info("源窗口已最小化");
|
||||
_isMaximized = false;
|
||||
minimized = true;
|
||||
} else {
|
||||
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
|
||||
}
|
||||
|
||||
_isMaximized = wp.showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
RECT curWindowRect;
|
||||
if (minimized) {
|
||||
WINDOWPLACEMENT wp{ sizeof(wp) };
|
||||
if (!GetWindowPlacement(_hWnd, &wp)) {
|
||||
Logger::Get().Win32Error("GetWindowPlacement 失败");
|
||||
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 失败");
|
||||
|
|
@ -266,7 +267,7 @@ bool SrcTracker::UpdateState(
|
|||
|
||||
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
|
||||
// 可能会有误判,但幸好后果不太严重。
|
||||
const bool isMoving = !minimized &&
|
||||
const bool isMoving = !isInvisibleOrMinimized &&
|
||||
(IsWindowMoving(_hWnd) || (rectChanged && IsPrimaryMouseButtonDown()));
|
||||
if (_isMoving != isMoving) {
|
||||
movingChanged = true;
|
||||
|
|
@ -435,20 +436,68 @@ static bool GetClientRectOfUWP(HWND hWnd, RECT& rect) noexcept {
|
|||
}
|
||||
|
||||
bool SrcTracker::SetFocus() const noexcept {
|
||||
if (_isOwnedWindowFocused) {
|
||||
return true;
|
||||
// 不要把被禁用的窗口设为前台,检查是否存在弹窗
|
||||
if (IsWindowEnabled(_hWnd)) {
|
||||
return SetForegroundWindow(_hWnd);
|
||||
}
|
||||
|
||||
// 如果源窗口存在弹窗(即被源窗口所有的窗口),应把弹窗设为前台窗口
|
||||
const HWND hwndPopup = GetWindow(_hWnd, GW_ENABLEDPOPUP);
|
||||
return SetForegroundWindow(hwndPopup ? hwndPopup : _hWnd);
|
||||
if (hwndPopup && IsWindowEnabled(hwndPopup)) {
|
||||
return SetForegroundWindow(hwndPopup);
|
||||
}
|
||||
|
||||
// 源窗口被禁用视为成功
|
||||
return true;
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept {
|
||||
if (_windowKind == SrcWindowKind::NoDecoration) {
|
||||
// NoDecoration 类型的窗口不裁剪非客户区。它们要么没有非客户区,要么非客户区不是由
|
||||
// DWM 绘制,前者无需裁剪,后者不能裁剪。
|
||||
_srcRect = _windowRect;
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
const bool isCaptureTitleBar = options.RealIsCaptureTitleBar();
|
||||
|
||||
|
|
@ -456,6 +505,7 @@ ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG border
|
|||
if (_windowKind == SrcWindowKind::NoTitleBar && !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;
|
||||
|
|
@ -507,27 +557,4 @@ ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG border
|
|||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
static bool IsOwnedWindow(HWND hwndOwner, HWND hwndTest) noexcept {
|
||||
HWND hwndCur = hwndTest;
|
||||
while (bool(hwndCur = GetWindowOwner(hwndCur))) {
|
||||
if (hwndCur == hwndOwner) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SrcTracker::_UpdateIsOwnedWindowFocused(HWND hwndFore) noexcept {
|
||||
// 支持两种形式的弹窗
|
||||
// 1. 弹窗被源窗口所有
|
||||
// 2. 弹窗没有被源窗口所有,但弹出时源窗口被禁用
|
||||
bool newValue = !_isFocused && (IsOwnedWindow(_hWnd, hwndFore) || !IsWindowEnabled(_hWnd));
|
||||
if (_isOwnedWindowFocused == newValue) {
|
||||
return false;
|
||||
} else {
|
||||
_isOwnedWindowFocused = newValue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ enum class SrcWindowKind {
|
|||
// 无标题栏,是否有边框取决于 OS 版本(Win10 中不存在边框,Win11 中边框
|
||||
// 被绘制到客户区内),有阴影,在窗口内调整大小,Win11 中有圆角
|
||||
NoBorder,
|
||||
// 无标题栏、边框和阴影,在窗口内调整大小,Win11 中无圆角
|
||||
NoDecoration,
|
||||
// 无标题栏、边框和阴影,在窗口内调整大小,Win11 中无圆角。可能自绘非客户区
|
||||
NoNativeFrame,
|
||||
// 无标题栏,系统边框但上边框较粗,有阴影,左右下三边在窗口外调整大小,Win11 中有圆角
|
||||
OnlyThickFrame
|
||||
};
|
||||
|
|
@ -27,15 +27,14 @@ public:
|
|||
SrcTracker(const SrcTracker&) = delete;
|
||||
SrcTracker(SrcTracker&&) = delete;
|
||||
|
||||
ScalingError Set(HWND hWnd, const ScalingOptions& options) noexcept;
|
||||
ScalingError Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept;
|
||||
|
||||
bool UpdateState(
|
||||
HWND hwndFore,
|
||||
bool isWindowedMode,
|
||||
bool isResizingOrMoving,
|
||||
bool& isInvisibleOrMinimized,
|
||||
bool& focusedChanged,
|
||||
bool& ownedWindowFocusedChanged,
|
||||
bool& minimized,
|
||||
bool& rectChanged,
|
||||
bool& sizeChanged,
|
||||
bool& movingChanged
|
||||
|
|
@ -65,10 +64,6 @@ public:
|
|||
return _isFocused;
|
||||
}
|
||||
|
||||
bool IsOwnedWindowFocused() const noexcept {
|
||||
return _isOwnedWindowFocused;
|
||||
}
|
||||
|
||||
bool SetFocus() const noexcept;
|
||||
|
||||
// IsMaximized 已定义为宏
|
||||
|
|
@ -86,9 +81,11 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
ScalingError _CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept;
|
||||
|
||||
bool _UpdateIsOwnedWindowFocused(HWND hwndFore) noexcept;
|
||||
ScalingError _CalcSrcRect(
|
||||
const ScalingOptions& options,
|
||||
bool hasCustomNonclient,
|
||||
LONG borderThicknessInFrame
|
||||
) noexcept;
|
||||
|
||||
HWND _hWnd = NULL;
|
||||
RECT _windowRect{};
|
||||
|
|
@ -97,7 +94,6 @@ private:
|
|||
SrcWindowKind _windowKind = SrcWindowKind::Native;
|
||||
|
||||
bool _isFocused = false;
|
||||
bool _isOwnedWindowFocused = false;
|
||||
bool _isMaximized = false;
|
||||
bool _isMoving = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "pch.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "StrHelper.h"
|
||||
#include <dcomp.h>
|
||||
#include <dwmapi.h>
|
||||
#include <io.h>
|
||||
#pragma push_macro("ShellExecute")
|
||||
|
|
@ -290,8 +291,9 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
|
|||
}
|
||||
|
||||
bool Win32Helper::IsWindowHung(HWND hWnd) noexcept {
|
||||
return 0 == SendMessageTimeout(hWnd, WM_NULL, 0, 0,
|
||||
SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT, 500, nullptr);
|
||||
// 保险起见不使用 SMTO_ABORTIFHUNG。我不知道 OS 怎么判断线程是否处于无响应
|
||||
// 状态,考虑到 IsHungAppWindow 有误报的情况 (GH#1244),最好不要依赖。
|
||||
return 0 == SendMessageTimeout(hWnd, WM_NULL, 0, 0, SMTO_ERRORONEXIT, 500, nullptr);
|
||||
}
|
||||
|
||||
bool Win32Helper::ReadFile(const wchar_t* fileName, std::vector<uint8_t>& result) noexcept {
|
||||
|
|
@ -696,6 +698,30 @@ 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);
|
||||
|
|
@ -855,4 +881,56 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ enum class MultiMonitorUsage {
|
|||
enum class CursorInterpolationMode {
|
||||
NearestNeighbor,
|
||||
Bilinear,
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct Cropping {
|
||||
|
|
@ -41,27 +42,17 @@ struct GraphicsCardId {
|
|||
uint32_t deviceId = 0;
|
||||
};
|
||||
|
||||
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 FP16Disabled = 1 << 19;
|
||||
static constexpr uint32_t BenchmarkMode = 1 << 20;
|
||||
static constexpr uint32_t DeveloperMode = 1 << 21;
|
||||
static constexpr uint32_t KeepOnTop = 1 << 22;
|
||||
enum class DestAlignment {
|
||||
LeftTop,
|
||||
Top,
|
||||
RightTop,
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
LeftBottom,
|
||||
Bottom,
|
||||
RightBottom,
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum class ScalingType {
|
||||
|
|
@ -155,12 +146,36 @@ enum class ScalingError {
|
|||
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(IsFP16Disabled, ScalingFlags::FP16Disabled, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsTopmostDisabled, ScalingFlags::DisableTopmost, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsFP16Disabled, ScalingFlags::DisableFP16, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsEffectCacheDisabled, ScalingFlags::DisableEffectCache, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsFontCacheDisabled, ScalingFlags::DisableFontCache, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsSaveEffectSources, ScalingFlags::SaveEffectSources, flags)
|
||||
|
|
@ -169,7 +184,6 @@ struct ScalingOptions {
|
|||
DEFINE_FLAG_ACCESSOR(IsInlineParams, ScalingFlags::InlineParams, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::TouchSupportEnabled, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsAllowScalingMaximized, ScalingFlags::AllowScalingMaximized, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsKeepOnTop, ScalingFlags::KeepOnTop, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsSimulateExclusiveFullscreen, ScalingFlags::SimulateExclusiveFullscreen, flags)
|
||||
DEFINE_FLAG_ACCESSOR(Is3DGameMode, ScalingFlags::Is3DGameMode, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsCaptureTitleBar, ScalingFlags::CaptureTitleBar, flags)
|
||||
|
|
@ -185,7 +199,9 @@ 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;
|
||||
|
|
@ -211,10 +227,6 @@ struct ScalingOptions {
|
|||
return IsAllowScalingMaximized() && !IsWindowedMode();
|
||||
}
|
||||
|
||||
bool RealIsKeepOnTop() const noexcept {
|
||||
return IsKeepOnTop() && !IsWindowedMode();
|
||||
}
|
||||
|
||||
bool RealIsSimulateExclusiveFullscreen() const noexcept {
|
||||
return IsSimulateExclusiveFullscreen() && !IsWindowedMode();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,25 +3,31 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
enum class ScalingState {
|
||||
Idle,
|
||||
Scaling,
|
||||
Waiting
|
||||
};
|
||||
|
||||
class ScalingRuntime {
|
||||
public:
|
||||
ScalingRuntime();
|
||||
~ScalingRuntime();
|
||||
|
||||
bool Start(HWND hwndSrc, struct ScalingOptions&& options);
|
||||
bool Start(HWND hwndSrc, struct ScalingOptions&& options, bool force);
|
||||
|
||||
void SwitchScalingState(bool isWindowedMode);
|
||||
void ToggleScaling(bool isWindowedMode);
|
||||
|
||||
void SwitchToolbarState();
|
||||
|
||||
void Stop();
|
||||
|
||||
bool IsScaling() const noexcept {
|
||||
return _isScaling.load(std::memory_order_relaxed);
|
||||
ScalingState State() const noexcept {
|
||||
return _state.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// 调用者应处理线程同步
|
||||
MultithreadEvent<bool> IsScalingChanged;
|
||||
MultithreadEvent<ScalingState> StateChanged;
|
||||
|
||||
private:
|
||||
void _ScalingThreadProc() noexcept;
|
||||
|
|
@ -29,7 +35,7 @@ private:
|
|||
// 确保 _dispatcher 完成初始化
|
||||
const winrt::DispatcherQueue& _Dispatcher() noexcept;
|
||||
|
||||
void _IsScaling(bool value);
|
||||
void _State(ScalingState value);
|
||||
|
||||
std::thread _scalingThread;
|
||||
|
||||
|
|
@ -38,7 +44,7 @@ private:
|
|||
// 只能在主线程访问,省下检查 _dispatcherInitialized 的开销
|
||||
bool _dispatcherInitializedCache = false;
|
||||
|
||||
std::atomic<bool> _isScaling = false;
|
||||
std::atomic<ScalingState> _state = ScalingState::Idle;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,10 @@ 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 {
|
||||
|
|
@ -133,7 +137,7 @@ struct Win32Helper {
|
|||
}
|
||||
|
||||
Variant(VARIANT&& varSrc) noexcept {
|
||||
std::memcpy(this, &varSrc, sizeof(varSrc));
|
||||
std::memcpy((VARIANT*)this, &varSrc, sizeof(varSrc));
|
||||
varSrc.vt = VT_EMPTY;
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +170,7 @@ struct Win32Helper {
|
|||
}
|
||||
|
||||
Variant& operator=(VARIANT&& other) noexcept {
|
||||
std::memcpy(this, &other, sizeof(other));
|
||||
std::memcpy((VARIANT*)this, &other, sizeof(other));
|
||||
other.vt = VT_EMPTY;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -210,6 +214,9 @@ struct Win32Helper {
|
|||
// 先转成 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.250325.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.260126.7" targetFramework="native" />
|
||||
</packages>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
SamplerState sam : register(s0);
|
||||
Texture2D tex : register(t0);
|
||||
|
||||
float4 main(float2 coord : TEXCOORD, float4 color : COLOR) : SV_Target {
|
||||
float4 main(noperspective float2 coord : TEXCOORD, noperspective 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(
|
||||
float4 pos : SV_POSITION,
|
||||
float2 pos : POSITION,
|
||||
float2 coord : TEXCOORD,
|
||||
float4 color : COLOR,
|
||||
out float2 outCoord : TEXCOORD,
|
||||
out float4 outColor : COLOR,
|
||||
out float4 outPos : SV_POSITION
|
||||
out noperspective float2 outCoord : TEXCOORD,
|
||||
out noperspective float4 outColor : COLOR,
|
||||
out noperspective float4 outPos : SV_POSITION
|
||||
) {
|
||||
outPos = mul(projectionMatrix, float4(pos.xy, 0.f, 1.f));
|
||||
outPos = mul(projectionMatrix, float4(pos, 0, 1));
|
||||
outCoord = coord;
|
||||
outColor = color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Texture2D cursorTex : register(t1);
|
|||
|
||||
SamplerState pointSampler : register(s0);
|
||||
|
||||
float4 main(float2 coord : TEXCOORD) : SV_TARGET {
|
||||
float4 main(noperspective float2 coord : TEXCOORD) : SV_TARGET {
|
||||
float4 mask = cursorTex.Sample(pointSampler, coord);
|
||||
|
||||
if (mask.a < 0.5f) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Texture2D<float2> cursorTex : register(t1);
|
|||
|
||||
SamplerState pointSampler : register(s0);
|
||||
|
||||
float4 main(float2 coord : TEXCOORD) : SV_TARGET {
|
||||
float4 main(noperspective float2 coord : TEXCOORD) : SV_TARGET {
|
||||
float2 mask = cursorTex.Sample(pointSampler, coord);
|
||||
|
||||
if (mask.x > 0.5f) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Texture2D tex : register(t0);
|
||||
SamplerState sam : register(s0);
|
||||
|
||||
float4 main(float2 coord : TEXCOORD) : SV_Target {
|
||||
float4 main(noperspective float2 coord : TEXCOORD) : SV_Target {
|
||||
return tex.Sample(sam, coord);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
void main(
|
||||
float4 pos : SV_POSITION,
|
||||
float2 pos : POSITION,
|
||||
float2 coord : TEXCOORD,
|
||||
out float2 outCoord : TEXCOORD,
|
||||
out float4 outPos : SV_POSITION
|
||||
out noperspective float2 outCoord : TEXCOORD,
|
||||
out noperspective float4 outPos : SV_POSITION
|
||||
) {
|
||||
outPos = pos;
|
||||
outPos = float4(pos, 0, 1);
|
||||
outCoord = coord;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,16 +58,15 @@ hstring AboutViewModel::Version() const noexcept {
|
|||
ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID);
|
||||
return hstring(StrHelper::Concat(
|
||||
resourceLoader.GetString(L"About_Version_Version"),
|
||||
#ifdef MP_VERSION_TAG
|
||||
L" ",
|
||||
&WIDEN(STRING(MP_VERSION_TAG))[1],
|
||||
#ifdef MP_VERSION_STRING
|
||||
L" " WIDEN_STRINGIFY(MP_VERSION_STRING),
|
||||
#else
|
||||
L" dev",
|
||||
#endif
|
||||
#ifdef MP_COMMIT_ID
|
||||
L" | ",
|
||||
resourceLoader.GetString(L"About_Version_CommitId"),
|
||||
L" " WIDEN(STRING(MP_COMMIT_ID)),
|
||||
L" " WIDEN_STRINGIFY(MP_COMMIT_ID),
|
||||
#endif
|
||||
L" | "
|
||||
#ifdef _M_X64
|
||||
|
|
@ -103,7 +102,7 @@ void AboutViewModel::IsCheckForPreviewUpdates(bool value) {
|
|||
|
||||
bool AboutViewModel::IsCheckForUpdatesButtonEnabled() const noexcept {
|
||||
// 只有发布版本能检查更新
|
||||
#ifdef MP_VERSION_TAG
|
||||
#ifdef MP_VERSION_STRING
|
||||
return !IsCheckingForUpdates() && !IsDownloadingOrLater();
|
||||
#else
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ bool AdaptersService::_GatherAdapterInfos(
|
|||
return info.idx == std::numeric_limits<uint32_t>::max();
|
||||
});
|
||||
|
||||
App::Get().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, adapterInfos(std::move(adapterInfos))]() {
|
||||
App::Get().Dispatcher().TryEnqueue([this, adapterInfos(std::move(adapterInfos))]() {
|
||||
_adapterInfos = std::move(adapterInfos);
|
||||
_UpdateProfiles();
|
||||
AdaptersChanged.Invoke();
|
||||
|
|
|
|||
|
|
@ -127,15 +127,17 @@ bool App::Initialize(const wchar_t* arguments) {
|
|||
// 初始化 XAML 框架。退出时也不要关闭,如果正在播放动画会崩溃。文档中的清空消息队列的做法无用。
|
||||
_windowsXamlManager = Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
|
||||
if (CoreWindow coreWindow = CoreWindow::GetForCurrentThread()) {
|
||||
// Win10 中隐藏 DesktopWindowXamlSource 窗口
|
||||
if (Win32Helper::GetOSVersion().IsWin10()) {
|
||||
// Win10 中 CoreDispatcher.RunAsync 存在内存泄露,因此我们始终使用 DispatcherQueue。
|
||||
// 初始化 WindowsXamlManager 时已经创建 DispatcherQueue。
|
||||
_dispatcher = winrt::DispatcherQueue::GetForCurrentThread();
|
||||
|
||||
// Win10 中隐藏 DesktopWindowXamlSource 窗口
|
||||
if (Win32Helper::GetOSVersion().IsWin10()) {
|
||||
if (CoreWindow coreWindow = CoreWindow::GetForCurrentThread()) {
|
||||
HWND hwndDWXS;
|
||||
coreWindow.try_as<ICoreWindowInterop>()->get_WindowHandle(&hwndDWXS);
|
||||
ShowWindow(hwndDWXS, SW_HIDE);
|
||||
}
|
||||
|
||||
_dispatcher = coreWindow.Dispatcher();
|
||||
}
|
||||
|
||||
LocalizationService::Get().EarlyInitialize();
|
||||
|
|
@ -193,7 +195,7 @@ bool App::Initialize(const wchar_t* arguments) {
|
|||
// 再检查显卡的功能级别。
|
||||
_mainWindow->Content()->Loaded([](const auto&, const auto&) {
|
||||
// 低优先级回调确保在初始化完毕后执行
|
||||
App::Get().Dispatcher().RunAsync(CoreDispatcherPriority::Low, []() {
|
||||
App::Get().Dispatcher().TryEnqueue(DispatcherQueuePriority::Low, []() {
|
||||
AdaptersService::Get().StartMonitor();
|
||||
});
|
||||
});
|
||||
|
|
@ -355,9 +357,7 @@ void App::_UpdateColorValuesChangedRevoker() {
|
|||
_colorValuesChangedRevoker = _uiSettings.ColorValuesChanged(
|
||||
auto_revoke,
|
||||
[this](const auto&, const auto&) {
|
||||
_dispatcher.RunAsync(CoreDispatcherPriority::Normal, [this] {
|
||||
_UpdateTheme();
|
||||
});
|
||||
_dispatcher.TryEnqueue([this] { _UpdateTheme(); });
|
||||
}
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public:
|
|||
|
||||
int Run();
|
||||
|
||||
const CoreDispatcher& Dispatcher() const noexcept {
|
||||
const DispatcherQueue& Dispatcher() const noexcept {
|
||||
return _dispatcher;
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ private:
|
|||
|
||||
std::unique_ptr<::Magpie::MainWindow> _mainWindow;
|
||||
|
||||
CoreDispatcher _dispatcher{ nullptr };
|
||||
DispatcherQueue _dispatcher{ nullptr };
|
||||
|
||||
::Magpie::Event<::Magpie::AppTheme>::EventRevoker _themeChangedRevoker;
|
||||
Windows::UI::ViewManagement::UISettings _uiSettings;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ using namespace winrt::Magpie;
|
|||
namespace Magpie {
|
||||
|
||||
// 如果配置文件和已发布的正式版本不再兼容,应提高此版本号
|
||||
static constexpr uint32_t CONFIG_VERSION = 3;
|
||||
static constexpr uint32_t CONFIG_VERSION = 4;
|
||||
|
||||
_AppSettingsData::_AppSettingsData() {}
|
||||
|
||||
|
|
@ -121,6 +121,10 @@ static void WriteProfile(rapidjson::PrettyWriter<rapidjson::StringBuffer>& write
|
|||
writer.Double(profile.customCursorScaling);
|
||||
writer.Key("cursorInterpolationMode");
|
||||
writer.Uint((uint32_t)profile.cursorInterpolationMode);
|
||||
writer.Key("autoHideCursorEnabled");
|
||||
writer.Bool(profile.isAutoHideCursorEnabled);
|
||||
writer.Key("autoHideCursorDelay");
|
||||
writer.Double(profile.autoHideCursorDelay);
|
||||
|
||||
writer.Key("croppingEnabled");
|
||||
writer.Bool(profile.isCroppingEnabled);
|
||||
|
|
@ -136,6 +140,9 @@ static void WriteProfile(rapidjson::PrettyWriter<rapidjson::StringBuffer>& write
|
|||
writer.Double(profile.cropping.Bottom);
|
||||
writer.EndObject();
|
||||
|
||||
writer.Key("destAlignment");
|
||||
writer.Uint((uint32_t)profile.destAlignment);
|
||||
|
||||
writer.EndObject();
|
||||
}
|
||||
|
||||
|
|
@ -508,9 +515,20 @@ void AppSettings::_UpdateWindowPlacement() noexcept {
|
|||
return;
|
||||
}
|
||||
|
||||
// rcNormalPosition 使用工作区坐标,应转换为屏幕坐标。
|
||||
// 见 https://github.com/Blinue/nt5src/blob/daad8a087a4e75422ec96b7911f1df4669989611/Source/XPSP1/NT/windows/core/ntuser/kernel/winmgr.c#L752
|
||||
HMONITOR hMon = MonitorFromWindow(hwndMain, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO mi{ sizeof(mi) };
|
||||
if (!GetMonitorInfo(hMon, &mi)) {
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
return;
|
||||
}
|
||||
|
||||
const LONG workingAreaOffsetX = mi.rcWork.left - mi.rcMonitor.left;
|
||||
const LONG workingAreaOffsetY = mi.rcWork.top - mi.rcMonitor.top;
|
||||
_mainWindowCenter = {
|
||||
(wp.rcNormalPosition.left + wp.rcNormalPosition.right) / 2.0f,
|
||||
(wp.rcNormalPosition.top + wp.rcNormalPosition.bottom) / 2.0f
|
||||
(wp.rcNormalPosition.left + wp.rcNormalPosition.right) / 2.0f + workingAreaOffsetX,
|
||||
(wp.rcNormalPosition.top + wp.rcNormalPosition.bottom) / 2.0f + workingAreaOffsetY,
|
||||
};
|
||||
|
||||
const float dpiFactor = GetDpiForWindow(hwndMain) / float(USER_DEFAULT_SCREEN_DPI);
|
||||
|
|
@ -575,6 +593,8 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
|
|||
writer.Bool(data._isDebugMode);
|
||||
writer.Key("benchmarkMode");
|
||||
writer.Bool(data._isBenchmarkMode);
|
||||
writer.Key("disableTopmost");
|
||||
writer.Bool(data._isTopmostDisabled);
|
||||
writer.Key("disableEffectCache");
|
||||
writer.Bool(data._isEffectCacheDisabled);
|
||||
writer.Key("disableFontCache");
|
||||
|
|
@ -607,8 +627,6 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
|
|||
writer.Double(data._minFrameRate);
|
||||
writer.Key("disableFP16");
|
||||
writer.Bool(data._isFP16Disabled);
|
||||
writer.Key("keepOnTop");
|
||||
writer.Bool(data._isKeepOnTop);
|
||||
|
||||
ScalingModesService::Get().Export(writer);
|
||||
|
||||
|
|
@ -774,6 +792,7 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
|
|||
JsonHelper::ReadBool(root, "developerMode", _isDeveloperMode);
|
||||
JsonHelper::ReadBool(root, "debugMode", _isDebugMode);
|
||||
JsonHelper::ReadBool(root, "benchmarkMode", _isBenchmarkMode);
|
||||
JsonHelper::ReadBool(root, "disableTopmost", _isTopmostDisabled);
|
||||
JsonHelper::ReadBool(root, "disableEffectCache", _isEffectCacheDisabled);
|
||||
JsonHelper::ReadBool(root, "disableFontCache", _isFontCacheDisabled);
|
||||
JsonHelper::ReadBool(root, "saveEffectSources", _isSaveEffectSources);
|
||||
|
|
@ -809,7 +828,6 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
|
|||
JsonHelper::ReadBool(root, "enableStatisticsForDynamicDetection", _isStatisticsForDynamicDetectionEnabled);
|
||||
JsonHelper::ReadFloat(root, "minFrameRate", _minFrameRate);
|
||||
JsonHelper::ReadBool(root, "disableFP16", _isFP16Disabled);
|
||||
JsonHelper::ReadBool(root, "keepOnTop", _isKeepOnTop);
|
||||
|
||||
[[maybe_unused]] bool result = ScalingModesService::Get().Import(root, true);
|
||||
assert(result);
|
||||
|
|
@ -1052,7 +1070,9 @@ bool AppSettings::_LoadProfile(
|
|||
|
||||
JsonHelper::ReadBool(profileObj, "frameRateLimiterEnabled", profile.isFrameRateLimiterEnabled);
|
||||
JsonHelper::ReadFloat(profileObj, "maxFrameRate", profile.maxFrameRate);
|
||||
if (profile.maxFrameRate < 10.0f || profile.maxFrameRate > 1000.0f) {
|
||||
if (profile.maxFrameRate <= 10.0f - FLOAT_EPSILON<float> ||
|
||||
profile.maxFrameRate >= 1000.0f + FLOAT_EPSILON<float>)
|
||||
{
|
||||
profile.maxFrameRate = 60.0f;
|
||||
}
|
||||
|
||||
|
|
@ -1081,12 +1101,20 @@ bool AppSettings::_LoadProfile(
|
|||
{
|
||||
uint32_t cursorInterpolationMode = (uint32_t)CursorInterpolationMode::NearestNeighbor;
|
||||
JsonHelper::ReadUInt(profileObj, "cursorInterpolationMode", cursorInterpolationMode);
|
||||
if (cursorInterpolationMode > 1) {
|
||||
if (cursorInterpolationMode >= (uint32_t)CursorInterpolationMode::COUNT) {
|
||||
cursorInterpolationMode = (uint32_t)CursorInterpolationMode::NearestNeighbor;
|
||||
}
|
||||
profile.cursorInterpolationMode = (CursorInterpolationMode)cursorInterpolationMode;
|
||||
}
|
||||
|
||||
JsonHelper::ReadBool(profileObj, "autoHideCursorEnabled", profile.isAutoHideCursorEnabled);
|
||||
JsonHelper::ReadFloat(profileObj, "autoHideCursorDelay", profile.autoHideCursorDelay);
|
||||
if (profile.autoHideCursorDelay <= 0.1f - FLOAT_EPSILON<float> ||
|
||||
profile.autoHideCursorDelay >= 5.0f + FLOAT_EPSILON<float>)
|
||||
{
|
||||
profile.autoHideCursorDelay = 3.0f;
|
||||
}
|
||||
|
||||
JsonHelper::ReadBool(profileObj, "croppingEnabled", profile.isCroppingEnabled);
|
||||
|
||||
auto croppingNode = profileObj.FindMember("cropping");
|
||||
|
|
@ -1106,6 +1134,15 @@ bool AppSettings::_LoadProfile(
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t destAlignment = (uint32_t)DestAlignment::Center;
|
||||
JsonHelper::ReadUInt(profileObj, "destAlignment", destAlignment);
|
||||
if (destAlignment >= (uint32_t)DestAlignment::COUNT) {
|
||||
destAlignment = (uint32_t)DestAlignment::Center;
|
||||
}
|
||||
profile.destAlignment = (DestAlignment)destAlignment;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ struct _AppSettingsData {
|
|||
bool _isDeveloperMode = false;
|
||||
bool _isDebugMode = false;
|
||||
bool _isBenchmarkMode = false;
|
||||
bool _isTopmostDisabled = false;
|
||||
bool _isEffectCacheDisabled = false;
|
||||
bool _isFontCacheDisabled = false;
|
||||
bool _isSaveEffectSources = false;
|
||||
|
|
@ -75,7 +76,6 @@ struct _AppSettingsData {
|
|||
bool _isCheckForPreviewUpdates = false;
|
||||
bool _isStatisticsForDynamicDetectionEnabled = false;
|
||||
bool _isFP16Disabled = false;
|
||||
bool _isKeepOnTop = false;
|
||||
};
|
||||
|
||||
class AppSettings : private _AppSettingsData {
|
||||
|
|
@ -162,6 +162,15 @@ public:
|
|||
SaveAsync();
|
||||
}
|
||||
|
||||
bool IsTopmostDisabled() const noexcept {
|
||||
return _isTopmostDisabled;
|
||||
}
|
||||
|
||||
void IsTopmostDisabled(bool value) noexcept {
|
||||
_isTopmostDisabled = value;
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
bool IsEffectCacheDisabled() const noexcept {
|
||||
return _isEffectCacheDisabled;
|
||||
}
|
||||
|
|
@ -207,15 +216,6 @@ public:
|
|||
SaveAsync();
|
||||
}
|
||||
|
||||
bool IsKeepOnTop() const noexcept {
|
||||
return _isKeepOnTop;
|
||||
}
|
||||
|
||||
void IsKeepOnTop(bool value) noexcept {
|
||||
_isKeepOnTop = value;
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
bool IsAllowScalingMaximized() const noexcept {
|
||||
return _isAllowScalingMaximized;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,8 +113,7 @@ fire_and_forget CandidateWindowItem::_ResolveWindow(bool resolveIcon, bool resol
|
|||
co_return;
|
||||
}
|
||||
|
||||
App::Get().Dispatcher().RunAsync(
|
||||
CoreDispatcherPriority::Normal,
|
||||
App::Get().Dispatcher().TryEnqueue(
|
||||
[this, defaultProfileName(std::move(defaultProfileName)), aumid(reader.AUMID())]() {
|
||||
if (!defaultProfileName.empty()) {
|
||||
_defaultProfileName = defaultProfileName;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@
|
|||
</local:ShortcutControl.Action>
|
||||
</local:ShortcutControl>
|
||||
</local:SettingsCard>
|
||||
<local:SettingsExpander x:Uid="Home_Activation_Timer">
|
||||
<local:SettingsExpander x:Uid="Home_Activation_Timer"
|
||||
Description="{x:Bind ViewModel.TimerDescription, Mode=OneWay}"
|
||||
IsWrapEnabled="True">
|
||||
<local:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsExpander.HeaderIcon>
|
||||
|
|
@ -80,19 +82,48 @@
|
|||
VerticalAlignment="Center"
|
||||
Text="{x:Bind ViewModel.TimerLabelText, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Button Click="{x:Bind ViewModel.ToggleTimer}"
|
||||
Content="{x:Bind ViewModel.TimerButtonText, Mode=OneWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsNotRunning, Mode=OneWay}" />
|
||||
<Button Click="{x:Bind ViewModel.ToggleTimerFullscreen}"
|
||||
IsEnabled="{x:Bind ViewModel.IsNotRunning, Mode=OneWay}">
|
||||
<local:SimpleStackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TimerButtonText(x:False), Mode=OneWay}" />
|
||||
</local:SimpleStackPanel>
|
||||
</Button>
|
||||
<Button Click="{x:Bind ViewModel.ToggleTimerWindowed}"
|
||||
IsEnabled="{x:Bind ViewModel.IsNotRunning, Mode=OneWay}">
|
||||
<local:SimpleStackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<FontIcon FontSize="{StaticResource StandardIconSize}"
|
||||
Glyph="" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TimerButtonText(x:True), Mode=OneWay}" />
|
||||
</local:SimpleStackPanel>
|
||||
</Button>
|
||||
</local:SimpleStackPanel>
|
||||
</local:SettingsExpander.Content>
|
||||
<local:SettingsExpander.Items>
|
||||
<local:SettingsCard x:Uid="Home_Activation_Timer_Delay"
|
||||
IsWrapEnabled="True">
|
||||
<Slider Loaded="TimerSlider_Loaded"
|
||||
Maximum="5"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
Value="{x:Bind ViewModel.Delay, Mode=TwoWay}" />
|
||||
<Grid MinWidth="{StaticResource SettingsCardContentMinWidth}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Slider Grid.Column="0"
|
||||
MinWidth="0"
|
||||
IsThumbToolTipEnabled="False"
|
||||
Loaded="TimerSlider_Loaded"
|
||||
Maximum="5"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
Value="{x:Bind ViewModel.Delay, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Width="20"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind ViewModel.DelayText, Mode=OneWay}"
|
||||
TextAlignment="Right" />
|
||||
</Grid>
|
||||
</local:SettingsCard>
|
||||
</local:SettingsExpander.Items>
|
||||
</local:SettingsExpander>
|
||||
|
|
@ -193,13 +224,6 @@
|
|||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsAllowScalingMaximized, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="Home_Advanced_KeepOnTop">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsKeepOnTop, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="Home_Advanced_SimulateExclusiveFullscreen">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
|
|
@ -272,6 +296,10 @@
|
|||
<CheckBox x:Uid="Home_Advanced_DeveloperOptions_BenchmarkMode"
|
||||
IsChecked="{x:Bind ViewModel.IsBenchmarkMode, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="Home_Advanced_DeveloperOptions_DisableTopmost"
|
||||
IsChecked="{x:Bind ViewModel.IsTopmostDisabled, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard ContentAlignment="Left">
|
||||
<CheckBox x:Uid="Home_Advanced_DeveloperOptions_DisableEffectCache"
|
||||
IsChecked="{x:Bind ViewModel.IsEffectCacheDisabled, Mode=TwoWay}" />
|
||||
|
|
|
|||
|
|
@ -42,6 +42,16 @@ HomeViewModel::HomeViewModel() {
|
|||
);
|
||||
}
|
||||
|
||||
hstring HomeViewModel::TimerDescription() const noexcept {
|
||||
ResourceLoader resourceLoader =
|
||||
ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID);
|
||||
hstring fmtStr = resourceLoader.GetString(L"Home_Activation_Timer_Description");
|
||||
return hstring(fmt::format(
|
||||
fmt::runtime(std::wstring_view(fmtStr)),
|
||||
AppSettings::Get().CountdownSeconds()
|
||||
));
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsTimerOn() const noexcept {
|
||||
return ScalingService::Get().IsTimerOn();
|
||||
}
|
||||
|
|
@ -56,34 +66,28 @@ hstring HomeViewModel::TimerLabelText() const noexcept {
|
|||
return to_hstring((int)std::ceil(ScalingService.SecondsLeft()));
|
||||
}
|
||||
|
||||
hstring HomeViewModel::TimerButtonText() const noexcept {
|
||||
ScalingService& ScalingService = ScalingService::Get();
|
||||
ResourceLoader resourceLoader =
|
||||
ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID);
|
||||
if (ScalingService.IsTimerOn()) {
|
||||
return resourceLoader.GetString(L"Home_Activation_Timer_Cancel");
|
||||
} else {
|
||||
hstring fmtStr = resourceLoader.GetString(L"Home_Activation_Timer_ButtonText");
|
||||
return hstring(fmt::format(
|
||||
fmt::runtime(std::wstring_view(fmtStr)),
|
||||
AppSettings::Get().CountdownSeconds()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsNotRunning() const noexcept {
|
||||
return !ScalingService::Get().IsScaling();
|
||||
}
|
||||
|
||||
void HomeViewModel::ToggleTimer() const noexcept {
|
||||
ScalingService& scalingService = ScalingService::Get();
|
||||
if (scalingService.IsTimerOn()) {
|
||||
scalingService.StopTimer();
|
||||
hstring HomeViewModel::TimerButtonText(bool windowedMode) const noexcept {
|
||||
ResourceLoader resourceLoader =
|
||||
ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID);
|
||||
if (ScalingService::Get().IsTimerOn(windowedMode)) {
|
||||
return resourceLoader.GetString(L"Home_Activation_Timer_Cancel");
|
||||
} else {
|
||||
scalingService.StartTimer();
|
||||
return resourceLoader.GetString(L"Home_Activation_Timer_Start");
|
||||
}
|
||||
}
|
||||
|
||||
void HomeViewModel::ToggleTimerFullscreen() const noexcept {
|
||||
_ToggleTimer(false);
|
||||
}
|
||||
|
||||
void HomeViewModel::ToggleTimerWindowed() const noexcept {
|
||||
_ToggleTimer(true);
|
||||
}
|
||||
|
||||
uint32_t HomeViewModel::Delay() const noexcept {
|
||||
return AppSettings::Get().CountdownSeconds();
|
||||
}
|
||||
|
|
@ -91,10 +95,15 @@ uint32_t HomeViewModel::Delay() const noexcept {
|
|||
void HomeViewModel::Delay(uint32_t value) {
|
||||
AppSettings::Get().CountdownSeconds(value);
|
||||
RaisePropertyChanged(L"Delay");
|
||||
RaisePropertyChanged(L"TimerButtonText");
|
||||
RaisePropertyChanged(L"DelayText");
|
||||
RaisePropertyChanged(L"TimerDescription");
|
||||
}
|
||||
|
||||
inline void HomeViewModel::ShowUpdateCard(bool value) noexcept {
|
||||
hstring HomeViewModel::DelayText() const noexcept {
|
||||
return App::Get().DoubleFormatter().FormatDouble(Delay());
|
||||
}
|
||||
|
||||
void HomeViewModel::ShowUpdateCard(bool value) noexcept {
|
||||
_showUpdateCard = value;
|
||||
if (!value) {
|
||||
UpdateService::Get().IsShowOnHomePage(false);
|
||||
|
|
@ -328,21 +337,6 @@ void HomeViewModel::IsAllowScalingMaximized(bool value) {
|
|||
}
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsKeepOnTop() const noexcept {
|
||||
return AppSettings::Get().IsKeepOnTop();
|
||||
}
|
||||
|
||||
void HomeViewModel::IsKeepOnTop(bool value) {
|
||||
AppSettings& settings = AppSettings::Get();
|
||||
|
||||
if (settings.IsKeepOnTop() == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.IsKeepOnTop(value);
|
||||
RaisePropertyChanged(L"IsKeepOnTop");
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsSimulateExclusiveFullscreen() const noexcept {
|
||||
return AppSettings::Get().IsSimulateExclusiveFullscreen();
|
||||
}
|
||||
|
|
@ -460,6 +454,21 @@ void HomeViewModel::LocateUpdaterLogs() noexcept {
|
|||
LocateTempLogs(CommonSharedConstants::UPDATER_LOG_NAME);
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsDebugMode() const noexcept {
|
||||
return AppSettings::Get().IsDebugMode();
|
||||
}
|
||||
|
||||
void HomeViewModel::IsDebugMode(bool value) {
|
||||
AppSettings& settings = AppSettings::Get();
|
||||
|
||||
if (settings.IsDebugMode() == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.IsDebugMode(value);
|
||||
RaisePropertyChanged(L"IsDebugMode");
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsBenchmarkMode() const noexcept {
|
||||
return AppSettings::Get().IsBenchmarkMode();
|
||||
}
|
||||
|
|
@ -475,19 +484,19 @@ void HomeViewModel::IsBenchmarkMode(bool value) {
|
|||
RaisePropertyChanged(L"IsBenchmarkMode");
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsDebugMode() const noexcept {
|
||||
return AppSettings::Get().IsDebugMode();
|
||||
bool HomeViewModel::IsTopmostDisabled() const noexcept {
|
||||
return AppSettings::Get().IsTopmostDisabled();
|
||||
}
|
||||
|
||||
void HomeViewModel::IsDebugMode(bool value) {
|
||||
void HomeViewModel::IsTopmostDisabled(bool value) {
|
||||
AppSettings& settings = AppSettings::Get();
|
||||
|
||||
if (settings.IsDebugMode() == value) {
|
||||
if (settings.IsTopmostDisabled() == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings.IsDebugMode(value);
|
||||
RaisePropertyChanged(L"IsDebugMode");
|
||||
settings.IsTopmostDisabled(value);
|
||||
RaisePropertyChanged(L"IsTopmostDisabled");
|
||||
}
|
||||
|
||||
bool HomeViewModel::IsEffectCacheDisabled() const noexcept {
|
||||
|
|
@ -611,7 +620,7 @@ void HomeViewModel::IsStatisticsForDynamicDetectionEnabled(bool value) {
|
|||
RaisePropertyChanged(L"IsStatisticsForDynamicDetectionEnabled");
|
||||
}
|
||||
|
||||
void HomeViewModel::_ScalingService_IsTimerOnChanged(bool value) {
|
||||
void HomeViewModel::_ScalingService_IsTimerOnChanged(bool value, bool) {
|
||||
if (!value) {
|
||||
RaisePropertyChanged(L"TimerProgressRingValue");
|
||||
}
|
||||
|
|
@ -631,4 +640,13 @@ void HomeViewModel::_ScalingService_IsScalingChanged(bool) {
|
|||
RaisePropertyChanged(L"IsNotRunning");
|
||||
}
|
||||
|
||||
void HomeViewModel::_ToggleTimer(bool windowedMode) const noexcept {
|
||||
ScalingService& scalingService = ScalingService::Get();
|
||||
if (scalingService.IsTimerOn(windowedMode)) {
|
||||
scalingService.StopTimer();
|
||||
} else {
|
||||
scalingService.StartTimer(windowedMode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,21 +7,27 @@ namespace winrt::Magpie::implementation {
|
|||
struct HomeViewModel : HomeViewModelT<HomeViewModel>, wil::notify_property_changed_base<HomeViewModel> {
|
||||
HomeViewModel();
|
||||
|
||||
hstring TimerDescription() const noexcept;
|
||||
|
||||
bool IsTimerOn() const noexcept;
|
||||
|
||||
double TimerProgressRingValue() const noexcept;
|
||||
|
||||
hstring TimerLabelText() const noexcept;
|
||||
|
||||
hstring TimerButtonText() const noexcept;
|
||||
|
||||
bool IsNotRunning() const noexcept;
|
||||
|
||||
void ToggleTimer() const noexcept;
|
||||
hstring TimerButtonText(bool windowedMode) const noexcept;
|
||||
|
||||
void ToggleTimerFullscreen() const noexcept;
|
||||
|
||||
void ToggleTimerWindowed() const noexcept;
|
||||
|
||||
uint32_t Delay() const noexcept;
|
||||
void Delay(uint32_t value);
|
||||
|
||||
hstring DelayText() const noexcept;
|
||||
|
||||
bool ShowUpdateCard() const noexcept {
|
||||
return _showUpdateCard;
|
||||
}
|
||||
|
|
@ -63,9 +69,6 @@ struct HomeViewModel : HomeViewModelT<HomeViewModel>, wil::notify_property_chang
|
|||
bool IsAllowScalingMaximized() const noexcept;
|
||||
void IsAllowScalingMaximized(bool value);
|
||||
|
||||
bool IsKeepOnTop() const noexcept;
|
||||
void IsKeepOnTop(bool value);
|
||||
|
||||
bool IsSimulateExclusiveFullscreen() const noexcept;
|
||||
void IsSimulateExclusiveFullscreen(bool value);
|
||||
|
||||
|
|
@ -84,11 +87,14 @@ struct HomeViewModel : HomeViewModelT<HomeViewModel>, wil::notify_property_chang
|
|||
void LocateTouchHelperLogs() noexcept;
|
||||
void LocateUpdaterLogs() noexcept;
|
||||
|
||||
bool IsDebugMode() const noexcept;
|
||||
void IsDebugMode(bool value);
|
||||
|
||||
bool IsBenchmarkMode() const noexcept;
|
||||
void IsBenchmarkMode(bool value);
|
||||
|
||||
bool IsDebugMode() const noexcept;
|
||||
void IsDebugMode(bool value);
|
||||
bool IsTopmostDisabled() const noexcept;
|
||||
void IsTopmostDisabled(bool value);
|
||||
|
||||
bool IsEffectCacheDisabled() const noexcept;
|
||||
void IsEffectCacheDisabled(bool value);
|
||||
|
|
@ -114,13 +120,15 @@ struct HomeViewModel : HomeViewModelT<HomeViewModel>, wil::notify_property_chang
|
|||
void IsStatisticsForDynamicDetectionEnabled(bool value);
|
||||
|
||||
private:
|
||||
void _ScalingService_IsTimerOnChanged(bool value);
|
||||
void _ScalingService_IsTimerOnChanged(bool value, bool windowedMode);
|
||||
|
||||
void _ScalingService_TimerTick(double);
|
||||
|
||||
void _ScalingService_IsScalingChanged(bool);
|
||||
|
||||
::Magpie::Event<bool>::EventRevoker _isTimerOnRevoker;
|
||||
void _ToggleTimer(bool windowedMode) const noexcept;
|
||||
|
||||
::Magpie::Event<bool, bool>::EventRevoker _isTimerOnRevoker;
|
||||
::Magpie::Event<double>::EventRevoker _timerTickRevoker;
|
||||
::Magpie::Event<bool>::EventRevoker _isScalingChangedRevoker;
|
||||
::Magpie::Event<bool>::EventRevoker _isShowOnHomePageChangedRevoker;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@ namespace Magpie {
|
|||
void ReleaseNotes();
|
||||
void RemindMeLater();
|
||||
|
||||
String TimerDescription { get; };
|
||||
Boolean IsTimerOn { get; };
|
||||
Double TimerProgressRingValue { get; };
|
||||
String TimerLabelText { get; };
|
||||
String TimerButtonText { get; };
|
||||
Boolean IsNotRunning { get; };
|
||||
UInt32 Delay;
|
||||
String DelayText { get; };
|
||||
|
||||
void ToggleTimer();
|
||||
String TimerButtonText(Boolean windowedMode);
|
||||
void ToggleTimerFullscreen();
|
||||
void ToggleTimerWindowed();
|
||||
|
||||
String InitialToolbarStateDescription { get; };
|
||||
Int32 FullscreenInitialToolbarState;
|
||||
|
|
@ -28,7 +31,6 @@ namespace Magpie {
|
|||
Boolean IsShowTouchSupportInfoBar { get; };
|
||||
|
||||
Boolean IsAllowScalingMaximized;
|
||||
Boolean IsKeepOnTop;
|
||||
Boolean IsSimulateExclusiveFullscreen;
|
||||
Boolean IsInlineParams;
|
||||
static IVector<IInspectable> MinFrameRateOptions { get; };
|
||||
|
|
@ -40,6 +42,7 @@ namespace Magpie {
|
|||
void LocateUpdaterLogs();
|
||||
Boolean IsDebugMode;
|
||||
Boolean IsBenchmarkMode;
|
||||
Boolean IsTopmostDisabled;
|
||||
Boolean IsEffectCacheDisabled;
|
||||
Boolean IsFontCacheDisabled;
|
||||
Boolean IsSaveEffectSources;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 2.4 KiB |