Compare commits

..

145 commits

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

* docs: update README_ZH.md [skip ci]

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

---------

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

* Translated using Weblate (Finnish)

Currently translated at 100.0% (312 of 312 strings)

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

---------

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

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

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

* docs: update README_ZH.md [skip ci]

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

---------

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

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

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

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

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

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

Co-authored-by: Matteo Crocetti <matteocrocetti@proton.me>
2026-03-14 11:38:43 +08:00
Xu
359c7ac176 chore: 修复 VS 更新后 _ConanDeps 可能不会触发重新编译 2026-03-12 18:59:45 +08:00
dependabot[bot]
1ffba2f447
chore(deps): bump actions/download-artifact from 7 to 8 (#1382)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 20:50:16 +08:00
dependabot[bot]
e3c29dbdf9
chore(deps): bump actions/upload-artifact from 6 to 7 (#1381)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 20:49:49 +08:00
allcontributors[bot]
000257a25a
docs: add arifpedia as a contributor for translation (#1380)
* docs: update README.md [skip ci]

* docs: update README_ZH.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2026-03-02 18:54:37 +08:00
Weblate (bot)
6d50a02af8
Translated using Weblate (Indonesian) (#1379)
Currently translated at 100.0% (312 of 312 strings)

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

Co-authored-by: Arif Budiman <arifpedia@gmail.com>
2026-03-02 18:54:13 +08:00
Xu
06c42c0826
fix: 修复 SettingsCard 中的 TextBox 无法键入空格 (#1376) 2026-02-19 17:09:22 +08:00
Weblate (bot)
198de957aa
Translated using Weblate (German) (#1375)
Currently translated at 71.4% (223 of 312 strings)

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

Co-authored-by: Arved Staske <arved.staske@gmail.com>
2026-02-17 10:18:37 +08:00
Xu
65ffd9a1aa chore: VS 更新后重新编译 conan 依赖 2026-02-13 09:35:48 +08:00
Xu
4e4fde380e chore: CI 升级到 VS2026 2026-02-07 23:00:49 +08:00
Xu
991695168e chore: 更新依赖 2026-01-30 17:15:04 +08:00
Xu
959f93a816 docs: 明确许可协议
close #1370
2026-01-29 16:10:49 +08:00
Xu
a2c6608d9c
Add copyright information to README
Add copyright section specifying project ownership and restrictions.
2026-01-29 14:13:54 +08:00
Weblate (bot)
dc8f59e8e3
Translated using Weblate (Spanish) (#1369)
Currently translated at 85.8% (268 of 312 strings)

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

Co-authored-by: Matias Ranzani <matiasranzani76@gmail.com>
2026-01-28 21:50:01 +08:00
Xu
a5e079b0ee fix: 全屏模式缩放时不限制最小尺寸 2025-12-27 20:16:11 +08:00
Xu
c3473a0604
源窗口有弹窗时不再置顶缩放窗口 (#1356)
* fix: 源窗口有弹窗时缩放窗口不再置顶

* feat: 检测前台窗口 IL

* chore: 修复编译警告和添加注释

* perf: 转到后台等待

* fix: 小优化
2025-12-22 18:53:30 +08:00
Xu
463d6f1246 chore: 工具项目迁移到 VS18 2025-12-18 20:54:15 +08:00
Xu
22484d88e1
修复 Win10 中内存泄漏 (#1355)
* fix: 用 DispatcherQueue 代替 CoreDispatcher 以避免内存泄露

* chore: 优化注释
2025-12-16 20:50:50 +08:00
dependabot[bot]
9ec6c5dc4a
chore(deps): bump actions/upload-artifact from 5 to 6 (#1353)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:15:41 +08:00
dependabot[bot]
de00805204
chore(deps): bump actions/cache from 4 to 5 (#1352)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:15:32 +08:00
dependabot[bot]
60494174ba
chore(deps): bump actions/download-artifact from 6 to 7 (#1351)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:15:21 +08:00
Weblate (bot)
6c9c47f3fb
Translated using Weblate (Korean) (#1346)
Currently translated at 100.0% (312 of 312 strings)

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

Co-authored-by: a6e5aa12f60f4a7a <jyw8484@gmail.com>
2025-12-02 21:15:25 +08:00
allcontributors[bot]
e67ef2b598
docs: add Howard20181 as a contributor for code (#1345)
* docs: update README.md [skip ci]

* docs: update README_ZH.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2025-12-02 18:33:04 +08:00
Howard Wu
b38dbf2c9b
设置文件版本信息 (#1344)
* 设置文件版本信息

不固定为 0.0.0.0,可以不打开应用时读取版本信息
TouchHelper 版本检查和 Updater 检查更新时可以直接从文件读取版本号

* 设置文件版本号时不考虑MP_VERSION_TAG

同时从 public.py 中移除替换逻辑

* chore: RC 优化
1. 用 MP_VERSION_STRING 取代 MP_VERSION_TAG,前者不包含开头的 v 字符
2. RC 文件不再支持 VS 编辑,删除了冗余代码。区域由非特定语言改为 en-US
3. STRINGIFY 和 WIDEN_STRINGIFY 宏移到通用头文件

* chore: 简化 resource.h

* chore: 删除 APSTUDIO_READONLY_SYMBOLS 宏
这个宏供 VS 资源编辑器使用

* chore: 始终定义 MP_MEOW_VERSION
通过 MP_VERSION_STRING 区分开发版本和发布版本

* chore: 简化版本字符串提取
使用了正则表达式的正向先行断言

---------

Co-authored-by: Xu <blinue@outlook.com>
2025-12-02 18:28:58 +08:00
Xu
dc6c65fe14 chore: slnx 指定启动项目 2025-12-01 14:17:10 +08:00
Xu
6fc3594d9d
支持 Visual Studio 2026 (#1343)
* chore: 支持 VS2026

* docs: 更新文档以及排除 pdb

* chore: 修复 pdb 生成

* chore: 禁止 Magpie.Core 生成 pdb

* chore: 清理
2025-11-27 21:57:38 +08:00
dependabot[bot]
2c4e6aa8ca
chore(deps): bump actions/checkout from 5 to 6 (#1342)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 20:25:01 +08:00
Xu
7287e8be40 fix: 修复特定情况下终止缩放时崩溃 2025-11-19 15:47:20 +08:00
Weblate (bot)
45583f105a
Translated using Weblate (French) (#1341)
Currently translated at 91.3% (285 of 312 strings)

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

Co-authored-by: Rémy Haitayan <remy.haitayan@gmail.com>
2025-11-18 08:26:58 +08:00
Xu
19bf67e285 perf: 删除 CompSwapchainPresenter 中无用传参 2025-11-16 17:15:24 +08:00
Xu
730d73ecea perf: 降低低帧率下 WGC 的延迟 2025-11-14 22:03:29 +08:00
Xu
c10034af50 perf: 优化 Deband 在启用内联效果参数时的性能 2025-11-11 16:11:57 +08:00
CHLBC
bcd5b6fed6 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (312 of 312 strings)

Translation: Magpie/UI
Translate-URL: https://hosted.weblate.org/projects/magpie/ui/zh_Hant/
2025-11-11 14:47:12 +08:00
Xu
046b2868c7 fix: 强制法语字体缓存过期 2025-11-10 19:46:13 +08:00
Xu
15d3e392c4
修复源窗口被最小化时偶尔没有移出屏幕 (#1335)
* fix: 修复源窗口被最小化后没有移出屏幕的错误

* fix: 位于中间状态时不再继续检查

* fix: 修复切换窗口时偶尔源窗口不会被带到顶部

* refactor: 常用 SWP 组合定义为宏

* perf: 提高细粒度

* fix: 置顶窗口可能失败,需多次尝试
2025-11-10 18:41:37 +08:00
Xu
2eb70761c9 fix: 修复叠加层法语显示错误 2025-11-10 09:07:00 +08:00
Weblate (bot)
8fba0716e6
Translations update from Hosted Weblate (#1334)
* Translated using Weblate (French)

Currently translated at 81.0% (253 of 312 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (312 of 312 strings)

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

* Translated using Weblate (Vietnamese)

Currently translated at 100.0% (312 of 312 strings)

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

---------

Co-authored-by: Louis <mail+github@louisroche.net>
Co-authored-by: Quân Trần <tquanwibu123@gmail.com>
2025-11-07 12:10:10 +08:00
Weblate (bot)
9e48ef9ab5
Translated using Weblate (Japanese) (#1331)
Currently translated at 100.0% (312 of 312 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-11-06 11:41:25 +08:00
Xu
1413dc3e69 perf: 优化性能测试模式流畅度 2025-11-05 11:11:55 +08:00
Xu
d3fe8e56bb perf: 避免多个 WM_FRONTEND_RENDER 导致重复渲染 2025-11-04 20:34:41 +08:00
Xu
1b637c774b feat: 实现开发者选项禁用缩放窗口置顶 2025-11-04 19:40:33 +08:00
Xu
b187013b29 perf: 着色器添加 noperspective 修饰符
我们不涉及 3D 渲染,noperspective 可以稍微提高性能
2025-11-04 10:59:29 +08:00
Xu
ac758361c4 chore: 更新依赖 2025-11-04 10:30:12 +08:00
Xu
27037c3b45 fix: 修复窗口模式缩放会遮挡权限更高的窗口 2025-11-04 10:11:18 +08:00
dependabot[bot]
37f38253c7
chore(deps): bump actions/download-artifact from 5 to 6 (#1326)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 22:02:12 +08:00
dependabot[bot]
1988cde675
chore(deps): bump actions/upload-artifact from 4 to 5 (#1325)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 22:01:56 +08:00
Weblate (bot)
88f6ac243f
Translated using Weblate (Russian) (#1312)
Currently translated at 100.0% (311 of 311 strings)

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

Co-authored-by: NightFox <NightFox@myied.org>
2025-10-04 08:12:23 +08:00
Weblate (bot)
1afc257050
Translated using Weblate (Chinese (Traditional Han script)) (#1311)
Currently translated at 100.0% (311 of 311 strings)

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

Co-authored-by: CHLBC <chris010613@yahoo.com.tw>
2025-10-01 08:36:55 +08:00
Weblate (bot)
4bef67d775
Translated using Weblate (Japanese) (#1308)
Currently translated at 100.0% (311 of 311 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-09-23 23:16:18 +08:00
Weblate (bot)
fb46f324c1
Translated using Weblate (Korean) (#1307)
Currently translated at 98.6% (296 of 300 strings)

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

Co-authored-by: TinyUD <dongwon6073@gmail.com>
2025-09-22 19:28:10 +08:00
Xu
81e314d3a6
存在黑边时支持更改输出画面位置 (#1306)
* feat: 初步实现改变画面位置

* feat: 实现保存配置项和调整选项位置

* feat: 本地化

* chore: 修改措辞

* chore: 修改措辞

* chore: 修改措辞
2025-09-22 19:12:58 +08:00
Weblate (bot)
28bbb84cea
Translated using Weblate (Korean) (#1298)
Currently translated at 92.3% (277 of 300 strings)

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

Co-authored-by: OkTeak Lim <downmix1@gmail.com>
2025-09-14 09:13:27 +08:00
dependabot[bot]
0d7ba8bd37
chore(deps): bump actions/setup-python from 5 to 6 (#1290)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 19:43:27 +08:00
Weblate (bot)
8fc3205844
Translated using Weblate (Portuguese (Brazil)) (#1289)
Currently translated at 84.6% (254 of 300 strings)

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

Co-authored-by: Luis <luisfelipekawaii@gmail.com>
2025-09-08 08:00:56 +08:00
Xu
2718439414 fix: 修复复制配置文件时不复制自动隐藏光标配置 2025-09-06 17:45:11 +08:00
Weblate (bot)
74b02f46ab
Translated using Weblate (Russian) (#1280)
Currently translated at 100.0% (300 of 300 strings)

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

Co-authored-by: udaffskij <udaffskij@gmail.com>
2025-08-28 22:46:10 +08:00
Xu
23b9774716 fix: 将 SGSR.hlsl 复制到输出文件夹 2025-08-27 19:26:18 +08:00
Blinue
69d6105d56 Update version.json 2025-08-27 10:31:19 +00:00
Xu
664e0f4c8a chore: 修复 Debug 配置编译警告 2025-08-27 18:13:06 +08:00
Weblate (bot)
1e7c571e6e
Translated using Weblate (Chinese (Traditional Han script)) (#1277)
Currently translated at 100.0% (300 of 300 strings)

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

Co-authored-by: CHLBC <chris010613@yahoo.com.tw>
2025-08-26 20:05:21 +08:00
Xu
74d26e4a0d
正在缩放窗口时禁止自动缩放它的弹窗 (#1276)
* feat(tool): Kirikiri 模拟添加弹窗

* fix: 禁止同一进程内类名相同的窗口打断缩放

* fix: 正在缩放窗口时禁止自动缩放它的弹窗

* fix: 增加两个窗口位于同一进程的条件

* chore: 添加注释
2025-08-26 20:02:26 +08:00
Xu
4622274cb3
fix: 修复单色光标渲染错误 (#1275) 2025-08-26 18:46:00 +08:00
Xu
b8c0801f93
fix(ci): 只在 main 和 dev 分支签名 2025-08-26 08:51:28 +08:00
dependabot[bot]
10176cf4fb
chore(deps): bump actions/checkout from 4 to 5 (#1274)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 08:38:57 +08:00
Xu
b4c71bd912 fix: 避免意外改变源窗口 Z 顺序 2025-08-25 19:00:26 +08:00
Xu
0e3a5e8608 fix: 切换前台窗口时稍微延迟更新置顶
希望可以进一步提高可靠性
2025-08-25 18:45:43 +08:00
Weblate (bot)
5e597f8f1d
Translated using Weblate (Japanese) (#1272)
Currently translated at 100.0% (300 of 300 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-08-25 18:41:20 +08:00
Weblate (bot)
33c3ed4c51
Translated using Weblate (Hungarian) (#1270)
Currently translated at 27.5% (82 of 298 strings)

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

Co-authored-by: oknem <valami6512@pm.me>
2025-08-24 17:46:33 +08:00
Xu
ab24bd768e
支持自动隐藏光标 (#1267)
* feat: 初步实现自动隐藏光标

* UI: 稍微调整 UI

* UI: 不再禁用某些子选项

* fix: 修复隐藏延迟不准确的问题

* chore: 添加模拟隐藏光标的窗口
重构和添加注释
2025-08-24 17:39:16 +08:00
Karl Hook
61765e4344
feat: 移植SGSR (#1266)
* feat: 移植SGSR

* doc: 文档增加SGSR的描述
2025-08-23 18:59:01 +08:00
Xu
d01956e3a7
源窗口位于前台时始终置顶缩放窗口 (#1259)
* feat: 模拟中途置顶的窗口

* fix: 按钮随 DPI 缩放

* fix: 始终置顶

* fix: 修复切换前台窗口

* feat: 删除置顶选项

* refactor: 检查逻辑

* refactor: 用更简单的方式将缩放窗口置于源窗口之前

* fix: 支持源窗口中途置顶

* refactor: 小优化

* fix: 修复特定操作下意外将源窗口置顶

* fix: 提高取消置顶的可靠性

* fix: 调试模式下不置顶

* chore: WindowCase 支持模拟弹窗

* fix: 不要把被禁用的窗口设为前台

* feat: WindowCase 支持模拟“模拟模态弹窗”

* chore: 避免大量错误日志

* fix: 优化 Z 顺序维护

* fix: 不再使用 SWP_NOOWNERZORDER

* fix: 修复消息弹窗可能影响窗口 Z 顺序

* refactor: 小优化

* chore: 添加注释

* chore: 添加注释

* fix: 小优化

* chore: 小优化
2025-08-21 17:49:05 +08:00
Xu
e3e82b3bd8 fix: 再次放松挂起窗口检测
即使存在幽灵窗口,也检查源窗口是否真的处于无响应状态
2025-08-20 18:38:26 +08:00
Xu
2c75a8f328
自绘非客户区的窗口不再保留非客户区 (#1255)
* fix: 自绘非客户区的窗口也裁剪非客户区

* fix: 适配滚动条

* chore: 添加注释
2025-08-15 20:15:53 +08:00
Weblate (bot)
6bdafe8106
Translated using Weblate (Chinese (Traditional Han script)) (#1254)
Currently translated at 100.0% (300 of 300 strings)

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

Co-authored-by: CHLBC <chris010613@yahoo.com.tw>
2025-08-15 08:03:34 +08:00
Xu
ae0c914c5c
修复有时误报源窗口无响应 (#1252)
* fix: 尝试修复错误检测源窗口挂起

* fix: 修复消息弹窗 bug
2025-08-14 22:36:28 +08:00
Weblate (bot)
9467b6aa2f
Translations update from Hosted Weblate (#1249)
* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (300 of 300 strings)

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

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (300 of 300 strings)

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

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (300 of 300 strings)

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

---------

Co-authored-by: CHLBC <chris010613@yahoo.com.tw>
Co-authored-by: 阿宇 addonian1123 <ian98765321@gmail.com>
2025-08-14 08:39:06 +08:00
Weblate (bot)
6c4d75ee3a
Translated using Weblate (Russian) (#1248)
Currently translated at 99.6% (299 of 300 strings)

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

Co-authored-by: NightFox <NightFox@myied.org>
2025-08-13 20:14:04 +08:00
Weblate (bot)
07a9643b14
Translated using Weblate (Portuguese (Brazil)) (#1247)
Currently translated at 76.3% (229 of 300 strings)

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

Co-authored-by: Toni Garces <tonigarces19@gmail.com>
2025-08-12 19:42:29 +08:00
dependabot[bot]
6ba65bd2ea
chore(deps): bump actions/download-artifact from 4 to 5 (#1245)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 08:27:35 +08:00
imgbot[bot]
4da359c524
[ImgBot] Optimize images (#1243)
*Total -- 2,663.46kb -> 2,303.87kb (13.5%)

/img/repo-card.png -- 39.81kb -> 26.83kb (32.61%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/128.png -- 11.38kb -> 7.67kb (32.58%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/128.png -- 11.12kb -> 7.59kb (31.73%)
/img/main-window-zh.png -- 137.77kb -> 95.21kb (30.89%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/96.png -- 8.60kb -> 6.06kb (29.58%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/96.png -- 8.30kb -> 6.03kb (27.3%)
/img/main-window.png -- 159.94kb -> 118.33kb (26.02%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/64.png -- 5.44kb -> 4.04kb (25.79%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/128.png -- 10.13kb -> 7.68kb (24.14%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/64.png -- 5.35kb -> 4.06kb (24.13%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/96.png -- 7.77kb -> 6.09kb (21.63%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/40.png -- 3.49kb -> 2.78kb (20.37%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/48.png -- 4.13kb -> 3.30kb (20.19%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/24.png -- 2.55kb -> 2.04kb (19.81%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/32.png -- 2.99kb -> 2.42kb (19.2%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/64.png -- 5.00kb -> 4.06kb (18.73%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/40.png -- 3.44kb -> 2.79kb (18.73%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/48.png -- 3.99kb -> 3.26kb (18.34%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/32.png -- 2.92kb -> 2.39kb (18.24%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/32.png -- 2.98kb -> 2.44kb (18.08%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/48.png -- 4.03kb -> 3.31kb (17.75%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/40.png -- 3.43kb -> 2.84kb (17.2%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/16.png -- 2.14kb -> 1.78kb (16.7%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/16.png -- 2.13kb -> 1.85kb (13.29%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/20.png -- 2.32kb -> 2.02kb (12.92%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/16.png -- 2.11kb -> 1.84kb (12.79%)
/img/screenshot.png -- 2,150.68kb -> 1,917.65kb (10.83%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/24.png -- 2.56kb -> 2.29kb (10.49%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/20.png -- 2.34kb -> 2.12kb (9.38%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/20.png -- 2.33kb -> 2.14kb (7.97%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/24.png -- 2.56kb -> 2.38kb (6.87%)
/src/Magpie/Icons/PNG/Magpie Icon Full Enabled/256.png -- 16.50kb -> 16.09kb (2.47%)
/src/Magpie/Icons/PNG/Magpie Icon Full Disabled/256.png -- 17.01kb -> 16.61kb (2.39%)
/src/Magpie/Icons/PNG/Magpie Icon Simplified/256.png -- 16.24kb -> 15.87kb (2.24%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-08-11 19:33:06 +08:00
Xu
6381424727 docs: 更新截图 2025-08-11 19:05:17 +08:00
Xu
9dea5f5f3e
修复截图菜单项 ID 冲突 (#1242)
* fix: 修复 ImGui ID 冲突

* fix: 修复 ImGui ID 冲突
2025-08-11 19:00:26 +08:00
Xu
8ea304bd54 docs: 更新截图 2025-08-10 21:43:13 +08:00
Xu
2ec4166905 docs: 更新 README 2025-08-10 21:04:19 +08:00
Xu
355676dc49 chore: 修复 CI 触发条件 2025-08-10 20:36:23 +08:00
Xu
19a5032da1 chore: 修复发布 wiki 脚本 2025-08-10 20:33:02 +08:00
Blinue
a350639eb3 Update version.json 2025-08-10 12:21:44 +00:00
Xu
f2888d5c81 docs: 更新文档 2025-08-10 19:40:57 +08:00
Weblate (bot)
debf547ab8
Translated using Weblate (Polish) (#1239)
Currently translated at 100.0% (300 of 300 strings)

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

Co-authored-by: Krzysztof <k_marc_2002@proton.me>
2025-08-10 19:27:40 +08:00
Xu
fb2270ec89
优化 Graphics Capture 对 Kirikiri 窗口的支持 (#1238)
* feat: 尝试模拟 kirikiri 窗口行为

* feat: 完善模拟 kirikiri 窗口

* feat: 优化 WGC 对 kirikiri 窗口的处理

* chore: 优化注释

* fix: 优化错误处理

* chore: 添加注释
2025-08-09 21:20:34 +08:00
Weblate (bot)
ffdc3e772b
Translated using Weblate (Japanese) (#1234)
Currently translated at 100.0% (300 of 300 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-08-08 12:17:07 +08:00
Xu
dce6adb097 fix: 修复 Win10 中工具栏图标错误 2025-08-07 09:43:46 +08:00
Xu
07a6dc2c62 feat: 工具栏支持最小化源窗口 2025-08-06 23:13:21 +08:00
Xu
cdb27a48e6
定时器和托盘菜单支持窗口模式缩放 (#1233)
* feat: 更新 UI

* feat: 实现定时器功能

* feat: 托盘菜单支持全屏/窗口缩放定时器

* fix: 修复偶尔取消计时会立刻缩放的问题
2025-08-06 20:17:37 +08:00
Weblate (bot)
f7f7ebbc56
Translated using Weblate (Polish) (#1229)
Currently translated at 100.0% (297 of 297 strings)

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

Co-authored-by: Krzysztof <k_marc_2002@proton.me>
2025-08-05 19:19:15 +08:00
allcontributors[bot]
396a1956c2
docs: add kangurek-kao as a contributor for translation (#1230)
* docs: update README.md [skip ci]

* docs: update README_ZH.md [skip ci]

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

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2025-08-05 19:19:06 +08:00
Xu
ea107ac9c3
优化自动缩放机制 (#1227)
* fix: 重复缩放时不显示错误消息

* fix: 减小 ScalingService 和 ScalingRuntime 状态不一致的窗口期

* fix: 避免自动缩放和最小化窗口还原机制冲突

* chore: 修复 clang 编译警告

* refactor: ScalingRuntime::SwitchScalingState 重命名为 ScalingRuntime::ToggleScaling

* fix: 恢复检查自动缩放

* feat: 自动缩放可以终止当前缩放

* chore: 添加注释

* feat: 自动缩放不再等待最小化和不可见的窗口
现已支持直接缩放这类窗口,由 ScalingRuntime 等待
2025-08-05 17:55:29 +08:00
Xu
96a1c7287a feat: 源窗口隐藏会等待显示然后恢复缩放 2025-08-03 17:14:21 +08:00
Xu
7b71454b5d fix: 修复任务栏不在底部导致启动时窗口位置错误的问题 2025-08-03 14:17:06 +08:00
Weblate (bot)
151bb3a6ed
Translated using Weblate (Japanese) (#1223)
Currently translated at 100.0% (297 of 297 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-08-01 23:16:49 +08:00
Xu
d84ad92b97 chore: 优化项目文件 2025-07-31 19:56:17 +08:00
Xu
5db358fdf3 fix: 修复窗口模式初始缩放倍数错误 2025-07-31 13:11:30 +08:00
Weblate (bot)
16457063fb
Translated using Weblate (Japanese) (#1222)
Currently translated at 100.0% (297 of 297 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-30 22:21:10 +08:00
Xu
02bf2a1c5f chore: 发布版本 CI 不再加载缓存 2025-07-30 09:27:34 +08:00
Xu
19a964195c fix: 修复发布脚本获取最新版本标签
不确定是否修好了,只能下次发布预览版再验证
2025-07-29 21:17:34 +08:00
Blinue
2b5c715d2b Update version.json 2025-07-29 12:53:45 +00:00
Xu
e70d5ee075 fix: 使结束缩放后清理更可靠 2025-07-29 20:29:40 +08:00
Xu
f116629169
源窗口最小化不再终止缩放 (#1219)
* fix: 优化窗口移动检测

* fix: 优化拖拽窗口时缩放行为
窗口模式立即缩放,全屏模式将等待拖拽结束

* feat: 源窗口最小化后等待其还原

* fix: 修复显示消息时窗口被销毁然后立即显示新消息会导致崩溃

* feat: 源窗口在最小化然后还原后可以还原缩放尺寸

* refactor: 微小重构

* feat: 缩放时禁用源窗口的窗口动画

* refactor: 禁用/还原窗口动画的逻辑集中在 WindowAnimationDisabler 类

* feat: 不再禁用窗口动画
Win10 的动画很突兀,Win11 却还行
2025-07-29 20:16:35 +08:00
Weblate (bot)
18f3749502
Translated using Weblate (Japanese) (#1218)
Currently translated at 100.0% (297 of 297 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-28 21:50:40 +08:00
Xu
78edfd5217
开机启动始终最小化到系统托盘 (#1217)
* feat: 开机启动时始终最小化到系统托盘

* feat: 开机启动选项添加描述
2025-07-28 19:02:06 +08:00
Xu
d1076bbb95
全屏缩放默认不再置顶 (#1216)
* feat: 全屏缩放时默认不再置顶,添加置顶选项

* fix: 修复消息弹窗会使窗口取消置顶的问题

* fix: 修复消息弹窗弹出动画 bug
2025-07-28 18:26:42 +08:00
Weblate (bot)
62a41d8da8
Translated using Weblate (Japanese) (#1214)
Currently translated at 100.0% (294 of 294 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-26 19:20:54 +08:00
Xu
25a36718a7 feat: 开发者选项支持打开日志位置 2025-07-24 18:53:27 +08:00
Xu
06ffbf76f7
进一步优化对弹窗的兼容性 (#1212)
* feat: 进一步优化对弹窗的兼容性

* fix

* fix: 调试模式只跳过置顶
2025-07-24 18:52:51 +08:00
Xu
dd458ff447 refactor: 简化 UpdateService 的 json 处理 2025-07-23 22:39:30 +08:00
Xu
b1ede5714f fix: 修复缩放时退出导致崩溃的问题 2025-07-23 22:08:44 +08:00
Xu
ef4053769d
IUnknown::as 替换为 IUnknown::try_as (#1211)
* refactor: IUnknown::as 替换为 IUnknown::try_as
删除 SettingsCard,ActionIconToolTip 属性

* refactor: 优化头文件包含
2025-07-23 21:40:08 +08:00
Weblate (bot)
e8d7732891
Translations update from Hosted Weblate (#1210)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (294 of 294 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (293 of 293 strings)

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

---------

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-23 19:37:20 +08:00
Xu
bf5fd328c6
不再支持导入 ScaleModels.json (#1209)
* feat: 不再支持导入旧版 ScaleModels.json

* fix: 修复新建缩放模式按钮位置

* fix: 重新实现缓解动画 bug

* fix: 修复 InfoBar 中 Tooltip 的主题

* fix: 优化检查更新按钮样式

* fix: 修复文件对话框的确定按钮有时被禁用
2025-07-23 19:17:51 +08:00
Xu
af56e660bd fix: 自动缩放等待窗口显示 2025-07-23 13:27:41 +08:00
Xu
7bc0762346 chore: pull request 的 CI 不签名
pull request 的 CI 不能使用 secrets
2025-07-22 20:45:44 +08:00
Xu
cad9ed18f5 fix: 修复特定窗口期内缩放导致崩溃 2025-07-22 20:36:43 +08:00
Xu
00073018df
fix: 自动缩放等待窗口开始响应消息 (#1207) 2025-07-22 19:48:56 +08:00
Weblate (bot)
36e16be0ed
Translated using Weblate (Japanese) (#1206)
Currently translated at 100.0% (292 of 292 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-22 19:20:20 +08:00
Xu
f882504df6
支持分别设置全屏和窗口模式缩放的工具栏初始状态 (#1205) 2025-07-22 19:06:42 +08:00
Xu
c16e3fb574
修复全屏缩放会遮挡弹窗的问题 (#1204)
* fix: 修复缩放窗口会遮挡弹窗的问题

* fix: 修复高 CPU 使用率

* fix: IFileOpenDialog 弹窗放到后台线程
在主线程使用 IFileOpenDialog 有些问题,Win10 中按一下 shift 就导致 UI 卡死

* refactor: 优化文件包含

* fix: 修复点击工具栏无法将源窗口带到前台的问题
2025-07-22 18:35:49 +08:00
Xu
bdd8ee1e56
工具栏优化 (#1199)
* feat: 允许在全屏/窗口模式之间切换

* feat: 只有源窗口在前台才允许切换

* feat: 工具栏支持切换全屏/窗口模式缩放

* chore: 工具栏按钮英文首字母大写
2025-07-19 22:26:43 +08:00
Xu
4df29d8d1b fix: 修复呈现错误 2025-07-15 22:51:16 +08:00
Xu
f385ee690b perf: 用 IDCompositionVirtualSurface 代替 IDCompositionSurface
提高调整缩放窗口尺寸的性能
2025-07-15 21:33:05 +08:00
Xu
04ff6ea0a3
支持使用 clang-cl 编译 (#1195)
* chore: native 项目支持 clang-cl

* chore: 修复 clang 编译错误

* chore: 修复部分 clang 编译警告

* chore: 修复警告

* chore: 修复所有警告,优化 llvm 查找

* chore: 启用 LTO

* chore: 支持 ARM64

* chore: _ConanDeps 支持 clang-cl

* chore: 编译选项改变 _ConanDeps 自动重新编译

* chore: profile 更改时重新编译 _ConanDeps

* chore: 将 hu 和 ka 加入项目文件,但不参与生成
否则不参与批量替换

* chore: Magpie 项目支持并行编译

* chore: 添加几个提高性能的编译选项

* chore: 优化 _ConanDeps

* chore: publish.py 支持参数

* chore: CI 支持 clang 编译

* chore: 修复 CI 的 conan 缓存

* chore: 优化编译选项,修复 CI

* chore: 修复 CI

* chore: 改变参数顺序

* chore: 添加编译选项支持针对当前硬件生成优化代码

* chore: 优化 CI 脚本

* chore: publish.py 添加 --use-native-march 选项

* chore: 脚本集中在 scripts 文件夹,msbuild 启用并行编译

* chore: clang, x64 配置启用 CX16 指令

* chore: 更新依赖

* chore: 更新格式设置

* chore: 增加额外的斜杠以提高兼容性

* chore: 修复效果文件复制
2025-07-15 17:40:04 +08:00
Weblate (bot)
d90470c179
Translations update from Hosted Weblate (#1196)
* Translated using Weblate (Russian)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (290 of 290 strings)

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

---------

Co-authored-by: NightFox <NightFox@myied.org>
2025-07-12 18:04:13 +08:00
Weblate (bot)
49857616b4
Translations update from Hosted Weblate (#1190)
* Translated using Weblate (Russian)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (290 of 290 strings)

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

---------

Co-authored-by: NightFox <NightFox@myied.org>
2025-07-06 15:53:11 +08:00
Weblate (bot)
ef63609009
Translated using Weblate (Japanese) (#1184)
Currently translated at 100.0% (290 of 290 strings)

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

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-04 16:56:22 +08:00
Weblate (bot)
e2a421e556
Translations update from Hosted Weblate (#1183)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (290 of 290 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (290 of 290 strings)

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

---------

Co-authored-by: animeojisan <engine54jp@gmail.com>
2025-07-01 20:31:41 +08:00
Blinue
4ed1ade579 Update version.json 2025-07-01 10:52:04 +00:00
308 changed files with 11368 additions and 4106 deletions

View file

@ -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,

View file

@ -9,8 +9,11 @@ cpp_generate_documentation_comments = xml
# Visual C++ 格式设置
indent_style = tab
indent_size = 4
tab_width= 4
cpp_indent_braces = false
cpp_indent_multi_line_relative_to = statement_begin
cpp_indent_multi_line_relative_to = innermost_parenthesis
cpp_indent_within_parentheses = indent
cpp_indent_preserve_within_parentheses = false
cpp_indent_case_contents = true
@ -21,7 +24,7 @@ cpp_indent_goto_labels = one_left
cpp_indent_preprocessor = leftmost_column
cpp_indent_access_specifiers = false
cpp_indent_namespace_contents = false
cpp_indent_preserve_comments = true
cpp_indent_preserve_comments = false
cpp_new_line_before_open_brace_namespace = same_line
cpp_new_line_before_open_brace_type = same_line
cpp_new_line_before_open_brace_function = same_line
@ -66,4 +69,17 @@ cpp_space_around_binary_operator = insert
cpp_space_around_assignment_operator = insert
cpp_space_pointer_reference_alignment = left
cpp_space_around_ternary_operator = insert
cpp_use_unreal_engine_macro_formatting = true
cpp_wrap_preserve_blocks = one_liners
# Visual C++ 包含清理设置
cpp_include_cleanup_add_missing_error_tag_type = none
cpp_include_cleanup_remove_unused_error_tag_type = dimmed
cpp_include_cleanup_optimize_unused_error_tag_type = suggestion
cpp_include_cleanup_sort_after_edits = false
cpp_sort_includes_error_tag_type = suggestion
cpp_sort_includes_priority_case_sensitive = false
cpp_sort_includes_priority_style = quoted
cpp_includes_style = default
cpp_includes_use_forward_slash = true

View file

@ -2,42 +2,48 @@ 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.platform }}
key: Conan-${{ hashFiles('src/**/conanfile.txt') }}-${{ matrix.compiler }}-${{ matrix.platform }}
- name: Build
run: python publish.py ${{ matrix.platform }} unpackaged certs\Magpie.pfx "${{ secrets.MAGPIE_PFX_PASSWORD }}"
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
id: hash
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.platform }}
name: Magpie-dev-${{ steps.hash.outputs.sha_short }}-${{ matrix.compiler }}-${{ matrix.platform }}
path: ./publish/${{ matrix.platform }}

View file

@ -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,15 +48,12 @@ jobs:
echo "tag=$tag" >> $env:GITHUB_OUTPUT
- name: Build
run: python publish.py ${{ matrix.platform }} unpackaged certs\Magpie.pfx "${{ secrets.MAGPIE_PFX_PASSWORD }}"
env:
MAJOR: ${{ inputs.major }}
MINOR: ${{ inputs.minor }}
PATCH: ${{ inputs.patch }}
TAG: ${{ steps.tag.outputs.tag }}
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 }}
@ -70,26 +61,19 @@ 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
- name: Publish release
run: python ci/release.py
env:
MAJOR: ${{ inputs.major }}
MINOR: ${{ inputs.minor }}
PATCH: ${{ inputs.patch }}
TAG: ${{ needs.build.outputs.tag }}
PRERELEASE: ${{ inputs.prerelease }}
ACCESS_TOKEN: ${{ secrets.CONTENTS_ACCESS_TOKEN }}
run: python scripts/release.py ${{ inputs.major }} ${{ inputs.minor }} ${{ inputs.patch }} ${{ needs.build.outputs.tag }} ${{ inputs.prerelease }} ${{ secrets.CONTENTS_ACCESS_TOKEN }}

View file

@ -3,20 +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 ci/wiki.py
env:
ACCESS_TOKEN: ${{ secrets.CONTENTS_ACCESS_TOKEN }}
run: python scripts/wiki.py ${{ secrets.CONTENTS_ACCESS_TOKEN }}

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
<PreferredToolArchitecture Condition="'$(PreferredToolArchitecture)' == ''">x64</PreferredToolArchitecture>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
@ -13,20 +13,10 @@
<SDLCheck>true</SDLCheck>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WarningLevel>Level4</WarningLevel>
<!-- /await:strict: 禁用协程的非标准语言扩展 -->
<!-- /utf-8: 源代码使用 UTF-8 格式 -->
<!-- /Zc:__cplusplus: 更新 __cplusplus 宏 -->
<!-- /volatile:iso: 禁用 volatile 的语义扩展 -->
<!-- /fp:contract: 生成浮点收缩指令。浮点收缩指令是将两个浮点操作合并为一个指令的机器指令,例如 Fused-Multiply-Add (FMA) -->
<!-- fp:contract 可以和其他 fp 选项同时存在,因此每个项目可以分别指定自己的浮点模型 -->
<AdditionalOptions>%(AdditionalOptions) /await:strict /utf-8 /Zc:__cplusplus /volatile:iso /fp:contract</AdditionalOptions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<!-- /Gw: 链接时删除未使用和重复的数据,可以减小二进制文件体积 -->
<!-- /Zc:checkGwOdr: 防止 /Gw 导致某些 ODR 违规被忽略 -->
<AdditionalOptions>%(AdditionalOptions) /Gw /Zc:checkGwOdr</AdditionalOptions>
<AdditionalOptions>/utf-8 /Zc:__cplusplus /volatile:iso %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
</ItemDefinitionGroup>
</Project>

View file

@ -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
View 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>

View file

@ -9,17 +9,13 @@
[![License](https://img.shields.io/github/license/Blinue/Magpie)](./LICENSE)
[![build](https://github.com/Blinue/Magpie/actions/workflows/build.yml/badge.svg)](https://github.com/Blinue/Magpie/actions/workflows/build.yml)
[![All Contributors](https://img.shields.io/github/all-contributors/Blinue/Magpie)](#acknowledgement-)
[![GitHub all releases](https://img.shields.io/github/downloads/Blinue/Magpie/total)](https://github.com/Blinue/Magpie/releases)
[![Translation status](https://hosted.weblate.org/widget/magpie/svg-badge.svg)](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.
[![Translation status](https://hosted.weblate.org/widgets/magpie/-/287x66-white.png)](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.
[![Translation status](https://hosted.weblate.org/widget/magpie/multi-auto.svg)](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.

View file

@ -9,17 +9,13 @@
[![许可协议](https://img.shields.io/github/license/Blinue/Magpie)](./LICENSE)
[![build](https://github.com/Blinue/Magpie/actions/workflows/build.yml/badge.svg)](https://github.com/Blinue/Magpie/actions/workflows/build.yml)
[![All Contributors](https://img.shields.io/github/all-contributors/Blinue/Magpie)](#%E8%B4%A1%E7%8C%AE%E8%80%85-)
[![GitHub all releases](https://img.shields.io/github/downloads/Blinue/Magpie/total)](https://github.com/Blinue/Magpie/releases)
[![翻译状态](https://hosted.weblate.org/widget/magpie/svg-badge.svg)](https://hosted.weblate.org/engage/magpie)
</div>
🌍 [English](./README.md) | **简体中文**
Magpie 是一个轻量级的窗口缩放工具,内置了多种高效的缩放算法和滤镜。它主要用于提升游戏画质和让不支持全屏化的游戏也能全屏显示等。
我们使用 [Weblate](https://weblate.org) 进行本地化工作,请帮助我们把 Magpie 翻译成更多语言。
[![翻译状态](https://hosted.weblate.org/widgets/magpie/-/287x66-white.png)](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/widget/magpie/multi-auto.svg)](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 许可协议。

View file

@ -126,6 +126,9 @@ Magpie ships with a handful of effects that can be used in combinations. Most of
* CuNNy familySuitable 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

View file

@ -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.

View file

@ -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`.

View file

@ -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.

View file

@ -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/性能优化建议)。

View file

@ -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 算法缩放输入。适合放大像素画
* 输出尺寸:取决于缩放选项

View file

@ -6,8 +6,7 @@ Magpie 提供数种捕获方式,根据使用场景,它们各有优劣。无
| 支持录制/串流 | 特殊情况下不支持<sup>[1]</sup> | 否 | 是 | 是 |
| 支持源窗口跨越多个屏幕 | 特殊情况下不支持<sup>[1]</sup> | 否 | 是 | 是 |
| 无视 DPI 虚拟化<sup>[2]</sup> | 否 | 否 | 是| 是 |
| 备注 | 首选捕获方式 | 要求 Win10 v2004 | | 占用的显存较少 |
| 备注 | 首选捕获方式 | 要求 Win10 v2004 | | 性能不稳定,不建议使用 |
[1]: (1) 源窗口不支持常规的窗口捕获 (2) 操作系统为 Windows 11

View file

@ -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`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

BIN
img/main-window-zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
img/main-window.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
img/repo-card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
img/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

View file

@ -2,34 +2,28 @@ import sys
import os
import subprocess
import glob
import shutil
from xml.etree import ElementTree
import argparse
majorVersion = None
try:
# https://docs.github.com/en/actions/learn-github-actions/variables
if os.environ["GITHUB_ACTIONS"].lower() == "true":
# 不知为何在 Github Actions 中运行时默认编码为 ANSI并且 print 需刷新流才能正常显示
for stream in [sys.stdout, sys.stderr]:
stream.reconfigure(encoding="utf-8")
# 存在 MAJOR 环境变量则发布新版本
majorVersion = os.environ["MAJOR"]
except:
pass
platform = "x64"
if len(sys.argv) >= 2:
platform = sys.argv[1]
if not platform in ["x64", "ARM64"]:
raise Exception("非法参数")
if majorVersion != None:
import re
minorVersion = os.environ["MINOR"]
patchVersion = os.environ["PATCH"]
tag = os.environ["TAG"]
argParser = argparse.ArgumentParser()
argParser.add_argument("--compiler", choices=["MSVC", "ClangCL"], default="MSVC")
argParser.add_argument("--platform", choices=["x64", "ARM64"], default="x64")
argParser.add_argument("--use-native-march", action="store_true")
argParser.add_argument("--version-major", type=int, default=0)
argParser.add_argument("--version-minor", type=int, default=0)
argParser.add_argument("--version-patch", type=int, default=0)
argParser.add_argument("--version-string", default="")
argParser.add_argument("--pfx-path", default="")
argParser.add_argument("--pfx-password", default="")
args = argParser.parse_args()
#####################################################################
#
@ -57,48 +51,16 @@ if not os.access(msbuildPath, os.X_OK):
#
#####################################################################
os.chdir(os.path.dirname(__file__))
os.chdir(os.path.dirname(__file__) + "\\..")
p = subprocess.run("git rev-parse --short HEAD", capture_output=True)
commitId = str(p.stdout, encoding="utf-8")[0:-1]
if majorVersion != None:
version_props = f";MajorVersion={majorVersion};MinorVersion={minorVersion};PatchVersion={patchVersion};VersionTag={tag}"
# 更新 RC 文件中的版本号
version = f"{majorVersion}.{minorVersion}.{patchVersion}.0"
version_comma = version.replace(".", ",")
for project in os.listdir("src"):
rcPath = f"src\\{project}\\{project}.rc"
if not os.access(rcPath, os.R_OK | os.W_OK):
continue
with open(rcPath, mode="r+", encoding="utf-8") as f:
src = f.read()
src = re.sub(
r"FILEVERSION .*?\n", "FILEVERSION " + version_comma + "\n", src
)
src = re.sub(
r"PRODUCTVERSION .*?\n", "PRODUCTVERSION " + version_comma + "\n", src
)
src = re.sub(
r'"FileVersion", *?".*?"\n', '"FileVersion", "' + version + '"\n', src
)
src = re.sub(
r'"ProductVersion", *?".*?"\n',
'"ProductVersion", "' + version + '"\n',
src,
)
f.seek(0)
f.truncate()
f.write(src)
else:
version_props = ""
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}" -restore -p:RestorePackagesConfig=true;Configuration=Release;Platform={platform};OutDir={os.getcwd()}\\publish\\{platform}\\;CommitId={commitId}{version_props} Magpie.sln'
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("编译失败")
@ -109,7 +71,7 @@ if p.returncode != 0:
#
#####################################################################
os.chdir("publish\\" + platform)
os.chdir("publish\\" + args.platform)
# 删除文件,忽略错误
@ -120,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)
@ -132,17 +93,16 @@ print("清理完毕", flush=True)
#
#####################################################################
if len(sys.argv) >= 5 and sys.argv[4] != "":
# sys.argv[2] 保留为打包选项
pfxPath = os.path.join("..\\..", sys.argv[3])
pfxPassword = sys.argv[4]
if args.pfx_path != "":
pfxPath = os.path.join("..\\..", args.pfx_path)
# 取最新的 Windows SDK
windowsSdkDir = max(
glob.glob(programFilesX86Path + "\\Windows Kits\\10\\bin\\10.*")
)
passwordOption = "" if args.pfx_password == "" else f'/p "{args.pfx_password}"'
p = subprocess.run(
f'"{windowsSdkDir}\\x64\\signtool.exe" sign /fd SHA256 /a /f "{pfxPath}" /p "{pfxPassword}" TouchHelper.exe'
f'"{windowsSdkDir}\\x64\\signtool.exe" sign /fd SHA256 /a /f "{pfxPath}" {passwordOption} TouchHelper.exe'
)
if p.returncode != 0:
raise Exception("签名失败")

View file

@ -5,6 +5,7 @@ import shutil
import requests
import hashlib
import json
import argparse
try:
# https://docs.github.com/en/actions/learn-github-actions/variables
@ -15,12 +16,17 @@ try:
except:
pass
majorVersion = os.environ["MAJOR"]
minorVersion = os.environ["MINOR"]
patchVersion = os.environ["PATCH"]
tag = os.environ["TAG"]
isPrerelease = os.environ["PRERELEASE"].lower() == "true"
githubAccessToken = os.environ["ACCESS_TOKEN"]
argParser = argparse.ArgumentParser()
argParser.add_argument("version_major", type=int)
argParser.add_argument("version_minor", type=int)
argParser.add_argument("version_patch", type=int)
argParser.add_argument("tag")
argParser.add_argument("prerelease")
argParser.add_argument("access_token")
args = argParser.parse_args()
isPrerelease = args.prerelease.lower() == "true"
repo = os.environ["GITHUB_REPOSITORY"]
actor = os.environ["GITHUB_ACTOR"]
@ -28,21 +34,21 @@ subprocess.run("git config user.name " + actor)
subprocess.run(f"git config user.email {actor}@users.noreply.github.com")
subprocess.run(
f"git remote set-url origin https://{githubAccessToken}@github.com/{repo}.git"
f"git remote set-url origin https://{args.access_token}@github.com/{repo}.git"
)
# 打标签
if subprocess.run(f"git tag -a {tag} -m {tag}").returncode != 0:
if subprocess.run(f"git tag -a {args.tag} -m {args.tag}").returncode != 0:
raise Exception("打标签失败")
if subprocess.run("git push origin " + tag).returncode != 0:
if subprocess.run("git push origin " + args.tag).returncode != 0:
raise Exception("推送标签失败")
print("已创建标签 " + tag, flush=True)
print("已创建标签 " + args.tag, flush=True)
headers = {
"Accept": "application/vnd.github+json",
"Authorization": "Bearer " + githubAccessToken,
"Authorization": "Bearer " + args.access_token,
"X-GitHub-Api-Version": "2022-11-28",
}
@ -53,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"]
@ -75,13 +81,13 @@ if prevReleaseTag == None:
body = ""
else:
# 默认发行说明为比较两个 tag
body = f"https://github.com/{repo}/compare/{prevReleaseTag}...{tag}"
body = f"https://github.com/{repo}/compare/{prevReleaseTag}...{args.tag}"
response = requests.post(
f"https://api.github.com/repos/{repo}/releases",
json={
"tag_name": tag,
"name": tag,
"tag_name": args.tag,
"name": args.tag,
"prerelease": isPrerelease,
"body": body,
"discussion_category_name": "Announcements",
@ -99,7 +105,7 @@ os.chdir(os.path.dirname(__file__) + "\\..\\publish")
pkgInfos = {}
for platform in ["x64", "ARM64"]:
# 打包成 zip
pkgName = "Magpie-" + tag + "-" + platform
pkgName = "Magpie-" + args.tag + "-" + platform
shutil.make_archive(pkgName, "zip", pkgName)
pkgName += ".zip"
@ -122,7 +128,7 @@ for platform in ["x64", "ARM64"]:
pkgInfos[platform] = (pkgName, md5)
print("已发布 " + tag, flush=True)
print("已发布 " + args.tag, flush=True)
# 更新 version.json
# 此步应在发布版本之后,因为程序使用 version.json 检查更新
@ -130,15 +136,15 @@ os.chdir("..")
with open("version.json", "w", encoding="utf-8") as f:
json.dump(
{
"version": f"{majorVersion}.{minorVersion}.{patchVersion}",
"tag": tag,
"version": f"{args.version_major}.{args.version_minor}.{args.version_patch}",
"tag": args.tag,
"binary": {
"x64": {
"url": f"https://github.com/{repo}/releases/download/{tag}/{pkgInfos['x64'][0]}",
"url": f"https://github.com/{repo}/releases/download/{args.tag}/{pkgInfos['x64'][0]}",
"hash": pkgInfos["x64"][1],
},
"ARM64": {
"url": f"https://github.com/{repo}/releases/download/{tag}/{pkgInfos['ARM64'][0]}",
"url": f"https://github.com/{repo}/releases/download/{args.tag}/{pkgInfos['ARM64'][0]}",
"hash": pkgInfos["ARM64"][1],
},
},

View file

@ -4,6 +4,7 @@ import tempfile
import glob
import shutil
import subprocess
import argparse
try:
# https://docs.github.com/en/actions/learn-github-actions/variables
@ -14,12 +15,11 @@ try:
except:
pass
if not "ACCESS_TOKEN" in os.environ:
raise Exception("未找到环境变量 ACCESS_TOKEN")
argParser = argparse.ArgumentParser()
argParser.add_argument("access_token")
args = argParser.parse_args()
wikiRepoUrl = os.path.expandvars(
"https://${ACCESS_TOKEN}@github.com/${GITHUB_REPOSITORY}.wiki.git"
)
wikiRepoUrl = f'https://{args.access_token}@github.com/{os.environ["GITHUB_REPOSITORY"]}.wiki.git'
# 创建临时目录
wikiRepoDir = tempfile.mkdtemp()

View file

@ -2,21 +2,26 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- 不要直接修改这些选项,应通过 BuildOptions.props.user 或命令行参数覆盖 -->
<PropertyGroup>
<CommitId></CommitId>
<MajorVersion></MajorVersion>
<MinorVersion></MinorVersion>
<PatchVersion></PatchVersion>
<VersionTag></VersionTag>
<!-- 窗口模式缩放时把用于调整窗口尺寸的辅助窗口标示出来 -->
<DebugBorder>false</DebugBorder>
<!-- 在性能分析器上显示调试信息 -->
<DebugInfoOnOverlay>false</DebugInfoOnOverlay>
<!--使用 composition swapchain 呈现-->
<UseCompSwapchain>false</UseCompSwapchain>
<!-- 使用 clang-cl 编译 -->
<UseClangCL>false</UseClangCL>
<!-- 针对当前硬件生成优化代码,只在使用 clang-cl 编译时有效 -->
<UseNativeMicroArch>false</UseNativeMicroArch>
<!-- 编译为打包应用 (暂不支持) -->
<IsPackaged>false</IsPackaged>
<!-- 窗口模式缩放时把用于调整窗口尺寸的辅助窗口标示出来 -->
<DebugBorder>false</DebugBorder>
<!-- 在性能分析器上显示调试信息 -->
<DebugInfoOnOverlay>false</DebugInfoOnOverlay>
<!-- 使用 composition swapchain 呈现 -->
<UseCompSwapchain>false</UseCompSwapchain>
<MajorVersion></MajorVersion>
<MinorVersion></MinorVersion>
<PatchVersion></PatchVersion>
<VersionString></VersionString>
<CommitId></CommitId>
</PropertyGroup>
<!-- 用户自定义编译选项 -->
<Import Project="$(MSBuildThisFileDirectory)BuildOptions.props.user" Condition="Exists('$(MSBuildThisFileDirectory)BuildOptions.props.user')" />
<Import Project="$(MSBuildThisFileDirectory)\BuildOptions.props.user" Condition="Exists('$(MSBuildThisFileDirectory)\BuildOptions.props.user')" />
</Project>

View file

@ -1,61 +1,94 @@
<?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 Condition="'$(CommitId)'!=''">MP_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(MajorVersion)'!='' And '$(MinorVersion)'!='' And '$(PatchVersion)'!='' And '$(VersionTag)'!=''">MP_MAJOR_VERSION=$(MajorVersion);MP_MINOR_VERSION=$(MinorVersion);MP_PATCH_VERSION=$(PatchVersion);MP_VERSION_TAG=$(VersionTag);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(DebugBorder)'=='true'">MP_DEBUG_BORDER;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(DebugInfoOnOverlay)'=='true'">MP_DEBUG_INFO_ON_OVERLAY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(UseCompSwapchain)'=='true'">MP_USE_COMPSWAPCHAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_WINDOWS;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;WINRT_NO_MODULE_LOCK;WIL_SUPPRESS_EXCEPTIONS;WIL_USE_STL=1;NOGDICAPMASKS;NOICONS;NOATOM;NOCLIPBOARD;NODRAWTEXT;NOMEMMGR;NOMETAFILE;NOMINMAX;NOOPENFILE;NOSCROLL;NOSERVICE;NOSOUND;NOTEXTMETRIC;NOCOMM;NOKANJI;NOHELP;NOPROFILER;NOMCX;NO_SHLWAPI_PATH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>MP_MAJOR_VERSION=$(MajorVersion);MP_MINOR_VERSION=$(MinorVersion);MP_PATCH_VERSION=$(PatchVersion);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(VersionString)' != ''">MP_VERSION_STRING=$(VersionString);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(CommitId)' != ''">MP_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(DebugBorder)">MP_DEBUG_BORDER;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(DebugInfoOnOverlay)">MP_DEBUG_INFO_ON_OVERLAY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="$(UseCompSwapchain)">MP_USE_COMPSWAPCHAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat Condition="'$(DisablePDB)' == 'true'">None</DebugInformationFormat>
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
<!-- /await:strict: 禁用协程的非标准语言扩展 -->
<AdditionalOptions Condition="!$(UseClangCL)">/await:strict %(AdditionalOptions)</AdditionalOptions>
<!-- -fstrict-vtable-pointers: 缓存虚表指针,这要求不能通过非法手段改变对象的动态类型 -->
<!-- -funsafe-math-optimizations: 以降低精度为代价提高浮点运算速度 -->
<!-- -fno-math-errno: 浮点操作不设置 errno这使一些数学函数可以内联 -->
<AdditionalOptions Condition="$(UseClangCL)">/clang:-Wno-missing-designated-field-initializers /clang:-Wno-missing-field-initializers /clang:-fstrict-vtable-pointers /clang:-funsafe-math-optimizations /clang:-fno-math-errno %(AdditionalOptions)</AdditionalOptions>
<!-- -mcx16: 启用 CX16 指令Windows 从 8.1 开始要求 CPU 支持它 -->
<AdditionalOptions Condition="$(UseClangCL) And '$(Platform)' == 'x64'">/clang:-mcx16 %(AdditionalOptions)</AdditionalOptions>
<!-- -march=native: 针对当前硬件生成优化代码 -->
<AdditionalOptions Condition="$(UseClangCL) And $(UseNativeMicroArch)">/clang:-march=native %(AdditionalOptions)</AdditionalOptions>
<!-- 使用 clang-cl 编译时禁用 cppwinrt 头文件中的编译警告 -->
<AdditionalOptions Condition="$(UseClangCL) And '$(GeneratedFilesDir)' != ''">/clang:-isystem /clang:"$(GeneratedFilesDir)\" %(AdditionalOptions)</AdditionalOptions>
<!-- 修复编译 Shared 中源文件找不到 pch.h 的问题 -->
<AdditionalIncludeDirectories Condition="$(UseClangCL)">.;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>MP_MAJOR_VERSION=$(MajorVersion);MP_MINOR_VERSION=$(MinorVersion);MP_PATCH_VERSION=$(PatchVersion);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(VersionString)' != ''">MP_VERSION_STRING=$(VersionString);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(CommitId)' != ''">MP_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\Shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ResourceCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<GenerateDebugInformation Condition="'$(DisablePDB)' == 'true'">false</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ItemDefinitionGroup Condition="'$(Configuration)' == 'Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ResourceCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ItemDefinitionGroup Condition="'$(Configuration)' == 'Release'">
<ClCompile>
<!-- Release 下不允许编译警告 -->
<TreatWarningAsError>true</TreatWarningAsError>
<!-- 存在同名全局属性,但表现很奇怪。首先,在导入 Microsoft.Cpp.Default.props 之前和之后定义 -->
<!-- 效果不同,似乎在导入前定义才能起作用;其次,全局 WholeProgramOptimization 属性起作用时由 -->
<!-- Microsoft.Cpp.targets 而不是 Microsoft.Cpp.props 更改编译选项,将用户设置覆盖;最后,该 -->
<!-- 属性起作用时会将 LinkTimeCodeGeneration 改为 UseFastLinkTimeCodeGeneration而我们想要 -->
<!-- UseLinkTimeCodeGeneration。为了精确控制编译参数我们不使用全局 -->
<!-- WholeProgramOptimization而是自己定义编译选项。全局 WholeProgramOptimization 影响的属 -->
<!-- 性见 $(VCTargetsPath)\Microsoft.Cpp.WholeProgramOptimization.props。 -->
<WholeProgramOptimization>true</WholeProgramOptimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<!-- /Gw: 链接时删除未使用和重复的数据,可以减小二进制文件体积 -->
<!-- /Zc:checkGwOdr: 防止 /Gw 导致某些 ODR 违规被忽略 -->
<AdditionalOptions>/Gw %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="!$(UseClangCL)">/Zc:checkGwOdr %(AdditionalOptions)</AdditionalOptions>
<!-- clang-cl 不支持 /LTCG应使用 LTO -->
<AdditionalOptions Condition="$(UseClangCL)">/clang:-flto %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
<Lib>
<LinkTimeCodeGeneration>true</LinkTimeCodeGeneration>
</Lib>
</ItemDefinitionGroup>
<!-- 所有项目共享的头文件 -->
<Import Project="$(MSBuildThisFileDirectory)Shared\Shared.vcxitems" Label="Shared" />
<Import Project="$(MSBuildThisFileDirectory)\Shared\Shared.vcxitems" Label="Shared" />
<!-- Conan 依赖 -->
<Import Project="$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props" Condition="Exists('$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props')" />
<Import Project="$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props" Condition="Exists('$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\$(MSBuildProjectName)\conandeps.props')" />
<!-- HybridCRT -->
<Import Project="$(MSBuildThisFileDirectory)HybridCRT.props" />
<Import Project="$(MSBuildThisFileDirectory)\HybridCRT.props" />
<!-- _CopyFilesMarkedCopyLocal 有一个 bug即使没有复制任何文件也会更改 @(FileWrites),经常导致 -->
<!-- up-to-date 检查失败。这个任务用于在 _CopyFilesMarkedCopyLocal 执行后修正 @(FileWrites)。 -->

View file

@ -1,30 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Microsoft.Cpp.Default.props 之后导入 -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<IsDebug>$(Configuration.StartsWith('Debug'))</IsDebug>
<IsPackaged>$(Configuration.EndsWith('Packaged'))</IsPackaged>
<!-- 编译选项 -->
<Import Project="$(MSBuildThisFileDirectory)\BuildOptions.props" />
<PropertyGroup Label="Globals">
<VS17 Condition="$([System.String]::new('$(MSBuildVersion)').StartsWith('17'))">true</VS17>
<VS17 Condition="'$(VS17)' != 'true'">false</VS17>
<VCProjectVersion Condition="$(VS17)">17.0</VCProjectVersion>
<VCProjectVersion Condition="!$(VS17)">18.0</VCProjectVersion>
<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>
<!-- 编译选项 -->
<Import Project="$(MSBuildThisFileDirectory)BuildOptions.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset Condition="$(UseClangCL)">ClangCL</PlatformToolset>
<PlatformToolset Condition="!$(UseClangCL) And $(VS17)">v143</PlatformToolset>
<PlatformToolset Condition="!$(UseClangCL) And !$(VS17)">v145</PlatformToolset>
<UseDebugLibraries Condition="'$(Configuration)' == 'Debug'">true</UseDebugLibraries>
</PropertyGroup>
</Project>

View file

@ -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);

View file

@ -1,18 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{62503530-b84b-4cc2-80b6-3f89618172b7}</ProjectGuid>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\Common.Pre.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Utility</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="PropertySheets">
@ -20,7 +18,7 @@
</ImportGroup>
<ItemDefinitionGroup>
<CopyFileToFolders>
<DestinationFolders>$(OutDir)effects</DestinationFolders>
<DestinationFolders>$(OutDir)\effects</DestinationFolders>
<DestinationFileName>%(RelativeDir)%(Filename)%(Extension)</DestinationFileName>
</CopyFileToFolders>
</ItemDefinitionGroup>
@ -429,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>
@ -464,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>

View file

@ -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
View 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);
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
#include "pch.h"
#include "AdaptivePresenter.h"
#include "DeviceResources.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "DeviceResources.h"
#include "Win32Helper.h"
namespace Magpie {
@ -15,6 +15,7 @@ bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
return false;
}
_isDCompPresenting = true;
return true;
}
@ -46,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,
@ -101,7 +102,7 @@ bool AdaptivePresenter::BeginFrame(
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
POINT& drawOffset
) noexcept {
if (_dcompSurface) {
if (_isDCompPresenting) {
HRESULT hr = _dcompSurface->BeginDraw(nullptr, IID_PPV_ARGS(&frameTex), &drawOffset);
if (FAILED(hr)) {
Logger::Get().ComError("BeginDraw 失败", hr);
@ -129,12 +130,14 @@ bool AdaptivePresenter::BeginFrame(
return true;
}
void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
if (_dcompSurface) {
void AdaptivePresenter::EndFrame(bool waitForGpu) noexcept {
if (_isDCompPresenting) {
_dcompSurface->EndDraw();
}
if (waitForRenderComplete || _isResized) {
if (waitForGpu || _isResized) {
_isResized = false;
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁。
//
// 我们希望 DWM 绘制新的窗口框架时刚好合成新帧,但这不是我们能控制的,尤其是混合架构
@ -151,13 +154,13 @@ void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
// 实用价值。
// 等待渲染完成
_WaitForRenderComplete();
_WaitForGpu();
// 等待 DWM 开始合成新一帧
_WaitForDwmComposition();
Win32Helper::WaitForDwmComposition();
}
if (_dcompSurface) {
if (_isDCompPresenting) {
_dcompDevice->Commit();
} else {
// 两个垂直同步之间允许渲染数帧SyncInterval = 0 只呈现最新的一帧,旧帧被丢弃
@ -171,22 +174,14 @@ void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
_isSwitchingToSwapChain = false;
// 等待交换链呈现新帧
_WaitForRenderComplete();
_WaitForDwmComposition();
_WaitForGpu();
Win32Helper::WaitForDwmComposition();
// 清除 DirectCompostion 内容
_dcompVisual->SetContent(nullptr);
_dcompSurface = nullptr;
_dcompDevice->Commit();
}
}
if (_isResized) {
_isResized = false;
} else {
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
_WaitForRenderComplete();
}
}
bool AdaptivePresenter::OnResize() noexcept {
@ -194,7 +189,8 @@ bool AdaptivePresenter::OnResize() noexcept {
if (ScalingWindow::Get().IsResizingOrMoving() || !_dxgiSwapChain) {
// 切换到 DirectComposition 呈现,失败则回落到交换链
if (_ResizeDCompVisual()) {
_isDCompPresenting = _ResizeDCompVisual();
if (_isDCompPresenting) {
return true;
}
@ -215,7 +211,7 @@ bool AdaptivePresenter::OnResize() noexcept {
}
void AdaptivePresenter::OnEndResize(bool& shouldRedraw) noexcept {
if (!_dcompSurface || !_dxgiSwapChain) {
if (!_isDCompPresenting || !_dxgiSwapChain) {
shouldRedraw = false;
return;
}
@ -223,7 +219,7 @@ void AdaptivePresenter::OnEndResize(bool& shouldRedraw) noexcept {
shouldRedraw = true;
_ResizeSwapChain();
_dcompSurface = nullptr;
_isDCompPresenting = false;
// 交换链呈现新帧后再清除 DirectCompostion 内容,确保无缝切换
_isSwitchingToSwapChain = true;
}
@ -271,10 +267,16 @@ bool AdaptivePresenter::_ResizeSwapChain() noexcept {
}
bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept {
if (_dcompVisual) {
// 先释放旧表面
_dcompVisual->SetContent(nullptr);
_dcompSurface = nullptr;
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
if (_dcompSurface) {
// 使用 IDCompositionVirtualSurface 而不是 IDCompositionSurface 的原因是
// IDCompositionDevice2::CreateSurface 有时相当慢,最坏情况下要几十毫秒。
HRESULT hr = _dcompSurface->Resize((UINT)rendererSize.cx, (UINT)rendererSize.cy);
if (FAILED(hr)) {
Logger::Get().ComError("Resize 失败", hr);
return false;
}
} else {
// 初始化 DirectComposition
HRESULT hr = DCompositionCreateDevice3(
@ -310,26 +312,23 @@ bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept {
Logger::Get().ComError("SetRoot 失败", hr);
return false;
}
hr = _dcompDevice->CreateVirtualSurface(
(UINT)rendererSize.cx,
(UINT)rendererSize.cy,
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_ALPHA_MODE_IGNORE,
_dcompSurface.put()
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateVirtualSurface 失败", hr);
return false;
}
}
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
HRESULT hr = _dcompDevice->CreateSurface(
(UINT)rendererSize.cx,
(UINT)rendererSize.cy,
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_ALPHA_MODE_IGNORE,
_dcompSurface.put()
);
if (FAILED(hr)) {
Logger::Get().ComError("CreateSurface 失败", hr);
return false;
}
hr = _dcompVisual->SetContent(_dcompSurface.get());
HRESULT hr = _dcompVisual->SetContent(_dcompSurface.get());
if (FAILED(hr)) {
Logger::Get().ComError("SetContent 失败", hr);
// 失败时确保 _dcompSurface 为空
_dcompSurface = nullptr;
return false;
}

View file

@ -7,7 +7,7 @@ namespace Magpie {
// 根据需要在交换链和 DirectComposition 两种呈现方式间切换。交换链可以触发
// DirectFlip/IndependentFlip 以最小化延迟DirectComposition 在调整尺寸
// 时闪烁更少,这个呈现器旨在结合两者的优势。
class AdaptivePresenter : public PresenterBase {
class AdaptivePresenter final : public PresenterBase {
protected:
bool _Initialize(HWND hwndAttach) noexcept override;
@ -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;
@ -38,8 +38,9 @@ private:
winrt::com_ptr<IDCompositionDesktopDevice> _dcompDevice;
winrt::com_ptr<IDCompositionTarget> _dcompTarget;
winrt::com_ptr<IDCompositionVisual2> _dcompVisual;
winrt::com_ptr<IDCompositionSurface> _dcompSurface;
winrt::com_ptr<IDCompositionVirtualSurface> _dcompSurface;
bool _isDCompPresenting = false;
bool _isResized = false;
bool _isframeLatencyWaited = false;
bool _isSwitchingToSwapChain = false;

View file

@ -2,20 +2,21 @@
#include "CompSwapchainPresenter.h"
#include "DeviceResources.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "ScalingWindow.h"
#include "Win32Helper.h"
namespace Magpie {
static winrt::com_ptr<IPresentationFactory> CreatePresentationFactory(ID3D11Device* d3dDevice) noexcept {
static const auto createPresentationFactory = []() {
HMODULE hDcomp = GetModuleHandle(L"dcomp.dll");
assert(hDcomp);
return (decltype(::CreatePresentationFactory)*)GetProcAddress(
hDcomp, "CreatePresentationFactory");
}();
winrt::com_ptr<IPresentationFactory> result;
static const auto createPresentationFactory =
Win32Helper::LoadSystemFunction<decltype(::CreatePresentationFactory)>(
L"dcomp.dll", "CreatePresentationFactory");
if (!createPresentationFactory) {
return result;
}
HRESULT hr = createPresentationFactory(d3dDevice, IID_PPV_ARGS(&result));
if (FAILED(hr)) {
Logger::Get().ComError("CreatePresentationFactory 失败", hr);
@ -30,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;
@ -57,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;
@ -227,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();
@ -244,7 +243,7 @@ void CompSwapchainPresenter::EndFrame(bool waitForRenderComplete) noexcept {
_isResized = false;
} else {
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
_WaitForRenderComplete();
_WaitForGpu();
}
}

View file

@ -5,7 +5,7 @@
namespace Magpie {
class CompSwapchainPresenter : public PresenterBase {
class CompSwapchainPresenter final : public PresenterBase {
protected:
bool _Initialize(HWND hwndAttach) noexcept override;
@ -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;

View file

@ -1,19 +1,18 @@
#include "pch.h"
#include "CursorDrawer.h"
#include "CursorManager.h"
#include "DeviceResources.h"
#include "Logger.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "Renderer.h"
#include "ScalingOptions.h"
#include "shaders/SimpleVS.h"
#include "shaders/SimplePS.h"
#include "ScalingWindow.h"
#include "shaders/MaskedCursorPS.h"
#include "shaders/MonochromeCursorPS.h"
#include <DirectXMath.h>
#include "shaders/SimplePS.h"
#include "shaders/SimpleVS.h"
#include "Win32Helper.h"
#include "ScalingWindow.h"
#include "Renderer.h"
#include "CursorManager.h"
#include "StrHelper.h"
#include <DirectXMath.h>
using namespace DirectX;
@ -40,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 },
};
};
@ -87,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 ||
@ -203,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(
@ -216,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 &&
@ -260,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());
@ -304,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);
}
@ -320,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 {

View file

@ -1,6 +1,5 @@
#pragma once
#include <parallel_hashmap/phmap.h>
#include "ScalingOptions.h"
namespace Magpie {
@ -27,6 +26,8 @@ public:
bool NeedRedraw() const noexcept;
private:
std::pair<HCURSOR, POINT> _GetCursorState(bool& isActive) const noexcept;
enum class _CursorType {
// 彩色光标,此时纹理中 RGB 通道已预乘 A 通道premultiplied alphaA 通道已预先取反
// 这是为了减少着色器的计算量以及确保(可能进行的)双线性差值的准确性
@ -71,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() };

View file

@ -1,12 +1,12 @@
#include "pch.h"
#include "CursorManager.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "Renderer.h"
#include "ScalingOptions.h"
#include "ScalingWindow.h"
#include "Renderer.h"
#include <magnification.h>
#include "Win32Helper.h"
#include <dwmapi.h>
#include <magnification.h>
namespace Magpie {
@ -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);
}
}
@ -200,11 +201,8 @@ void CursorManager::_ShowSystemCursor(bool show, bool onDestory) {
return;
}
static void (WINAPI* const showSystemCursor)(BOOL bShow) = []()->void(WINAPI*)(BOOL) {
HMODULE hUser32 = GetModuleHandle(L"user32.dll");
assert(hUser32);
return (void(WINAPI*)(BOOL))GetProcAddress(hUser32, "ShowSystemCursor");
}();
static const auto showSystemCursor =
Win32Helper::LoadSystemFunction<void WINAPI(BOOL)>(L"user32.dll", "ShowSystemCursor");
if (showSystemCursor) {
showSystemCursor((BOOL)show);
@ -398,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)) {
@ -582,7 +580,6 @@ void CursorManager::_UpdateCursorState() noexcept {
POINT cursorPos;
if (!GetCursorPos(&cursorPos)) {
Logger::Get().Win32Error("GetCursorPos 失败");
_RestoreClipCursor();
return;
}
@ -969,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) {
@ -1114,7 +1111,6 @@ void CursorManager::_StartCapture(POINT& cursorPos) noexcept {
}
const Renderer& renderer = ScalingWindow::Get().Renderer();
const RECT& srcRect = renderer.SrcRect();
const RECT& destRect = renderer.DestRect();
// 在以下情况下进入捕获状态:
@ -1127,9 +1123,6 @@ void CursorManager::_StartCapture(POINT& cursorPos) noexcept {
//
// 在有黑边的情况下自动将光标调整到画面内
SIZE srcFrameSize = Win32Helper::GetSizeOfRect(srcRect);
SIZE outputSize = Win32Helper::GetSizeOfRect(destRect);
_AdjustCursorSpeed();
// 移动光标位置,应跳过黑边

View file

@ -52,7 +52,7 @@ public:
}
void IsCursorCapturedOnOverlay(bool value) noexcept;
const int16_t SrcHitTest() const noexcept {
int16_t SrcHitTest() const noexcept {
return _lastCompletedHitTestResult;
}

View file

@ -1,5 +1,5 @@
#include "pch.h"
#include "DDShelper.h"
#include "DDSHelper.h"
#include "DDS.h"
#include "Logger.h"

View file

@ -1,11 +1,11 @@
#include "pch.h"
#include "DesktopDuplicationFrameSource.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "ScalingWindow.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "SmallVector.h"
#include "Win32Helper.h"
namespace Magpie {

View file

@ -1,6 +1,5 @@
#pragma once
#include "FrameSourceBase.h"
#include "Win32Helper.h"
#include "SmallVector.h"
namespace Magpie {

View file

@ -1,10 +1,10 @@
#include "pch.h"
#include "DeviceResources.h"
#include "ScalingOptions.h"
#include "Logger.h"
#include "StrHelper.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingOptions.h"
#include "ScalingWindow.h"
#include "StrHelper.h"
namespace Magpie {

View file

@ -1,6 +1,6 @@
#pragma once
#include <parallel_hashmap/phmap.h>
#include "ScalingOptions.h"
#include <parallel_hashmap/phmap.h>
namespace Magpie {

View file

@ -1,8 +1,8 @@
#include "pch.h"
#include "DirectXHelper.h"
#include <d3dcompiler.h>
#include "Logger.h"
#include "StrHelper.h"
#include <d3dcompiler.h>
namespace Magpie {

View file

@ -1,9 +1,10 @@
#include "pch.h"
#include "DwmSharedSurfaceFrameSource.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "DirectXHelper.h"
#include "DeviceResources.h"
#include "Win32Helper.h"
namespace Magpie {
@ -16,18 +17,18 @@ using DwmGetDxSharedSurfaceFunc = BOOL(
ULONGLONG* pWin32KUpdateId
);
static DwmGetDxSharedSurfaceFunc* dwmGetDxSharedSurface = nullptr;
static DwmGetDxSharedSurfaceFunc* DwmGetDxSharedSurface = nullptr;
bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
if (!dwmGetDxSharedSurface) {
HMODULE hUser32 = GetModuleHandle(L"user32.dll");
assert(hUser32);
dwmGetDxSharedSurface = (DwmGetDxSharedSurfaceFunc*)GetProcAddress(hUser32, "DwmGetDxSharedSurface");
[[maybe_unused]] static Ignore _ = [] {
DwmGetDxSharedSurface = Win32Helper::LoadSystemFunction<DwmGetDxSharedSurfaceFunc>(
L"user32.dll", "DwmGetDxSharedSurface");
return Ignore();
}();
if (!dwmGetDxSharedSurface) {
Logger::Get().Win32Error("获取函数 DwmGetDxSharedSurface 地址失败");
return false;
}
if (!DwmGetDxSharedSurface) {
Logger::Get().Win32Error("获取函数 DwmGetDxSharedSurface 地址失败");
return false;
}
const SrcTracker& srcTracker = ScalingWindow::Get().SrcTracker();
@ -85,7 +86,7 @@ bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
FrameSourceState DwmSharedSurfaceFrameSource::_Update() noexcept {
HANDLE sharedTextureHandle = NULL;
if (!dwmGetDxSharedSurface(ScalingWindow::Get().SrcTracker().Handle(),
if (!DwmGetDxSharedSurface(ScalingWindow::Get().SrcTracker().Handle(),
&sharedTextureHandle, nullptr, nullptr, nullptr, nullptr)
|| !sharedTextureHandle
) {

View file

@ -1,11 +1,12 @@
#include "pch.h"
#include "EffectCacheManager.h"
#include "StrHelper.h"
#include "Logger.h"
#include "CommonSharedConstants.h"
#include "Logger.h"
#include "StrHelper.h"
#include "Win32Helper.h"
#include "YasHelper.h"
#include <d3dcompiler.h>
#include <rapidhash.h>
#include "YasHelper.h"
namespace yas::detail {
@ -255,7 +256,7 @@ void EffectCacheManager::Save(
int i = 6;
for (; i < 22; ++i) {
const wchar_t c = fileName[effectNameLen + i];
if (!(c >= L'0' && c <= L'9' || c >= L'a' && c <= L'f')) {
if (!((c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f'))) {
break;
}
}
@ -271,7 +272,7 @@ void EffectCacheManager::Save(
int i = 1;
for (; i < 18; ++i) {
const wchar_t c = fileName[effectNameLen + i];
if (!(c >= L'0' && c <= L'9' || c >= L'a' && c <= L'f')) {
if (!((c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f'))) {
break;
}
}

View file

@ -1,5 +1,4 @@
#pragma once
#include "Win32Helper.h"
#include "EffectDesc.h"
#include <parallel_hashmap/phmap.h>

View file

@ -1,16 +1,15 @@
#include "pch.h"
#include "EffectCompiler.h"
#include <bitset>
#include <charconv>
#include "EffectCacheManager.h"
#include "StrHelper.h"
#include "Logger.h"
#include "CommonSharedConstants.h"
#include <bit> // std::has_single_bit
#include "DirectXHelper.h"
#include "EffectHelper.h"
#include "Win32Helper.h"
#include "EffectCacheManager.h"
#include "EffectDesc.h"
#include "EffectHelper.h"
#include "Logger.h"
#include "StrHelper.h"
#include "Win32Helper.h"
#include <bit> // std::has_single_bit
#include <bitset>
namespace Magpie {
@ -1160,21 +1159,23 @@ static uint32_t GeneratePassSource(
////////////////////////////////////////////////////////////////////////////////////////////////////////
// SRV
for (int i = 0; i < passDesc.inputs.size(); ++i) {
for (uint32_t i = 0, end = (uint32_t)passDesc.inputs.size(); i < end; ++i) {
auto& texDesc = desc.textures[passDesc.inputs[i]];
result.append(fmt::format("Texture2D<{}> {} : register(t{});\n", EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].srvTexelType, texDesc.name, i));
result.append(fmt::format("Texture2D<{}> {} : register(t{});\n",
EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].srvTexelType, texDesc.name, i));
}
// UAV
for (int i = 0; i < passDesc.outputs.size(); ++i) {
for (uint32_t i = 0, end = (uint32_t)passDesc.outputs.size(); i < end; ++i) {
auto& texDesc = desc.textures[passDesc.outputs[i]];
result.append(fmt::format("RWTexture2D<{}> {} : register(u{});\n", EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].uavTexelType, texDesc.name, i));
result.append(fmt::format("RWTexture2D<{}> {} : register(u{});\n",
EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].uavTexelType, texDesc.name, i));
}
if (!desc.samplers.empty()) {
// 采样器
for (int i = 0; i < desc.samplers.size(); ++i) {
for (uint32_t i = 0, end = (uint32_t)desc.samplers.size(); i < end; ++i) {
result.append(fmt::format("SamplerState {} : register(s{});\n", desc.samplers[i].name, i));
}
}
@ -1344,7 +1345,9 @@ MF4 MulAdd(MF4 x, MF4x4 y, MF4 a) {
//
////////////////////////////////////////////////////////////////////////////////////////////////////////
if (passDesc.flags & EffectPassFlags::PSStyle) {
if (passDesc.outputs.size() <= 1) {
const uint32_t outputCount = (uint32_t)passDesc.outputs.size();
if (outputCount <= 1u) {
std::string outputSize;
std::string outputPt;
if (passIdx == desc.passes.size()) {
@ -1397,7 +1400,7 @@ void __M(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {{
float2 pos = (gxy + 0.5f) * __pass{0}OutputPt;
float2 step = 8 * __pass{0}OutputPt;
)", passIdx));
for (int i = 0; i < passDesc.outputs.size(); ++i) {
for (uint32_t i = 0; i < outputCount; ++i) {
auto& texDesc = desc.textures[passDesc.outputs[i]];
result.append(fmt::format("\t{} c{};\n",
EffectHelper::FORMAT_DESCS[(uint32_t)texDesc.format].srvTexelType, i));
@ -1405,12 +1408,13 @@ void __M(uint3 tid : SV_GroupThreadID, uint3 gid : SV_GroupID) {{
std::string callPass = fmt::format("\tPass{}(pos, ", passIdx);
for (int i = 0; i < passDesc.outputs.size() - 1; ++i) {
for (uint32_t i = 0, end = outputCount - 1; i < end; ++i) {
callPass.append(fmt::format("c{}, ", i));
}
callPass.append(fmt::format("c{});\n", passDesc.outputs.size() - 1));
for (int i = 0; i < passDesc.outputs.size(); ++i) {
callPass.append(fmt::format("\t\t\t{}[gxy] = c{};\n", desc.textures[passDesc.outputs[i]].name, i));
callPass.append(fmt::format("c{});\n", outputCount - 1));
for (uint32_t i = 0; i < outputCount; ++i) {
callPass.append(fmt::format("\t\t\t{}[gxy] = c{};\n",
desc.textures[passDesc.outputs[i]].name, i));
}
result.append(fmt::format(R"({0}

View file

@ -1,16 +1,16 @@
#include "pch.h"
#include "EffectDrawer.h"
#include "ScalingOptions.h"
#include "Win32Helper.h"
#include "Logger.h"
#include "BackendDescriptorStore.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "EffectHelper.h"
#include "EffectsProfiler.h"
#include "Logger.h"
#include "ScalingOptions.h"
#include "ScalingWindow.h"
#include "StrHelper.h"
#include "TextureHelper.h"
#include "EffectHelper.h"
#include "DirectXHelper.h"
#include "ScalingWindow.h"
#include "BackendDescriptorStore.h"
#include "EffectsProfiler.h"
#include "Win32Helper.h"
namespace Magpie {

View file

@ -1,10 +1,8 @@
#pragma once
#include "EffectDesc.h"
#include "SmallVector.h"
#include "EffectHelper.h"
#pragma push_macro("_UNICODE")
// Conan 的 muparser 不含 UNICODE 支持
#pragma push_macro("_UNICODE")
#undef _UNICODE
#pragma warning(push)
#pragma warning(disable: 4310) // 类型强制转换截断常量值

View file

@ -1,6 +1,6 @@
#pragma once
#include <dxgi.h>
#include <cstdint>
#include <dxgi.h>
namespace Magpie {

View file

@ -1,6 +1,5 @@
#include "pch.h"
#include "EffectsProfiler.h"
#include "DeviceResources.h"
namespace Magpie {

View file

@ -1,6 +1,5 @@
#pragma once
#include "SmallVector.h"
#include "Win32Helper.h"
namespace Magpie {

View file

@ -1,5 +1,4 @@
#pragma once
#include "Win32Helper.h"
namespace Magpie {

View file

@ -1,14 +1,13 @@
#include "pch.h"
#include "FrameSourceBase.h"
#include "ScalingOptions.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "SmallVector.h"
#include "DirectXHelper.h"
#include "DeviceResources.h"
#include "shaders/DuplicateFrameCS.h"
#include "ScalingWindow.h"
#include "BackendDescriptorStore.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingOptions.h"
#include "ScalingWindow.h"
#include "shaders/DuplicateFrameCS.h"
#include "Win32Helper.h"
#include <dwmapi.h>
namespace Magpie {

View file

@ -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;
};
}

View file

@ -1,9 +1,8 @@
#include "pch.h"
#include "GDIFrameSource.h"
#include "Logger.h"
#include "ScalingOptions.h"
#include "DirectXHelper.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "Logger.h"
#include "ScalingWindow.h"
namespace Magpie {

View file

@ -1,14 +1,13 @@
#include "pch.h"
#include "GraphicsCaptureFrameSource.h"
#include "StrHelper.h"
#include "DeviceResources.h"
#include "Logger.h"
#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
#include "Win32Helper.h"
#include "DirectXHelper.h"
#include "ScalingOptions.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "StrHelper.h"
#include "Win32Helper.h"
#include <dwmapi.h>
#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
namespace winrt {
using namespace Windows::Graphics;
@ -91,11 +90,20 @@ FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept {
return FrameSourceState::Waiting;
}
// 取最新帧,帧率较低时可以有效降低延迟
while (true) {
if (winrt::Direct3D11CaptureFrame nextFrame = _captureFramePool.TryGetNextFrame()) {
frame = std::move(nextFrame);
} else {
break;
}
}
// 从帧获取 IDXGISurface
winrt::IDirect3DSurface d3dSurface = frame.Surface();
winrt::com_ptr<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess> dxgiInterfaceAccess(
d3dSurface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()
d3dSurface.try_as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>()
);
winrt::com_ptr<ID3D11Texture2D> withFrame;
@ -176,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();
@ -203,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 {
@ -282,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;
@ -329,7 +323,7 @@ bool GraphicsCaptureFrameSource::_StartCapture() noexcept {
_captureFramePool = winrt::Direct3D11CaptureFramePool::Create(
_wrappedD3DDevice,
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
1, // 帧的缓存数量
4, // 帧的缓存数量,更大的值有利于在低帧率下降低延迟
{ (int)_frameBox.right, (int)_frameBox.bottom } // 帧的尺寸为包含源窗口的最小尺寸
);
@ -387,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);
}
}

View file

@ -1,8 +1,8 @@
#pragma once
#include "FrameSourceBase.h"
#include <winrt/Windows.Graphics.Capture.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <ShlObj.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <winrt/Windows.Graphics.Capture.h>
namespace Magpie {
@ -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;
};
}

View file

@ -1,13 +1,11 @@
#include "pch.h"
#include "ImGuiBackend.h"
#include <d3dcompiler.h>
#include <imgui.h>
#include "DeviceResources.h"
#include "StrHelper.h"
#include "Logger.h"
#include "DirectXHelper.h"
#include "shaders/ImGuiImplVS.h"
#include "Logger.h"
#include "shaders/ImGuiImplPS.h"
#include "shaders/ImGuiImplVS.h"
#include <imgui.h>
namespace Magpie {
@ -84,7 +82,7 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData, POINT viewportOffs
_vertexBufferSize = drawData.TotalVtxCount + 5000;
D3D11_BUFFER_DESC desc{
.ByteWidth = _vertexBufferSize * sizeof(ImDrawVert),
.ByteWidth = UINT(_vertexBufferSize * sizeof(ImDrawVert)),
.Usage = D3D11_USAGE_DYNAMIC,
.BindFlags = D3D11_BIND_VERTEX_BUFFER,
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE
@ -99,7 +97,7 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData, POINT viewportOffs
_indexBufferSize = drawData.TotalIdxCount + 10000;
D3D11_BUFFER_DESC desc{
.ByteWidth = _indexBufferSize * sizeof(ImDrawIdx),
.ByteWidth = UINT(_indexBufferSize * sizeof(ImDrawIdx)),
.Usage = D3D11_USAGE_DYNAMIC,
.BindFlags = D3D11_BIND_INDEX_BUFFER,
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE
@ -228,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 },
};

View file

@ -1,11 +1,10 @@
#include "pch.h"
#include "ImGuiFontsCacheManager.h"
#include <imgui.h>
#include "YasHelper.h"
#include "CommonSharedConstants.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "CommonSharedConstants.h"
#include "StrHelper.h"
#include "YasHelper.h"
#include <imgui.h>
namespace yas::detail {
@ -140,7 +139,7 @@ struct serializer<
namespace Magpie {
// 缓存版本号。当缓存文件结构有更改时更新它,使旧缓存失效
static constexpr uint32_t FONTS_CACHE_VERSION = 3;
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);

View file

@ -1,16 +1,16 @@
#include "pch.h"
#include "ImGuiImpl.h"
#include <imgui.h>
#include <imgui_internal.h>
#include "ImGuiBackend.h"
#include "CursorManager.h"
#include "DeviceResources.h"
#include "Renderer.h"
#include "ImGuiBackend.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "Renderer.h"
#include "ScalingWindow.h"
#include <ranges>
#include "StrHelper.h"
#include "Win32Helper.h"
#include <imgui.h>
#include <imgui_internal.h>
#include <ranges>
namespace Magpie {
@ -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 失败");

View file

@ -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;
};
}

View file

@ -1,25 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="..\Common.Pre.props" />
<PropertyGroup Label="Globals">
<CppWinRTFastAbi>true</CppWinRTFastAbi>
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<CppWinRTGenerateWindowsMetadata>false</CppWinRTGenerateWindowsMetadata>
<CppWinRTVerbosity>low</CppWinRTVerbosity>
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{0e5205ae-dfa9-4cb8-b662-e43cd6512e2a}</ProjectGuid>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<GeneratedFilesDir>$(IntDir)Generated Files\</GeneratedFilesDir>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)\obj\$(Platform)\$(Configuration)\$(MSBuildProjectName)\</IntDir>
<OutDir>$(SolutionDir)\bin\$(Platform)\$(Configuration)\</OutDir>
<GeneratedFilesDir>$(IntDir)\Generated Files\</GeneratedFilesDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\Common.Pre.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
@ -29,12 +27,11 @@
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\Common.Post.props" />
<Import Project="$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props" Condition="Exists('$(SolutionDir)obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props')" />
<Import Project="$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props" Condition="Exists('$(SolutionDir)\obj\$(Platform)\$(Configuration)\_ConanDeps\Magpie\conandeps.props')" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<FloatingPointModel>Fast</FloatingPointModel>
<AdditionalIncludeDirectories>include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<FxCompile>
@ -42,7 +39,7 @@
<AllResourcesBound>true</AllResourcesBound>
<TreatWarningAsError>true</TreatWarningAsError>
<VariableName>%(Filename)</VariableName>
<HeaderFileOutput>$(GeneratedFilesDir)shaders\%(Filename).h</HeaderFileOutput>
<HeaderFileOutput>$(GeneratedFilesDir)\shaders\%(Filename).h</HeaderFileOutput>
<ObjectFileOutput />
</FxCompile>
</ItemDefinitionGroup>
@ -71,7 +68,6 @@
<ClInclude Include="include\DirectXHelper.h" />
<ClInclude Include="include\EffectCompiler.h" />
<ClInclude Include="include\EffectDesc.h" />
<ClInclude Include="include\ScalingError.h" />
<ClInclude Include="include\ScalingOptions.h" />
<ClInclude Include="include\ScalingRuntime.h" />
<ClInclude Include="include\Win32Helper.h" />
@ -157,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>

View file

@ -64,12 +64,6 @@
<ClInclude Include="include\WindowHelper.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="include\StrHelper.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="include\Version.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="include\Win32Helper.h">
<Filter>Include</Filter>
</ClInclude>
@ -79,9 +73,6 @@
<ClInclude Include="include\WindowBase.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="include\ScalingError.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="include\DirectXHelper.h">
<Filter>Include</Filter>
</ClInclude>
@ -176,13 +167,9 @@
<Filter>Capture</Filter>
</ClCompile>
<ClCompile Include="ScalingOptions.cpp" />
<ClCompile Include="Version.cpp" />
<ClCompile Include="Win32Helper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="StrHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="BackendDescriptorStore.cpp">
<Filter>Render</Filter>
</ClCompile>

View file

@ -1,19 +1,17 @@
#include "pch.h"
#include "OverlayDrawer.h"
#include "CursorManager.h"
#include "DeviceResources.h"
#include "Renderer.h"
#include "StepTimer.h"
#include "EffectDesc.h"
#include "FrameSourceBase.h"
#include "ImGuiFontsCacheManager.h"
#include "Logger.h"
#include "OverlayHelper.h"
#include "Renderer.h"
#include "ScalingWindow.h"
#include "StrHelper.h"
#include "Win32Helper.h"
#include "FrameSourceBase.h"
#include "CommonSharedConstants.h"
#include "EffectDesc.h"
#include "OverlayHelper.h"
#include "ImGuiFontsCacheManager.h"
#include "ScalingWindow.h"
#include <ShlObj.h>
#include "CursorManager.h"
using namespace std::chrono;
@ -112,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;
}
@ -340,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);
@ -644,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;
@ -655,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;
@ -679,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);
@ -722,7 +727,7 @@ bool OverlayDrawer::_DrawToolbar(uint32_t fps) noexcept {
_isToolbarItemActive = true;
}
ImGui::PopFont();
if (ImGui::IsItemHovered() || ImGui::IsItemClicked()) {
if (ImGui::IsItemHovered()) {
_imguiImpl.Tooltip(tooltip, _dpiScale, description);
}
return clicked;
@ -769,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) {
@ -776,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();
}
}
@ -816,23 +831,52 @@ 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();
const ImWchar icon = isWindowedMode ?
OverlayHelper::SegoeIcons::FullScreen : OverlayHelper::SegoeIcons::Favicon;
const std::string& switchScalingStr = _GetResourceString(
isWindowedMode ? L"Overlay_Toolbar_SwitchToFullscreen" : L"Overlay_Toolbar_SwitchToWindowed");
if (drawButton(icon, switchScalingStr.c_str())) {
ScalingWindow::Dispatcher().TryEnqueue([]() {
ScalingWindow::Get().ToggleScaling(!ScalingWindow::Get().Options().IsWindowedMode());
});
}
}
ImGui::SameLine();
// 和主窗口保持一致 (#C42B1C)
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.769f, 0.169f, 0.11f, 1.0f });
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.769f, 0.169f, 0.11f, 0.8f });
const std::string& stopScalingStr = _GetResourceString(L"Overlay_Toolbar_StopScaling");
if (drawButton(OverlayHelper::SegoeIcons::BackToWindow, stopScalingStr.c_str())) {
const std::string& closeStr = _GetResourceString(L"Overlay_Toolbar_Close");
const std::string& closeDescStr = _GetResourceString(L"Overlay_Toolbar_Close_Description");
if (drawButton(OverlayHelper::SegoeIcons::Cancel, closeStr.c_str(), closeDescStr.c_str())) {
ScalingWindow::Dispatcher().TryEnqueue([]() {
ScalingWindow::Get().Destroy();
ScalingWindow::Get().Stop();
});
}
ImGui::SameLine();
const std::string& closeStr = _GetResourceString(L"Overlay_Toolbar_Close");
if (drawButton(OverlayHelper::SegoeIcons::Cancel, closeStr.c_str())) {
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
ScalingWindow::Dispatcher().TryEnqueue([this, runId(ScalingWindow::RunId())]() {
if (runId == ScalingWindow::RunId()) {
ToolbarState(ToolbarState::Off);
@ -844,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;
}
@ -865,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();
@ -1067,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();

View file

@ -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;

View file

@ -1,6 +1,6 @@
#pragma once
#include <imgui.h>
#include "SmallVector.h"
#include <imgui.h>
namespace Magpie {
@ -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,14 @@ struct OverlayHelper {
//
/////////////////////////////////////////////////////
// 更改图标后记得更新 FONTS_CACHE_VERSION
struct SegoeIcons {
static const ImWchar Cancel = 0xE711;
static const ImWchar Camera = 0xE722;
static const ImWchar BackToWindow = 0xE73F;
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;
#ifdef _DEBUG
@ -63,7 +69,10 @@ struct OverlayHelper {
static constexpr ImWchar ICON_RANGES[] = {
SegoeIcons::Cancel, SegoeIcons::Cancel,
SegoeIcons::Camera, SegoeIcons::Camera,
SegoeIcons::BackToWindow, SegoeIcons::BackToWindow,
SegoeIcons::Favicon, SegoeIcons::Favicon,
SegoeIcons::Remove, SegoeIcons::Remove,
SegoeIcons::CheckboxIndeterminate, SegoeIcons::CheckboxIndeterminate,
SegoeIcons::FullScreen, SegoeIcons::FullScreen,
SegoeIcons::Pinned, SegoeIcons::Pinned,
SegoeIcons::Diagnostic, SegoeIcons::Diagnostic,
#ifdef _DEBUG

View file

@ -2,10 +2,7 @@
#include "PresenterBase.h"
#include "DeviceResources.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "ScalingWindow.h"
#include <dwmapi.h>
#include <dcomp.h>
namespace Magpie {
@ -30,66 +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 = []() {
HMODULE hDcomp = GetModuleHandle(L"dcomp.dll");
assert(hDcomp);
return (decltype(::DCompositionWaitForCompositorClock)*)GetProcAddress(
hDcomp, "DCompositionWaitForCompositorClock");
}();
dCompositionWaitForCompositorClock(0, nullptr, INFINITE);
return;
}
LARGE_INTEGER qpf;
QueryPerformanceFrequency(&qpf);
qpf.QuadPart /= 10000000;
DWM_TIMING_INFO info{};
info.cbSize = sizeof(info);
DwmGetCompositionTimingInfo(NULL, &info);
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
if (time.QuadPart >= (LONGLONG)info.qpcCompose) {
return;
}
// 提前 1ms 结束然后忙等待
time.QuadPart += 10000;
if (time.QuadPart < (LONGLONG)info.qpcCompose) {
LARGE_INTEGER liDueTime{
.QuadPart = -((LONGLONG)info.qpcCompose - time.QuadPart) / qpf.QuadPart
};
static HANDLE timer = CreateWaitableTimerEx(nullptr, nullptr,
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
SetWaitableTimerEx(timer, &liDueTime, 0, NULL, NULL, 0, 0);
WaitForSingleObject(timer, INFINITE);
} else {
Sleep(0);
}
while (true) {
QueryPerformanceCounter(&time);
if (time.QuadPart >= (LONGLONG)info.qpcCompose) {
return;
}
Sleep(0);
}
}
uint32_t PresenterBase::_CalcBufferCount() noexcept {
// 缓冲区数量取决于 ScalingRuntime::_ScalingThreadProc 中检查光标移动的频率
return ScalingWindow::Get().Options().Is3DGameMode() ? 4 : 8;
}
void PresenterBase::_WaitForRenderComplete() noexcept {
void PresenterBase::_WaitForGpu() noexcept {
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
// 等待渲染完成

View file

@ -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;

View file

@ -1,29 +1,28 @@
#include "pch.h"
#include "Renderer.h"
#include "DeviceResources.h"
#include "ScalingOptions.h"
#include "Logger.h"
#include "Win32Helper.h"
#include "EffectDrawer.h"
#include "StrHelper.h"
#include "EffectCompiler.h"
#include "GraphicsCaptureFrameSource.h"
#include "DesktopDuplicationFrameSource.h"
#include "GDIFrameSource.h"
#include "DwmSharedSurfaceFrameSource.h"
#include "DirectXHelper.h"
#include "ScalingWindow.h"
#include "OverlayDrawer.h"
#include "CursorManager.h"
#include "EffectsProfiler.h"
#include "CommonSharedConstants.h"
#include "DesktopDuplicationFrameSource.h"
#include "DeviceResources.h"
#include "DirectXHelper.h"
#include "DwmSharedSurfaceFrameSource.h"
#include "EffectCompiler.h"
#include "EffectDrawer.h"
#include "EffectsProfiler.h"
#include "GDIFrameSource.h"
#include "GraphicsCaptureFrameSource.h"
#include "Logger.h"
#include "OverlayDrawer.h"
#include "Renderer.h"
#include "ScalingOptions.h"
#include "ScalingWindow.h"
#include "ScreenshotHelper.h"
#include "StrHelper.h"
#include "TextureHelper.h"
#include "Win32Helper.h"
#ifdef MP_USE_COMPSWAPCHAIN
#include "CompSwapchainPresenter.h"
#else
#include "AdaptivePresenter.h"
#endif
#include "ScreenshotHelper.h"
#include "TextureHelper.h"
#include <dispatcherqueue.h>
#include <d3dkmthk.h>
@ -134,8 +133,10 @@ ScalingError Renderer::Initialize(HWND hwndAttach, OverlayOptions& overlayOption
return ScalingError::ScalingFailedGeneral;
}
if (!ScalingWindow::Get().Options().Is3DGameMode()) {
_overlayDrawer.ToolbarState(ScalingWindow::Get().Options().initialToolbarState);
const ScalingOptions& options = ScalingWindow::Get().Options();
if (!options.Is3DGameMode()) {
_overlayDrawer.ToolbarState(options.IsWindowedMode() ?
options.windowedInitialToolbarState : options.fullscreenInitialToolbarState);
}
_hKeyboardHook.reset(SetWindowsHookEx(WH_KEYBOARD_LL, _LowLevelKeyboardHook, NULL, 0));
@ -198,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;
@ -266,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) {
// 第一帧尚未完成
@ -281,7 +282,7 @@ bool Renderer::Render(bool force, bool waitForRenderComplete) noexcept {
}
}
_FrontendRender(waitForRenderComplete);
_FrontendRender(waitForGpu);
return true;
}
@ -356,7 +357,7 @@ void Renderer::OnMove() noexcept {
_UpdateDestRect();
}
void Renderer::ToggleToolbarState() noexcept {
void Renderer::SwitchToolbarState() noexcept {
const ScalingWindow& scalingWindow = ScalingWindow::Get();
if (scalingWindow.Options().Is3DGameMode()) {
@ -698,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 {
@ -815,8 +845,9 @@ void Renderer::_BackendThreadProc() noexcept {
case FrameSourceState::Error:
// 捕获出错,退出缩放
ScalingWindow::Dispatcher().TryEnqueue([]() {
ScalingWindow::Get().RuntimeError(ScalingError::CaptureFailed);
ScalingWindow::Get().Destroy();
ScalingWindow& scalingWindow = ScalingWindow::Get();
scalingWindow.ShowError(ScalingError::CaptureFailed);
scalingWindow.Stop();
});
while (GetMessage(&msg, NULL, 0, 0)) {
@ -865,7 +896,7 @@ HANDLE Renderer::_InitBackend() noexcept {
// 某些捕获方式不会限制捕获帧率,因此将捕获帧率限制为屏幕刷新率
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
if (HMONITOR hMon = MonitorFromWindow(hwndSrc, MONITOR_DEFAULTTONEAREST)) {
MONITORINFOEX mi{ sizeof(MONITORINFOEX) };
MONITORINFOEX mi{ { sizeof(MONITORINFOEX) } };
GetMonitorInfo(hMon, &mi);
DEVMODE dm{ .dmSize = sizeof(DEVMODE) };

View file

@ -1,14 +1,12 @@
#pragma once
#include "DeviceResources.h"
#include "BackendDescriptorStore.h"
#include "EffectDrawer.h"
#include "Win32Helper.h"
#include "CursorDrawer.h"
#include "StepTimer.h"
#include "DeviceResources.h"
#include "EffectDrawer.h"
#include "EffectsProfiler.h"
#include "ScalingError.h"
#include "PresenterBase.h"
#include "OverlayDrawer.h"
#include "PresenterBase.h"
#include "StepTimer.h"
namespace Magpie {
@ -24,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;
@ -32,7 +30,7 @@ public:
void OnMove() noexcept;
void ToggleToolbarState() noexcept;
void SwitchToolbarState() noexcept;
const RECT& SrcRect() const noexcept;
@ -68,7 +66,7 @@ public:
) noexcept;
private:
void _FrontendRender(bool waitForRenderComplete = false) noexcept;
void _FrontendRender(bool waitForGpu = false) noexcept;
void _BackendThreadProc() noexcept;

View file

@ -36,46 +36,12 @@ static std::string LogEffects(const std::vector<EffectOption>& effects) noexcept
return result;
}
bool ScalingOptions::Prepare() noexcept {
if (screenshotsDir.empty()) {
Logger::Get().Error("screenshotsDir 为空");
return false;
}
if (!showToast) {
Logger::Get().Error("showToast 为空");
return false;
}
if (!save) {
Logger::Get().Error("save 为空");
return false;
}
if (IsWindowedMode()) {
if (Is3DGameMode()) {
return false;
}
// Desktop Duplication 不支持窗口模式缩放
if (captureMethod == CaptureMethod::DesktopDuplication) {
captureMethod = CaptureMethod::GraphicsCapture;
}
}
// GDI 和 DwmSharedSurface 不支持捕获标题栏
if (captureMethod == CaptureMethod::GDI || captureMethod == CaptureMethod::DwmSharedSurface) {
IsCaptureTitleBar(false);
}
return true;
}
void ScalingOptions::Log() const noexcept {
Logger::Get().Info(fmt::format(R"(缩放选项
IsWindowedMode: {}
IsDebugMode: {}
IsBenchmarkMode: {}
IsTopmostDisabled: {}
IsFP16Disabled: {}
IsEffectCacheDisabled: {}
IsFontCacheDisabled: {}
@ -102,12 +68,14 @@ void ScalingOptions::Log() const noexcept {
multiMonitorUsage: {}
cursorInterpolationMode: {}
duplicateFrameDetectionMode: {}
initialToolbarState: {}
fullscreenInitialToolbarState: {}
windowedInitialToolbarState: {}
screenshotsDir: {}
effects: {})",
IsWindowedMode(),
IsDebugMode(),
IsBenchmarkMode(),
IsTopmostDisabled(),
IsFP16Disabled(),
IsEffectCacheDisabled(),
IsFontCacheDisabled(),
@ -133,7 +101,8 @@ void ScalingOptions::Log() const noexcept {
(int)multiMonitorUsage,
(int)cursorInterpolationMode,
(int)duplicateFrameDetectionMode,
(int)initialToolbarState,
(int)fullscreenInitialToolbarState,
(int)windowedInitialToolbarState,
StrHelper::UTF16ToUTF8(screenshotsDir.native()),
LogEffects(effects)
));

View file

@ -1,8 +1,8 @@
#include "pch.h"
#include "ScalingRuntime.h"
#include "CommonSharedConstants.h"
#include "Logger.h"
#include "ScalingWindow.h"
#include "CommonSharedConstants.h"
#include "Win32Helper.h"
#include <dispatcherqueue.h>
@ -14,8 +14,6 @@ ScalingRuntime::ScalingRuntime() : _scalingThread(&ScalingRuntime::_ScalingThrea
}
ScalingRuntime::~ScalingRuntime() {
Stop();
if (_scalingThread.joinable()) {
const HANDLE hScalingThread = _scalingThread.native_handle();
@ -50,86 +48,81 @@ ScalingRuntime::~ScalingRuntime() {
}
}
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
if (!options.Prepare()) {
return false;
}
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options, bool force) {
assert(!options.screenshotsDir.empty() && options.showToast && options.showError && options.save);
_State expected = _State::Idle;
if (!_state.compare_exchange_strong(
expected, _State::Initializing, std::memory_order_relaxed)) {
return false;
}
IsRunningChanged.Invoke(true, ScalingError::NoError);
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options))]() mutable {
options.Log();
ScalingError error = ScalingWindow::Get().Create(hwndSrc, std::move(options));
if (error == ScalingError::NoError) {
_state.store(_State::Scaling, std::memory_order_relaxed);
} else {
// 缩放失败
_state.store(_State::Idle, std::memory_order_relaxed);
IsRunningChanged.Invoke(false, error);
_Dispatcher().TryEnqueue([this, hwndSrc, options(std::move(options)), force]() mutable {
ScalingWindow& scalingWindow = ScalingWindow::Get();
// 如果正在缩放且 force 为假则忽略
if (scalingWindow && !force) {
return;
}
scalingWindow.Stop();
// 初始化时视为处于缩放状态
_State(ScalingState::Scaling);
scalingWindow.Start(hwndSrc, std::move(options));
});
return true;
}
void ScalingRuntime::ToggleToolbarState() {
if (!IsRunning()) {
return;
}
void ScalingRuntime::ToggleScaling(bool isWindowedMode) {
_Dispatcher().TryEnqueue([isWindowedMode]() {
if (ScalingWindow& scalingWindow = ScalingWindow::Get()) {
scalingWindow.ToggleScaling(isWindowedMode);
};
});
}
void ScalingRuntime::SwitchToolbarState() {
_Dispatcher().TryEnqueue([]() {
if (ScalingWindow& scalingWindow = ScalingWindow::Get()) {
scalingWindow.ToggleToolbarState();
scalingWindow.SwitchToolbarState();
};
});
}
void ScalingRuntime::Stop() {
if (!IsRunning()) {
return;
}
_Dispatcher().TryEnqueue([]() {
// 消息循环会更改 _state
ScalingWindow& scalingWindow = ScalingWindow::Get();
if (scalingWindow.IsSrcRepositioning()) {
scalingWindow.CleanAfterSrcRepositioned();
} else {
scalingWindow.Destroy();
}
ScalingWindow::Get().Stop();
});
}
// 返回值:
// -1: 应取消缩放
// 0: 仍在调整中
// 1: 调整完毕
static int GetSrcRepositionState(HWND hwndSrc, bool allowScalingMaximized) noexcept {
if (!IsWindow(hwndSrc) || GetForegroundWindow() != hwndSrc) {
return -1;
static std::optional<bool> IsSrcRepositioning(HWND hwndSrc) noexcept {
if (!IsWindow(hwndSrc)) {
Logger::Get().Info("源窗口已销毁");
return std::nullopt;
}
if (UINT showCmd = Win32Helper::GetWindowShowCmd(hwndSrc); showCmd != SW_NORMAL) {
if (showCmd != SW_SHOWMAXIMIZED || !allowScalingMaximized) {
return -1;
}
// 窗口不可见或最小化则继续等待。注意 showCmd 不能准确判断窗口可见性,
// 应使用 IsWindowVisible。
if (!IsWindowVisible(hwndSrc)) {
return true;
}
if (Win32Helper::IsWindowHung(hwndSrc)) {
Logger::Get().Info("源窗口已挂起");
return std::nullopt;
}
const UINT showCmd = Win32Helper::GetWindowShowCmd(hwndSrc);
if (showCmd == SW_SHOWMAXIMIZED) {
// 窗口最大化则尝试缩放,失败会显示错误消息
return false;
} else if (showCmd == SW_SHOWMINIMIZED) {
return true;
}
// 检查源窗口是否正在调整大小或移动
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 {
@ -163,20 +156,15 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
ScalingWindow::Dispatcher(_dispatcher);
time_point<steady_clock> lastRenderTime;
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
MSG msg;
while (true) {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
scalingWindow.Destroy();
if (_state.exchange(_State::Idle, std::memory_order_relaxed) != _State::Idle) {
IsRunningChanged.Invoke(false, ScalingError::NoError);
}
scalingWindow.Stop();
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();
}
@ -184,14 +172,12 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
DispatchMessage(&msg);
}
if (_state.load(std::memory_order_relaxed) != _State::Scaling) {
WaitMessage();
continue;
}
if (scalingWindow) {
_State(ScalingState::Scaling);
const auto now = steady_clock::now();
// 限制检测光标移动的频率
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
nanoseconds rest = timeout - (now - lastRenderTime);
if (rest.count() <= 0) {
lastRenderTime = now;
@ -205,24 +191,27 @@ 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(),
scalingWindow.Options().IsAllowScalingMaximized()
);
if (state == 0) {
// 等待调整完成
MsgWaitForMultipleObjectsEx(0, nullptr, 10, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
} else if (state == 1) {
// 重新缩放
ScalingWindow::Get().RecreateAfterSrcRepositioned();
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.store(_State::Idle, std::memory_order_relaxed);
IsRunningChanged.Invoke(false, scalingWindow.RuntimeError());
_State(ScalingState::Idle);
lastRenderTime = {};
WaitMessage();
}
}
}
@ -236,4 +225,10 @@ const winrt::DispatcherQueue& ScalingRuntime::_Dispatcher() noexcept {
return _dispatcher;
}
void ScalingRuntime::_State(ScalingState value) {
if (_state.exchange(value, std::memory_order_relaxed) != value) {
StateChanged.Invoke(value);
}
}
}

View file

@ -1,17 +1,15 @@
#include "pch.h"
#include "ScalingWindow.h"
#include "CommonSharedConstants.h"
#include "CursorManager.h"
#include "ExclModeHelper.h"
#include "Logger.h"
#include "Renderer.h"
#include "Win32Helper.h"
#include "WindowHelper.h"
#include "CursorManager.h"
#include "FrameSourceBase.h"
#include "ExclModeHelper.h"
#include "StrHelper.h"
#include <timeapi.h>
#include <dwmapi.h>
#include <ShellScalingApi.h>
#include <timeapi.h>
namespace Magpie {
@ -21,7 +19,7 @@ static UINT WM_MAGPIE_SCALINGCHANGED;
static constexpr int WINDOWED_MODE_MIN_SPACE_AROUND = 2 * 50;
static void InitMessage() noexcept {
static Ignore _ = []() {
[[maybe_unused]] static Ignore _ = []() {
WM_MAGPIE_SCALINGCHANGED =
RegisterWindowMessage(CommonSharedConstants::WM_MAGPIE_SCALINGCHANGED);
@ -29,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,15 +50,24 @@ static void LogRects(const RECT& srcRect, const RECT& rendererRect, const RECT&
windowRect.right - windowRect.left, windowRect.bottom - windowRect.top));
}
ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcept {
if (Handle()) {
return ScalingError::ScalingFailedGeneral;
}
ScalingError ScalingWindow::_StartImpl(HWND hwndSrc) noexcept {
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
#ifdef MP_VERSION_STRING
STRINGIFY(MP_VERSION_STRING),
#elif defined(MP_COMMIT_ID)
"dev (" STRINGIFY(MP_COMMIT_ID) ")",
#else
"dev",
#endif
Win32Helper::GetOSVersion().ToString<char>(),
Win32Helper::IsProcessElevated() ? "" : ""
));
InitMessage();
#if _DEBUG
OutputDebugString(fmt::format(L"可执行文件路径: {}\n窗口类: {}\n",
Win32Helper::GetWindowPath(hwndSrc), Win32Helper::GetWindowClassName(hwndSrc)).c_str());
#endif
// 缩放结束后失效
_options = std::move(options);
_runtimeError = ScalingError::NoError;
_isFirstFrame = true;
_isResizingOrMoving = false;
@ -66,42 +77,46 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
_areResizeHelperWindowsVisible = false;
_isSrcRepositioning = false;
if (_options.IsWindowedMode()) {
if (_options.Is3DGameMode()) {
return ScalingError::Windowed3DGameMode;
} else if (_options.captureMethod == CaptureMethod::DesktopDuplication) {
return ScalingError::WindowedDesktopDuplication;
}
}
if (FindWindow(CommonSharedConstants::SCALING_WINDOW_CLASS_NAME, nullptr)) {
Logger::Get().Error("已存在缩放窗口");
return ScalingError::ScalingFailedGeneral;
}
Logger::Get().Info(fmt::format("缩放开始\n\t程序版本: {}\n\tOS 版本: {}\n\t管理员: {}",
#ifdef MP_VERSION_TAG
STRING(MP_VERSION_TAG),
#else
"dev",
#endif
Win32Helper::GetOSVersion().ToString<char>(),
Win32Helper::IsProcessElevated() ? "" : ""
));
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("已最大化的窗口不支持窗口模式缩放");
return ScalingError::BannedInWindowedMode;
} else if (!_options.IsAllowScalingMaximized()) {
} else if (!_options.RealIsAllowScalingMaximized()) {
Logger::Get().Info("源窗口已最大化");
return ScalingError::Maximized;
}
}
#if _DEBUG
OutputDebugString(fmt::format(L"可执行文件路径: {}\n窗口类: {}\n",
Win32Helper::GetWindowPath(hwndSrc), Win32Helper::GetWindowClassName(hwndSrc)).c_str());
#endif
static Ignore _ = []() {
[[maybe_unused]] static Ignore _ = []() {
WNDCLASSEXW wcex{
.cbSize = sizeof(wcex),
.lpfnWndProc = _WndProc,
@ -122,7 +137,7 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
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();
@ -131,15 +146,15 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
(srcWindowRect.top + srcWindowRect.bottom) / 2
};
HMONITOR hMon = MonitorFromPoint(windowCenter, MONITOR_DEFAULTTONEAREST);
if (isAllClient) {
_topBorderThicknessInClient = 0;
_nonTopBorderThicknessInClient = 0;
} else {
GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &_currentDpi, &_currentDpi);
if (isWin11 && srcWindowKind == SrcWindowKind::NoDecoration) {
// NoDecoration 在 Win11 中和 NoTitleBar 一样处理并禁用边框,优点是只需一个辅助窗口
if (isWin11 && srcWindowKind == SrcWindowKind::NoNativeFrame) {
// NoNativeFrame 在 Win11 中和 NoTitleBar 一样处理并禁用边框,优点是只需一个辅助窗口
_topBorderThicknessInClient = 0;
} else {
_topBorderThicknessInClient = Win32Helper::GetNativeWindowBorderThickness(_currentDpi);
@ -158,27 +173,32 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
// 填入渲染矩形尺寸
int windowWidth = 0;
int windowHeight = 0;
if (_options.initialWindowedScaleFactor < 1.0f) {
// 根据屏幕的工作区尺寸计算
MONITORINFO mi{ .cbSize = sizeof(mi) };
if (GetMonitorInfo(hMon, &mi)) {
const SIZE monitorSize = Win32Helper::GetSizeOfRect(mi.rcWork);
const float srcAspectRatio = (float)srcSize.cy / srcSize.cx;
if (_lastWindowedRendererWidth == 0) {
if (_options.initialWindowedScaleFactor < 1.0f) {
// 根据屏幕的工作区尺寸计算
MONITORINFO mi{ .cbSize = sizeof(mi) };
if (GetMonitorInfo(hMon, &mi)) {
const SIZE monitorSize = Win32Helper::GetSizeOfRect(mi.rcWork);
const float srcAspectRatio = (float)srcSize.cy / srcSize.cx;
// 放大到显示器的 3/4且最少放大 1/3 倍
if ((float)monitorSize.cy / monitorSize.cx > srcAspectRatio) {
windowWidth = std::max(monitorSize.cx * 3 / 4, srcSize.cx * 4 / 3);
// 放大到显示器的 3/4且最少放大 1/3 倍
if ((float)monitorSize.cy / monitorSize.cx > srcAspectRatio) {
windowWidth = std::max(monitorSize.cx * 3 / 4, srcSize.cx * 4 / 3);
} else {
windowHeight = std::max(monitorSize.cy * 3 / 4, srcSize.cy * 4 / 3);
}
} else {
windowHeight = std::max(monitorSize.cy * 3 / 4, srcSize.cy * 4 / 3);
Logger::Get().Win32Error("GetMonitorInfo 失败");
windowWidth = srcSize.cx;
}
} else {
Logger::Get().Win32Error("GetMonitorInfo 失败");
windowWidth = srcSize.cx;
windowWidth = (LONG)std::lroundf(srcSize.cx * _options.initialWindowedScaleFactor);
}
} else {
windowWidth = (LONG)std::lroundf(srcSize.cx * _options.initialWindowedScaleFactor);
// 恢复上次窗口模式缩放尺寸
windowWidth = _lastWindowedRendererWidth;
}
if (!_CalcWindowedScalingWindowSize(windowWidth, windowHeight, true)) {
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
return ScalingError::InvalidSourceWindow;
@ -281,12 +301,11 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
}
LogRects(_srcTracker.SrcRect(), _rendererRect, _windowRect);
if (!_options.IsWindowedMode() && !_options.IsAllowScalingMaximized()) {
if (!_options.RealIsAllowScalingMaximized()) {
// 检查源窗口是否是无边框全屏窗口
if (srcWindowKind == SrcWindowKind::NoDecoration && _srcTracker.WindowRect() == _rendererRect) {
if (srcWindowKind == SrcWindowKind::NoNativeFrame && _srcTracker.WindowRect() == _rendererRect) {
Logger::Get().Info("源窗口已全屏");
Destroy();
return ScalingError::Maximized;
}
}
@ -295,7 +314,6 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
ScalingError error = _renderer->Initialize(_hwndRenderer, _options.overlayOptions);
if (error != ScalingError::NoError) {
Logger::Get().Error("初始化 Renderer 失败");
Destroy();
return error;
}
@ -309,16 +327,72 @@ ScalingError ScalingWindow::Create(HWND hwndSrc, ScalingOptions options) noexcep
return ScalingError::NoError;
}
void ScalingWindow::Render() noexcept {
const bool originIsSrcFocused = _srcTracker.IsFocused();
void ScalingWindow::Start(HWND hwndSrc, ScalingOptions&& options) noexcept {
assert(!Handle());
if (!_UpdateSrcState()) {
Logger::Get().Info("源窗口状态改变");
_DelayedDestroy();
assert(!options.effects.empty());
assert(options.cropping.Left >= 0 && options.cropping.Top >= 0 &&
options.cropping.Right >= 0 && options.cropping.Bottom >= 0);
assert(options.minFrameRate >= 0);
assert(!options.maxFrameRate.has_value() || *options.maxFrameRate > 0);
assert(options.cursorScaling >= 0);
assert(!options.autoHideCursorDelay.has_value() || *options.autoHideCursorDelay > 0);
assert(options.initialWindowedScaleFactor >= 0);
assert(!options.screenshotsDir.empty());
assert(options.showToast && options.showError && options.save);
options.Log();
// 缩放结束后失效
_options = std::move(options);
ScalingError error = _StartImpl(hwndSrc);
if (error != ScalingError::NoError) {
_options.showError(hwndSrc, error);
// 清理
Stop();
}
}
void ScalingWindow::Stop() noexcept {
Destroy();
// 为了简化逻辑和确保可靠清理,这里始终调用 CleanAfterSrcRepositioned
CleanAfterSrcRepositioned();
}
void ScalingWindow::ToggleScaling(bool isWindowedMode) noexcept {
assert(Handle());
if (_options.IsWindowedMode() == isWindowedMode || !_srcTracker.IsFocused()) {
Stop();
return;
}
if (_srcTracker.IsFocused() != originIsSrcFocused) {
// 源窗口在前台时按快捷键可以切换全屏/窗口模式缩放
_isSrcRepositioning = true;
if (_options.IsWindowedMode()) {
_lastWindowedRendererWidth = _rendererRect.right - _rendererRect.left;
}
Destroy();
_options.IsWindowedMode(isWindowedMode);
RestartAfterSrcRepositioned();
}
void ScalingWindow::SwitchToolbarState() noexcept {
if (_renderer) {
_renderer->SwitchToolbarState();
}
}
void ScalingWindow::Render() noexcept {
bool isSrcRepositioning = false;
bool srcFocusedChanged = false;
if (!_UpdateSrcState(isSrcRepositioning, srcFocusedChanged)) {
Logger::Get().Info("源窗口状态改变");
_DelayedStop(false, isSrcRepositioning);
return;
}
if (srcFocusedChanged) {
_UpdateFocusStateAsync();
}
@ -334,18 +408,15 @@ void ScalingWindow::Render() noexcept {
}
}
void ScalingWindow::ToggleToolbarState() noexcept {
if (_renderer) {
_renderer->ToggleToolbarState();
}
}
void ScalingWindow::RecreateAfterSrcRepositioned() noexcept {
Create(_srcTracker.Handle(), std::move(_options));
void ScalingWindow::RestartAfterSrcRepositioned() noexcept {
Start(_srcTracker.Handle(), std::move(_options));
}
void ScalingWindow::CleanAfterSrcRepositioned() noexcept {
_options = {};
if (_options.save) {
_options = {};
}
_lastWindowedRendererWidth = 0;
_isSrcRepositioning = false;
}
@ -390,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));
@ -413,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;
}
@ -439,7 +524,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
if (!_srcTracker.MoveOnEndResizeMove()) {
Logger::Get().Error("SrcTracker::MoveOnEndResizeMove 失败");
_DelayedDestroy();
_DelayedStop();
return 0;
}
@ -467,7 +552,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
newSize = SIZE{ width, height };
} else {
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
_DelayedDestroy();
_DelayedStop();
}
} else {
newSize = _AdjustFullscreenWindowSize(
@ -522,7 +607,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 这时鼠标点击将激活源窗口
const HWND hwndForground = GetForegroundWindow();
if (hwndForground != _srcTracker.Handle()) {
if (!SetForegroundWindow(_srcTracker.Handle())) {
if (!_srcTracker.SetFocus()) {
// 设置前台窗口失败,可能是因为前台窗口是开始菜单
if (WindowHelper::IsStartMenu(hwndForground)) {
using namespace std::chrono;
@ -555,7 +640,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
Sleep(1);
}
SetForegroundWindow(_srcTracker.Handle());
_srcTracker.SetFocus();
}
}
}
@ -565,7 +650,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
{
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
Logger::Get().Error("源窗口已挂起");
_DelayedDestroy(true);
_DelayedStop(true);
// 阻止拖拽和调整大小
return 0;
}
@ -618,7 +703,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
if (!_CalcWindowedScalingWindowSize(windowWidth, windowHeight, false)) {
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
_DelayedDestroy();
_DelayedStop();
return TRUE;
}
@ -640,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)) {
@ -662,7 +759,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
if (!_CalcWindowedScalingWindowSize(windowPos.cx, windowPos.cy, false)) {
Logger::Get().Error("_CalcWindowedScalingWindowSize 失败");
_DelayedDestroy();
_DelayedStop();
return 0;
}
}
@ -721,7 +818,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 焦点。进行下面的操作:调整缩放窗口尺寸,打开开始菜单然后关闭,缩放窗口便
// 得到焦点了。这应该是 OS 的 bug下面的代码用于规避它。
if (!(windowPos.flags & SWP_NOACTIVATE)) {
SetForegroundWindow(_srcTracker.Handle());
_srcTracker.SetFocus();
}
}
@ -735,7 +832,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 中不会捕获光标,因此即使触发了也不会造成严重后果。
// if (_isResizingOrMoving && Win32Helper::IsWindowHung(_srcTracker.Handle())) {
// Logger::Get().Error("源窗口已挂起");
// _DelayedDestroy(true);
// _DelayedStop(true);
// }
// 更新窗口矩形和渲染器矩形,因为 WM_NCCALCSIZE 和 WM_WINDOWPOSCHANGING 都是可选的
@ -765,7 +862,7 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
// 使用 WM_SYSCOMMAND 区分接下来的 WM_ENTERSIZEMOVE 是调整大小还是移动
_isPreparingForResizing = (wParam & 0xFFF0) == SC_SIZE;
if (_isPreparingForResizing) {
SetForegroundWindow(_srcTracker.Handle());
_srcTracker.SetFocus();
}
break;
}
@ -791,11 +888,10 @@ LRESULT ScalingWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) n
_renderer.reset();
Logger::Get().Info("Renderer 已析构");
// 如果正在源窗口正在调整,暂时不清理这些成员
if (!_isSrcRepositioning) {
// 缩放结束时保存配置
_options.save(_options, NULL);
_options = {};
CleanAfterSrcRepositioned();
}
// 还原时钟精度
@ -1029,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 {
@ -1115,7 +1202,7 @@ void ScalingWindow::_Show() noexcept {
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
Logger::Get().Error("源窗口已挂起");
_DelayedDestroy(true);
_DelayedStop(true);
return;
}
@ -1128,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
);
// 广播开始缩放
@ -1142,7 +1229,7 @@ void ScalingWindow::_Show() noexcept {
// 如果源窗口位于前台则将缩放窗口置顶
if (_srcTracker.IsFocused()) {
_UpdateFocusStateAsync(true);
_UpdateFocusStateAsync();
}
if (_options.IsTouchSupportEnabled()) {
@ -1158,7 +1245,7 @@ void ScalingWindow::_Show() noexcept {
}
// 模拟独占全屏
if (_options.IsSimulateExclusiveFullscreen()) {
if (_options.RealIsSimulateExclusiveFullscreen()) {
// 延迟 1s 以避免干扰游戏的初始化,见 #495
([]()->winrt::fire_and_forget {
const uint32_t runId = RunId();
@ -1196,14 +1283,17 @@ void ScalingWindow::_MoveRenderer() noexcept {
}
}
bool ScalingWindow::_UpdateSrcState() noexcept {
bool ScalingWindow::_UpdateSrcState(
bool& isSrcRepositioning,
bool& srcFocusedChanged
) noexcept {
HWND hwndFore = GetForegroundWindow();
if (hwndFore == Handle()) {
// 缩放窗口不应该得到焦点,我们通过 WS_EX_NOACTIVATE 样式和处理 WM_MOUSEACTIVATE
// 等消息来做到这一点。但如果由于某种我们尚未了解的机制这些手段都失败了,这里
// 进行纠正。
SetForegroundWindow(_srcTracker.Handle());
_srcTracker.SetFocus();
hwndFore = GetForegroundWindow();
}
@ -1212,21 +1302,31 @@ bool ScalingWindow::_UpdateSrcState() noexcept {
return false;
}
bool isSrcInvisibleOrMinimized = false;
bool srcRectChanged = false;
bool srcSizeChanged = false;
bool srcMovingChanged = false;
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(),
_isResizingOrMoving, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
if (!_srcTracker.UpdateState(hwndFore, _options.IsWindowedMode(), _isResizingOrMoving,
isSrcInvisibleOrMinimized, srcFocusedChanged, srcRectChanged, srcSizeChanged, srcMovingChanged)) {
return false;
}
if (srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
_isSrcRepositioning = true;
if (isSrcInvisibleOrMinimized || srcSizeChanged || (!_options.IsWindowedMode() && srcRectChanged)) {
// 不要立刻设置 _isSrcSizing销毁窗口是异步的
isSrcRepositioning = true;
if (srcSizeChanged) {
// 源窗口大小改变则清除记忆
_lastWindowedRendererWidth = 0;
} else if (isSrcInvisibleOrMinimized) {
if (_options.IsWindowedMode()) {
_lastWindowedRendererWidth = _rendererRect.right - _rendererRect.left;
}
}
return false;
}
// DirectFlip 可能使窗口移动很卡,目前发现缩放 Magpie 主窗口有这个
// 问题。因此源窗口移动过程中临时禁用 DirectFlip。
if (srcMovingChanged) {
assert(_options.IsWindowedMode());
@ -1494,7 +1594,7 @@ LRESULT ScalingWindow::_BorderHelperWndProc(HWND hWnd, UINT msg, WPARAM wParam,
}
void ScalingWindow::_CreateBorderHelperWindows() noexcept {
static Ignore _ = [] {
[[maybe_unused]] static Ignore _ = [] {
WNDCLASSEXW wcex{
.cbSize = sizeof(wcex),
.lpfnWndProc = _BorderHelperWndProc,
@ -1508,7 +1608,7 @@ void ScalingWindow::_CreateBorderHelperWindows() noexcept {
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
Logger::Get().Error("源窗口已挂起");
_DelayedDestroy(true);
_DelayedStop(true);
return;
}
@ -1558,7 +1658,7 @@ void ScalingWindow::_RepostionBorderHelperWindows() noexcept {
// 可能卡死。
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
Logger::Get().Error("源窗口已挂起");
_DelayedDestroy(true);
_DelayedStop(true);
return;
}
}
@ -1677,7 +1777,7 @@ void ScalingWindow::_UpdateTouchHoleWindows(bool onInit) noexcept {
const RECT srcTouchRect = _CalcSrcTouchRect();
_UpdateTouchProps(srcTouchRect);
static Ignore _ = [] {
[[maybe_unused]] static Ignore _ = [] {
WNDCLASSEXW wcex{
.cbSize = sizeof(wcex),
.lpfnWndProc = BkgWndProc,
@ -1770,40 +1870,115 @@ void ScalingWindow::_UpdateFrameMargins() const noexcept {
DwmExtendFrameIntoClientArea(Handle(), &margins);
}
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(bool onShow) const noexcept {
winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync() const noexcept {
if (_options.IsWindowedMode()) {
// 根据源窗口状态绘制非客户区,我们必须自己控制非客户区是绘制成焦点状态还是非焦点
// 状态,因为缩放窗口实际上永远不会得到焦点。
DefWindowProc(Handle(), WM_NCACTIVATE, _srcTracker.IsFocused(), 0);
} else if (!_options.IsDebugMode()) {
if (!onShow) {
const uint32_t runId = RunId();
// 前台窗口变化后立刻调用 SetWindowPos 有时会导致 Z 顺序混乱,因此等待一段时间
co_await 20ms;
co_await _dispatcher;
if (runId != RunId()) {
co_return;
}
}
}
if (!_options.IsDebugMode()) {
if (Win32Helper::IsWindowHung(_srcTracker.Handle())) {
Logger::Get().Error("源窗口已挂起");
Destroy();
_DelayedStop();
co_return;
}
// 源窗口位于前台时将缩放窗口置顶,这使不支持 MPO 的显卡更容易激活 DirectFlip
if (_srcTracker.IsFocused()) {
SetWindowPos(Handle(), HWND_TOPMOST,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
// 再次调用 SetWindowPos 确保缩放窗口在所有置顶窗口之上
SetWindowPos(Handle(), HWND_TOP,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
} else {
SetWindowPos(Handle(), HWND_NOTOPMOST,
0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
// 这里搞得很复杂,是我反复实验得到的,若要修改应测试下列情形:
// 1. 缩放 WindowCase 中的 TopmostWindow 和 PopupHostWindow
// 2. 缩放常规窗口然后切换到管理员身份的窗口。测试这一条时应直接运行,不要调试,因
// 为调试状态下 SetWindowPos 的行为有变化
// 3. 缩放时将任意窗口最小化然后还原
// 4. 缩放时拖动任意窗口
const bool oldTopmost = IsTopmostWindow(Handle());
const bool newTopmost = _CalcTopmostState();
if (oldTopmost != newTopmost) {
if (newTopmost) {
for (int i = 0; i < 10; ++i) {
SetWindowPos(Handle(), HWND_TOPMOST, 0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
if (IsTopmostWindow(Handle()) == newTopmost) {
break;
}
}
// 全屏模式缩放时确保缩放窗口在所有置顶窗口之上,这使不支持 MPO 的显卡更容易激
// 活 DirectFlip。
if (!_options.IsWindowedMode()) {
SetWindowPos(Handle(), HWND_TOP, 0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
}
} else {
const uint32_t runId = ScalingWindow::RunId();
bool isInBackground = false;
HWND hwndFore = GetForegroundWindow();
if (!hwndFore) {
// 切换窗口时有一个瞬间无前台窗口,这里等待切换完成
co_await winrt::resume_after(1ms);
isInBackground = true;
hwndFore = GetForegroundWindow();
}
bool isForeMovable = true;
if (hwndFore) {
DWORD windowIL;
isForeMovable = Win32Helper::GetWindowIntegrityLevel(hwndFore, windowIL) &&
windowIL <= Win32Helper::GetCurrentProcessIntegrityLevel();
}
if (!isForeMovable) {
if (!isInBackground) {
co_await winrt::resume_background();
isInBackground = true;
}
// 等待 DWM 开始合成新帧以避免显示中间状态
Win32Helper::WaitForDwmComposition();
}
if (isInBackground) {
co_await ScalingWindow::Get().Dispatcher();
// 等待时源窗口重新回到前台了应放弃后续操作
if (runId != ScalingWindow::RunId() || _srcTracker.IsFocused()) {
co_return;
}
}
for (int i = 0; i < 10; ++i) {
HDWP hDwp = BeginDeferWindowPos(isForeMovable ? 2 : 3);
// 改变置顶状态
hDwp = DeferWindowPos(hDwp, Handle(), HWND_NOTOPMOST,
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
// 将缩放窗口恰好置于源窗口前
if (isForeMovable) {
// 这个方式没有中间状态,但会导致源窗口遮挡前台窗口,之后会手动将前台窗口移到顶部
hDwp = DeferWindowPos(hDwp, Handle(), _srcTracker.Handle(),
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
} else {
// 这个方式不会移动源窗口,但有中间状态。如果前台窗口 IL 更高,这是唯一的办法
hDwp = DeferWindowPos(hDwp, Handle(), _srcTracker.Handle(),
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE | SWP_NOOWNERZORDER);
hDwp = DeferWindowPos(hDwp, _srcTracker.Handle(), Handle(),
0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE | SWP_NOOWNERZORDER);
}
EndDeferWindowPos(hDwp);
// 确保缩放窗口刚好在源窗口前
if (IsTopmostWindow(Handle()) == newTopmost &&
GetWindow(_srcTracker.Handle(), GW_HWNDPREV) == Handle()) {
break;
}
}
if (isForeMovable && hwndFore && GetForegroundWindow() == hwndFore) {
SetWindowPos(hwndFore, HWND_TOP, 0, 0, 0, 0, SWP_NO_ACTIVATE_MOVE_SIZE);
}
}
}
}
@ -1815,14 +1990,29 @@ winrt::fire_and_forget ScalingWindow::_UpdateFocusStateAsync(bool onShow) const
}
}
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 {
@ -1841,14 +2031,15 @@ 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;
const int offsetY = (_windowRect.top + _windowRect.bottom - srcRect.top - srcRect.bottom) / 2;
if (!_srcTracker.Move(offsetX, offsetY, _isResizingOrMoving)) {
Logger::Get().Error("SrcTracker::Move 失败");
_DelayedDestroy();
_DelayedStop();
return;
}
@ -1878,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;
@ -1949,20 +2140,25 @@ void ScalingWindow::_UpdateWindowRectFromWindowPos(const WINDOWPOS& windowPos) n
}
}
void ScalingWindow::_DelayedDestroy(bool onSrcHung) const noexcept {
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);
}
}
// 延迟销毁可以避免中间状态
_dispatcher.TryEnqueue([runId(RunId())]() {
_dispatcher.TryEnqueue([runId(RunId()), onSrcRepositioning]() {
if (runId == RunId()) {
ScalingWindow::Get().Destroy();
if (onSrcRepositioning) {
ScalingWindow::Get()._isSrcRepositioning = true;
ScalingWindow::Get().Destroy();
} else {
ScalingWindow::Get().Stop();
}
}
});
}

View file

@ -1,16 +1,15 @@
#pragma once
#include "WindowBase.h"
#include "ScalingOptions.h"
#include "ScalingError.h"
#include "SrcTracker.h"
#include "WindowBase.h"
namespace Magpie {
class CursorManager;
class ScalingWindow : public WindowBaseT<ScalingWindow> {
class ScalingWindow final : public WindowBaseT<ScalingWindow> {
using base_type = WindowBaseT<ScalingWindow>;
friend class base_type;
friend base_type;
public:
static ScalingWindow& Get() noexcept {
@ -31,12 +30,16 @@ public:
return _dispatcher;
}
ScalingError Create(HWND hwndSrc, ScalingOptions options) noexcept;
void Start(HWND hwndSrc, ScalingOptions&& options) noexcept;
void Stop() noexcept;
void ToggleScaling(bool isWindowedMode) noexcept;
void SwitchToolbarState() noexcept;
void Render() noexcept;
void ToggleToolbarState() noexcept;
const RECT& RendererRect() const noexcept {
return _rendererRect;
}
@ -45,7 +48,11 @@ public:
return _options;
}
SrcTracker& SrcTracker() noexcept {
class SrcTracker& SrcTracker() noexcept {
return _srcTracker;
}
const class SrcTracker& SrcTracker() const noexcept {
return _srcTracker;
}
@ -53,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;
}
@ -61,7 +76,7 @@ public:
return _isSrcRepositioning;
}
void RecreateAfterSrcRepositioned() noexcept;
void RestartAfterSrcRepositioned() noexcept;
void CleanAfterSrcRepositioned() noexcept;
@ -71,19 +86,14 @@ public:
winrt::hstring GetLocalizedString(std::wstring_view resName) const;
// 缩放过程中出现的错误
ScalingError RuntimeError() const noexcept {
return _runtimeError;
}
void RuntimeError(ScalingError value) noexcept {
_runtimeError = value;
}
void ShowToast(std::wstring_view msg) const noexcept {
_options.showToast(Handle(), msg);
}
void ShowError(ScalingError error) const noexcept {
_options.showError(_srcTracker.Handle(), error);
}
protected:
LRESULT _MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
@ -91,6 +101,8 @@ private:
ScalingWindow() noexcept;
~ScalingWindow() noexcept;
ScalingError _StartImpl(HWND hwndSrc) noexcept;
// 确保渲染窗口长宽比不变,且限制最小和最大尺寸。必须提供 width 和 height 之一,另一个
// 应为 0。如果 isRendererSize 为真,传入的 width 和 height 为渲染矩形尺寸,否则为缩
// 放窗口尺寸。返回时 width 和 height 是新的缩放窗口尺寸。
@ -106,7 +118,10 @@ private:
void _Show() noexcept;
bool _UpdateSrcState() noexcept;
bool _UpdateSrcState(
bool& isSrcRepositioning,
bool& srcFocusedChanged
) noexcept;
bool _CheckForegroundFor3DGameMode(HWND hwndFore) const noexcept;
@ -136,7 +151,9 @@ private:
void _UpdateFrameMargins() const noexcept;
winrt::fire_and_forget _UpdateFocusStateAsync(bool onShow = false) const noexcept;
winrt::fire_and_forget _UpdateFocusStateAsync() const noexcept;
bool _CalcTopmostState() const noexcept;
bool _IsBorderless() const noexcept;
@ -146,7 +163,7 @@ private:
void _UpdateWindowRectFromWindowPos(const WINDOWPOS& windowPos) noexcept;
void _DelayedDestroy(bool onSrcHung = false) const noexcept;
void _DelayedStop(bool onSrcHung = false, bool onSrcRepositioning = false) const noexcept;
static inline std::atomic<uint32_t> _runId = 0;
static inline winrt::DispatcherQueue _dispatcher{ nullptr };
@ -175,6 +192,9 @@ private:
ScalingError _runtimeError = ScalingError::NoError;
// 窗口缩放时切换到全屏缩放或最小化前保存尺寸供以后恢复
LONG _lastWindowedRendererWidth = 0;
// 第一帧渲染完成后再显示
bool _isFirstFrame = false;
bool _isResizingOrMoving = false;

View file

@ -2,7 +2,6 @@
#include "ScreenshotHelper.h"
#include "Logger.h"
#include "StrHelper.h"
#include <wincodec.h>
#include <charconv>
namespace Magpie {

View file

@ -1,6 +1,5 @@
#pragma once
namespace Magpie {
struct ScreenshotHelper {

View file

@ -1,45 +1,31 @@
#include "pch.h"
#include "SrcTracker.h"
#include "Win32Helper.h"
#include "Logger.h"
#include "SmallVector.h"
#include "Win32Helper.h"
#ifdef _DEBUG
#include "WindowHelper.h"
#endif
#include <dwmapi.h>
#include "SmallVector.h"
#include <ShellScalingApi.h>
namespace Magpie {
static bool GetWindowIntegrityLevel(HWND hWnd, DWORD& integrityLevel) noexcept {
wil::unique_process_handle hProc = Win32Helper::GetWindowProcessHandle(hWnd);
if (!hProc) {
Logger::Get().Error("GetWindowProcessHandle 失败");
static bool IsWindowMoving(HWND hWnd) noexcept {
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
if (GetGUIThreadInfo(GetWindowThreadProcessId(hWnd, nullptr), &guiThreadInfo)) {
return guiThreadInfo.flags & GUI_INMOVESIZE;
} else {
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
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;
}();
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options, bool& isInvisibleOrMinimized) noexcept {
assert(!isInvisibleOrMinimized);
DWORD windowIL;
return GetWindowIntegrityLevel(hwndSrc, windowIL) && windowIL <= thisIL;
}
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept {
_hWnd = hWnd;
// 这里不检查源窗口是否挂起,将在创建缩放窗口前检查
if (!IsWindow(_hWnd)) {
@ -47,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 中阻止
@ -77,6 +78,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
}
_isFocused = GetForegroundWindow() == hWnd;
_isMoving = IsWindowMoving(_hWnd);
if (!GetWindowRect(hWnd, &_windowRect)) {
Logger::Get().Win32Error("GetWindowRect 失败");
@ -96,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。
// 技术上说这很合理,上边框处的非客户区当然可以视为标题栏,对后续计算也没有影响。
@ -124,7 +120,7 @@ ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept
// 一个窗口要么有边框,要么没有。只要左右下三边中有一条边没有边框,我们就将它视为无边框窗口。
//
// FIXME: 有的窗口(如微信)通过 WM_NCCALCSIZE 移除边框,但不使用 DwmExtendFrameIntoClientArea
// 还原阴影,这种窗口实际上是 NoDecoration 类型。遗憾的是没有办法获知窗口是否调用了
// 还原阴影,这种窗口实际上是 NoNativeFrame 类型。遗憾的是没有办法获知窗口是否调用了
// DwmExtendFrameIntoClientArea因此我们假设所有使用 WM_NCCALCSIZE 移除边框的窗口都有阴影,
// 一方面有阴影的情况更多,比如基于 electron 的窗口,另一方面如果假设没有阴影会使得 Win11 中
// 不能正确裁剪边框导致黑边,而如果假设有阴影,猜错的后果相对较轻。
@ -141,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 来计算边框宽度
@ -155,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 {
@ -168,88 +164,118 @@ bool SrcTracker::UpdateState(
HWND hwndFore,
bool isWindowedMode,
bool isResizingOrMoving,
bool& srcRectChanged,
bool& srcSizeChanged,
bool& srcMovingChanged
bool& isInvisibleOrMinimized,
bool& focusedChanged,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged
) noexcept {
assert(!srcRectChanged && !srcSizeChanged && !srcMovingChanged);
assert(!isInvisibleOrMinimized && !focusedChanged &&
!rectChanged && !sizeChanged && !movingChanged);
if (!IsWindow(_hWnd)) {
Logger::Get().Error("源窗口已销毁");
Logger::Get().Info("源窗口已销毁");
return false;
}
if (!IsWindowVisible(_hWnd)) {
Logger::Get().Error("源窗口已隐藏");
return false;
isInvisibleOrMinimized = !IsWindowVisible(_hWnd);
// 缩放中途对源窗口响应性的检查更宽松,即使源窗口挂起一段时间,只要用户不做额
// 外的操作就不会终止缩放,直到源窗口被替换为幽灵窗口。
//
// 不要使用 IsHungAppWindow它有误报的情况见 GH#1244。这里用了未记录函数
// GhostWindowFromHungWindow它可以准确检查源窗口是否已被替换为幽灵窗口。
static const auto ghostWindowFromHungWindow =
Win32Helper::LoadSystemFunction<HWND WINAPI(HWND)>(
L"user32.dll", "GhostWindowFromHungWindow");
if (ghostWindowFromHungWindow && ghostWindowFromHungWindow(_hWnd)) {
// 检查源窗口是否真的处于无响应状态
if (Win32Helper::IsWindowHung(_hWnd)) {
Logger::Get().Info("源窗口已挂起");
return false;
}
}
// Win32Helper::IsWindowHung 更准确,但它会向源窗口发送消息,比较耗时。
// IsHungAppWindow 的另一个好处是它不如 Win32Helper::IsWindowHung 严
// 格,因此即使源窗口挂起一段时间,只要用户不做额外的操作就不会结束缩放,
// 直到源窗口被替换为幽灵窗口。
if (IsHungAppWindow(_hWnd)) {
Logger::Get().Error("源窗口已挂起");
return false;
if (_isFocused != (hwndFore == _hWnd)) {
_isFocused = !_isFocused;
focusedChanged = true;
}
_isFocused = hwndFore == _hWnd;
const bool oldMaximized = _isMaximized;
UINT showCmd = Win32Helper::GetWindowShowCmd(_hWnd);
if (showCmd == SW_SHOWMINIMIZED) {
Logger::Get().Error("源窗口处于最小化状态");
WINDOWPLACEMENT wp{ sizeof(wp) };
if (!GetWindowPlacement(_hWnd, &wp)) {
Logger::Get().Win32Error("GetWindowPlacement 失败");
return false;
}
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
_isMaximized = wp.showCmd == SW_SHOWMAXIMIZED;
RECT curWindowRect;
if (!GetWindowRect(_hWnd, &curWindowRect)) {
Logger::Get().Win32Error("GetWindowRect 失败");
return false;
if (wp.showCmd == SW_SHOWMINIMIZED) {
// 窗口最小化有两步:先将窗口状态设为最小化,然后将窗口移出屏幕 (左上角坐标
// (-32000,-32000))。如果我们刚好在两步之间停止缩放,第二步将无法执行,这和缩
// 放窗口被源窗口所有有关,不确定是否是 OS 的 bug。这个检查确保第二步完成后再
// 停止缩放。
if (wp.rcNormalPosition.left == wp.ptMinPosition.x) {
return true;
}
isInvisibleOrMinimized = true;
// rcNormalPosition 使用工作区坐标,应转换为屏幕坐标
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO mi{ sizeof(mi) };
if (!GetMonitorInfo(hMon, &mi)) {
Logger::Get().Win32Error("GetMonitorInfo 失败");
return false;
}
curWindowRect = wp.rcNormalPosition;
Win32Helper::OffsetRect(
curWindowRect, mi.rcWork.left - mi.rcMonitor.left, mi.rcWork.top - mi.rcMonitor.top);
} else {
if (!GetWindowRect(_hWnd, &curWindowRect)) {
Logger::Get().Win32Error("GetWindowRect 失败");
return false;
}
}
srcSizeChanged = oldMaximized != _isMaximized ||
sizeChanged = oldMaximized != _isMaximized ||
Win32Helper::GetSizeOfRect(curWindowRect) != Win32Helper::GetSizeOfRect(_windowRect);
if (sizeChanged) {
rectChanged = true;
return true;
}
// 缩放窗口正在调整大小或被拖动时源窗口的移动是异步的,暂时不检查源窗口是否移动
if (isResizingOrMoving) {
srcRectChanged = oldMaximized != _isMaximized;
rectChanged = oldMaximized != _isMaximized;
return true;
}
// 最大化状态改变视为尺寸发生变化
srcRectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
rectChanged = oldMaximized != _isMaximized || curWindowRect != _windowRect;
if (isWindowedMode && !srcSizeChanged) {
bool isMoving = false;
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
if (GetGUIThreadInfo(GetWindowThreadProcessId(_hWnd, nullptr), &guiThreadInfo)) {
isMoving = guiThreadInfo.flags & GUI_INMOVESIZE;
} else {
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
}
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
// 可能会有误判,但幸好后果不太严重。
if (_isMoving || (!_isMoving && srcRectChanged)) {
isMoving = isMoving || IsPrimaryMouseButtonDown();
}
if (srcRectChanged) {
if (isWindowedMode && !sizeChanged) {
if (rectChanged) {
const LONG offsetX = curWindowRect.left - _windowRect.left;
const LONG offsetY = curWindowRect.top - _windowRect.top;
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
Win32Helper::OffsetRect(_srcRect, offsetX, offsetY);
}
// 处理自己实现拖拽逻辑的窗口:将鼠标左键按下视为开始拖拽,释放视为拖拽结束。
// 可能会有误判,但幸好后果不太严重。
const bool isMoving = !isInvisibleOrMinimized &&
(IsWindowMoving(_hWnd) || (rectChanged && IsPrimaryMouseButtonDown()));
if (_isMoving != isMoving) {
srcMovingChanged = true;
movingChanged = true;
_isMoving = isMoving;
}
}
if (srcRectChanged) {
if (rectChanged) {
_windowRect = curWindowRect;
}
@ -371,8 +397,9 @@ static HWND FindClientWindowOfUWP(HWND hwndSrc, const wchar_t* clientWndClassNam
}
// 如果有多个匹配的子窗口,取最大的(一般不会出现)
int maxSize = 0, maxIdx = 0;
for (int i = 0; i < data.childWindows.size(); ++i) {
int maxSize = 0;
uint32_t maxIdx = 0;
for (uint32_t i = 0, end = (uint32_t)data.childWindows.size(); i < end; ++i) {
RECT rect;
if (!GetClientRect(data.childWindows[i], &rect)) {
continue;
@ -408,22 +435,83 @@ static bool GetClientRectOfUWP(HWND hWnd, RECT& rect) noexcept {
return true;
}
ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept {
if (_windowKind == SrcWindowKind::NoDecoration) {
// NoDecoration 类型的窗口不裁剪非客户区。它们要么没有非客户区,要么非客户区不是由
// DWM 绘制,前者无需裁剪,后者不能裁剪。
_srcRect = _windowRect;
bool SrcTracker::SetFocus() const noexcept {
// 不要把被禁用的窗口设为前台,检查是否存在弹窗
if (IsWindowEnabled(_hWnd)) {
return SetForegroundWindow(_hWnd);
}
const HWND hwndPopup = GetWindow(_hWnd, GW_ENABLEDPOPUP);
if (hwndPopup && IsWindowEnabled(hwndPopup)) {
return SetForegroundWindow(hwndPopup);
}
// 源窗口被禁用视为成功
return true;
}
ScalingError SrcTracker::_CalcSrcRect(
const ScalingOptions& options,
bool hasCustomNonclient,
LONG borderThicknessInFrame
) noexcept {
if (_windowKind == SrcWindowKind::NoNativeFrame) {
if (hasCustomNonclient) {
if (options.RealIsCaptureTitleBar()) {
// 窗口的非客户区是自绘的,无法模拟,因此启用捕获标题栏时捕获整个窗口
_srcRect = _windowRect;
} else {
RECT clientRect;
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
Logger::Get().Error("GetClientScreenRect 失败");
return ScalingError::ScalingFailedGeneral;
}
// 如果有滚动条需特殊处理
if (const DWORD srcStyle = GetWindowStyle(_hWnd); srcStyle & (WS_VSCROLL | WS_HSCROLL)) {
// 左右两边不可能都有滚动条,据此找到边框宽度
const LONG borderThickness = std::min(
clientRect.left - _windowRect.left,
_windowRect.right - clientRect.right
);
if (srcStyle & WS_VSCROLL) {
_srcRect.left = _windowRect.left + borderThickness;
_srcRect.right = _windowRect.right - borderThickness;
} else {
_srcRect.left = clientRect.left;
_srcRect.right = clientRect.right;
}
if (srcStyle & WS_HSCROLL) {
_srcRect.bottom = _windowRect.bottom - borderThickness;
} else {
_srcRect.bottom = clientRect.bottom;
}
_srcRect.top = clientRect.top;
} else {
_srcRect = clientRect;
}
}
} else {
// 不存在非客户区
_srcRect = _windowRect;
}
} else {
const bool isCaptureTitleBar = options.RealIsCaptureTitleBar();
// UWP 窗口都是 NoTitleBar 类型,但可能使用子窗口作为“客户区”
if (_windowKind == SrcWindowKind::NoTitleBar && !options.IsCaptureTitleBar() && GetClientRectOfUWP(_hWnd, _srcRect)) {
if (_windowKind == SrcWindowKind::NoTitleBar && !isCaptureTitleBar && GetClientRectOfUWP(_hWnd, _srcRect)) {
_srcRect.top = std::max(_srcRect.top, _windowFrameRect.top + borderThicknessInFrame);
} else {
// 不要使用客户区矩形,它不包含滚动条
_srcRect.left = _windowFrameRect.left + borderThicknessInFrame;
_srcRect.top = _windowFrameRect.top + borderThicknessInFrame;
_srcRect.right = _windowFrameRect.right - borderThicknessInFrame;
_srcRect.bottom = _windowFrameRect.bottom - borderThicknessInFrame;
if (!options.IsCaptureTitleBar() || _windowKind == SrcWindowKind::OnlyThickFrame) {
if (!isCaptureTitleBar || _windowKind == SrcWindowKind::OnlyThickFrame) {
RECT clientRect;
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
Logger::Get().Error("GetClientScreenRect 失败");

View file

@ -1,6 +1,5 @@
#pragma once
#include "ScalingOptions.h"
#include "ScalingError.h"
namespace Magpie {
@ -14,8 +13,8 @@ enum class SrcWindowKind {
// 无标题栏,是否有边框取决于 OS 版本Win10 中不存在边框Win11 中边框
// 被绘制到客户区内有阴影在窗口内调整大小Win11 中有圆角
NoBorder,
// 无标题栏、边框和阴影在窗口内调整大小Win11 中无圆角
NoDecoration,
// 无标题栏、边框和阴影在窗口内调整大小Win11 中无圆角。可能自绘非客户区
NoNativeFrame,
// 无标题栏系统边框但上边框较粗有阴影左右下三边在窗口外调整大小Win11 中有圆角
OnlyThickFrame
};
@ -28,15 +27,17 @@ 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& srcRectChanged,
bool& srcSizeChanged,
bool& srcMovingChanged
bool& isInvisibleOrMinimized,
bool& focusedChanged,
bool& rectChanged,
bool& sizeChanged,
bool& movingChanged
) noexcept;
bool Move(int offsetX, int offsetY, bool async) noexcept;
@ -63,6 +64,8 @@ public:
return _isFocused;
}
bool SetFocus() const noexcept;
// IsMaximized 已定义为宏
bool IsZoomed() const noexcept {
return _isMaximized;
@ -78,7 +81,11 @@ public:
}
private:
ScalingError _CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept;
ScalingError _CalcSrcRect(
const ScalingOptions& options,
bool hasCustomNonclient,
LONG borderThicknessInFrame
) noexcept;
HWND _hWnd = NULL;
RECT _windowRect{};

View file

@ -1,5 +1,4 @@
#pragma once
#include "Win32Helper.h"
namespace Magpie {

View file

@ -1,9 +1,9 @@
#include "pch.h"
#include "TextureHelper.h"
#include "Logger.h"
#include "DDSHelper.h"
#include "DirectXHelper.h"
#include "EffectHelper.h"
#include "Logger.h"
#include <wincodec.h>
namespace Magpie {

View file

@ -1,15 +1,15 @@
#include "pch.h"
#include "Win32Helper.h"
#include "Logger.h"
#include "StrHelper.h"
#include <io.h>
#include <Psapi.h>
#include <winternl.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <parallel_hashmap/phmap.h>
#include <wil/token_helpers.h>
#include <ShlObj.h>
#include <io.h>
#pragma push_macro("ShellExecute")
#undef ShellExecute
#include <shellapi.h>
#pragma pop_macro("ShellExecute")
#include <ShlObj.h>
#include <wil/token_helpers.h>
namespace Magpie {
@ -39,32 +39,33 @@ std::wstring Win32Helper::GetWindowTitle(HWND hWnd) noexcept {
}
wil::unique_process_handle Win32Helper::GetWindowProcessHandle(HWND hWnd) noexcept {
wil::unique_process_handle hProc;
wil::unique_process_handle result;
if (DWORD dwProcId = 0; GetWindowThreadProcessId(hWnd, &dwProcId)) {
hProc.reset(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId));
if (!hProc) {
result.reset(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwProcId));
if (result) {
return result;
} else {
Logger::Get().Win32Error("OpenProcess 失败");
}
} else {
Logger::Get().Win32Error("GetWindowThreadProcessId 失败");
}
if (!hProc) {
// 在某些窗口上 OpenProcess 会失败(如暗黑 2尝试使用 GetProcessHandleFromHwnd
static const auto getProcessHandleFromHwnd = (HANDLE(WINAPI*)(HWND))GetProcAddress(
LoadLibraryEx(L"Oleacc.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32),
"GetProcessHandleFromHwnd"
);
if (getProcessHandleFromHwnd) {
hProc.reset(getProcessHandleFromHwnd(hWnd));
if (!hProc) {
Logger::Get().Win32Error("GetProcessHandleFromHwnd 失败");
}
}
// 在某些窗口上 OpenProcess 会失败(如暗黑 2尝试使用 GetProcessHandleFromHwnd
static const auto getProcessHandleFromHwnd =
Win32Helper::LoadSystemFunction<HANDLE WINAPI(HWND)>(L"Oleacc.dll", "GetProcessHandleFromHwnd");
if (!getProcessHandleFromHwnd) {
return result;
}
return hProc;
result.reset(getProcessHandleFromHwnd(hWnd));
if (!result) {
Logger::Get().Win32Error("GetProcessHandleFromHwnd 失败");
return result;
}
return result;
}
std::wstring Win32Helper::GetWindowPath(HWND hWnd) noexcept {
@ -199,7 +200,7 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
DWORD_PTR area = HTNOWHERE;
SendMessageTimeout(hwndChild, WM_NCHITTEST, 0, MAKELPARAM(ptScreen.x, ptScreen.y),
SMTO_NORMAL, timeout, &area);
if (area != HTTRANSPARENT) {
if ((int)area != HTTRANSPARENT) {
hwndCur = hwndChild;
hittest = (int16_t)area;
continue;
@ -258,7 +259,7 @@ int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeo
DWORD_PTR area = HTNOWHERE;
SendMessageTimeout(hWnd, WM_NCHITTEST, 0,
MAKELPARAM(data->ptScreen.x, data->ptScreen.y), SMTO_NORMAL, data->timeout, &area);
if (area == HTTRANSPARENT) {
if ((int)area == HTTRANSPARENT) {
return TRUE;
}
@ -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 {
@ -422,21 +424,17 @@ bool Win32Helper::CreateDir(const std::wstring& path, bool recursive) noexcept {
}
const Win32Helper::OSVersion& Win32Helper::GetOSVersion() noexcept {
static OSVersion version = []() -> OSVersion {
HMODULE hNtDll = GetModuleHandle(L"ntdll.dll");
assert(hNtDll);
auto rtlGetVersion = (LONG(WINAPI*)(PRTL_OSVERSIONINFOW))GetProcAddress(hNtDll, "RtlGetVersion");
static OSVersion version = [] {
const auto rtlGetVersion =
LoadSystemFunction<LONG WINAPI(PRTL_OSVERSIONINFOW)>(L"ntdll.dll", "RtlGetVersion");
if (!rtlGetVersion) {
Logger::Get().Win32Error("获取 RtlGetVersion 地址失败");
assert(false);
return {};
return OSVersion();
}
RTL_OSVERSIONINFOW versionInfo{ .dwOSVersionInfoSize = sizeof(versionInfo) };
rtlGetVersion(&versionInfo);
return { versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, versionInfo.dwBuildNumber };
return OSVersion(versionInfo.dwMajorVersion, versionInfo.dwMinorVersion, versionInfo.dwBuildNumber);
}();
return version;
@ -700,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);
@ -720,7 +742,7 @@ static winrt::com_ptr<IShellView> FindDesktopFolderView() noexcept {
}
winrt::com_ptr<IShellBrowser> shellBrowser;
hr = dispatch.as<IServiceProvider>()->QueryService(
hr = dispatch.try_as<IServiceProvider>()->QueryService(
SID_STopLevelBrowser, IID_PPV_ARGS(&shellBrowser));
if (FAILED(hr)) {
Logger::Get().ComError("IServiceProvider::QueryService 失败", hr);
@ -859,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);
}
}
}

View file

@ -1,7 +1,7 @@
#include "pch.h"
#include "WindowHelper.h"
#include "Win32Helper.h"
#include "StrHelper.h"
#include "Win32Helper.h"
#include <parallel_hashmap/phmap.h>
namespace Magpie {

View file

@ -1,4 +1,5 @@
#pragma once
#include "SmallVector.h"
// YAS 暂不支持 ARM64
// https://github.com/niXman/yas/pull/121
#ifdef _M_ARM64
@ -18,8 +19,6 @@
#include <yas/types/std/variant.hpp>
#pragma warning(pop)
#include "SmallVector.h"
namespace yas::detail {
// 可平凡复制类型

View file

@ -1,5 +1,4 @@
#pragma once
#include <dxgi1_6.h>
#include <d3d11_4.h>
namespace Magpie {

View file

@ -1,9 +1,7 @@
#pragma once
#include <variant>
#include "SmallVector.h"
struct ID3D10Blob;
typedef ID3D10Blob ID3DBlob;
#include <d3dcommon.h>
#include <variant>
namespace Magpie {

View file

@ -1,41 +0,0 @@
#pragma once
enum class ScalingError {
NoError,
/////////////////////////////////////
//
// 先决条件错误
//
/////////////////////////////////////
// 未配置缩放模式或者缩放模式不合法
InvalidScalingMode,
// 启用触控支持失败
TouchSupport,
// 3D 游戏模式下不支持窗口模式缩放
Windowed3DGameMode,
// 通用的不支持缩放错误
InvalidSourceWindow,
// 因窗口已最大化或全屏而无法缩放,可通过更改设置强制缩放
Maximized,
// 因窗口的 IL 更高而无法缩放
LowIntegrityLevel,
// 应用自定义裁剪后尺寸太小或为负
InvalidCropping,
// 窗口不符合窗口模式缩放的条件,如已最大化
BannedInWindowedMode,
/////////////////////////////////////
//
// 初始化和缩放时错误
//
/////////////////////////////////////
// 通用的缩放失败错误
ScalingFailedGeneral,
// FrameSource 初始化失败
CaptureFailed,
// ID3D11Device5::CreateFence 失败
CreateFenceFailed
};

View file

@ -21,6 +21,7 @@ enum class MultiMonitorUsage {
enum class CursorInterpolationMode {
NearestNeighbor,
Bilinear,
COUNT
};
struct Cropping {
@ -41,26 +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 IsTouchSupportEnabled = 1 << 17;
static constexpr uint32_t InlineParams = 1 << 18;
static constexpr uint32_t IsFP16Disabled = 1 << 19;
static constexpr uint32_t BenchmarkMode = 1 << 20;
static constexpr uint32_t DeveloperMode = 1 << 21;
enum class DestAlignment {
LeftTop,
Top,
RightTop,
Left,
Center,
Right,
LeftBottom,
Bottom,
RightBottom,
COUNT
};
enum class ScalingType {
@ -112,19 +104,85 @@ struct OverlayOptions {
phmap::flat_hash_map<std::string, OverlayWindowOption> windows;
};
enum class ScalingError {
NoError,
/////////////////////////////////////
//
// 先决条件错误
//
/////////////////////////////////////
// 未配置缩放模式或者缩放模式不合法
InvalidScalingMode,
// 启用触控支持失败
TouchSupport,
// 3D 游戏模式下不支持窗口模式缩放
Windowed3DGameMode,
// Desktop Duplication 不支持窗口模式缩放
WindowedDesktopDuplication,
// 通用的不支持缩放错误
InvalidSourceWindow,
// 因窗口已最大化或全屏而无法缩放,可通过更改设置强制缩放
Maximized,
// 因窗口的 IL 更高而无法缩放
LowIntegrityLevel,
// 应用自定义裁剪后尺寸太小或为负
InvalidCropping,
// 窗口不符合窗口模式缩放的条件,如已最大化
BannedInWindowedMode,
/////////////////////////////////////
//
// 初始化和缩放时错误
//
/////////////////////////////////////
// 通用的缩放失败错误
ScalingFailedGeneral,
// FrameSource 初始化失败
CaptureFailed,
// ID3D11Device5::CreateFence 失败
CreateFenceFailed
};
struct ScalingFlags {
static constexpr uint32_t WindowedMode = 1;
static constexpr uint32_t DebugMode = 1 << 1;
static constexpr uint32_t DisableEffectCache = 1 << 2;
static constexpr uint32_t SaveEffectSources = 1 << 3;
static constexpr uint32_t WarningsAreErrors = 1 << 4;
static constexpr uint32_t SimulateExclusiveFullscreen = 1 << 5;
static constexpr uint32_t Is3DGameMode = 1 << 6;
static constexpr uint32_t CaptureTitleBar = 1 << 10;
static constexpr uint32_t AdjustCursorSpeed = 1 << 11;
static constexpr uint32_t DisableDirectFlip = 1 << 13;
static constexpr uint32_t DisableFontCache = 1 << 14;
static constexpr uint32_t AllowScalingMaximized = 1 << 15;
static constexpr uint32_t EnableStatisticsForDynamicDetection = 1 << 16;
// 只影响缩放行为Magpie.Core 不负责启动 TouchHelper.exe
static constexpr uint32_t TouchSupportEnabled = 1 << 17;
static constexpr uint32_t InlineParams = 1 << 18;
static constexpr uint32_t DisableFP16 = 1 << 19;
static constexpr uint32_t BenchmarkMode = 1 << 20;
static constexpr uint32_t DeveloperMode = 1 << 21;
static constexpr uint32_t DisableTopmost = 1 << 22;
};
struct ScalingOptions {
DEFINE_FLAG_ACCESSOR(IsWindowedMode, ScalingFlags::WindowedMode, flags)
DEFINE_FLAG_ACCESSOR(IsDeveloperMode, ScalingFlags::DeveloperMode, flags)
DEFINE_FLAG_ACCESSOR(IsDebugMode, ScalingFlags::DebugMode, flags)
DEFINE_FLAG_ACCESSOR(IsBenchmarkMode, ScalingFlags::BenchmarkMode, flags)
DEFINE_FLAG_ACCESSOR(IsFP16Disabled, ScalingFlags::IsFP16Disabled, 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)
DEFINE_FLAG_ACCESSOR(IsWarningsAreErrors, ScalingFlags::WarningsAreErrors, flags)
DEFINE_FLAG_ACCESSOR(IsStatisticsForDynamicDetectionEnabled, ScalingFlags::EnableStatisticsForDynamicDetection, flags)
DEFINE_FLAG_ACCESSOR(IsInlineParams, ScalingFlags::InlineParams, flags)
DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::IsTouchSupportEnabled, flags)
DEFINE_FLAG_ACCESSOR(IsTouchSupportEnabled, ScalingFlags::TouchSupportEnabled, flags)
DEFINE_FLAG_ACCESSOR(IsAllowScalingMaximized, ScalingFlags::AllowScalingMaximized, flags)
DEFINE_FLAG_ACCESSOR(IsSimulateExclusiveFullscreen, ScalingFlags::SimulateExclusiveFullscreen, flags)
DEFINE_FLAG_ACCESSOR(Is3DGameMode, ScalingFlags::Is3DGameMode, flags)
@ -132,9 +190,6 @@ struct ScalingOptions {
DEFINE_FLAG_ACCESSOR(IsAdjustCursorSpeed, ScalingFlags::AdjustCursorSpeed, flags)
DEFINE_FLAG_ACCESSOR(IsDirectFlipDisabled, ScalingFlags::DisableDirectFlip, flags)
bool Prepare() noexcept;
void Log() const noexcept;
std::vector<EffectOption> effects;
uint32_t flags = ScalingFlags::AdjustCursorSpeed;
Cropping cropping{};
@ -144,17 +199,37 @@ 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 initialToolbarState = ToolbarState::AutoHide;
ToolbarState fullscreenInitialToolbarState = ToolbarState::AutoHide;
ToolbarState windowedInitialToolbarState = ToolbarState::AutoHide;
float initialWindowedScaleFactor = 0.0f;
std::filesystem::path screenshotsDir;
// 下面的成员支持在缩放时修改
OverlayOptions overlayOptions;
void (*showToast)(HWND hwndTarget, std::wstring_view msg) = nullptr;
void (*save)(const ScalingOptions& options, HWND hwndScaling) = nullptr;
void (*showToast)(HWND hwndTarget, std::wstring_view msg) noexcept = nullptr;
void (*showError)(HWND hwndTarget, ScalingError error) noexcept = nullptr;
void (*save)(const ScalingOptions& options, HWND hwndScaling) noexcept = nullptr;
void Log() const noexcept;
bool RealIsCaptureTitleBar() const noexcept {
// GDI 和 DwmSharedSurface 不支持捕获标题栏
return IsCaptureTitleBar() &&
captureMethod != CaptureMethod::GDI && captureMethod != CaptureMethod::DwmSharedSurface;
}
bool RealIsAllowScalingMaximized() const noexcept {
return IsAllowScalingMaximized() && !IsWindowedMode();
}
bool RealIsSimulateExclusiveFullscreen() const noexcept {
return IsSimulateExclusiveFullscreen() && !IsWindowedMode();
}
};
}

View file

@ -1,29 +1,33 @@
#pragma once
#include "Event.h"
#include <Windows.h>
#include <winrt/base.h>
#include <winrt/Windows.System.h>
#include "ScalingError.h"
namespace Magpie {
enum class ScalingState {
Idle,
Scaling,
Waiting
};
class ScalingRuntime {
public:
ScalingRuntime();
~ScalingRuntime();
bool Start(HWND hwndSrc, struct ScalingOptions&& options);
bool Start(HWND hwndSrc, struct ScalingOptions&& options, bool force);
void ToggleToolbarState();
void ToggleScaling(bool isWindowedMode);
void SwitchToolbarState();
void Stop();
bool IsRunning() const noexcept {
return _state.load(std::memory_order_relaxed) != _State::Idle;
ScalingState State() const noexcept {
return _state.load(std::memory_order_relaxed);
}
// 调用者应处理线程同步
MultithreadEvent<bool, ScalingError> IsRunningChanged;
MultithreadEvent<ScalingState> StateChanged;
private:
void _ScalingThreadProc() noexcept;
@ -31,19 +35,16 @@ private:
// 确保 _dispatcher 完成初始化
const winrt::DispatcherQueue& _Dispatcher() noexcept;
std::thread _scalingThread;
void _State(ScalingState value);
enum class _State {
Idle,
Initializing,
Scaling
};
std::atomic<_State> _state{ _State::Idle };
std::thread _scalingThread;
winrt::DispatcherQueue _dispatcher{ nullptr };
std::atomic<bool> _dispatcherInitialized = false;
// 只能在主线程访问,省下检查 _dispatcherInitialized 的开销
bool _dispatcherInitializedCache = false;
std::atomic<ScalingState> _state = ScalingState::Idle;
};
}

View file

@ -1,4 +1,6 @@
#pragma once
#include "Logger.h"
#include "StrHelper.h"
#include "Version.h"
namespace Magpie {
@ -115,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 {
@ -131,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;
}
@ -164,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;
}
@ -175,6 +181,42 @@ struct Win32Helper {
static bool OpenFolderAndSelectFile(const wchar_t* fileName) noexcept;
static const std::filesystem::path& GetExePath() noexcept;
template<typename T, std::enable_if_t<std::is_function_v<T>, int> = 0>
static T* LoadSystemFunction(const wchar_t* dllName, const char* funcName) noexcept {
assert(dllName && funcName);
HMODULE hMod = GetModuleHandle(dllName);
if (!hMod) {
hMod = LoadLibraryEx(dllName, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!hMod) {
Logger::Get().Win32Error(fmt::format("加载 {} 失败",
StrHelper::UTF16ToUTF8(dllName)));
return nullptr;
}
}
const FARPROC address = GetProcAddress(hMod, funcName);
if (!address) {
const uintptr_t ordinal = reinterpret_cast<uintptr_t>(funcName);
// 小于 0xFFFF 则为序号
if (ordinal <= 0xFFFFu) {
Logger::Get().Win32Error(fmt::format("加载 {}!{} 失败",
StrHelper::UTF16ToUTF8(dllName), ordinal));
} else {
Logger::Get().Win32Error(fmt::format("加载 {}!{} 失败",
StrHelper::UTF16ToUTF8(dllName), funcName));
}
return nullptr;
}
// 先转成 void* 以避免警告
return reinterpret_cast<T*>(reinterpret_cast<void*>(address));
}
// 和 DwmFlush 效果相同但更准确
static void WaitForDwmComposition() noexcept;
};
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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;
}

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