mirror of
https://github.com/Blinue/Magpie.git
synced 2026-06-24 02:04:10 +00:00
窗口化缩放 (#1071)
* feat: 上边框外可以调整大小 * fix: 修正 Win11 中无边框窗口绘制 * fix: LoadLibrary 换成 GetModuleHandle * feat: 缩放开始时记录 OS 版本 * feat: 适配不同的窗口 * feat: 支持 Win11 的无边框窗口 * fix: 窗口隐藏后停止缩放 * feat: 程序启动时记录 OS 版本 * feat: 缩放无边框窗口支持调整窗口尺寸 * feat: 对于无框架窗口 Win11 中禁用边框和圆角 * feat: Win11 中无边框窗口可以在边框上调整窗口尺寸 * fix: 修复窗口化缩放时最大化窗口会反复尝试缩放的问题 * feat: 无框架窗口在 Win11 中和无标题栏一样处理并禁用边框 * feat: 缩放时以源窗口框架空心点作为目标矩形中心 * feat: 交换链窗口随缩放窗口调整尺寸 * feat: 全屏化缩放前自动调整源窗口位置 Graphics Capture 不再支持屏幕捕获 * feat: 裁剪失败显示错误消息 * feat: 调整缩放窗口尺寸时缩放区域长宽比保持不变 * feat: 缩放窗口位置或尺寸有变化时自动调整源窗口位置 * fix: 修复无法调整尺寸的 bug * fix: 修复窗口化缩放时缩放窗口有时在假边框下面 * feat: 平滑地调整窗口尺寸 * perf: 检查交换链尺寸是否发生变化 * feat: 初步实现调整渲染分辨率 存在显存泄露 * fix: 修复显存泄露 * fix: 修复 CursorDrawer 保存旧 back buffer 的问题 * fix: 修复特定情况下 imgui 中崩溃 * fix: 尝试修复调整尺寸时的闪烁 * feat: 避免调整缩放窗口大小时渲染暂停 * perf: 避免调整大小时重复渲染 * fix: 修复调整大小时有时 FPS 无法及时更新的问题 * chore: 更新依赖 * fix: 使用 DwmFlush 等待屏幕刷新 * fix: ImGui 的更新导致字体缓存不再兼容 * fix: 修复移动缩放窗口后渲染出错 * feat: 鼠标在源窗口的标题栏上时可以拖动缩放窗口 * feat: 删除禁用窗口大小调整选项 * chore: 修改措辞 * fix: 优化调整尺寸的流畅度 * chore: 修复编译 * refactor: 添加抽象对象 Presenter,以在 SwapChain 和 DComp 两种呈现方式间切换 * fix: 修复字体缓存保存问题和优化 Win32Helper::WriteFile * feat: 实现 DirectComposition 呈现 * refactor: 两个呈现器共用的代码移动到基类 * feat: 降低延迟和添加注释 * feat: 完成 DirectComposition 支持 * feat: 禁用 DirectFlip 时使用 DirectComposition 呈现,无需其他操作 * fix: 修复叠加层渲染 * fix: 修复叠加层鼠标输入 * feat: 3D 游戏模式不再支持叠加层 * perf: 3D 游戏模式下降低渲染光标的频率 * chore: 修复编译警告 * feat: 不再单独显示 FPS 将集成在工具栏里 * feat: 实现工具栏原型 * feat: 优化工具栏样式 * feat: 不再支持隐藏光标 * fix: 修复 Win11 加载字体 * refactor: 叠加层多处重构 * feat: 叠加层支持追加和删除 Bicubic * perf: 稍微优化构建字体性能 * refactor: 简化 OverlayDrawer * chore: 更新依赖 * feat: 为工具栏添加更多按钮 * feat: 缩放时支持显示消息 * feat: 实现自动隐藏工具栏 * fix: 修复鼠标在窗口外移动不会更新工具栏透明度的问题 * fix: 优化 ImGui 样式 * feat: ImGui 窗口可关闭 * fix: 修复鼠标被前台窗口捕获时工具栏突然消失 * feat: 工具栏上可以拖拽缩放窗口 * fix: 鼠标被工具栏中的按钮捕获时不要隐藏工具栏 * feat: 调试模式不再影响鼠标逻辑 * fix: 优化工具栏行为 * fix: 修复调整缩放窗口大小时鼠标被叠加层捕获的问题 * fix: 修复拖拽缩放窗口时鼠标跳跃 * feat: 优化工具栏以符合 Fitts 法则 * refactor: Magpie.Core 的接口始终使用 UTF-8 * chore: 更新依赖 * fix: 提高移动光标的可靠性 * feat: 添加新的设置项用于保存叠加层窗口位置 * feat: 初步实现叠加层窗口适配大小调整 * feat: 优化叠加层窗口贴靠规则 * feat: 简化贴靠规则 * feat: 再次优化贴靠规则 * fix: 修复捕获光标后有时光标形状不更新的问题 * feat: 工具栏和其他窗口状态独立 * feat: 调整主页 UI * feat: 调整 UI * feat: 新 UI 支持多语言 * feat: 实现工具栏状态切换 * feat: 切换工具栏时显示消息 * fix: 叠加层调试信息在定义 DEBUG_OVERLAY 时显示 * perf: 非 WGC 使用 D3D11_CREATE_DEVICE_SINGLETHREADED * feat: 添加实验性 composition swapchain 呈现器 * feat: 增加错误处理和日志 * feat: 窗口化缩放时通常情况使用交换链,调整大小时使用 DirectComposition Win11 24H2 中交换链即使在窗口化时也能触发 independent flip * fix: 修复 Win10 缺失“缩放模式”图标的问题 * fix: 修复关闭工具栏按钮功能 * chore: 更新依赖 * refactor: SwapChainPresenter 重命名为 AdaptivePresenter * fix: 禁用 DirectFlip 时始终使用 DirectComposition 呈现 以及添加错误处理 * fix: 修复调试模式下缩放窗口非客户区更新 * feat: 工具栏支持本地化 * feat: Win11 中使用 DCompositionWaitForCompositorClock 等待 DWM 以及更新 CompSwapchainPresenter * chore: 修正文件名 * fix: 修复被调试器暂停后消息弹窗无法关闭的问题 * perf: 优化 composition swapchain 的延迟 * feat: 添加 3D 游戏模式的解释和错误弹窗 * feat: 初步实现截图 * perf: 在后台线程编码图片 * chore: 实验或调试用宏集中在 CommonDefines.h 中 * feat: 支持保存任意效果的输出 * feat: 支持指定截图保存目录 * refactor: 全局包含 <filesystem> * refactor: 更多使用 filesystem 支持长路径,而 Win32 API 在这方面进展缓慢 * refactor: 更多使用 filesystem * refactor: 更多使用 filesystem * feat: 实现截图命名机制 * fix: 优化并发支持 * fix: 修复大批量并发截图可能导致死锁的问题 * feat: 本地化截图消息 * feat: 截图按钮支持右键保存指定效果的输出 * feat: 本地化 * feat: 统一 Win10 和 Win11 中“缩放时调整光标速度”选项的图标 * chore: 避免全局包含 shellapi.h 和 Shlwapi.h * fix: 默认快捷键的 Win 改为 Alt 以避免和系统快捷键冲突 (#1150) * fix: 不在任何屏幕上的窗口不检查自动缩放 (#1151) * feat: 只允许最后一个通道写入 OUTPUT 为了方便截图 * feat: 效果的中间结果保存为 dds * chore: 删除加载 DDS 代码中无用部分 不再支持读取缓冲区、1D/3D 纹理等,节省了约 3KB * docs: 明确 DDS 图像只支持 2D 纹理,但允许使用 mipmap * feat: 实现导出任意通道的任意输出 * perf: 优化导出中间结果的性能 * feat: 调整 UI 文字 * feat: 优化初始缩放窗口尺寸 * fix: 修复点击标题栏无法激活缩放窗口的问题 * fix: 修复源窗口被遮挡就无法通过标题栏拖拽缩放窗口 * fix: 修复有时工具栏无法拖拽的问题 * chore: 增加日志 * feat: 支持改变窗口模式缩放初始缩放倍数 * feat: 本地化 * feat: 第一帧完成再显示缩放窗口 * fix: 减少缩放开始时的光标闪烁 * fix: 提前隐藏光标 * feat: 推迟显示黄色边框和禁用窗口圆角 * feat: 上边框视为标题栏 * chore: 更新依赖 * feat: 窗口化缩放初始位置确保标题栏在屏幕内 * fix: 缩放窗口移动后调整光标位置 * feat: 初步支持触控 * fix: 稍微提高 _ReliableSetCursorPos 等待时长 * feat: 调整缩放窗口大小或移动缩放窗口暂时禁用触控变换 * feat: 优化检查标题栏逻辑 支持 Win11 文件管理器、Windows Terminal 等 * fix: 修复调整大小时纹理尺寸计算错误 * perf: 使用 UpdateSubresource1 更新缓冲区 * fix: 窗口模式缩放时将缩放比例为 1 的 Fit 视为 Fill * feat: 不再支持 Magpie.ToolWindow 属性 现在源窗口不在前台也能继续缩放,而 3D 游戏模式下这个功能没有意义 * feat: 源窗口离开和回到前台时广播事件 * chore: 注释和文档 * docs: 更新文档 * feat: WM_WINDOWPOSCHANGING 中确保等比例缩放 * feat: 全屏模式缩放时允许被 SetWindowPos 移动和改变尺寸 * feat: 自定义初始缩放倍数改为 1.25 和 UI 优化 * feat: 将缩放后的光标限制在屏幕内 * chore: 代码优化 * perf: 只有一个显卡时避免计算显卡选项 * fix: 优化鼠标行为 * fix: 优化叠加层存在弹窗时鼠标交互 * feat: 为一些选项添加说明 增大自动计算的初始缩放倍数 * refactor: 重构调整大小消息的响应 * feat: 删除初始缩放倍数的说明 * feat: 自动缩放可以选择全屏或窗口模式 * feat: 缩放窗口可跟随源窗口移动 由于命中测试可能非常耗时,转到后台执行 * fix: 使通过上边框拖动缩放窗口更加可靠 * fix: 修复鼠标从标题栏移到上边框的过程有一瞬间的闪烁 * feat: 使得源窗口调整尺寸的区域可以调整缩放窗口尺寸 * fix: 优化全屏缩放的鼠标行为 * fix: 优化鼠标行为 * feat: 源窗口移动时临时还原光标移动速度和禁用 DirectFlip * fix: 修复内存损坏错误 * fix: 更可靠地检查缩放是否结束 * feat: 拖拽源窗口时保持光标位置稳定 * feat: 拖拽缩放窗口时保持光标位置稳定 * fix: 优化拖拽 * fix: 修复禁用 DirectFlip 导致的崩溃 * feat: 适配自己实现拖拽逻辑的窗口 * chore: 更新依赖 * fix: 防止拖动源窗口时光标超出屏幕 * fix: 修复舍入误差导致光标可能移到屏幕外 * fix: 优化和被 DPI 虚拟化的窗口的兼容性 * feat: 删除源窗口移动时临时禁用 DirectFlip * fix: 光标不在缩放窗口上时禁止和工具栏交互 * fix: 移动源窗口结束后确保缩放窗口标题栏可见 * fix: 避免调整窗口使标题栏可见导致光标位置跳跃 * fix: 优化拖动源窗口时将光标限制在屏幕内 * perf: 异步移动源窗口,使缩放的移动和调整大小更加平滑 * fix: 拖动缩放窗口时检测源窗口大小变化 * fix: 正确处理缩放窗口的 DPI 变化 * fix: 拖动源窗口时广播 WM_MAGPIE_SCALINGCHANGED * feat: 声明首选显示器选项仅适用于全屏模式缩放 * fix: 优化触控调整缩放窗口大小的体验 * fix: 销毁缩放窗口前取消置顶,添加日志 * chore: 修复编译 * refactor: 检查源窗口逻辑集中在 SrcTracker * fix: 修复源窗口挂起时缩放线程在 SetWindowPos 中卡死的问题 * fix: 修复调整大小中途源窗口挂起导致卡死的问题 * fix: CursorManager 析构时等待异步命中测试完成 * fix: 优化命中测试,不受源窗口卡死影响 * fix: 修复叠加层窗口圆角 * fix: 取消修复源窗口挂起时缩放线程在 SetWindowPos 中卡死的问题 它导致拖动性能下降 * fix: 修复有时切换前台窗口后 Z 顺序混乱 * fix: 删除不再需要的 SetWindowPos * feat: 暂时删除格鲁吉亚语和匈牙利语 完成度太低 * fix: 修复全屏缩放对 DPI 变化的响应 * fix: 修复错误裁剪滚动条和 Win11 24H2 中 Graphics Capture 错误裁剪最大化窗口 * feat: 优化触控拖动源窗口的体验 * refactor: 重构 TouchHelper 和项目文件 TouchHelper 支持日志 Magpie.Core 部分文件移动到 Shared * chore: TouchHelper 不再向主程序发送消息 * refactor: 简化 TouchHelper 逻辑 * fix: 日志文件路径使用宽字符 spdlog 默认使用 ANSI 编码,但支持使用宽字符文件名 * fix: 不再使用 SetForegroundWindow 的 trick 有时会导致奇怪的行为 * feat: Updater 支持日志 * feat: 添加工具栏不支持触控输入的警告 * fix: 修复 TouchHelper 日志错误
This commit is contained in:
parent
8987dd0dd0
commit
a54763050c
209 changed files with 10983 additions and 6570 deletions
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -38,8 +38,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Natvis", "Natvis", "{9808D3
|
|||
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
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Magpie provides mechanisms for interaction with other programs. Through these me
|
|||
|
||||
## How to Receive Notifications When Scaling State Changes
|
||||
|
||||
You should listen for the MagpieScalingChanged message.
|
||||
You should listen for the `MagpieScalingChanged` message.
|
||||
|
||||
```c++
|
||||
UINT WM_MAGPIE_SCALINGCHANGED = RegisterWindowMessage(L"MagpieScalingChanged");
|
||||
|
|
@ -12,60 +12,57 @@ UINT WM_MAGPIE_SCALINGCHANGED = RegisterWindowMessage(L"MagpieScalingChanged");
|
|||
|
||||
### Parameters
|
||||
|
||||
`wParam` is the event ID. For different events, `lParam` has different meanings. Currently, two events are supported:
|
||||
The `wParam` parameter indicates the event ID, and the meaning of `lParam` varies depending on the event. The following events are supported:
|
||||
|
||||
* 0: Scaling has ended. `lParam` is not used.
|
||||
* 1: Scaling has started. `lParam` is the handle of the scaling window.
|
||||
* 0: Scaling has ended or the source window has lost focus. `lParam` is 0 if scaling has ended, or 1 if the source window has lost focus.
|
||||
* 1: Scaling has started or the source window has returned to the foreground. `lParam` contains the handle of the scaled window.
|
||||
* 2: The scaled window’s position or size has changed. This event is exclusive to windowed scaling.
|
||||
* 3: User has started resizing or moving the scaled window. This event is exclusive to windowed scaling.
|
||||
|
||||
### Notes
|
||||
|
||||
If your process has a higher integrity level than Magpie, you won't receive messages broadcasted by Magpie due to User Interface Privilege Isolation (UIPI). In such cases, call [ChangeWindowMessageFilterEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex) to allow receiving the MagpieScalingChanged message.
|
||||
If your process has a higher integrity level than Magpie, you won't receive messages broadcasted by Magpie due to User Interface Privilege Isolation (UIPI). In such cases, call [ChangeWindowMessageFilterEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex) to allow receiving the `MagpieScalingChanged` message.
|
||||
|
||||
```c++
|
||||
ChangeWindowMessageFilterEx(hYourWindow, WM_MAGPIE_SCALINGCHANGED, MSGFLT_ADD, nullptr);
|
||||
```
|
||||
|
||||
## How to Get the Handle of the Scaling Window
|
||||
## How to Get the Handle of the Scaled Window
|
||||
|
||||
You can listen for the MagpieScalingChanged message to obtain the handle of the scaling window. Additionally, while Magpie is scaling, you can also search for the window with the class name `Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22`. Magpie ensures that this class name remains unchanged and that only one scaling window exists at a time.
|
||||
You can listen for the `MagpieScalingChanged` message to obtain the handle of the scaled window. Additionally, while Magpie is scaling, you can also search for the window with the class name `Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22`. Magpie ensures that this class name remains unchanged and that only one scaled window exists at a time.
|
||||
|
||||
```c++
|
||||
HWND hwndScaling = FindWindow(L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22", nullptr);
|
||||
```
|
||||
|
||||
## How to Place Your Window Above the Scaling Window
|
||||
## How to Place Your Window Above the Scaled Window
|
||||
|
||||
Your window must be topmost. You should also listen for the MagpieScalingChanged message; you will receive one after the scaling window is shown, and then you can use `BringWindowToTop` to place your window above it. The scaling window does not attempt to adjust its position on the Z-axis while it exists.
|
||||
You should listen for the `MagpieScalingChanged` message and adjust your window's Z-order accordingly. Below is a simple example. For more advanced use cases, refer to [MagpieWatcher](https://github.com/Blinue/MagpieWatcher).
|
||||
|
||||
```c++
|
||||
HWND hWnd = CreateWindowEx(WS_EX_TOPMOST, ...);
|
||||
...
|
||||
if (message == WM_MAGPIE_SCALINGCHANGED) {
|
||||
switch (wParam) {
|
||||
case 0:
|
||||
// Scaling has ended
|
||||
break;
|
||||
case 1:
|
||||
// Scaling has started
|
||||
// Place this window above the scaling window
|
||||
BringWindowToTop(hWnd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (wParam == 0) {
|
||||
// Remove topmost status
|
||||
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
|
||||
} else if (wParam == 1) {
|
||||
// Ensure your window stays above the scaled window
|
||||
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## How to Obtain Scaling Information
|
||||
|
||||
Scaling information is stored in the [window properties](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-properties) of the scaling window. Currently available properties include:
|
||||
Scaling information is stored in the [window properties](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-properties) of the scaled window. Currently available properties include:
|
||||
|
||||
* `Magpie.Windowed`:Indicates whether Magpie is performing windowed scaling
|
||||
* `Magpie.SrcHWND`: Handle of the source window
|
||||
* `Magpie.SrcLeft`、`Magpie.SrcTop`、`Magpie.SrcRight`、`Magpie.SrcBottom`: Source region of scaling
|
||||
* `Magpie.DestLeft`、`Magpie.DestTop`、`Magpie.DestRight`、`Magpie.DestBottom`: Destination region of scaling
|
||||
|
||||
```c++
|
||||
HWND hwndSrc = (HWND)GetProp(hwndScaling, L"Magpie.SrcHWND");
|
||||
bool isWindowed = (bool)GetProp(hwndScaling, L"Magpie.Windowed");
|
||||
|
||||
RECT srcRect;
|
||||
srcRect.left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcLeft");
|
||||
|
|
@ -82,17 +79,5 @@ destRect.bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestBottom");
|
|||
|
||||
### Notes
|
||||
|
||||
1. These properties are only guaranteed to exist after the scaling window has completed its initialization. Therefore, it is advisable to check whether the scaling window is visible before retrieving these properties, especially when the window handle is obtained using the class name.
|
||||
1. These properties are only guaranteed to exist after the scaled window has completed its initialization. Therefore, it is advisable to check whether the scaled window is visible before retrieving these properties, especially when the window handle is obtained using the class name.
|
||||
2. The coordinates stored in these properties are not DPI-virtualized. To use them correctly, you need to set your application's DPI awareness level to Per-Monitor V2. For more details, please refer to [High DPI Desktop Application Development on Windows](https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows).
|
||||
|
||||
## How to Keep Magpie Scaling When Your Window Is in the Foreground
|
||||
|
||||
Magpie stops scaling when the foreground window changes, with some system windows being exceptions. By setting the `Magpie.ToolWindow` property, you can include your window and all its owned windows in the exceptions list.
|
||||
|
||||
```c++
|
||||
SetProp(hYourWindow, L"Magpie.ToolWindow", (HANDLE)TRUE);
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
According to the [documentation](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setpropw), you should use RemoveProp to clear this property before your window is destroyed. However, if you forget to do so, there's no need to worry: [the system will automatically clean it up](https://devblogs.microsoft.com/oldnewthing/20231030-00/?p=108939).
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ float sharpness;
|
|||
// Definition of textures
|
||||
// "INPUT" and "OUTPUT" are special keywords.
|
||||
// "INPUT" cannot be used as the output of a pass; "OUTPUT" cannot be used as the input of a pass.
|
||||
// Only the last pass is allowed to write to OUTPUT, and it must write only to OUTPUT.
|
||||
// Defining INPUT/OUTPUT is optional, but it is recommended to define them explicitly for the
|
||||
// sake of semantic completeness.
|
||||
// The size of the OUTPUT represents the output size of this effect. Not specifying it indicates
|
||||
|
|
@ -196,6 +197,6 @@ void Pass1(float2 pos, out MF4 target1, out MF4 target2);
|
|||
Texture2D testTex;
|
||||
```
|
||||
|
||||
The TEXTURE instruction supports loading textures from files in common image formats such as BMP, PNG, JPG, and DDS. The texture size is the same as the source image size. FORMAT can be optionally specified to help the parser generate the correct definition. If FORMAT is not specified, it is always assumed to be of type float4.
|
||||
The TEXTURE instruction supports loading textures from files in common image formats such as BMP, PNG, JPG, and DDS. For DDS files, only 2D textures are supported, but mipmaps are allowed. FORMAT can be optionally specified to help the parser generate the correct definition. If FORMAT is not specified, it is always assumed to be of type float4.
|
||||
|
||||
Textures loaded from files cannot be used as the output of passes.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ float sharpness;
|
|||
// 纹理定义
|
||||
// INPUT、OUTPUT 是特殊关键字
|
||||
// INPUT 不能作为通道的输出,OUTPUT 不能作为通道的输入
|
||||
// 只有最后一个通道可以输出到 OUTPUT,最后一个通道也只能输出到 OUTPUT
|
||||
// 定义 INPUT 和 OUTPUT 是可选的,但为了保持语义的完整性,建议显式定义
|
||||
// OUTPUT 的尺寸即为此效果的输出尺寸,不指定则表示支持任意尺寸的输出
|
||||
|
||||
|
|
@ -189,6 +190,6 @@ void Pass1(float2 pos, out MF4 target1, out MF4 target2);
|
|||
Texture2D testTex;
|
||||
```
|
||||
|
||||
TEXTURE 指令支持从文件加载纹理,支持的格式有 bmp,png,jpg 等常见图像格式以及 DDS 文件。纹理尺寸与源图像尺寸相同。可选使用 FORMAT,指定后可以帮助解析器生成正确的定义,不指定始终假设是 float4 类型。
|
||||
TEXTURE 指令支持从文件加载纹理,支持的格式有 BMP,PNG,JPG 等常见图像格式以及 DDS 文件。加载 DDS 文件时只支持 2D 纹理,但允许使用 mipmap。可选使用 FORMAT,指定后可以帮助解析器生成正确的定义,不指定始终假设是 float4 类型。
|
||||
|
||||
从文件加载的纹理不能作为通道的输出。
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Magpie 提供了和其他程序交互的机制。通过它们,你的应用可
|
|||
|
||||
## 如何在缩放状态改变时得到通知
|
||||
|
||||
你应该监听 MagpieScalingChanged 消息。
|
||||
你应该监听 `MagpieScalingChanged` 消息。
|
||||
|
||||
```c++
|
||||
UINT WM_MAGPIE_SCALINGCHANGED = RegisterWindowMessage(L"MagpieScalingChanged");
|
||||
|
|
@ -12,14 +12,18 @@ UINT WM_MAGPIE_SCALINGCHANGED = RegisterWindowMessage(L"MagpieScalingChanged");
|
|||
|
||||
### 参数
|
||||
|
||||
`wParam` 为事件 ID,对于不同的事件 `lParam` 有不同的含义。目前支持两个事件:
|
||||
`wParam` 为事件 ID,对于不同的事件 `lParam` 有不同的含义。支持以下事件:
|
||||
|
||||
* 0: 缩放已结束。不使用 `lParam`。
|
||||
* 1: 缩放已开始。`lParam` 为缩放窗口句柄。
|
||||
* 0: 缩放已结束或源窗口失去焦点。缩放结束时 `lParam` 为 0,否则为 1。
|
||||
* 1: 缩放已开始或源窗口回到前台。`lParam` 为缩放窗口句柄。
|
||||
* 2: 缩放窗口位置或大小改变。窗口模式缩放时才会生成这个事件。
|
||||
* 3: 用户开始调整缩放窗口大小或移动缩放窗口。窗口模式缩放时才会生成这个事件。
|
||||
|
||||
在调整缩放窗口大小或移动缩放窗口的过程中不会产生事件,但你可以定期检查窗口属性来获得更新的信息。
|
||||
|
||||
### 注意事项
|
||||
|
||||
如果你的进程完整性级别 (Integration level) 比 Magpie 更高,由于用户界面特权隔离 (UIPI),你将无法收到 Magpie 广播的消息。这种情况下请调用 [ChangeWindowMessageFilterEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex) 以允许接收 MagpieScalingChanged 消息。
|
||||
如果你的进程完整性级别 (Integration level) 比 Magpie 更高,由于用户界面特权隔离 (UIPI),你将无法收到 Magpie 广播的消息。这种情况下请调用 [ChangeWindowMessageFilterEx](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-changewindowmessagefilterex) 以允许接收 `MagpieScalingChanged` 消息。
|
||||
|
||||
```c++
|
||||
ChangeWindowMessageFilterEx(hYourWindow, WM_MAGPIE_SCALINGCHANGED, MSGFLT_ADD, nullptr);
|
||||
|
|
@ -27,7 +31,7 @@ ChangeWindowMessageFilterEx(hYourWindow, WM_MAGPIE_SCALINGCHANGED, MSGFLT_ADD, n
|
|||
|
||||
## 如何获取缩放窗口句柄
|
||||
|
||||
你可以监听 MagpieScalingChanged 消息来获取缩放窗口句柄,也可以查找类名为`Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22`的窗口以在缩放中途获取该句柄。Magpie 将确保此类名不会改变,且不会同时存在多个缩放窗口。
|
||||
你可以监听 `MagpieScalingChanged` 消息来获取缩放窗口句柄,也可以查找类名为`Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22`的窗口以在缩放中途获取该句柄。Magpie 将确保此类名不会改变,且不会同时存在多个缩放窗口。
|
||||
|
||||
```c++
|
||||
HWND hwndScaling = FindWindow(L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A22", nullptr);
|
||||
|
|
@ -35,23 +39,16 @@ HWND hwndScaling = FindWindow(L"Window_Magpie_967EB565-6F73-4E94-AE53-00CC42592A
|
|||
|
||||
## 如何将你的窗口置于缩放窗口上方
|
||||
|
||||
你的窗口必须是置顶的。你还应该监听 MagpieScalingChanged 消息,当收到该消息时缩放窗口已经显示,然后你可以使用 `BringWindowToTop` 函数将自己的窗口置于缩放窗口上方。缩放窗口在存在期间不会尝试调整自己在 Z 轴的位置。
|
||||
你应该监听 `MagpieScalingChanged` 消息,根据事件调整自己的 Z 轴顺序。下面是一个简单的示例,更复杂的用例参见 [MagpieWatcher](https://github.com/Blinue/MagpieWatcher)。
|
||||
|
||||
```c++
|
||||
HWND hWnd = CreateWindowEx(WS_EX_TOPMOST, ...);
|
||||
...
|
||||
if (message == WM_MAGPIE_SCALINGCHANGED) {
|
||||
switch (wParam) {
|
||||
case 0:
|
||||
// 缩放已结束
|
||||
break;
|
||||
case 1:
|
||||
// 缩放已开始
|
||||
// 将本窗口置于缩放窗口上面
|
||||
BringWindowToTop(hWnd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (wParam == 0) {
|
||||
// 取消置顶
|
||||
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
|
||||
} else if (wParam == 1) {
|
||||
// 确保本窗口在缩放窗口上面
|
||||
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -60,12 +57,14 @@ if (message == WM_MAGPIE_SCALINGCHANGED) {
|
|||
|
||||
缩放窗口的[窗口属性](https://learn.microsoft.com/en-us/windows/win32/winmsg/about-window-properties)中存储着缩放信息。目前支持以下属性:
|
||||
|
||||
* `Magpie.Windowed`:是否处于窗口模式缩放
|
||||
* `Magpie.SrcHWND`: 源窗口句柄
|
||||
* `Magpie.SrcLeft`、`Magpie.SrcTop`、`Magpie.SrcRight`、`Magpie.SrcBottom`: 被缩放区域的边界
|
||||
* `Magpie.DestLeft`、`Magpie.DestTop`、`Magpie.DestRight`、`Magpie.DestBottom`: 缩放后区域矩形边界
|
||||
|
||||
```c++
|
||||
HWND hwndSrc = (HWND)GetProp(hwndScaling, L"Magpie.SrcHWND");
|
||||
bool isWindowed = (bool)GetProp(hwndScaling, L"Magpie.Windowed");
|
||||
|
||||
RECT srcRect;
|
||||
srcRect.left = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.SrcLeft");
|
||||
|
|
@ -84,15 +83,3 @@ destRect.bottom = (LONG)(INT_PTR)GetProp(hwndScaling, L"Magpie.DestBottom");
|
|||
|
||||
1. 这些属性只在缩放窗口初始化完成后才保证存在,因此建议检索属性前检查缩放窗口是否可见,尤其是当窗口句柄是使用类名获取到的。
|
||||
2. 这些属性中存储的坐标不受 DPI 虚拟化影响,你需要将程序的 DPI 感知级别设置为 Per-Monitor V2 才能正确使用它们。有关详细信息,请参阅 [High DPI Desktop Application Development on Windows](https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows)。
|
||||
|
||||
## 如何使 Magpie 在你的窗口位于前台时保持缩放
|
||||
|
||||
前台窗口改变时 Magpie 会停止缩放,只对某些系统窗口例外。你可以通过设置属性 `Magpie.ToolWindow` 将自己的窗口添加入例外,这对由该窗口拥有 (owned) 的窗口也有效。
|
||||
|
||||
```c++
|
||||
SetProp(hYourWindow, L"Magpie.ToolWindow", (HANDLE)TRUE);
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
根据[文档](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setpropw)的要求,你应该在你的窗口被销毁前使用 RemoveProp 清理这个属性。但如果你忘了也不会有问题,[系统会自动清理它](https://devblogs.microsoft.com/oldnewthing/20231030-00/?p=108939)。
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<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;%(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;NODEFERWINDOWPOS;NOMCX;NO_SHLWAPI_PATH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(CommitId)'!=''">MAGPIE_COMMIT_ID=$(CommitId);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(MajorVersion)'!='' And '$(MinorVersion)'!='' And '$(PatchVersion)'!='' And '$(VersionTag)'!=''">MAGPIE_VERSION_MAJOR=$(MajorVersion);MAGPIE_VERSION_MINOR=$(MinorVersion);MAGPIE_VERSION_PATCH=$(PatchVersion);MAGPIE_VERSION_TAG=$(VersionTag);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalOptions>/bigobj %(AdditionalOptions)</AdditionalOptions>
|
||||
|
|
@ -45,6 +45,12 @@
|
|||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- 所有项目共享的头文件 -->
|
||||
<Import Project="$(SolutionDir)\src\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')" />
|
||||
|
||||
<!-- HybridCRT -->
|
||||
<Import Project="$(MSBuildThisFileDirectory)HybridCRT.props" />
|
||||
|
||||
|
|
@ -55,7 +61,7 @@
|
|||
<FileWrites Remove="@(CopyUpToDateMarker)" Condition="'@(ReferencesCopiedInThisBuild)' == '' Or '$(WroteAtLeastOneFile)' != 'true'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
|
||||
<!-- _AppendToWriteTlogFile 不会删除原始内容,如果某个中间文件不再生成会导致 up-to-date 检查失败 -->
|
||||
<Target Name="FixWriteLog" BeforeTargets="_AppendToWriteTlogFile">
|
||||
<Delete Files="$(TLogLocation)$(ProjectName).write.1u.tlog" />
|
||||
|
|
|
|||
339
src/Magpie.Core/AdaptivePresenter.cpp
Normal file
339
src/Magpie.Core/AdaptivePresenter.cpp
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
#include "pch.h"
|
||||
#include "AdaptivePresenter.h"
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Win32Helper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
bool AdaptivePresenter::_Initialize(HWND hwndAttach) noexcept {
|
||||
if (ScalingWindow::Get().Options().IsDirectFlipDisabled()) {
|
||||
// 禁用 DirectFlip 时始终使用 DirectComposition 呈现
|
||||
if (!_ResizeDCompVisual(hwndAttach)) {
|
||||
Logger::Get().Error("_ResizeDCompVisual 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t bufferCount = _CalcBufferCount();
|
||||
|
||||
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
|
||||
DXGI_SWAP_CHAIN_DESC1 sd{
|
||||
.Width = (UINT)rendererSize.cx,
|
||||
.Height = (UINT)rendererSize.cy,
|
||||
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
.SampleDesc = {
|
||||
.Count = 1
|
||||
},
|
||||
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||
.BufferCount = bufferCount,
|
||||
#ifdef _DEBUG
|
||||
// 我们应确保两种渲染方式可以无缝切换,DXGI_SCALING_NONE 使错误更容易观察到
|
||||
.Scaling = DXGI_SCALING_NONE,
|
||||
#else
|
||||
// 如果两种渲染方式无法无缝切换,DXGI_SCALING_STRETCH 使视觉变化尽可能小
|
||||
.Scaling = DXGI_SCALING_STRETCH,
|
||||
#endif
|
||||
// 渲染每帧之前都会清空后缓冲区,因此无需 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL
|
||||
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
|
||||
.AlphaMode = DXGI_ALPHA_MODE_IGNORE,
|
||||
// 只要显卡支持始终启用 DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING 以支持可变刷新率
|
||||
.Flags = UINT((_deviceResources->IsTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0)
|
||||
| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)
|
||||
};
|
||||
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
winrt::com_ptr<IDXGISwapChain1> dxgiSwapChain = nullptr;
|
||||
HRESULT hr = _deviceResources->GetDXGIFactory()->CreateSwapChainForHwnd(
|
||||
d3dDevice,
|
||||
hwndAttach,
|
||||
&sd,
|
||||
nullptr,
|
||||
nullptr,
|
||||
dxgiSwapChain.put()
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("创建交换链失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
_dxgiSwapChain = dxgiSwapChain.try_as<IDXGISwapChain4>();
|
||||
if (!_dxgiSwapChain) {
|
||||
Logger::Get().Error("获取 IDXGISwapChain2 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 为了降低延迟,两个垂直同步之间允许渲染 bufferCount - 1 帧
|
||||
_dxgiSwapChain->SetMaximumFrameLatency(bufferCount - 1);
|
||||
|
||||
_frameLatencyWaitableObject.reset(_dxgiSwapChain->GetFrameLatencyWaitableObject());
|
||||
if (!_frameLatencyWaitableObject) {
|
||||
Logger::Get().Error("GetFrameLatencyWaitableObject 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _deviceResources->GetDXGIFactory()->MakeWindowAssociation(
|
||||
hwndAttach, DXGI_MWA_NO_ALT_ENTER);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("MakeWindowAssociation 失败", hr);
|
||||
}
|
||||
|
||||
hr = _dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(_backBuffer.put()));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("获取后缓冲区失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = d3dDevice->CreateRenderTargetView(_backBuffer.get(), nullptr, _backBufferRtv.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateRenderTargetView 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdaptivePresenter::BeginFrame(
|
||||
winrt::com_ptr<ID3D11Texture2D>& frameTex,
|
||||
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
|
||||
POINT& drawOffset
|
||||
) noexcept {
|
||||
if (_dcompSurface) {
|
||||
HRESULT hr = _dcompSurface->BeginDraw(nullptr, IID_PPV_ARGS(&frameTex), &drawOffset);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("BeginDraw 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _deviceResources->GetD3DDevice()->CreateRenderTargetView(
|
||||
frameTex.get(), nullptr, frameRtv.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateRenderTargetView 失败", hr);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
drawOffset = {};
|
||||
|
||||
if (!_isframeLatencyWaited) {
|
||||
_frameLatencyWaitableObject.wait(1000);
|
||||
_isframeLatencyWaited = true;
|
||||
}
|
||||
|
||||
frameTex = _backBuffer;
|
||||
frameRtv = _backBufferRtv;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdaptivePresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
||||
if (_dcompSurface) {
|
||||
_dcompSurface->EndDraw();
|
||||
}
|
||||
|
||||
if (waitForRenderComplete || _isResized) {
|
||||
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁。
|
||||
//
|
||||
// 我们希望 DWM 绘制新的窗口框架时刚好合成新帧,但这不是我们能控制的,尤其是混合架构
|
||||
// 下需要在显卡间传输帧数据,无法预测 Present/Commit 后多久 DWM 能收到。我们只能尽
|
||||
// 可能为 DWM 合成新帧预留时间,这包括两个步骤:
|
||||
//
|
||||
// 1. 首先等待渲染完成,确保新帧对 DWM 随时可用。
|
||||
// 2. 然后在新一轮合成开始时提交,这让 DWM 有更多时间合成新帧。
|
||||
//
|
||||
// 目前看来除非像 UWP 一般有 DWM 协助,否则彻底摆脱闪烁是不可能的。
|
||||
//
|
||||
// https://github.com/Blinue/Magpie/pull/1071#issuecomment-2718314731 讨论了 UWP
|
||||
// 调整尺寸的方法,测试表明可以彻底解决闪烁问题。不过它使用了很不稳定的私有接口,没有
|
||||
// 实用价值。
|
||||
|
||||
// 等待渲染完成
|
||||
_WaitForRenderComplete();
|
||||
|
||||
// 等待 DWM 开始合成新一帧
|
||||
_WaitForDwmComposition();
|
||||
}
|
||||
|
||||
if (_dcompSurface) {
|
||||
_dcompDevice->Commit();
|
||||
} else {
|
||||
// 两个垂直同步之间允许渲染数帧,SyncInterval = 0 只呈现最新的一帧,旧帧被丢弃
|
||||
_dxgiSwapChain->Present(0, 0);
|
||||
_isframeLatencyWaited = false;
|
||||
|
||||
// 丢弃渲染目标的内容
|
||||
_deviceResources->GetD3DDC()->DiscardView(_backBufferRtv.get());
|
||||
|
||||
if (_isSwitchingToSwapChain) {
|
||||
_isSwitchingToSwapChain = false;
|
||||
|
||||
// 等待交换链呈现新帧
|
||||
_WaitForRenderComplete();
|
||||
_WaitForDwmComposition();
|
||||
|
||||
// 清除 DirectCompostion 内容
|
||||
_dcompVisual->SetContent(nullptr);
|
||||
_dcompSurface = nullptr;
|
||||
_dcompDevice->Commit();
|
||||
}
|
||||
}
|
||||
|
||||
if (_isResized) {
|
||||
_isResized = false;
|
||||
} else {
|
||||
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
|
||||
_WaitForRenderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
bool AdaptivePresenter::OnResize() noexcept {
|
||||
_isResized = true;
|
||||
|
||||
if (ScalingWindow::Get().IsResizingOrMoving() || !_dxgiSwapChain) {
|
||||
// 切换到 DirectComposition 呈现,失败则回落到交换链
|
||||
if (_ResizeDCompVisual()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger::Get().Error("_ResizeDCompVisual 失败");
|
||||
|
||||
// 禁用 DirectFlip 时不存在交换链
|
||||
if (!_dxgiSwapChain) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_ResizeSwapChain()) {
|
||||
Logger::Get().Error("_ResizeSwapChain 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdaptivePresenter::OnEndResize(bool& shouldRedraw) noexcept {
|
||||
if (!_dcompSurface || !_dxgiSwapChain) {
|
||||
shouldRedraw = false;
|
||||
return;
|
||||
}
|
||||
|
||||
shouldRedraw = true;
|
||||
|
||||
_ResizeSwapChain();
|
||||
_dcompSurface = nullptr;
|
||||
// 交换链呈现新帧后再清除 DirectCompostion 内容,确保无缝切换
|
||||
_isSwitchingToSwapChain = true;
|
||||
}
|
||||
|
||||
bool AdaptivePresenter::_ResizeSwapChain() noexcept {
|
||||
assert(_dxgiSwapChain);
|
||||
|
||||
if (!_isframeLatencyWaited) {
|
||||
_frameLatencyWaitableObject.wait(1000);
|
||||
_isframeLatencyWaited = true;
|
||||
}
|
||||
|
||||
_backBuffer = nullptr;
|
||||
_backBufferRtv = nullptr;
|
||||
|
||||
const RECT& swapChainRect = ScalingWindow::Get().RendererRect();
|
||||
const SIZE swapChainSize = Win32Helper::GetSizeOfRect(swapChainRect);
|
||||
HRESULT hr = _dxgiSwapChain->ResizeBuffers(
|
||||
0,
|
||||
(UINT)swapChainSize.cx,
|
||||
(UINT)swapChainSize.cy,
|
||||
DXGI_FORMAT_UNKNOWN,
|
||||
UINT((_deviceResources->IsTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0)
|
||||
| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT)
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("ResizeBuffers 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(_backBuffer.put()));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("获取后缓冲区失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _deviceResources->GetD3DDevice()->CreateRenderTargetView(
|
||||
_backBuffer.get(), nullptr, _backBufferRtv.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateRenderTargetView 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AdaptivePresenter::_ResizeDCompVisual(HWND hwndAttach) noexcept {
|
||||
if (_dcompVisual) {
|
||||
// 先释放旧表面
|
||||
_dcompVisual->SetContent(nullptr);
|
||||
_dcompSurface = nullptr;
|
||||
} else {
|
||||
// 初始化 DirectComposition
|
||||
HRESULT hr = DCompositionCreateDevice3(
|
||||
_deviceResources->GetD3DDevice(), IID_PPV_ARGS(&_dcompDevice));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DCompositionCreateDevice3 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hwndAttach) {
|
||||
// 没有禁用 DirectFlip 时才会在调整大小时初始化,因此必定存在交换链
|
||||
hr = _dxgiSwapChain->GetHwnd(&hwndAttach);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetHwnd 失败", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
hr = _dcompDevice->CreateTargetForHwnd(hwndAttach, TRUE, _dcompTarget.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateTargetForHwnd 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompDevice->CreateVisual(_dcompVisual.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateVisual 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompTarget->SetRoot(_dcompVisual.get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetRoot 失败", 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());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetContent 失败", hr);
|
||||
// 失败时确保 _dcompSurface 为空
|
||||
_dcompSurface = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
48
src/Magpie.Core/AdaptivePresenter.h
Normal file
48
src/Magpie.Core/AdaptivePresenter.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
#include "PresenterBase.h"
|
||||
#include <dcomp.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
// 根据需要在交换链和 DirectComposition 两种呈现方式间切换。交换链可以触发
|
||||
// DirectFlip/IndependentFlip 以最小化延迟,DirectComposition 在调整尺寸
|
||||
// 时闪烁更少,这个呈现器旨在结合两者的优势。
|
||||
class AdaptivePresenter : public PresenterBase {
|
||||
protected:
|
||||
bool _Initialize(HWND hwndAttach) noexcept override;
|
||||
|
||||
public:
|
||||
bool BeginFrame(
|
||||
winrt::com_ptr<ID3D11Texture2D>& frameTex,
|
||||
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
|
||||
POINT& drawOffset
|
||||
) noexcept override;
|
||||
|
||||
void EndFrame(bool waitForRenderComplete = false) noexcept override;
|
||||
|
||||
bool OnResize() noexcept override;
|
||||
|
||||
void OnEndResize(bool& shouldRedraw) noexcept override;
|
||||
|
||||
private:
|
||||
bool _ResizeSwapChain() noexcept;
|
||||
|
||||
bool _ResizeDCompVisual(HWND hwndAttach = NULL) noexcept;
|
||||
|
||||
winrt::com_ptr<IDXGISwapChain4> _dxgiSwapChain;
|
||||
wil::unique_event_nothrow _frameLatencyWaitableObject;
|
||||
winrt::com_ptr<ID3D11Texture2D> _backBuffer;
|
||||
winrt::com_ptr<ID3D11RenderTargetView> _backBufferRtv;
|
||||
|
||||
// 调整大小或禁用 DirectFlip 时使用
|
||||
winrt::com_ptr<IDCompositionDesktopDevice> _dcompDevice;
|
||||
winrt::com_ptr<IDCompositionTarget> _dcompTarget;
|
||||
winrt::com_ptr<IDCompositionVisual2> _dcompVisual;
|
||||
winrt::com_ptr<IDCompositionSurface> _dcompSurface;
|
||||
|
||||
bool _isResized = false;
|
||||
bool _isframeLatencyWaited = false;
|
||||
bool _isSwitchingToSwapChain = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -39,7 +39,11 @@ ID3D11UnorderedAccessView* BackendDescriptorStore::GetUnorderedAccessView(ID3D11
|
|||
return _uavMap.emplace(texture, std::move(uav)).first->second.get();
|
||||
}
|
||||
|
||||
ID3D11UnorderedAccessView* BackendDescriptorStore::GetUnorderedAccessView(ID3D11Buffer* buffer, uint32_t numElements, DXGI_FORMAT format) noexcept {
|
||||
ID3D11UnorderedAccessView* BackendDescriptorStore::GetUnorderedAccessView(
|
||||
ID3D11Buffer* buffer,
|
||||
uint32_t numElements,
|
||||
DXGI_FORMAT format
|
||||
) noexcept {
|
||||
if (auto it = _uavMap.find(buffer); it != _uavMap.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
|
@ -63,4 +67,9 @@ ID3D11UnorderedAccessView* BackendDescriptorStore::GetUnorderedAccessView(ID3D11
|
|||
return _uavMap.emplace(buffer, std::move(uav)).first->second.get();
|
||||
}
|
||||
|
||||
void BackendDescriptorStore::RemoveCache(ID3D11Texture2D* texture) noexcept {
|
||||
_srvMap.erase(texture);
|
||||
_uavMap.erase(texture);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ public:
|
|||
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN
|
||||
) noexcept;
|
||||
|
||||
void RemoveCache(ID3D11Texture2D* texture) noexcept;
|
||||
|
||||
private:
|
||||
ID3D11Device5* _d3dDevice = nullptr;
|
||||
|
||||
|
|
|
|||
264
src/Magpie.Core/CompSwapchainPresenter.cpp
Normal file
264
src/Magpie.Core/CompSwapchainPresenter.cpp
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
#include "pch.h"
|
||||
#include "CompSwapchainPresenter.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.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;
|
||||
HRESULT hr = createPresentationFactory(d3dDevice, IID_PPV_ARGS(&result));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreatePresentationFactory 失败", hr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CompSwapchainPresenter::_Initialize(HWND hwndAttach) noexcept {
|
||||
if (Win32Helper::GetOSVersion().IsWin10()) {
|
||||
Logger::Get().Error("OS 不支持 composition swapchain");
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
|
||||
HRESULT hr = DCompositionCreateDevice3(d3dDevice, IID_PPV_ARGS(&_dcompDevice));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DCompositionCreateDevice3 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompDevice->CreateTargetForHwnd(hwndAttach, TRUE, _dcompTarget.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateTargetForHwnd 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompDevice->CreateVisual(_dcompVisual.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateVisual 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompTarget->SetRoot(_dcompVisual.get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetRoot 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IPresentationFactory> presentationFactory =
|
||||
CreatePresentationFactory(d3dDevice);
|
||||
if (!presentationFactory) {
|
||||
Logger::Get().Error("CreatePresentationFactory 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!presentationFactory->IsPresentationSupported()) {
|
||||
Logger::Get().Error("此 D3D 设备不支持 composition swapchain");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!presentationFactory->IsPresentationSupportedWithIndependentFlip()) {
|
||||
Logger::Get().Info("此 D3D 设备不支持 independent flip");
|
||||
}
|
||||
|
||||
hr = presentationFactory->CreatePresentationManager(_presentationManager.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreatePresentationManager 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::unique_handle hCompSurface;
|
||||
hr = DCompositionCreateSurfaceHandle(
|
||||
COMPOSITIONOBJECT_ALL_ACCESS,
|
||||
nullptr,
|
||||
hCompSurface.put()
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DCompositionCreateSurfaceHandle 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _presentationManager->CreatePresentationSurface(
|
||||
hCompSurface.get(), _presentationSurface.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreatePresentationSurface 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IUnknown> compSurface;
|
||||
hr = _dcompDevice->CreateSurfaceFromHandle(hCompSurface.get(), compSurface.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateSurfaceFromHandle 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompVisual->SetContent(compSurface.get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetContent 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _dcompDevice->Commit();
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("Commit 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _presentationManager->GetPresentRetiringFence(IID_PPV_ARGS(&_presentationFence));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetPresentRetiringFence 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t bufferCount = _CalcBufferCount();
|
||||
_presentationBuffers.resize(bufferCount);
|
||||
_presentationBufferAvailableEvents.resize(bufferCount);
|
||||
_bufferTextures.resize(bufferCount);
|
||||
_bufferRtvs.resize(bufferCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompSwapchainPresenter::BeginFrame(
|
||||
winrt::com_ptr<ID3D11Texture2D>& frameTex,
|
||||
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
|
||||
POINT& drawOffset
|
||||
) noexcept {
|
||||
// 寻找可用的缓冲区
|
||||
uint32_t curIdx = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
// 先寻找未初始化的缓冲区
|
||||
const uint32_t bufferCount = (uint32_t)_presentationBuffers.size();
|
||||
for (uint32_t i = 0; i < bufferCount; ++i) {
|
||||
if (_presentationBuffers[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
|
||||
|
||||
D3D11_TEXTURE2D_DESC desc{};
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Width = (UINT)rendererSize.cx;
|
||||
desc.Height = (UINT)rendererSize.cy;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
|
||||
desc.MiscFlags =
|
||||
D3D11_RESOURCE_MISC_SHARED |
|
||||
D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
|
||||
D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE;
|
||||
|
||||
HRESULT hr = _deviceResources->GetD3DDevice()->CreateTexture2D(
|
||||
&desc, nullptr, _bufferTextures[i].put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateTexture2D 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _presentationManager->AddBufferFromResource(
|
||||
_bufferTextures[i].get(), _presentationBuffers[i].put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("AddBufferFromResource 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = _presentationBuffers[i]->GetAvailableEvent(
|
||||
_presentationBufferAvailableEvents[i].put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetAvailableEvent 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT srcRect{ 0,0,rendererSize.cx,rendererSize.cy };
|
||||
hr = _presentationSurface->SetSourceRect(&srcRect);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetSourceRect 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
curIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (curIdx == std::numeric_limits<uint32_t>::max()) {
|
||||
// 等待某个缓冲区空闲
|
||||
DWORD waitResult = WaitForMultipleObjects(
|
||||
bufferCount, (HANDLE*)_presentationBufferAvailableEvents.data(), FALSE, INFINITE);
|
||||
if (waitResult < WAIT_OBJECT_0 || waitResult > WAIT_OBJECT_0 + bufferCount - 1) {
|
||||
Logger::Get().Error("WaitForMultipleObjects 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
curIdx = waitResult - WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
HRESULT hr = _presentationSurface->SetBuffer(_presentationBuffers[curIdx].get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("SetBuffer 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID3D11RenderTargetView>& curRtv = _bufferRtvs[curIdx];
|
||||
if (!curRtv) {
|
||||
hr = _deviceResources->GetD3DDevice()->CreateRenderTargetView(
|
||||
_bufferTextures[curIdx].get(), nullptr, curRtv.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateRenderTargetView 失败", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
drawOffset = {};
|
||||
frameTex = _bufferTextures[curIdx];
|
||||
frameRtv = curRtv;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompSwapchainPresenter::EndFrame(bool waitForRenderComplete) noexcept {
|
||||
if (waitForRenderComplete || _isResized) {
|
||||
// 下面两个调用用于减少调整窗口尺寸时的边缘闪烁,参见 AdaptivePresenter::EndFrame
|
||||
|
||||
// 等待渲染完成
|
||||
_WaitForRenderComplete();
|
||||
|
||||
// 等待 DWM 开始合成新一帧
|
||||
_WaitForDwmComposition();
|
||||
}
|
||||
|
||||
_presentationManager->Present();
|
||||
|
||||
if (_isResized) {
|
||||
_isResized = false;
|
||||
} else {
|
||||
// 确保前一帧渲染完成再渲染下一帧,既降低了 GPU 负载,也能降低延迟
|
||||
_WaitForRenderComplete();
|
||||
}
|
||||
}
|
||||
|
||||
bool CompSwapchainPresenter::OnResize() noexcept {
|
||||
_isResized = true;
|
||||
|
||||
// 缓冲区在 BeginFrame 中按需创建
|
||||
std::fill(_presentationBuffers.begin(), _presentationBuffers.end(), nullptr);
|
||||
std::fill(_presentationBufferAvailableEvents.begin(),
|
||||
_presentationBufferAvailableEvents.end(), nullptr);
|
||||
std::fill(_bufferTextures.begin(), _bufferTextures.end(), nullptr);
|
||||
std::fill(_bufferRtvs.begin(), _bufferRtvs.end(), nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
41
src/Magpie.Core/CompSwapchainPresenter.h
Normal file
41
src/Magpie.Core/CompSwapchainPresenter.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
#include "PresenterBase.h"
|
||||
#include <dcomp.h>
|
||||
#include <Presentation.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
class CompSwapchainPresenter : public PresenterBase {
|
||||
protected:
|
||||
bool _Initialize(HWND hwndAttach) noexcept override;
|
||||
|
||||
public:
|
||||
bool BeginFrame(
|
||||
winrt::com_ptr<ID3D11Texture2D>& frameTex,
|
||||
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
|
||||
POINT& drawOffset
|
||||
) noexcept override;
|
||||
|
||||
void EndFrame(bool waitForRenderComplete = false) noexcept override;
|
||||
|
||||
bool OnResize() noexcept override;
|
||||
|
||||
private:
|
||||
winrt::com_ptr<IDCompositionDesktopDevice> _dcompDevice;
|
||||
winrt::com_ptr<IDCompositionTarget> _dcompTarget;
|
||||
winrt::com_ptr<IDCompositionVisual2> _dcompVisual;
|
||||
winrt::com_ptr<IDCompositionSurface> _dcompSurface;
|
||||
|
||||
winrt::com_ptr<IPresentationManager> _presentationManager;
|
||||
winrt::com_ptr<IPresentationSurface> _presentationSurface;
|
||||
winrt::com_ptr<ID3D11Fence> _presentationFence;
|
||||
|
||||
std::vector<winrt::com_ptr<IPresentationBuffer>> _presentationBuffers;
|
||||
std::vector<wil::unique_event_nothrow> _presentationBufferAvailableEvents;
|
||||
std::vector<winrt::com_ptr<ID3D11Texture2D>> _bufferTextures;
|
||||
std::vector<winrt::com_ptr<ID3D11RenderTargetView>> _bufferRtvs;
|
||||
|
||||
bool _isResized = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -47,19 +47,8 @@ struct VertexPositionTexture {
|
|||
};
|
||||
};
|
||||
|
||||
bool CursorDrawer::Initialize(DeviceResources& deviceResources, ID3D11Texture2D* backBuffer) noexcept {
|
||||
bool CursorDrawer::Initialize(DeviceResources& deviceResources) noexcept {
|
||||
_deviceResources = &deviceResources;
|
||||
_backBuffer = backBuffer;
|
||||
|
||||
const RECT& scalingWndRect = ScalingWindow::Get().WndRect();
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
|
||||
_viewportRect = {
|
||||
destRect.left - scalingWndRect.left,
|
||||
destRect.top - scalingWndRect.top,
|
||||
destRect.right - scalingWndRect.left,
|
||||
destRect.bottom - scalingWndRect.top
|
||||
};
|
||||
|
||||
ID3D11Device* d3dDevice = deviceResources.GetD3DDevice();
|
||||
|
||||
|
|
@ -97,14 +86,17 @@ bool CursorDrawer::Initialize(DeviceResources& deviceResources, ID3D11Texture2D*
|
|||
return true;
|
||||
}
|
||||
|
||||
void CursorDrawer::Draw() noexcept {
|
||||
void CursorDrawer::Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept {
|
||||
if (!_isCursorVisible) {
|
||||
// 截屏时暂时不渲染光标
|
||||
return;
|
||||
}
|
||||
|
||||
const CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
const HCURSOR hCursor = cursorManager.Cursor();
|
||||
const HCURSOR hCursor = cursorManager.CursorHandle();
|
||||
POINT cursorPos = cursorManager.CursorPos();
|
||||
|
||||
_lastCursorHandle = NULL;
|
||||
|
||||
if (!hCursor) {
|
||||
return;
|
||||
|
|
@ -115,11 +107,17 @@ void CursorDrawer::Draw() noexcept {
|
|||
return;
|
||||
}
|
||||
|
||||
const POINT cursorPos = cursorManager.CursorPos();
|
||||
// 转换为渲染矩形局部坐标
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
cursorPos.x -= rendererRect.left;
|
||||
cursorPos.y -= rendererRect.top;
|
||||
|
||||
_lastCursorHandle = hCursor;
|
||||
_lastCursorPos = cursorPos;
|
||||
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
float cursorScaling = options.cursorScaling;
|
||||
if (cursorScaling < 1e-5) {
|
||||
if (cursorScaling < FLOAT_EPSILON<float>) {
|
||||
// 光标缩放和源窗口相同
|
||||
const Renderer& renderer = ScalingWindow::Get().Renderer();
|
||||
const SIZE srcSize = Win32Helper::GetSizeOfRect(renderer.SrcRect());
|
||||
|
|
@ -138,18 +136,26 @@ void CursorDrawer::Draw() noexcept {
|
|||
.bottom = cursorRect.top + cursorSize.cy
|
||||
};
|
||||
|
||||
if (cursorRect.left >= _viewportRect.right ||
|
||||
cursorRect.top >= _viewportRect.bottom ||
|
||||
cursorRect.right <= _viewportRect.left ||
|
||||
cursorRect.bottom <= _viewportRect.top
|
||||
const bool isSrcFocused = ScalingWindow::Get().SrcTracker().IsFocused();
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
const RECT viewportRect{
|
||||
isSrcFocused ? destRect.left - rendererRect.left : 0,
|
||||
isSrcFocused ? destRect.top - rendererRect.top : 0,
|
||||
(isSrcFocused ? destRect.right : rendererRect.right) - rendererRect.left,
|
||||
(isSrcFocused ? destRect.bottom : rendererRect.bottom) - rendererRect.top
|
||||
};
|
||||
|
||||
if (cursorRect.left >= viewportRect.right ||
|
||||
cursorRect.top >= viewportRect.bottom ||
|
||||
cursorRect.right <= viewportRect.left ||
|
||||
cursorRect.bottom <= viewportRect.top
|
||||
) {
|
||||
// 光标在窗口外,不应发生这种情况
|
||||
return;
|
||||
}
|
||||
|
||||
const SIZE viewportSize = Win32Helper::GetSizeOfRect(_viewportRect);
|
||||
float left = (cursorRect.left - _viewportRect.left) / (float)viewportSize.cx * 2 - 1.0f;
|
||||
float top = 1.0f - (cursorRect.top - _viewportRect.top) / (float)viewportSize.cy * 2;
|
||||
const SIZE viewportSize = Win32Helper::GetSizeOfRect(viewportRect);
|
||||
float left = (cursorRect.left - viewportRect.left) / (float)viewportSize.cx * 2 - 1.0f;
|
||||
float top = 1.0f - (cursorRect.top - viewportRect.top) / (float)viewportSize.cy * 2;
|
||||
float right = left + cursorSize.cx / (float)viewportSize.cx * 2;
|
||||
float bottom = top - cursorSize.cy / (float)viewportSize.cy * 2;
|
||||
|
||||
|
|
@ -186,10 +192,10 @@ void CursorDrawer::Draw() noexcept {
|
|||
// 配置渲染视口
|
||||
{
|
||||
D3D11_VIEWPORT vp{
|
||||
(float)_viewportRect.left,
|
||||
(float)_viewportRect.top,
|
||||
(float)viewportSize.cx,
|
||||
(float)viewportSize.cy,
|
||||
float(viewportRect.left + drawOffset.x),
|
||||
float(viewportRect.top + drawOffset.y),
|
||||
float(viewportSize.cx),
|
||||
float(viewportSize.cy),
|
||||
0.0f,
|
||||
1.0f
|
||||
};
|
||||
|
|
@ -255,20 +261,20 @@ void CursorDrawer::Draw() noexcept {
|
|||
}
|
||||
|
||||
D3D11_BOX srcBox{
|
||||
(UINT)std::max(cursorRect.left, _viewportRect.left),
|
||||
(UINT)std::max(cursorRect.top, _viewportRect.top),
|
||||
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),
|
||||
(UINT)std::min(cursorRect.bottom, _viewportRect.bottom),
|
||||
UINT(std::min(cursorRect.right, viewportRect.right) + drawOffset.x),
|
||||
UINT(std::min(cursorRect.bottom, viewportRect.bottom) + drawOffset.y),
|
||||
1
|
||||
};
|
||||
d3dDC->CopySubresourceRegion(
|
||||
_tempCursorTexture.get(),
|
||||
0,
|
||||
srcBox.left - cursorRect.left,
|
||||
srcBox.top - cursorRect.top,
|
||||
UINT(std::max(0l, viewportRect.left - cursorRect.left)) ,
|
||||
UINT(std::max(0l, viewportRect.top - cursorRect.left)),
|
||||
0,
|
||||
_backBuffer,
|
||||
backBuffer,
|
||||
0,
|
||||
&srcBox
|
||||
);
|
||||
|
|
@ -313,6 +319,28 @@ void CursorDrawer::Draw() noexcept {
|
|||
d3dDC->Draw(4, 0);
|
||||
}
|
||||
|
||||
bool CursorDrawer::NeedRedraw() const noexcept {
|
||||
const CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
const HCURSOR hCursor = cursorManager.CursorHandle();
|
||||
POINT cursorPos = cursorManager.CursorPos();
|
||||
|
||||
// 检查光标形状是否变化
|
||||
if (hCursor != _lastCursorHandle) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hCursor) {
|
||||
// 一直不可见
|
||||
return false;
|
||||
}
|
||||
|
||||
// 光标可见则检查位置是否变化。为了适配缩放窗口位置变化,比较在缩放窗口中的相对位置
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
cursorPos.x -= rendererRect.left;
|
||||
cursorPos.y -= rendererRect.top;
|
||||
return cursorPos != _lastCursorPos;
|
||||
}
|
||||
|
||||
const CursorDrawer::_CursorInfo* CursorDrawer::_ResolveCursor(HCURSOR hCursor) noexcept {
|
||||
if (auto it = _cursorInfos.find(hCursor); it != _cursorInfos.end()) {
|
||||
return &it->second;
|
||||
|
|
@ -520,9 +548,6 @@ const CursorDrawer::_CursorInfo* CursorDrawer::_ResolveCursor(HCURSOR hCursor) n
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const char* CURSOR_TYPES[] = { "彩色","彩色掩码","单色" };
|
||||
Logger::Get().Info(StrHelper::Concat("已解析", CURSOR_TYPES[(int)cursorInfo.type], "光标"));
|
||||
|
||||
return &_cursorInfos.emplace(hCursor, std::move(cursorInfo)).first->second;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ public:
|
|||
CursorDrawer(const CursorDrawer&) = delete;
|
||||
CursorDrawer(CursorDrawer&&) = delete;
|
||||
|
||||
bool Initialize(DeviceResources& deviceResources, ID3D11Texture2D* backBuffer) noexcept;
|
||||
bool Initialize(DeviceResources& deviceResources) noexcept;
|
||||
|
||||
void Draw() noexcept;
|
||||
void Draw(ID3D11Texture2D* backBuffer, POINT drawOffset) noexcept;
|
||||
|
||||
void IsCursorVisible(bool value) noexcept {
|
||||
_isCursorVisible = value;
|
||||
|
|
@ -24,6 +24,8 @@ public:
|
|||
return _isCursorVisible;
|
||||
}
|
||||
|
||||
bool NeedRedraw() const noexcept;
|
||||
|
||||
private:
|
||||
enum class _CursorType {
|
||||
// 彩色光标,此时纹理中 RGB 通道已预乘 A 通道(premultiplied alpha),A 通道已预先取反
|
||||
|
|
@ -53,9 +55,6 @@ private:
|
|||
bool _SetPremultipliedAlphaBlend() noexcept;
|
||||
|
||||
DeviceResources* _deviceResources = nullptr;
|
||||
ID3D11Texture2D* _backBuffer = nullptr;
|
||||
|
||||
RECT _viewportRect{};
|
||||
|
||||
phmap::flat_hash_map<HCURSOR, _CursorInfo> _cursorInfos;
|
||||
|
||||
|
|
@ -72,6 +71,9 @@ private:
|
|||
winrt::com_ptr<ID3D11ShaderResourceView> _tempCursorTextureRtv;
|
||||
SIZE _tempCursorTextureSize{};
|
||||
|
||||
HCURSOR _lastCursorHandle = NULL;
|
||||
POINT _lastCursorPos{ std::numeric_limits<LONG>::max(), std::numeric_limits<LONG>::max() };
|
||||
|
||||
bool _isCursorVisible = true;
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,19 +10,34 @@ public:
|
|||
|
||||
~CursorManager() noexcept;
|
||||
|
||||
void Initialize() noexcept;
|
||||
|
||||
void Update() noexcept;
|
||||
|
||||
HCURSOR Cursor() const noexcept {
|
||||
void OnScalingPosChanged() noexcept;
|
||||
|
||||
void OnSrcStartMove() noexcept;
|
||||
|
||||
void OnSrcEndMove() noexcept;
|
||||
|
||||
void OnStartMove() noexcept;
|
||||
|
||||
void OnEndResizeMove() noexcept;
|
||||
|
||||
void OnSrcRectChanged() noexcept;
|
||||
|
||||
// 光标不在缩放窗口上或隐藏时为 NULL
|
||||
HCURSOR CursorHandle() const noexcept {
|
||||
return _hCursor;
|
||||
}
|
||||
|
||||
// 缩放窗口局部坐标
|
||||
// 屏幕坐标
|
||||
POINT CursorPos() const noexcept {
|
||||
return _cursorPos;
|
||||
}
|
||||
|
||||
bool IsCursorCaptured() const noexcept {
|
||||
return _isUnderCapture;
|
||||
}
|
||||
|
||||
bool IsCursorCapturedOnForeground() const noexcept {
|
||||
return _isCapturedOnForeground;
|
||||
}
|
||||
|
|
@ -37,12 +52,30 @@ public:
|
|||
}
|
||||
void IsCursorCapturedOnOverlay(bool value) noexcept;
|
||||
|
||||
const int16_t SrcHitTest() const noexcept {
|
||||
return _lastCompletedHitTestResult;
|
||||
}
|
||||
|
||||
private:
|
||||
void _ShowSystemCursor(bool show, bool onDestory = false);
|
||||
|
||||
void _AdjustCursorSpeed() noexcept;
|
||||
|
||||
void _UpdateCursorClip() noexcept;
|
||||
void _RestoreCursorSpeed() noexcept;
|
||||
|
||||
void _ReliableSetCursorPos(POINT pos) const noexcept;
|
||||
|
||||
winrt::fire_and_forget _SrcHitTestAsync(POINT screenPos) noexcept;
|
||||
|
||||
void _ClearHitTestResult() noexcept;
|
||||
|
||||
void _UpdateCursorState() noexcept;
|
||||
|
||||
void _ClipCursorForMonitors(POINT cursorPos) noexcept;
|
||||
|
||||
void _ClipCursorOnSrcMoving() noexcept;
|
||||
|
||||
void _UpdateCursorPos() noexcept;
|
||||
|
||||
void _StartCapture(POINT& cursorPos) noexcept;
|
||||
|
||||
|
|
@ -50,26 +83,42 @@ private:
|
|||
|
||||
void _SetClipCursor(const RECT& clipRect, bool is3DGameMode = false) noexcept;
|
||||
|
||||
void _RestoreClipCursor() const noexcept;
|
||||
void _RestoreClipCursor() noexcept;
|
||||
|
||||
HCURSOR _hCursor = NULL;
|
||||
POINT _cursorPos { std::numeric_limits<LONG>::max(),std::numeric_limits<LONG>::max() };
|
||||
POINT _cursorPos{ std::numeric_limits<LONG>::max() };
|
||||
|
||||
// 用于确保拖拽源窗口和缩放窗口时光标位置稳定,使用相对于渲染矩形的局部坐标
|
||||
POINT _localCursorPosOnMoving{ std::numeric_limits<LONG>::max() };
|
||||
|
||||
// 用于防止光标移动到边框的过程中闪烁
|
||||
std::chrono::steady_clock::time_point _sizeCursorStartTime{};
|
||||
|
||||
RECT _originClip{ std::numeric_limits<LONG>::max() };
|
||||
RECT _lastClip{ std::numeric_limits<LONG>::max() };
|
||||
RECT _lastRealClip{ std::numeric_limits<LONG>::max() };
|
||||
|
||||
int _originCursorSpeed = 0;
|
||||
|
||||
bool _isUnderCapture = false;
|
||||
// 当缩放后的光标位置在缩放窗口上且没有被其他窗口挡住时应绘制光标
|
||||
bool _shouldDrawCursor = false;
|
||||
uint32_t _nextHitTestId = 0;
|
||||
uint32_t _lastCompletedHitTestId = 0;
|
||||
POINT _lastCompletedHitTestPos{ std::numeric_limits<LONG>::max() };
|
||||
int16_t _lastCompletedHitTestResult = HTNOWHERE;
|
||||
|
||||
bool _isUnderCapture = false;
|
||||
// 当缩放后的光标位置在交换链窗口上且没有被其他窗口挡住时应绘制光标
|
||||
bool _shouldDrawCursor = false;
|
||||
|
||||
bool _isCapturedOnForeground = false;
|
||||
|
||||
bool _isOnOverlay = false;
|
||||
bool _isCapturedOnOverlay = false;
|
||||
|
||||
bool _isSystemCursorShown = true;
|
||||
|
||||
static inline const HCURSOR _hDiagonalSize1Cursor = LoadCursor(NULL, IDC_SIZENWSE);
|
||||
static inline const HCURSOR _hDiagonalSize2Cursor = LoadCursor(NULL, IDC_SIZENESW);
|
||||
static inline const HCURSOR _hHorizontalSizeCursor = LoadCursor(NULL, IDC_SIZEWE);
|
||||
static inline const HCURSOR _hVerticalSizeCursor = LoadCursor(NULL, IDC_SIZENS);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// 复制自 https://github.com/microsoft/DirectXTex/blob/652cc82b35ff9e14097d12eff73f53348361ff15/DirectXTex/DDS.h
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// DDS.h
|
||||
//
|
||||
|
|
@ -17,47 +19,53 @@
|
|||
//--------------------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <dxgiformat.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS "
|
||||
|
||||
struct DDS_PIXELFORMAT {
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t fourCC;
|
||||
uint32_t RGBBitCount;
|
||||
uint32_t RBitMask;
|
||||
uint32_t GBitMask;
|
||||
uint32_t BBitMask;
|
||||
uint32_t ABitMask;
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t fourCC;
|
||||
uint32_t RGBBitCount;
|
||||
uint32_t RBitMask;
|
||||
uint32_t GBitMask;
|
||||
uint32_t BBitMask;
|
||||
uint32_t ABitMask;
|
||||
};
|
||||
|
||||
#define DDS_FOURCC 0x00000004 // DDPF_FOURCC
|
||||
#define DDS_RGB 0x00000040 // DDPF_RGB
|
||||
#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS
|
||||
#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
|
||||
#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
|
||||
#define DDS_ALPHAPIXELS 0x00000001 // DDPF_ALPHAPIXELS
|
||||
#define DDS_ALPHA 0x00000002 // DDPF_ALPHA
|
||||
#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8
|
||||
#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS
|
||||
#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV
|
||||
// DDS_BUMPLUMINANCE 0x00040000
|
||||
|
||||
#define DDS_FOURCC 0x00000004 // DDPF_FOURCC
|
||||
#define DDS_RGB 0x00000040 // DDPF_RGB
|
||||
#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS
|
||||
#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE
|
||||
#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS
|
||||
#define DDS_ALPHAPIXELS 0x00000001 // DDPF_ALPHAPIXELS
|
||||
#define DDS_ALPHA 0x00000002 // DDPF_ALPHA
|
||||
#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8
|
||||
#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS
|
||||
#define DDS_BUMPLUMINANCE 0x00040000 // DDPF_BUMPLUMINANCE
|
||||
#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV
|
||||
#define DDS_BUMPDUDVA 0x00080001 // DDPF_BUMPDUDV | DDPF_ALPHAPIXELS
|
||||
|
||||
#ifndef MAKEFOURCC
|
||||
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(ch0)) \
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(ch1)) << 8) \
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(ch2)) << 16) \
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(ch3)) << 24))
|
||||
|
||||
#define DDSGLOBALCONST inline
|
||||
(static_cast<uint32_t>(static_cast<uint8_t>(ch0)) \
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(ch1)) << 8) \
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(ch2)) << 16) \
|
||||
| (static_cast<uint32_t>(static_cast<uint8_t>(ch3)) << 24))
|
||||
#endif /* MAKEFOURCC */
|
||||
|
||||
#ifndef DDSGLOBALCONST
|
||||
#if defined(__GNUC__) && !defined(__MINGW32__)
|
||||
#define DDSGLOBALCONST extern const __attribute__((weak))
|
||||
#else
|
||||
#define DDSGLOBALCONST extern const __declspec(selectany)
|
||||
#endif
|
||||
#endif /* DDSGLOBALCONST */
|
||||
|
||||
DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_DXT1 =
|
||||
{ sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','1'), 0, 0, 0, 0, 0 };
|
||||
|
|
@ -179,10 +187,13 @@ DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_A2R10G10B10 =
|
|||
DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_A2B10G10R10 =
|
||||
{ sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000 };
|
||||
|
||||
// We do not support the following legacy Direct3D 9 formats:
|
||||
// DDSPF_A2W10V10U10 = { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000 };
|
||||
// DDSPF_L6V5U5 = { sizeof(DDS_PIXELFORMAT), DDS_BUMPLUMINANCE, 0, 16, 0x001f, 0x03e0, 0xfc00, 0 };
|
||||
// DDSPF_X8L8V8U8 = { sizeof(DDS_PIXELFORMAT), DDS_BUMPLUMINANCE, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0 };
|
||||
// The following legacy Direct3D 9 formats use 'mixed' signed & unsigned channels so requires special handling
|
||||
DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_A2W10V10U10 =
|
||||
{ sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDVA, 0, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000 };
|
||||
DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_L6V5U5 =
|
||||
{ sizeof(DDS_PIXELFORMAT), DDS_BUMPLUMINANCE, 0, 16, 0x001f, 0x03e0, 0xfc00, 0 };
|
||||
DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_X8L8V8U8 =
|
||||
{ sizeof(DDS_PIXELFORMAT), DDS_BUMPLUMINANCE, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0 };
|
||||
|
||||
// This indicates the DDS_HEADER_DXT10 extension is present (the format is in dxgiFormat)
|
||||
DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_DX10 =
|
||||
|
|
@ -209,8 +220,8 @@ DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_DX10 =
|
|||
#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
|
||||
|
||||
#define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\
|
||||
DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\
|
||||
DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ )
|
||||
DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\
|
||||
DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ )
|
||||
|
||||
#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP
|
||||
|
||||
|
|
@ -218,55 +229,64 @@ DDSGLOBALCONST DDS_PIXELFORMAT DDSPF_DX10 =
|
|||
|
||||
// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION
|
||||
enum DDS_RESOURCE_DIMENSION : uint32_t {
|
||||
DDS_DIMENSION_TEXTURE1D = 2,
|
||||
DDS_DIMENSION_TEXTURE2D = 3,
|
||||
DDS_DIMENSION_TEXTURE3D = 4,
|
||||
DDS_DIMENSION_TEXTURE1D = 2,
|
||||
DDS_DIMENSION_TEXTURE2D = 3,
|
||||
DDS_DIMENSION_TEXTURE3D = 4,
|
||||
};
|
||||
|
||||
// Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG
|
||||
enum DDS_RESOURCE_MISC_FLAG : uint32_t {
|
||||
DDS_RESOURCE_MISC_TEXTURECUBE = 0x4L,
|
||||
DDS_RESOURCE_MISC_TEXTURECUBE = 0x4L,
|
||||
};
|
||||
|
||||
enum DDS_MISC_FLAGS2 : uint32_t {
|
||||
DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L,
|
||||
DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L,
|
||||
};
|
||||
|
||||
|
||||
#ifndef DDS_ALPHA_MODE_DEFINED
|
||||
#define DDS_ALPHA_MODE_DEFINED
|
||||
enum DDS_ALPHA_MODE : uint32_t {
|
||||
DDS_ALPHA_MODE_UNKNOWN = 0,
|
||||
DDS_ALPHA_MODE_STRAIGHT = 1,
|
||||
DDS_ALPHA_MODE_PREMULTIPLIED = 2,
|
||||
DDS_ALPHA_MODE_OPAQUE = 3,
|
||||
DDS_ALPHA_MODE_CUSTOM = 4,
|
||||
DDS_ALPHA_MODE_UNKNOWN = 0,
|
||||
DDS_ALPHA_MODE_STRAIGHT = 1,
|
||||
DDS_ALPHA_MODE_PREMULTIPLIED = 2,
|
||||
DDS_ALPHA_MODE_OPAQUE = 3,
|
||||
DDS_ALPHA_MODE_CUSTOM = 4,
|
||||
};
|
||||
#endif
|
||||
|
||||
struct DDS_HEADER {
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t pitchOrLinearSize;
|
||||
uint32_t depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags
|
||||
uint32_t mipMapCount;
|
||||
uint32_t reserved1[11];
|
||||
DDS_PIXELFORMAT ddspf;
|
||||
uint32_t caps;
|
||||
uint32_t caps2;
|
||||
uint32_t caps3;
|
||||
uint32_t caps4;
|
||||
uint32_t reserved2;
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t pitchOrLinearSize;
|
||||
uint32_t depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags
|
||||
uint32_t mipMapCount;
|
||||
uint32_t reserved1[11];
|
||||
DDS_PIXELFORMAT ddspf;
|
||||
uint32_t caps;
|
||||
uint32_t caps2;
|
||||
uint32_t caps3;
|
||||
uint32_t caps4;
|
||||
uint32_t reserved2;
|
||||
};
|
||||
|
||||
struct DDS_HEADER_DXT10 {
|
||||
DXGI_FORMAT dxgiFormat;
|
||||
uint32_t resourceDimension;
|
||||
uint32_t miscFlag; // see D3D11_RESOURCE_MISC_FLAG
|
||||
uint32_t arraySize;
|
||||
uint32_t miscFlags2; // see DDS_MISC_FLAGS2
|
||||
DXGI_FORMAT dxgiFormat;
|
||||
uint32_t resourceDimension;
|
||||
uint32_t miscFlag; // see D3D11_RESOURCE_MISC_FLAG
|
||||
uint32_t arraySize;
|
||||
uint32_t miscFlags2; // see DDS_MISC_FLAGS2
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(DDS_PIXELFORMAT) == 32, "DDS pixel format size mismatch");
|
||||
static_assert(sizeof(DDS_HEADER) == 124, "DDS Header size mismatch");
|
||||
static_assert(sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch");
|
||||
|
||||
constexpr size_t DDS_MIN_HEADER_SIZE = sizeof(uint32_t) + sizeof(DDS_HEADER);
|
||||
constexpr size_t DDS_DX10_HEADER_SIZE = sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10);
|
||||
static_assert(DDS_DX10_HEADER_SIZE > DDS_MIN_HEADER_SIZE, "DDS DX10 Header should be larger than standard header");
|
||||
|
||||
}
|
||||
|
|
|
|||
1049
src/Magpie.Core/DDSHelper.cpp
Normal file
1049
src/Magpie.Core/DDSHelper.cpp
Normal file
File diff suppressed because it is too large
Load diff
19
src/Magpie.Core/DDSHelper.h
Normal file
19
src/Magpie.Core/DDSHelper.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct DDSHelper {
|
||||
static winrt::com_ptr<ID3D11Texture2D> Load(
|
||||
const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept;
|
||||
|
||||
static bool Save(
|
||||
const wchar_t* fileName,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
DXGI_FORMAT format,
|
||||
std::span<uint8_t> pixelData,
|
||||
uint32_t rowPitch
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,675 +0,0 @@
|
|||
//--------------------------------------------------------------------------------------
|
||||
// File: LoaderHelpers.h
|
||||
//
|
||||
// Helper functions for texture loaders and screen grabber
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
// http://go.microsoft.com/fwlink/?LinkId=248929
|
||||
// http://go.microsoft.com/fwlink/?LinkID=615561
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DDS.h"
|
||||
#include "pch.h"
|
||||
#include "Win32Helper.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Return the BPP for a particular format
|
||||
//--------------------------------------------------------------------------------------
|
||||
inline size_t BitsPerPixel(_In_ DXGI_FORMAT fmt) noexcept {
|
||||
switch (fmt) {
|
||||
case DXGI_FORMAT_R32G32B32A32_TYPELESS:
|
||||
case DXGI_FORMAT_R32G32B32A32_FLOAT:
|
||||
case DXGI_FORMAT_R32G32B32A32_UINT:
|
||||
case DXGI_FORMAT_R32G32B32A32_SINT:
|
||||
return 128;
|
||||
|
||||
case DXGI_FORMAT_R32G32B32_TYPELESS:
|
||||
case DXGI_FORMAT_R32G32B32_FLOAT:
|
||||
case DXGI_FORMAT_R32G32B32_UINT:
|
||||
case DXGI_FORMAT_R32G32B32_SINT:
|
||||
return 96;
|
||||
|
||||
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
|
||||
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
||||
case DXGI_FORMAT_R16G16B16A16_UNORM:
|
||||
case DXGI_FORMAT_R16G16B16A16_UINT:
|
||||
case DXGI_FORMAT_R16G16B16A16_SNORM:
|
||||
case DXGI_FORMAT_R16G16B16A16_SINT:
|
||||
case DXGI_FORMAT_R32G32_TYPELESS:
|
||||
case DXGI_FORMAT_R32G32_FLOAT:
|
||||
case DXGI_FORMAT_R32G32_UINT:
|
||||
case DXGI_FORMAT_R32G32_SINT:
|
||||
case DXGI_FORMAT_R32G8X24_TYPELESS:
|
||||
case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
|
||||
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
|
||||
case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
|
||||
case DXGI_FORMAT_Y416:
|
||||
case DXGI_FORMAT_Y210:
|
||||
case DXGI_FORMAT_Y216:
|
||||
return 64;
|
||||
|
||||
case DXGI_FORMAT_R10G10B10A2_TYPELESS:
|
||||
case DXGI_FORMAT_R10G10B10A2_UNORM:
|
||||
case DXGI_FORMAT_R10G10B10A2_UINT:
|
||||
case DXGI_FORMAT_R11G11B10_FLOAT:
|
||||
case DXGI_FORMAT_R8G8B8A8_TYPELESS:
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_R8G8B8A8_UINT:
|
||||
case DXGI_FORMAT_R8G8B8A8_SNORM:
|
||||
case DXGI_FORMAT_R8G8B8A8_SINT:
|
||||
case DXGI_FORMAT_R16G16_TYPELESS:
|
||||
case DXGI_FORMAT_R16G16_FLOAT:
|
||||
case DXGI_FORMAT_R16G16_UNORM:
|
||||
case DXGI_FORMAT_R16G16_UINT:
|
||||
case DXGI_FORMAT_R16G16_SNORM:
|
||||
case DXGI_FORMAT_R16G16_SINT:
|
||||
case DXGI_FORMAT_R32_TYPELESS:
|
||||
case DXGI_FORMAT_D32_FLOAT:
|
||||
case DXGI_FORMAT_R32_FLOAT:
|
||||
case DXGI_FORMAT_R32_UINT:
|
||||
case DXGI_FORMAT_R32_SINT:
|
||||
case DXGI_FORMAT_R24G8_TYPELESS:
|
||||
case DXGI_FORMAT_D24_UNORM_S8_UINT:
|
||||
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
|
||||
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
|
||||
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
|
||||
case DXGI_FORMAT_R8G8_B8G8_UNORM:
|
||||
case DXGI_FORMAT_G8R8_G8B8_UNORM:
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM:
|
||||
case DXGI_FORMAT_B8G8R8X8_UNORM:
|
||||
case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
|
||||
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_B8G8R8X8_TYPELESS:
|
||||
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_AYUV:
|
||||
case DXGI_FORMAT_Y410:
|
||||
case DXGI_FORMAT_YUY2:
|
||||
return 32;
|
||||
|
||||
case DXGI_FORMAT_P010:
|
||||
case DXGI_FORMAT_P016:
|
||||
case DXGI_FORMAT_V408:
|
||||
return 24;
|
||||
|
||||
case DXGI_FORMAT_R8G8_TYPELESS:
|
||||
case DXGI_FORMAT_R8G8_UNORM:
|
||||
case DXGI_FORMAT_R8G8_UINT:
|
||||
case DXGI_FORMAT_R8G8_SNORM:
|
||||
case DXGI_FORMAT_R8G8_SINT:
|
||||
case DXGI_FORMAT_R16_TYPELESS:
|
||||
case DXGI_FORMAT_R16_FLOAT:
|
||||
case DXGI_FORMAT_D16_UNORM:
|
||||
case DXGI_FORMAT_R16_UNORM:
|
||||
case DXGI_FORMAT_R16_UINT:
|
||||
case DXGI_FORMAT_R16_SNORM:
|
||||
case DXGI_FORMAT_R16_SINT:
|
||||
case DXGI_FORMAT_B5G6R5_UNORM:
|
||||
case DXGI_FORMAT_B5G5R5A1_UNORM:
|
||||
case DXGI_FORMAT_A8P8:
|
||||
case DXGI_FORMAT_B4G4R4A4_UNORM:
|
||||
case DXGI_FORMAT_P208:
|
||||
case DXGI_FORMAT_V208:
|
||||
return 16;
|
||||
|
||||
case DXGI_FORMAT_NV12:
|
||||
case DXGI_FORMAT_420_OPAQUE:
|
||||
case DXGI_FORMAT_NV11:
|
||||
return 12;
|
||||
|
||||
case DXGI_FORMAT_R8_TYPELESS:
|
||||
case DXGI_FORMAT_R8_UNORM:
|
||||
case DXGI_FORMAT_R8_UINT:
|
||||
case DXGI_FORMAT_R8_SNORM:
|
||||
case DXGI_FORMAT_R8_SINT:
|
||||
case DXGI_FORMAT_A8_UNORM:
|
||||
case DXGI_FORMAT_BC2_TYPELESS:
|
||||
case DXGI_FORMAT_BC2_UNORM:
|
||||
case DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC3_TYPELESS:
|
||||
case DXGI_FORMAT_BC3_UNORM:
|
||||
case DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC5_TYPELESS:
|
||||
case DXGI_FORMAT_BC5_UNORM:
|
||||
case DXGI_FORMAT_BC5_SNORM:
|
||||
case DXGI_FORMAT_BC6H_TYPELESS:
|
||||
case DXGI_FORMAT_BC6H_UF16:
|
||||
case DXGI_FORMAT_BC6H_SF16:
|
||||
case DXGI_FORMAT_BC7_TYPELESS:
|
||||
case DXGI_FORMAT_BC7_UNORM:
|
||||
case DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
case DXGI_FORMAT_AI44:
|
||||
case DXGI_FORMAT_IA44:
|
||||
case DXGI_FORMAT_P8:
|
||||
return 8;
|
||||
|
||||
case DXGI_FORMAT_R1_UNORM:
|
||||
return 1;
|
||||
|
||||
case DXGI_FORMAT_BC1_TYPELESS:
|
||||
case DXGI_FORMAT_BC1_UNORM:
|
||||
case DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC4_TYPELESS:
|
||||
case DXGI_FORMAT_BC4_UNORM:
|
||||
case DXGI_FORMAT_BC4_SNORM:
|
||||
return 4;
|
||||
|
||||
case DXGI_FORMAT_UNKNOWN:
|
||||
case DXGI_FORMAT_FORCE_UINT:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
inline DXGI_FORMAT MakeSRGB(_In_ DXGI_FORMAT format) noexcept {
|
||||
switch (format) {
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
|
||||
case DXGI_FORMAT_BC1_UNORM:
|
||||
return DXGI_FORMAT_BC1_UNORM_SRGB;
|
||||
|
||||
case DXGI_FORMAT_BC2_UNORM:
|
||||
return DXGI_FORMAT_BC2_UNORM_SRGB;
|
||||
|
||||
case DXGI_FORMAT_BC3_UNORM:
|
||||
return DXGI_FORMAT_BC3_UNORM_SRGB;
|
||||
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM:
|
||||
return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
|
||||
|
||||
case DXGI_FORMAT_B8G8R8X8_UNORM:
|
||||
return DXGI_FORMAT_B8G8R8X8_UNORM_SRGB;
|
||||
|
||||
case DXGI_FORMAT_BC7_UNORM:
|
||||
return DXGI_FORMAT_BC7_UNORM_SRGB;
|
||||
|
||||
default:
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
inline HRESULT LoadTextureDataFromFile(
|
||||
_In_z_ const wchar_t* fileName,
|
||||
std::unique_ptr<uint8_t[]>& ddsData,
|
||||
const DDS_HEADER** header,
|
||||
const uint8_t** bitData,
|
||||
size_t* bitSize) noexcept {
|
||||
if (!header || !bitData || !bitSize) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
*bitSize = 0;
|
||||
|
||||
// open the file
|
||||
wil::unique_hfile hFile(CreateFile2(
|
||||
fileName,
|
||||
GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING,
|
||||
nullptr
|
||||
));
|
||||
|
||||
if (!hFile) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
// Get the file size
|
||||
FILE_STANDARD_INFO fileInfo{};
|
||||
if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
// File is too big for 32-bit allocation, so reject read
|
||||
if (fileInfo.EndOfFile.HighPart > 0) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Need at least enough data to fill the header and magic number to be a valid DDS
|
||||
if (fileInfo.EndOfFile.LowPart < (sizeof(uint32_t) + sizeof(DDS_HEADER))) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// create enough space for the file data
|
||||
ddsData.reset(new (std::nothrow) uint8_t[fileInfo.EndOfFile.LowPart]);
|
||||
if (!ddsData) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
// read the data in
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(hFile.get(),
|
||||
ddsData.get(),
|
||||
fileInfo.EndOfFile.LowPart,
|
||||
&bytesRead,
|
||||
nullptr
|
||||
)) {
|
||||
ddsData.reset();
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
if (bytesRead < fileInfo.EndOfFile.LowPart) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// DDS files always start with the same magic number ("DDS ")
|
||||
auto const dwMagicNumber = *reinterpret_cast<const uint32_t*>(ddsData.get());
|
||||
if (dwMagicNumber != DDS_MAGIC) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
auto hdr = reinterpret_cast<const DDS_HEADER*>(ddsData.get() + sizeof(uint32_t));
|
||||
|
||||
// Verify header to validate DDS file
|
||||
if (hdr->size != sizeof(DDS_HEADER) ||
|
||||
hdr->ddspf.size != sizeof(DDS_PIXELFORMAT)) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Check for DX10 extension
|
||||
bool bDXT10Header = false;
|
||||
if ((hdr->ddspf.flags & DDS_FOURCC) &&
|
||||
(MAKEFOURCC('D', 'X', '1', '0') == hdr->ddspf.fourCC)) {
|
||||
// Must be long enough for both headers and magic value
|
||||
if (fileInfo.EndOfFile.LowPart < (sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10))) {
|
||||
ddsData.reset();
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
bDXT10Header = true;
|
||||
}
|
||||
|
||||
// setup the pointers in the process request
|
||||
*header = hdr;
|
||||
auto offset = sizeof(uint32_t) + sizeof(DDS_HEADER)
|
||||
+ (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0u);
|
||||
*bitData = ddsData.get() + offset;
|
||||
*bitSize = fileInfo.EndOfFile.LowPart - offset;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Get surface information for a particular format
|
||||
//--------------------------------------------------------------------------------------
|
||||
inline HRESULT GetSurfaceInfo(
|
||||
_In_ size_t width,
|
||||
_In_ size_t height,
|
||||
_In_ DXGI_FORMAT fmt,
|
||||
_Out_opt_ size_t* outNumBytes,
|
||||
_Out_opt_ size_t* outRowBytes,
|
||||
_Out_opt_ size_t* outNumRows) noexcept {
|
||||
uint64_t numBytes = 0;
|
||||
uint64_t rowBytes = 0;
|
||||
uint64_t numRows = 0;
|
||||
|
||||
bool bc = false;
|
||||
bool packed = false;
|
||||
bool planar = false;
|
||||
size_t bpe = 0;
|
||||
switch (fmt) {
|
||||
case DXGI_FORMAT_BC1_TYPELESS:
|
||||
case DXGI_FORMAT_BC1_UNORM:
|
||||
case DXGI_FORMAT_BC1_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC4_TYPELESS:
|
||||
case DXGI_FORMAT_BC4_UNORM:
|
||||
case DXGI_FORMAT_BC4_SNORM:
|
||||
bc = true;
|
||||
bpe = 8;
|
||||
break;
|
||||
|
||||
case DXGI_FORMAT_BC2_TYPELESS:
|
||||
case DXGI_FORMAT_BC2_UNORM:
|
||||
case DXGI_FORMAT_BC2_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC3_TYPELESS:
|
||||
case DXGI_FORMAT_BC3_UNORM:
|
||||
case DXGI_FORMAT_BC3_UNORM_SRGB:
|
||||
case DXGI_FORMAT_BC5_TYPELESS:
|
||||
case DXGI_FORMAT_BC5_UNORM:
|
||||
case DXGI_FORMAT_BC5_SNORM:
|
||||
case DXGI_FORMAT_BC6H_TYPELESS:
|
||||
case DXGI_FORMAT_BC6H_UF16:
|
||||
case DXGI_FORMAT_BC6H_SF16:
|
||||
case DXGI_FORMAT_BC7_TYPELESS:
|
||||
case DXGI_FORMAT_BC7_UNORM:
|
||||
case DXGI_FORMAT_BC7_UNORM_SRGB:
|
||||
bc = true;
|
||||
bpe = 16;
|
||||
break;
|
||||
|
||||
case DXGI_FORMAT_R8G8_B8G8_UNORM:
|
||||
case DXGI_FORMAT_G8R8_G8B8_UNORM:
|
||||
case DXGI_FORMAT_YUY2:
|
||||
packed = true;
|
||||
bpe = 4;
|
||||
break;
|
||||
|
||||
case DXGI_FORMAT_Y210:
|
||||
case DXGI_FORMAT_Y216:
|
||||
packed = true;
|
||||
bpe = 8;
|
||||
break;
|
||||
|
||||
case DXGI_FORMAT_NV12:
|
||||
case DXGI_FORMAT_420_OPAQUE:
|
||||
case DXGI_FORMAT_P208:
|
||||
planar = true;
|
||||
bpe = 2;
|
||||
break;
|
||||
|
||||
case DXGI_FORMAT_P010:
|
||||
case DXGI_FORMAT_P016:
|
||||
planar = true;
|
||||
bpe = 4;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bc) {
|
||||
uint64_t numBlocksWide = 0;
|
||||
if (width > 0) {
|
||||
numBlocksWide = std::max<uint64_t>(1u, (uint64_t(width) + 3u) / 4u);
|
||||
}
|
||||
uint64_t numBlocksHigh = 0;
|
||||
if (height > 0) {
|
||||
numBlocksHigh = std::max<uint64_t>(1u, (uint64_t(height) + 3u) / 4u);
|
||||
}
|
||||
rowBytes = numBlocksWide * bpe;
|
||||
numRows = numBlocksHigh;
|
||||
numBytes = rowBytes * numBlocksHigh;
|
||||
} else if (packed) {
|
||||
rowBytes = ((uint64_t(width) + 1u) >> 1) * bpe;
|
||||
numRows = uint64_t(height);
|
||||
numBytes = rowBytes * height;
|
||||
} else if (fmt == DXGI_FORMAT_NV11) {
|
||||
rowBytes = ((uint64_t(width) + 3u) >> 2) * 4u;
|
||||
numRows = uint64_t(height) * 2u; // Direct3D makes this simplifying assumption, although it is larger than the 4:1:1 data
|
||||
numBytes = rowBytes * numRows;
|
||||
} else if (planar) {
|
||||
rowBytes = ((uint64_t(width) + 1u) >> 1) * bpe;
|
||||
numBytes = (rowBytes * uint64_t(height)) + ((rowBytes * uint64_t(height) + 1u) >> 1);
|
||||
numRows = height + ((uint64_t(height) + 1u) >> 1);
|
||||
} else {
|
||||
const size_t bpp = BitsPerPixel(fmt);
|
||||
if (!bpp)
|
||||
return E_INVALIDARG;
|
||||
|
||||
rowBytes = (uint64_t(width) * bpp + 7u) / 8u; // round up to nearest byte
|
||||
numRows = uint64_t(height);
|
||||
numBytes = rowBytes * height;
|
||||
}
|
||||
|
||||
static_assert(sizeof(size_t) == 8, "Not a 64-bit platform!");
|
||||
|
||||
|
||||
if (outNumBytes) {
|
||||
*outNumBytes = static_cast<size_t>(numBytes);
|
||||
}
|
||||
if (outRowBytes) {
|
||||
*outRowBytes = static_cast<size_t>(rowBytes);
|
||||
}
|
||||
if (outNumRows) {
|
||||
*outNumRows = static_cast<size_t>(numRows);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
#define ISBITMASK( r,g,b,a ) ( ddpf.RBitMask == r && ddpf.GBitMask == g && ddpf.BBitMask == b && ddpf.ABitMask == a )
|
||||
|
||||
inline DXGI_FORMAT GetDXGIFormat(const DDS_PIXELFORMAT& ddpf) noexcept {
|
||||
if (ddpf.flags & DDS_RGB) {
|
||||
// Note that sRGB formats are written using the "DX10" extended header
|
||||
|
||||
switch (ddpf.RGBBitCount) {
|
||||
case 32:
|
||||
if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) {
|
||||
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
|
||||
if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000)) {
|
||||
return DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
|
||||
if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0)) {
|
||||
return DXGI_FORMAT_B8G8R8X8_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x000000ff,0x0000ff00,0x00ff0000,0) aka D3DFMT_X8B8G8R8
|
||||
|
||||
// Note that many common DDS reader/writers (including D3DX) swap the
|
||||
// the RED/BLUE masks for 10:10:10:2 formats. We assume
|
||||
// below that the 'backwards' header mask is being used since it is most
|
||||
// likely written by D3DX. The more robust solution is to use the 'DX10'
|
||||
// header extension and specify the DXGI_FORMAT_R10G10B10A2_UNORM format directly
|
||||
|
||||
// For 'correct' writers, this should be 0x000003ff,0x000ffc00,0x3ff00000 for RGB data
|
||||
if (ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000)) {
|
||||
return DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x000003ff,0x000ffc00,0x3ff00000,0xc0000000) aka D3DFMT_A2R10G10B10
|
||||
|
||||
if (ISBITMASK(0x0000ffff, 0xffff0000, 0, 0)) {
|
||||
return DXGI_FORMAT_R16G16_UNORM;
|
||||
}
|
||||
|
||||
if (ISBITMASK(0xffffffff, 0, 0, 0)) {
|
||||
// Only 32-bit color channel format in D3D9 was R32F
|
||||
return DXGI_FORMAT_R32_FLOAT; // D3DX writes this out as a FourCC of 114
|
||||
}
|
||||
break;
|
||||
|
||||
case 24:
|
||||
// No 24bpp DXGI formats aka D3DFMT_R8G8B8
|
||||
break;
|
||||
|
||||
case 16:
|
||||
if (ISBITMASK(0x7c00, 0x03e0, 0x001f, 0x8000)) {
|
||||
return DXGI_FORMAT_B5G5R5A1_UNORM;
|
||||
}
|
||||
if (ISBITMASK(0xf800, 0x07e0, 0x001f, 0)) {
|
||||
return DXGI_FORMAT_B5G6R5_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x7c00,0x03e0,0x001f,0) aka D3DFMT_X1R5G5B5
|
||||
|
||||
if (ISBITMASK(0x0f00, 0x00f0, 0x000f, 0xf000)) {
|
||||
return DXGI_FORMAT_B4G4R4A4_UNORM;
|
||||
}
|
||||
|
||||
// NVTT versions 1.x wrote this as RGB instead of LUMINANCE
|
||||
if (ISBITMASK(0x00ff, 0, 0, 0xff00)) {
|
||||
return DXGI_FORMAT_R8G8_UNORM;
|
||||
}
|
||||
if (ISBITMASK(0xffff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R16_UNORM;
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x0f00,0x00f0,0x000f,0) aka D3DFMT_X4R4G4B4
|
||||
|
||||
// No 3:3:2:8 or paletted DXGI formats aka D3DFMT_A8R3G3B2, D3DFMT_A8P8, etc.
|
||||
break;
|
||||
|
||||
case 8:
|
||||
// NVTT versions 1.x wrote this as RGB instead of LUMINANCE
|
||||
if (ISBITMASK(0xff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R8_UNORM;
|
||||
}
|
||||
|
||||
// No 3:3:2 or paletted DXGI formats aka D3DFMT_R3G3B2, D3DFMT_P8
|
||||
break;
|
||||
}
|
||||
} else if (ddpf.flags & DDS_LUMINANCE) {
|
||||
switch (ddpf.RGBBitCount) {
|
||||
case 16:
|
||||
if (ISBITMASK(0xffff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R16_UNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
if (ISBITMASK(0x00ff, 0, 0, 0xff00)) {
|
||||
return DXGI_FORMAT_R8G8_UNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
if (ISBITMASK(0xff, 0, 0, 0)) {
|
||||
return DXGI_FORMAT_R8_UNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x0f,0,0,0xf0) aka D3DFMT_A4L4
|
||||
|
||||
if (ISBITMASK(0x00ff, 0, 0, 0xff00)) {
|
||||
return DXGI_FORMAT_R8G8_UNORM; // Some DDS writers assume the bitcount should be 8 instead of 16
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (ddpf.flags & DDS_ALPHA) {
|
||||
if (8 == ddpf.RGBBitCount) {
|
||||
return DXGI_FORMAT_A8_UNORM;
|
||||
}
|
||||
} else if (ddpf.flags & DDS_BUMPDUDV) {
|
||||
switch (ddpf.RGBBitCount) {
|
||||
case 32:
|
||||
if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) {
|
||||
return DXGI_FORMAT_R8G8B8A8_SNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
if (ISBITMASK(0x0000ffff, 0xffff0000, 0, 0)) {
|
||||
return DXGI_FORMAT_R16G16_SNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
|
||||
// No DXGI format maps to ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000) aka D3DFMT_A2W10V10U10
|
||||
break;
|
||||
|
||||
case 16:
|
||||
if (ISBITMASK(0x00ff, 0xff00, 0, 0)) {
|
||||
return DXGI_FORMAT_R8G8_SNORM; // D3DX10/11 writes this out as DX10 extension
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// No DXGI format maps to DDPF_BUMPLUMINANCE aka D3DFMT_L6V5U5, D3DFMT_X8L8V8U8
|
||||
} else if (ddpf.flags & DDS_FOURCC) {
|
||||
if (MAKEFOURCC('D', 'X', 'T', '1') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC1_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('D', 'X', 'T', '3') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC2_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('D', 'X', 'T', '5') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC3_UNORM;
|
||||
}
|
||||
|
||||
// While pre-multiplied alpha isn't directly supported by the DXGI formats,
|
||||
// they are basically the same as these BC formats so they can be mapped
|
||||
if (MAKEFOURCC('D', 'X', 'T', '2') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC2_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('D', 'X', 'T', '4') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC3_UNORM;
|
||||
}
|
||||
|
||||
if (MAKEFOURCC('A', 'T', 'I', '1') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC4_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('B', 'C', '4', 'U') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC4_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('B', 'C', '4', 'S') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC4_SNORM;
|
||||
}
|
||||
|
||||
if (MAKEFOURCC('A', 'T', 'I', '2') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC5_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('B', 'C', '5', 'U') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC5_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('B', 'C', '5', 'S') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_BC5_SNORM;
|
||||
}
|
||||
|
||||
// BC6H and BC7 are written using the "DX10" extended header
|
||||
|
||||
if (MAKEFOURCC('R', 'G', 'B', 'G') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_R8G8_B8G8_UNORM;
|
||||
}
|
||||
if (MAKEFOURCC('G', 'R', 'G', 'B') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_G8R8_G8B8_UNORM;
|
||||
}
|
||||
|
||||
if (MAKEFOURCC('Y', 'U', 'Y', '2') == ddpf.fourCC) {
|
||||
return DXGI_FORMAT_YUY2;
|
||||
}
|
||||
|
||||
// Check for D3DFORMAT enums being set here
|
||||
switch (ddpf.fourCC) {
|
||||
case 36: // D3DFMT_A16B16G16R16
|
||||
return DXGI_FORMAT_R16G16B16A16_UNORM;
|
||||
|
||||
case 110: // D3DFMT_Q16W16V16U16
|
||||
return DXGI_FORMAT_R16G16B16A16_SNORM;
|
||||
|
||||
case 111: // D3DFMT_R16F
|
||||
return DXGI_FORMAT_R16_FLOAT;
|
||||
|
||||
case 112: // D3DFMT_G16R16F
|
||||
return DXGI_FORMAT_R16G16_FLOAT;
|
||||
|
||||
case 113: // D3DFMT_A16B16G16R16F
|
||||
return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
|
||||
case 114: // D3DFMT_R32F
|
||||
return DXGI_FORMAT_R32_FLOAT;
|
||||
|
||||
case 115: // D3DFMT_G32R32F
|
||||
return DXGI_FORMAT_R32G32_FLOAT;
|
||||
|
||||
case 116: // D3DFMT_A32B32G32R32F
|
||||
return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
|
||||
// No DXGI format maps to D3DFMT_CxV8U8
|
||||
}
|
||||
}
|
||||
|
||||
return DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
#undef ISBITMASK
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
inline DDS_ALPHA_MODE GetAlphaMode(_In_ const DDS_HEADER* header) noexcept {
|
||||
if (header->ddspf.flags & DDS_FOURCC) {
|
||||
if (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC) {
|
||||
auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>(reinterpret_cast<const uint8_t*>(header) + sizeof(DDS_HEADER));
|
||||
auto const mode = static_cast<DDS_ALPHA_MODE>(d3d10ext->miscFlags2 & DDS_MISC_FLAGS2_ALPHA_MODE_MASK);
|
||||
switch (mode) {
|
||||
case DDS_ALPHA_MODE_STRAIGHT:
|
||||
case DDS_ALPHA_MODE_PREMULTIPLIED:
|
||||
case DDS_ALPHA_MODE_OPAQUE:
|
||||
case DDS_ALPHA_MODE_CUSTOM:
|
||||
return mode;
|
||||
|
||||
case DDS_ALPHA_MODE_UNKNOWN:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if ((MAKEFOURCC('D', 'X', 'T', '2') == header->ddspf.fourCC)
|
||||
|| (MAKEFOURCC('D', 'X', 'T', '4') == header->ddspf.fourCC)) {
|
||||
return DDS_ALPHA_MODE_PREMULTIPLIED;
|
||||
}
|
||||
}
|
||||
|
||||
return DDS_ALPHA_MODE_UNKNOWN;
|
||||
}
|
||||
|
|
@ -44,13 +44,11 @@ bool DesktopDuplicationFrameSource::_Initialize() noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
const RECT& srcRect = ScalingWindow::Get().SrcTracker().SrcRect();
|
||||
|
||||
HMONITOR hMonitor = MonitorFromWindow(hwndSrc, MONITOR_DEFAULTTONEAREST);
|
||||
if (!hMonitor) {
|
||||
Logger::Get().Win32Error("MonitorFromWindow 失败");
|
||||
return false;
|
||||
}
|
||||
HMONITOR hMonitor = MonitorFromWindow(hwndSrc, MONITOR_DEFAULTTONULL);
|
||||
assert(hMonitor);
|
||||
|
||||
{
|
||||
MONITORINFO mi{ .cbSize = sizeof(mi) };
|
||||
|
|
@ -59,25 +57,16 @@ bool DesktopDuplicationFrameSource::_Initialize() noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
// 最大化的窗口无需调整位置
|
||||
if (Win32Helper::GetWindowShowCmd(hwndSrc) != SW_SHOWMAXIMIZED) {
|
||||
if (!_CenterWindowIfNecessary(hwndSrc, mi.rcWork)) {
|
||||
Logger::Get().Error("居中源窗口失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_CalcSrcRect()) {
|
||||
Logger::Get().Error("_CalcSrcRect 失败");
|
||||
return false;
|
||||
}
|
||||
// ScalingWindow::_InitialMoveSrcWindowInFullscreen 已调整窗口位置
|
||||
assert(srcRect.left >= mi.rcMonitor.left && srcRect.top >= mi.rcMonitor.top &&
|
||||
srcRect.right <= mi.rcMonitor.right && srcRect.bottom <= mi.rcMonitor.bottom);
|
||||
|
||||
// 计算源窗口客户区在该屏幕上的位置,用于计算新帧是否有更新
|
||||
_srcClientInMonitor = {
|
||||
_srcRect.left - mi.rcMonitor.left,
|
||||
_srcRect.top - mi.rcMonitor.top,
|
||||
_srcRect.right - mi.rcMonitor.left,
|
||||
_srcRect.bottom - mi.rcMonitor.top
|
||||
srcRect.left - mi.rcMonitor.left,
|
||||
srcRect.top - mi.rcMonitor.top,
|
||||
srcRect.right - mi.rcMonitor.left,
|
||||
srcRect.bottom - mi.rcMonitor.top
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -93,8 +82,8 @@ bool DesktopDuplicationFrameSource::_Initialize() noexcept {
|
|||
_output = DirectXHelper::CreateTexture2D(
|
||||
_deviceResources->GetD3DDevice(),
|
||||
DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
_srcRect.right - _srcRect.left,
|
||||
_srcRect.bottom - _srcRect.top,
|
||||
srcRect.right - srcRect.left,
|
||||
srcRect.bottom - srcRect.top,
|
||||
D3D11_BIND_SHADER_RESOURCE
|
||||
);
|
||||
if (!_output) {
|
||||
|
|
@ -102,19 +91,13 @@ bool DesktopDuplicationFrameSource::_Initialize() noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IDXGIOutput1> output = FindMonitor(
|
||||
_dxgiOutput = FindMonitor(
|
||||
_deviceResources->GetGraphicsAdapter(), hMonitor);
|
||||
if (!output) {
|
||||
if (!_dxgiOutput) {
|
||||
Logger::Get().Error("无法找到 IDXGIOutput");
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT hr = output->DuplicateOutput(_deviceResources->GetD3DDevice(), _outputDup.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DuplicateOutput 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 使全屏窗口无法被捕获到
|
||||
if (!SetWindowDisplayAffinity(ScalingWindow::Get().Handle(), WDA_EXCLUDEFROMCAPTURE)) {
|
||||
Logger::Get().Win32Error("SetWindowDisplayAffinity 失败");
|
||||
|
|
@ -125,6 +108,18 @@ bool DesktopDuplicationFrameSource::_Initialize() noexcept {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DesktopDuplicationFrameSource::Start() noexcept {
|
||||
_DisableRoundCornerInWin11();
|
||||
|
||||
HRESULT hr = _dxgiOutput->DuplicateOutput(_deviceResources->GetD3DDevice(), _outputDup.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DuplicateOutput 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameSourceState DesktopDuplicationFrameSource::_Update() noexcept {
|
||||
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
|
||||
|
||||
|
|
@ -172,7 +167,7 @@ FrameSourceState DesktopDuplicationFrameSource::_Update() noexcept {
|
|||
for (uint32_t i = 0; i < nRect; ++i) {
|
||||
const DXGI_OUTDUPL_MOVE_RECT& rect =
|
||||
((DXGI_OUTDUPL_MOVE_RECT*)_dupMetaData.data())[i];
|
||||
if (Win32Helper::CheckOverlap(_srcClientInMonitor, rect.DestinationRect)) {
|
||||
if (Win32Helper::IsRectOverlap(_srcClientInMonitor, rect.DestinationRect)) {
|
||||
noUpdate = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -192,7 +187,7 @@ FrameSourceState DesktopDuplicationFrameSource::_Update() noexcept {
|
|||
nRect = bufSize / sizeof(RECT);
|
||||
for (uint32_t i = 0; i < nRect; ++i) {
|
||||
const RECT& rect = ((RECT*)_dupMetaData.data())[i];
|
||||
if (Win32Helper::CheckOverlap(_srcClientInMonitor, rect)) {
|
||||
if (Win32Helper::IsRectOverlap(_srcClientInMonitor, rect)) {
|
||||
noUpdate = false;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ namespace Magpie {
|
|||
|
||||
class DesktopDuplicationFrameSource final : public FrameSourceBase {
|
||||
public:
|
||||
bool IsScreenCapture() const noexcept override {
|
||||
return true;
|
||||
}
|
||||
bool Start() noexcept override;
|
||||
|
||||
FrameSourceWaitType WaitType() const noexcept override {
|
||||
return FrameSourceWaitType::WaitForFrame;
|
||||
|
|
@ -20,19 +18,12 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
bool _HasRoundCornerInWin11() noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _CanCaptureTitleBar() noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _Initialize() noexcept override;
|
||||
|
||||
FrameSourceState _Update() noexcept override;
|
||||
|
||||
private:
|
||||
winrt::com_ptr<IDXGIOutput1> _dxgiOutput;
|
||||
winrt::com_ptr<IDXGIOutputDuplication> _outputDup;
|
||||
|
||||
SmallVector<uint8_t, 0> _dupMetaData;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
bool DeviceResources::Initialize() noexcept {
|
||||
bool DeviceResources::Initialize(bool isForeground) noexcept {
|
||||
#ifdef _DEBUG
|
||||
UINT flag = DXGI_CREATE_FACTORY_DEBUG;
|
||||
#else
|
||||
|
|
@ -31,7 +31,7 @@ bool DeviceResources::Initialize() noexcept {
|
|||
_isTearingSupported = supportTearing;
|
||||
Logger::Get().Info(fmt::format("可变刷新率支持: {}", supportTearing ? "是" : "否"));
|
||||
|
||||
if (!_ObtainAdapterAndDevice(ScalingWindow::Get().Options().graphicsCardId)) {
|
||||
if (!_ObtainAdapterAndDevice(ScalingWindow::Get().Options().graphicsCardId, isForeground)) {
|
||||
Logger::Get().Error("找不到可用的图形适配器");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ ID3D11SamplerState* DeviceResources::GetSampler(D3D11_FILTER filterMode, D3D11_T
|
|||
return _samMap.emplace(key, std::move(sam)).first->second.get();
|
||||
}
|
||||
|
||||
bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noexcept {
|
||||
bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId, bool isForeground) noexcept {
|
||||
winrt::com_ptr<IDXGIAdapter1> adapter;
|
||||
// 记录不支持 FL11 的显卡索引,防止重复尝试
|
||||
int failedIdx = -1;
|
||||
|
|
@ -79,7 +79,7 @@ bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noe
|
|||
hr = adapter->GetDesc1(&desc);
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (desc.VendorId == graphicsCardId.vendorId && desc.DeviceId == graphicsCardId.deviceId) {
|
||||
if (_TryCreateD3DDevice(adapter)) {
|
||||
if (_TryCreateD3DDevice(adapter, isForeground)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noe
|
|||
}
|
||||
|
||||
if (desc.VendorId == graphicsCardId.vendorId && desc.DeviceId == graphicsCardId.deviceId) {
|
||||
if (_TryCreateD3DDevice(adapter)) {
|
||||
if (_TryCreateD3DDevice(adapter, isForeground)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noe
|
|||
continue;
|
||||
}
|
||||
|
||||
if (_TryCreateD3DDevice(adapter)) {
|
||||
if (_TryCreateD3DDevice(adapter, isForeground)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +151,7 @@ bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noe
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!_TryCreateD3DDevice(adapter)) {
|
||||
if (!_TryCreateD3DDevice(adapter, isForeground)) {
|
||||
Logger::Get().ComError("创建 WARP 设备失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -159,18 +159,30 @@ bool DeviceResources::_ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noe
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DeviceResources::_TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& adapter) noexcept {
|
||||
bool DeviceResources::_TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& adapter, bool isForeground) noexcept {
|
||||
D3D_FEATURE_LEVEL featureLevels[] = {
|
||||
D3D_FEATURE_LEVEL_11_1,
|
||||
D3D_FEATURE_LEVEL_11_0
|
||||
};
|
||||
UINT nFeatureLevels = ARRAYSIZE(featureLevels);
|
||||
const UINT nFeatureLevels = ARRAYSIZE(featureLevels);
|
||||
|
||||
UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||
// DEBUG 配置下启用调试层
|
||||
if (DirectXHelper::IsDebugLayersAvailable()) {
|
||||
// 在 DEBUG 配置启用调试层
|
||||
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||
}
|
||||
// WGC 和 D3D11_CREATE_DEVICE_SINGLETHREADED 不兼容
|
||||
if (isForeground || ScalingWindow::Get().Options().captureMethod != CaptureMethod::GraphicsCapture) {
|
||||
createDeviceFlags |= D3D11_CREATE_DEVICE_SINGLETHREADED;
|
||||
}
|
||||
#ifdef MP_USE_COMPSWAPCHAIN
|
||||
if (isForeground) {
|
||||
// 文档说 composition swapchain 和驱动程序内部线程不兼容,如果没有这个标志,创建
|
||||
// IPresentationFactory 将失败。但根据我在 Win11 24H2 上的测试,不指定这个标志也
|
||||
// 可以正常使用,可能文档已经过时。安全起见加上了这个标志。
|
||||
createDeviceFlags |= D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
|
||||
}
|
||||
#endif
|
||||
|
||||
winrt::com_ptr<ID3D11Device> d3dDevice;
|
||||
winrt::com_ptr<ID3D11DeviceContext> d3dDC;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public:
|
|||
DeviceResources(const DeviceResources&) = delete;
|
||||
DeviceResources(DeviceResources&&) = default;
|
||||
|
||||
bool Initialize() noexcept;
|
||||
bool Initialize(bool isForeground) noexcept;
|
||||
|
||||
IDXGIFactory7* GetDXGIFactory() const noexcept { return _dxgiFactory.get(); }
|
||||
ID3D11Device5* GetD3DDevice() const noexcept { return _d3dDevice.get(); }
|
||||
|
|
@ -23,8 +23,8 @@ public:
|
|||
ID3D11SamplerState* GetSampler(D3D11_FILTER filterMode, D3D11_TEXTURE_ADDRESS_MODE addressMode) noexcept;
|
||||
|
||||
private:
|
||||
bool _ObtainAdapterAndDevice(GraphicsCardId graphicsCardId) noexcept;
|
||||
bool _TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& adapter) noexcept;
|
||||
bool _ObtainAdapterAndDevice(GraphicsCardId graphicsCardId, bool isForeground) noexcept;
|
||||
bool _TryCreateD3DDevice(const winrt::com_ptr<IDXGIAdapter1>& adapter, bool isForeground) noexcept;
|
||||
|
||||
winrt::com_ptr<IDXGIFactory7> _dxgiFactory;
|
||||
winrt::com_ptr<IDXGIAdapter4> _graphicsAdapter;
|
||||
|
|
|
|||
|
|
@ -30,16 +30,11 @@ bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
if (!_CalcSrcRect()) {
|
||||
Logger::Get().Error("_CalcSrcRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
|
||||
const SrcTracker& srcTracker = ScalingWindow::Get().SrcTracker();
|
||||
|
||||
RECT frameRect;
|
||||
double a, bx, by;
|
||||
if (!_GetMapToOriginDPI(hwndSrc, a, bx, by)) {
|
||||
if (!_GetMapToOriginDPI(srcTracker.Handle(), a, bx, by)) {
|
||||
// 很可能是因为窗口没有重定向表面,这种情况下 DwmSharedSurface 捕获肯定失败
|
||||
Logger::Get().Error("_GetMapToOriginDPI 失败");
|
||||
return false;
|
||||
|
|
@ -47,11 +42,12 @@ bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
|
|||
|
||||
Logger::Get().Info(fmt::format("源窗口 DPI 缩放为 {}", 1 / a));
|
||||
|
||||
const RECT& srcRect = srcTracker.SrcRect();
|
||||
frameRect = RECT{
|
||||
std::lround(_srcRect.left * a + bx),
|
||||
std::lround(_srcRect.top * a + by),
|
||||
std::lround(_srcRect.right * a + bx),
|
||||
std::lround(_srcRect.bottom * a + by)
|
||||
std::lround(srcRect.left * a + bx),
|
||||
std::lround(srcRect.top * a + by),
|
||||
std::lround(srcRect.right * a + bx),
|
||||
std::lround(srcRect.bottom * a + by)
|
||||
};
|
||||
|
||||
if (frameRect.left < 0 || frameRect.top < 0 || frameRect.right < 0
|
||||
|
|
@ -89,7 +85,7 @@ bool DwmSharedSurfaceFrameSource::_Initialize() noexcept {
|
|||
|
||||
FrameSourceState DwmSharedSurfaceFrameSource::_Update() noexcept {
|
||||
HANDLE sharedTextureHandle = NULL;
|
||||
if (!dwmGetDxSharedSurface(ScalingWindow::Get().HwndSrc(),
|
||||
if (!dwmGetDxSharedSurface(ScalingWindow::Get().SrcTracker().Handle(),
|
||||
&sharedTextureHandle, nullptr, nullptr, nullptr, nullptr)
|
||||
|| !sharedTextureHandle
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ class DwmSharedSurfaceFrameSource final : public FrameSourceBase {
|
|||
public:
|
||||
virtual ~DwmSharedSurfaceFrameSource() {}
|
||||
|
||||
bool IsScreenCapture() const noexcept override {
|
||||
return false;
|
||||
}
|
||||
|
||||
FrameSourceWaitType WaitType() const noexcept override {
|
||||
return FrameSourceWaitType::NoWait;
|
||||
}
|
||||
|
|
@ -24,14 +20,6 @@ protected:
|
|||
|
||||
FrameSourceState _Update() noexcept override;
|
||||
|
||||
bool _HasRoundCornerInWin11() noexcept override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _CanCaptureTitleBar() noexcept override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
D3D11_BOX _frameInWnd{};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ struct serializer<
|
|||
ar& size;
|
||||
HRESULT hr = D3DCreateBlob(size, blob.put());
|
||||
if (FAILED(hr)) {
|
||||
Magpie::Logger::Get().ComError("D3DCreateBlob 失败", hr);
|
||||
Logger::Get().ComError("D3DCreateBlob 失败", hr);
|
||||
throw new std::exception();
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ static std::wstring GetLinearEffectName(std::wstring_view effectName) {
|
|||
static std::wstring GetCacheFileName(std::wstring_view linearEffectName, uint32_t flags, uint64_t hash) {
|
||||
assert(flags <= 0xFFFF);
|
||||
// 缓存文件的命名: {效果名}_{标志位(4)}_{哈希(16))}
|
||||
return fmt::format(L"{}{}_{:04x}_{:016x}", CommonSharedConstants::CACHE_DIR, linearEffectName, flags, hash);
|
||||
return fmt::format(L"{}\\{}_{:04x}_{:016x}", CommonSharedConstants::CACHE_DIR, linearEffectName, flags, hash);
|
||||
}
|
||||
|
||||
void EffectCacheManager::_AddToMemCache(const std::wstring& cacheFileName, std::string& key, const EffectDesc& desc) {
|
||||
|
|
@ -212,11 +212,11 @@ void EffectCacheManager::Save(
|
|||
) {
|
||||
const std::wstring linearEffectName = GetLinearEffectName(effectName);
|
||||
|
||||
std::vector<BYTE> buf;
|
||||
buf.reserve(4096);
|
||||
std::vector<BYTE> buffer;
|
||||
buffer.reserve(4096);
|
||||
|
||||
try {
|
||||
yas::vector_ostream os(buf);
|
||||
yas::vector_ostream os(buffer);
|
||||
yas::binary_oarchive<yas::vector_ostream<BYTE>, yas::binary> oa(os);
|
||||
|
||||
oa.write(EFFECT_CACHE_VERSION);
|
||||
|
|
@ -235,7 +235,7 @@ void EffectCacheManager::Save(
|
|||
// 清理缓存
|
||||
WIN32_FIND_DATA findData{};
|
||||
wil::unique_hfind hFind(FindFirstFileEx(
|
||||
StrHelper::Concat(CommonSharedConstants::CACHE_DIR, L"*").c_str(),
|
||||
StrHelper::Concat(CommonSharedConstants::CACHE_DIR, L"\\*").c_str(),
|
||||
FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH));
|
||||
if (hFind) {
|
||||
do {
|
||||
|
|
@ -282,7 +282,9 @@ void EffectCacheManager::Save(
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!DeleteFile(StrHelper::Concat(CommonSharedConstants::CACHE_DIR, findData.cFileName).c_str())) {
|
||||
if (!DeleteFile(StrHelper::Concat(
|
||||
CommonSharedConstants::CACHE_DIR, L"\\", findData.cFileName).c_str()))
|
||||
{
|
||||
Logger::Get().Win32Error(StrHelper::Concat("删除缓存文件 ",
|
||||
StrHelper::UTF16ToUTF8(findData.cFileName), " 失败"));
|
||||
}
|
||||
|
|
@ -293,7 +295,7 @@ void EffectCacheManager::Save(
|
|||
}
|
||||
|
||||
std::wstring cacheFileName = GetCacheFileName(linearEffectName, flags, hash);
|
||||
if (!Win32Helper::WriteFile(cacheFileName.c_str(), buf.data(), buf.size())) {
|
||||
if (!Win32Helper::WriteFile(cacheFileName.c_str(), buffer)) {
|
||||
Logger::Get().Error("保存缓存失败");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -986,8 +986,9 @@ static uint32_t ResolvePasses(SmallVector<std::string_view>& blocks, EffectDesc&
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (it->second == 0 || !desc.textures[it->second].source.empty()) {
|
||||
// INPUT 和从文件读取的纹理不能作为输出
|
||||
// INPUT 和从文件读取的纹理不能作为输出。
|
||||
// 只有最后一个通道能输出到 OUTPUT,这是为了方便截图。
|
||||
if (it->second == 0 || it->second == 1 || !desc.textures[it->second].source.empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -1458,7 +1459,7 @@ static uint32_t CompilePasses(
|
|||
uint32_t flags,
|
||||
const SmallVector<std::string_view>& commonBlocks,
|
||||
const SmallVector<std::string_view>& passBlocks,
|
||||
const phmap::flat_hash_map<std::wstring, float>* inlineParams
|
||||
const phmap::flat_hash_map<std::string, float>* inlineParams
|
||||
) noexcept {
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -1494,14 +1495,14 @@ static uint32_t CompilePasses(
|
|||
cbHlsl.append("};\n\n");
|
||||
|
||||
if (desc.flags & EffectFlags::InlineParams) {
|
||||
phmap::flat_hash_set<std::wstring> paramNames;
|
||||
phmap::flat_hash_set<std::string> paramNames;
|
||||
for (const auto& d : desc.params) {
|
||||
cbHlsl.append("static const ")
|
||||
.append(d.constant.index() == 0 ? "float " : "int ")
|
||||
.append(d.name)
|
||||
.append(" = ");
|
||||
|
||||
const std::wstring& name = *paramNames.emplace(StrHelper::UTF8ToUTF16(d.name)).first;
|
||||
const std::string& name = *paramNames.emplace(d.name).first;
|
||||
|
||||
auto it = inlineParams->find(name);
|
||||
if (it == inlineParams->end()) {
|
||||
|
|
@ -1531,7 +1532,8 @@ static uint32_t CompilePasses(
|
|||
cbHlsl.append("\n");
|
||||
}
|
||||
|
||||
std::wstring sourcesPathName = StrHelper::Concat(CommonSharedConstants::SOURCES_DIR, StrHelper::UTF8ToUTF16(desc.name));
|
||||
std::wstring sourcesPathName = StrHelper::Concat(
|
||||
CommonSharedConstants::SOURCES_DIR, L"\\", StrHelper::UTF8ToUTF16(desc.name));
|
||||
std::wstring sourcesPath = sourcesPathName.substr(0, sourcesPathName.find_last_of(L'\\'));
|
||||
|
||||
if ((flags & EffectCompilerFlags::SaveSources) && !Win32Helper::DirExists(sourcesPath.c_str())) {
|
||||
|
|
@ -1559,7 +1561,7 @@ static uint32_t CompilePasses(
|
|||
? StrHelper::Concat(sourcesPathName, L".hlsl")
|
||||
: fmt::format(L"{}_Pass{}.hlsl", sourcesPathName, id + 1);
|
||||
|
||||
if (!Win32Helper::WriteFile(fileName.c_str(), source.data(), source.size())) {
|
||||
if (!Win32Helper::WriteTextFile(fileName.c_str(), source)) {
|
||||
Logger::Get().Error(fmt::format("保存 Pass{} 源码失败", id + 1));
|
||||
}
|
||||
}
|
||||
|
|
@ -1582,7 +1584,8 @@ static uint32_t CompilePasses(
|
|||
}
|
||||
|
||||
static std::string ReadEffectSource(const std::wstring& effectName) noexcept {
|
||||
std::wstring fileName = StrHelper::Concat(CommonSharedConstants::EFFECTS_DIR, effectName, L".hlsl");
|
||||
std::wstring fileName = StrHelper::Concat(
|
||||
CommonSharedConstants::EFFECTS_DIR, L"\\", effectName, L".hlsl");
|
||||
|
||||
std::string source;
|
||||
if (!Win32Helper::ReadTextFile(fileName.c_str(), source)) {
|
||||
|
|
@ -1595,7 +1598,7 @@ static std::string ReadEffectSource(const std::wstring& effectName) noexcept {
|
|||
uint32_t EffectCompiler::Compile(
|
||||
EffectDesc& desc,
|
||||
uint32_t flags,
|
||||
const phmap::flat_hash_map<std::wstring, float>* inlineParams
|
||||
const phmap::flat_hash_map<std::string, float>* inlineParams
|
||||
) noexcept {
|
||||
bool noCompile = flags & EffectCompilerFlags::NoCompile;
|
||||
bool noCache = noCompile || (flags & EffectCompilerFlags::NoCache);
|
||||
|
|
@ -1631,7 +1634,7 @@ uint32_t EffectCompiler::Compile(
|
|||
|
||||
if (flags & EffectCompilerFlags::InlineParams) {
|
||||
for (const auto& pair : *inlineParams) {
|
||||
cacheKey.append(fmt::format("{}={}\n", StrHelper::UTF16ToUTF8(pair.first), std::lroundf(pair.second * 10000)));
|
||||
cacheKey.append(fmt::format("{}={}\n", pair.first, std::lroundf(pair.second * 10000)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,82 +5,21 @@
|
|||
#include "Logger.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "StrHelper.h"
|
||||
#include "TextureLoader.h"
|
||||
#include "TextureHelper.h"
|
||||
#include "EffectHelper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "BackendDescriptorStore.h"
|
||||
#include "EffectsProfiler.h"
|
||||
|
||||
#pragma push_macro("_UNICODE")
|
||||
// Conan 的 muparser 不含 UNICODE 支持
|
||||
#undef _UNICODE
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4310) // 类型强制转换截断常量值
|
||||
#include <muParser.h>
|
||||
#pragma warning(push)
|
||||
#pragma pop_macro("_UNICODE")
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static SIZE CalcOutputSize(
|
||||
const std::pair<std::string, std::string>& outputSizeExpr,
|
||||
const EffectOption& option,
|
||||
SIZE scalingWndSize,
|
||||
SIZE inputSize,
|
||||
mu::Parser& exprParser
|
||||
) noexcept {
|
||||
SIZE outputSize{};
|
||||
|
||||
if (outputSizeExpr.first.empty()) {
|
||||
switch (option.scalingType) {
|
||||
case ScalingType::Normal:
|
||||
{
|
||||
outputSize.cx = std::lroundf(inputSize.cx * option.scale.first);
|
||||
outputSize.cy = std::lroundf(inputSize.cy * option.scale.second);
|
||||
break;
|
||||
}
|
||||
case ScalingType::Fit:
|
||||
{
|
||||
const float fillScale = std::min(
|
||||
float(scalingWndSize.cx) / inputSize.cx,
|
||||
float(scalingWndSize.cy) / inputSize.cy
|
||||
);
|
||||
outputSize.cx = std::lroundf(inputSize.cx * fillScale * option.scale.first);
|
||||
outputSize.cy = std::lroundf(inputSize.cy * fillScale * option.scale.second);
|
||||
break;
|
||||
}
|
||||
case ScalingType::Absolute:
|
||||
{
|
||||
outputSize.cx = std::lroundf(option.scale.first);
|
||||
outputSize.cy = std::lroundf(option.scale.second);
|
||||
break;
|
||||
}
|
||||
case ScalingType::Fill:
|
||||
{
|
||||
outputSize = scalingWndSize;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
assert(!outputSizeExpr.second.empty());
|
||||
|
||||
try {
|
||||
exprParser.SetExpr(outputSizeExpr.first);
|
||||
outputSize.cx = std::lround(exprParser.Eval());
|
||||
|
||||
exprParser.SetExpr(outputSizeExpr.second);
|
||||
outputSize.cy = std::lround(exprParser.Eval());
|
||||
} catch (const mu::ParserError& e) {
|
||||
Logger::Get().Error(fmt::format("计算输出尺寸 {} 失败: {}", e.GetExpr(), e.GetMsg()));
|
||||
return {};
|
||||
}
|
||||
EffectDrawer::~EffectDrawer() {
|
||||
// [0] 为输入,由前一个 EffectDrawer 管理
|
||||
const uint32_t textureCount = (uint32_t)_textures.size();
|
||||
for (uint32_t i = 1; i < textureCount; ++i) {
|
||||
_descriptorStore->RemoveCache(_textures[i].get());
|
||||
}
|
||||
|
||||
return outputSize;
|
||||
}
|
||||
|
||||
bool EffectDrawer::Initialize(
|
||||
|
|
@ -91,6 +30,7 @@ bool EffectDrawer::Initialize(
|
|||
ID3D11Texture2D** inOutTexture
|
||||
) noexcept {
|
||||
_d3dDC = deviceResources.GetD3DDC();
|
||||
_descriptorStore = &descriptorStore;
|
||||
|
||||
SIZE inputSize{};
|
||||
{
|
||||
|
|
@ -99,20 +39,12 @@ bool EffectDrawer::Initialize(
|
|||
inputSize = { (LONG)inputDesc.Width, (LONG)inputDesc.Height };
|
||||
}
|
||||
|
||||
static mu::Parser exprParser;
|
||||
exprParser.DefineConst("INPUT_WIDTH", inputSize.cx);
|
||||
exprParser.DefineConst("INPUT_HEIGHT", inputSize.cy);
|
||||
|
||||
const SIZE scalingWndSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().WndRect());
|
||||
const SIZE outputSize = CalcOutputSize(desc.GetOutputSizeExpr(), option, scalingWndSize, inputSize, exprParser);
|
||||
const SIZE outputSize = _CalcOutputSize(desc, option, inputSize);
|
||||
if (outputSize.cx <= 0 || outputSize.cy <= 0) {
|
||||
Logger::Get().Error("非法的输出尺寸");
|
||||
return false;
|
||||
}
|
||||
|
||||
exprParser.DefineConst("OUTPUT_WIDTH", outputSize.cx);
|
||||
exprParser.DefineConst("OUTPUT_HEIGHT", outputSize.cy);
|
||||
|
||||
_samplers.resize(desc.samplers.size());
|
||||
for (UINT i = 0; i < _samplers.size(); ++i) {
|
||||
const EffectSamplerDesc& samDesc = desc.samplers[i];
|
||||
|
|
@ -156,7 +88,7 @@ bool EffectDrawer::Initialize(
|
|||
std::string texPath = delimPos == std::string::npos
|
||||
? StrHelper::Concat("effects\\", texDesc.source)
|
||||
: StrHelper::Concat("effects\\", std::string_view(desc.name.c_str(), delimPos + 1), texDesc.source);
|
||||
_textures[i] = TextureLoader::Load(
|
||||
_textures[i] = TextureHelper::LoadTexture(
|
||||
StrHelper::UTF8ToUTF16(texPath).c_str(), deviceResources.GetD3DDevice());
|
||||
if (!_textures[i]) {
|
||||
Logger::Get().Error(fmt::format("加载纹理 {} 失败", texDesc.source));
|
||||
|
|
@ -172,14 +104,13 @@ bool EffectDrawer::Initialize(
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
SIZE texSize{};
|
||||
try {
|
||||
exprParser.SetExpr(texDesc.sizeExpr.first);
|
||||
texSize.cx = std::lround(exprParser.Eval());
|
||||
exprParser.SetExpr(texDesc.sizeExpr.second);
|
||||
texSize.cy = std::lround(exprParser.Eval());
|
||||
_exprParser.SetExpr(texDesc.sizeExpr.first);
|
||||
texSize.cx = std::lround(_exprParser.Eval());
|
||||
_exprParser.SetExpr(texDesc.sizeExpr.second);
|
||||
texSize.cy = std::lround(_exprParser.Eval());
|
||||
} catch (const mu::ParserError& e) {
|
||||
Logger::Get().Error(fmt::format("计算中间纹理尺寸 {} 失败: {}", e.GetExpr(), e.GetMsg()));
|
||||
return false;
|
||||
|
|
@ -204,10 +135,13 @@ bool EffectDrawer::Initialize(
|
|||
}
|
||||
}
|
||||
|
||||
_shaders.resize(desc.passes.size());
|
||||
_srvs.resize(desc.passes.size());
|
||||
_uavs.resize(desc.passes.size());
|
||||
for (UINT i = 0; i < _shaders.size(); ++i) {
|
||||
uint32_t passCount = (uint32_t)desc.passes.size();
|
||||
_shaders.resize(passCount);
|
||||
_srvs.resize(passCount);
|
||||
_uavs.resize(passCount);
|
||||
_dispatches.resize(passCount);
|
||||
|
||||
for (uint32_t i = 0; i < passCount; ++i) {
|
||||
const EffectPassDesc& passDesc = desc.passes[i];
|
||||
|
||||
HRESULT hr = deviceResources.GetD3DDevice()->CreateComputeShader(
|
||||
|
|
@ -218,33 +152,16 @@ bool EffectDrawer::Initialize(
|
|||
}
|
||||
|
||||
_srvs[i].resize(passDesc.inputs.size());
|
||||
for (UINT j = 0; j < passDesc.inputs.size(); ++j) {
|
||||
auto srv = _srvs[i][j] = descriptorStore.GetShaderResourceView(_textures[passDesc.inputs[j]].get());
|
||||
if (!srv) {
|
||||
Logger::Get().Error("GetShaderResourceView 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_uavs[i].resize(passDesc.outputs.size() * 2);
|
||||
for (UINT j = 0; j < passDesc.outputs.size(); ++j) {
|
||||
auto uav = _uavs[i][j] = descriptorStore.GetUnorderedAccessView(_textures[passDesc.outputs[j]].get());
|
||||
if (!uav) {
|
||||
Logger::Get().Error("GetUnorderedAccessView 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC outputDesc;
|
||||
_textures[passDesc.outputs[0]]->GetDesc(&outputDesc);
|
||||
_dispatches.emplace_back(
|
||||
(outputDesc.Width + passDesc.blockSize.first - 1) / passDesc.blockSize.first,
|
||||
(outputDesc.Height + passDesc.blockSize.second - 1) / passDesc.blockSize.second
|
||||
);
|
||||
}
|
||||
|
||||
if (!_InitializeConstants(desc, option, deviceResources, inputSize, outputSize)) {
|
||||
Logger::Get().Error("_InitializeConstants 失败");
|
||||
if (!_UpdatePassResources(desc)) {
|
||||
Logger::Get().Error("_UpdatePassResources 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_UpdateConstants(desc, option, deviceResources, inputSize, outputSize)) {
|
||||
Logger::Get().Error("_UpdateConstants 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -252,11 +169,7 @@ bool EffectDrawer::Initialize(
|
|||
}
|
||||
|
||||
void EffectDrawer::Draw(EffectsProfiler& profiler) const noexcept {
|
||||
{
|
||||
ID3D11Buffer* t = _constantBuffer.get();
|
||||
_d3dDC->CSSetConstantBuffers(0, 1, &t);
|
||||
}
|
||||
_d3dDC->CSSetSamplers(0, (UINT)_samplers.size(), _samplers.data());
|
||||
_PrepareForDraw();
|
||||
|
||||
for (uint32_t i = 0; i < _dispatches.size(); ++i) {
|
||||
_DrawPass(i);
|
||||
|
|
@ -264,19 +177,238 @@ void EffectDrawer::Draw(EffectsProfiler& profiler) const noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
void EffectDrawer::_DrawPass(uint32_t i) const noexcept {
|
||||
_d3dDC->CSSetShader(_shaders[i].get(), nullptr, 0);
|
||||
void EffectDrawer::DrawForExport(const EffectDesc& desc, uint32_t passIdx) const noexcept {
|
||||
_PrepareForDraw();
|
||||
|
||||
_d3dDC->CSSetShaderResources(0, (UINT)_srvs[i].size(), _srvs[i].data());
|
||||
UINT uavCount = (UINT)_uavs[i].size() / 2;
|
||||
_d3dDC->CSSetUnorderedAccessViews(0, uavCount, _uavs[i].data(), nullptr);
|
||||
|
||||
_d3dDC->Dispatch(_dispatches[i].first, _dispatches[i].second, 1);
|
||||
|
||||
_d3dDC->CSSetUnorderedAccessViews(0, uavCount, _uavs[i].data() + uavCount, nullptr);
|
||||
for (uint32_t i : _CalcPassesToDrawForExport(desc, passIdx)) {
|
||||
_DrawPass(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool EffectDrawer::_InitializeConstants(
|
||||
bool EffectDrawer::ResizeTextures(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
DeviceResources& deviceResources,
|
||||
ID3D11Texture2D** inOutTexture
|
||||
) noexcept {
|
||||
bool anyChange = false;
|
||||
|
||||
if (*inOutTexture != _textures[0].get()) {
|
||||
_textures[0].copy_from(*inOutTexture);
|
||||
anyChange = true;
|
||||
}
|
||||
|
||||
SIZE inputSize{};
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC inputDesc;
|
||||
_textures[0]->GetDesc(&inputDesc);
|
||||
inputSize = { (LONG)inputDesc.Width, (LONG)inputDesc.Height };
|
||||
}
|
||||
|
||||
const SIZE outputSize = _CalcOutputSize(desc, option, inputSize);
|
||||
if (outputSize.cx <= 0 || outputSize.cy <= 0) {
|
||||
Logger::Get().Error("非法的输出尺寸");
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC texDesc;
|
||||
_textures[1]->GetDesc(&texDesc);
|
||||
|
||||
if ((LONG)texDesc.Width != outputSize.cx || (LONG)texDesc.Height != outputSize.cy) {
|
||||
_descriptorStore->RemoveCache(_textures[1].get());
|
||||
|
||||
_textures[1] = DirectXHelper::CreateTexture2D(
|
||||
deviceResources.GetD3DDevice(),
|
||||
texDesc.Format,
|
||||
outputSize.cx,
|
||||
outputSize.cy,
|
||||
texDesc.BindFlags
|
||||
);
|
||||
|
||||
if (!_textures[1]) {
|
||||
Logger::Get().Error("创建输出纹理失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
anyChange = true;
|
||||
}
|
||||
|
||||
*inOutTexture = _textures[1].get();
|
||||
|
||||
for (size_t i = 2; i < _textures.size(); ++i) {
|
||||
const std::pair<std::string, std::string>& sizeExpr = desc.textures[i].sizeExpr;
|
||||
if (sizeExpr.first.empty()) {
|
||||
// 从文件加载的纹理无需调整尺寸
|
||||
continue;
|
||||
}
|
||||
|
||||
SIZE texSize{};
|
||||
try {
|
||||
_exprParser.SetExpr(sizeExpr.first);
|
||||
texSize.cx = std::lround(_exprParser.Eval());
|
||||
_exprParser.SetExpr(sizeExpr.second);
|
||||
texSize.cy = std::lround(_exprParser.Eval());
|
||||
} catch (const mu::ParserError& e) {
|
||||
Logger::Get().Error(fmt::format("计算中间纹理尺寸 {} 失败: {}", e.GetExpr(), e.GetMsg()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texSize.cx <= 0 || texSize.cy <= 0) {
|
||||
Logger::Get().Error("非法的中间纹理尺寸");
|
||||
return false;
|
||||
}
|
||||
|
||||
_textures[i]->GetDesc(&texDesc);
|
||||
|
||||
if ((LONG)texDesc.Width != texSize.cx || (LONG)texDesc.Height != texSize.cy) {
|
||||
_descriptorStore->RemoveCache(_textures[i].get());
|
||||
|
||||
_textures[i] = DirectXHelper::CreateTexture2D(
|
||||
deviceResources.GetD3DDevice(),
|
||||
texDesc.Format,
|
||||
texSize.cx,
|
||||
texSize.cy,
|
||||
texDesc.BindFlags
|
||||
);
|
||||
|
||||
if (!_textures[i]) {
|
||||
Logger::Get().Error("创建纹理失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
anyChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyChange) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_UpdatePassResources(desc)) {
|
||||
Logger::Get().Error("_UpdatePassResources 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_UpdateConstants(desc, option, deviceResources, inputSize, outputSize)) {
|
||||
Logger::Get().Error("_UpdateConstants 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SIZE EffectDrawer::_CalcOutputSize(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
SIZE inputSize
|
||||
) const noexcept {
|
||||
_exprParser.DefineConst("INPUT_WIDTH", inputSize.cx);
|
||||
_exprParser.DefineConst("INPUT_HEIGHT", inputSize.cy);
|
||||
|
||||
SIZE outputSize{};
|
||||
const std::pair<std::string, std::string>& outputSizeExpr = desc.GetOutputSizeExpr();
|
||||
|
||||
if (outputSizeExpr.first.empty()) {
|
||||
const SIZE rendererSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().RendererRect());
|
||||
|
||||
switch (option.scalingType) {
|
||||
case ScalingType::Normal:
|
||||
{
|
||||
outputSize.cx = std::lroundf(inputSize.cx * option.scale.first);
|
||||
outputSize.cy = std::lroundf(inputSize.cy * option.scale.second);
|
||||
break;
|
||||
}
|
||||
case ScalingType::Absolute:
|
||||
{
|
||||
outputSize.cx = std::lroundf(option.scale.first);
|
||||
outputSize.cy = std::lroundf(option.scale.second);
|
||||
break;
|
||||
}
|
||||
case ScalingType::Fit:
|
||||
{
|
||||
// 窗口模式缩放时将缩放比例为 1 的 Fit 视为 Fill。此时缩放确保是等比例的,但由于舍入
|
||||
// 可能存在一个像素的误差。考虑长 100 高 50 的矩形窗口,长调整到 101 时高将四舍五入到
|
||||
// 51,再将长调整到 102 高仍是 51,Fit 的计算方式会使这两次调整中有一次存在黑边,而且
|
||||
// 也会影响后续计算是否追加 Bicubic。
|
||||
const bool treatFitAsFill = ScalingWindow::Get().Options().IsWindowedMode() &&
|
||||
IsApprox(option.scale.first, 1.0f) && IsApprox(option.scale.second, 1.0f);
|
||||
|
||||
if (!treatFitAsFill) {
|
||||
const float fillScale = std::min(
|
||||
float(rendererSize.cx) / inputSize.cx,
|
||||
float(rendererSize.cy) / inputSize.cy
|
||||
);
|
||||
outputSize.cx = std::lroundf(inputSize.cx * fillScale * option.scale.first);
|
||||
outputSize.cy = std::lroundf(inputSize.cy * fillScale * option.scale.second);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
case ScalingType::Fill:
|
||||
{
|
||||
outputSize = rendererSize;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
assert(!outputSizeExpr.second.empty());
|
||||
|
||||
try {
|
||||
_exprParser.SetExpr(outputSizeExpr.first);
|
||||
outputSize.cx = std::lround(_exprParser.Eval());
|
||||
|
||||
_exprParser.SetExpr(outputSizeExpr.second);
|
||||
outputSize.cy = std::lround(_exprParser.Eval());
|
||||
} catch (const mu::ParserError& e) {
|
||||
Logger::Get().Error(fmt::format("计算输出尺寸 {} 失败: {}", e.GetExpr(), e.GetMsg()));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
_exprParser.DefineConst("OUTPUT_WIDTH", outputSize.cx);
|
||||
_exprParser.DefineConst("OUTPUT_HEIGHT", outputSize.cy);
|
||||
|
||||
return outputSize;
|
||||
}
|
||||
|
||||
bool EffectDrawer::_UpdatePassResources(const EffectDesc& desc) noexcept {
|
||||
const uint32_t passCount = (uint32_t)desc.passes.size();
|
||||
for (uint32_t i = 0; i < passCount; ++i) {
|
||||
const SmallVector<uint32_t>& inputs = desc.passes[i].inputs;
|
||||
const SmallVector<uint32_t>& outputs = desc.passes[i].outputs;
|
||||
const std::pair<uint32_t, uint32_t>& blockSize = desc.passes[i].blockSize;
|
||||
|
||||
for (uint32_t j = 0; j < inputs.size(); ++j) {
|
||||
auto srv = _srvs[i][j] = _descriptorStore->GetShaderResourceView(_textures[inputs[j]].get());
|
||||
if (!srv) {
|
||||
Logger::Get().Error("GetShaderResourceView 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < outputs.size(); ++j) {
|
||||
auto uav = _uavs[i][j] = _descriptorStore->GetUnorderedAccessView(_textures[outputs[j]].get());
|
||||
if (!uav) {
|
||||
Logger::Get().Error("GetUnorderedAccessView 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC outputDesc;
|
||||
_textures[outputs[0]]->GetDesc(&outputDesc);
|
||||
_dispatches[i] = {
|
||||
(outputDesc.Width + blockSize.first - 1) / blockSize.first,
|
||||
(outputDesc.Height + blockSize.second - 1) / blockSize.second
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EffectDrawer::_UpdateConstants(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
DeviceResources& deviceResources,
|
||||
|
|
@ -285,6 +417,8 @@ bool EffectDrawer::_InitializeConstants(
|
|||
) noexcept {
|
||||
const bool isInlineParams = desc.flags & EffectFlags::InlineParams;
|
||||
|
||||
SmallVector<EffectHelper::Constant32, 32> constants;
|
||||
|
||||
// 大小必须为 4 的倍数
|
||||
const size_t builtinConstantCount = 10;
|
||||
size_t psStylePassParams = 0;
|
||||
|
|
@ -293,7 +427,7 @@ bool EffectDrawer::_InitializeConstants(
|
|||
psStylePassParams += 4;
|
||||
}
|
||||
}
|
||||
_constants.resize((builtinConstantCount + psStylePassParams + (isInlineParams ? 0 : desc.params.size()) + 3) / 4 * 4);
|
||||
constants.resize((builtinConstantCount + psStylePassParams + (isInlineParams ? 0 : desc.params.size()) + 3) / 4 * 4);
|
||||
// cbuffer __CB1 : register(b0) {
|
||||
// uint2 __inputSize;
|
||||
// uint2 __outputSize;
|
||||
|
|
@ -302,19 +436,19 @@ bool EffectDrawer::_InitializeConstants(
|
|||
// float2 __scale;
|
||||
// [PARAMETERS...]
|
||||
// );
|
||||
_constants[0].uintVal = inputSize.cx;
|
||||
_constants[1].uintVal = inputSize.cy;
|
||||
_constants[2].uintVal = outputSize.cx;
|
||||
_constants[3].uintVal = outputSize.cy;
|
||||
_constants[4].floatVal = 1.0f / inputSize.cx;
|
||||
_constants[5].floatVal = 1.0f / inputSize.cy;
|
||||
_constants[6].floatVal = 1.0f / outputSize.cx;
|
||||
_constants[7].floatVal = 1.0f / outputSize.cy;
|
||||
_constants[8].floatVal = outputSize.cx / (FLOAT)inputSize.cx;
|
||||
_constants[9].floatVal = outputSize.cy / (FLOAT)inputSize.cy;
|
||||
constants[0].uintVal = inputSize.cx;
|
||||
constants[1].uintVal = inputSize.cy;
|
||||
constants[2].uintVal = outputSize.cx;
|
||||
constants[3].uintVal = outputSize.cy;
|
||||
constants[4].floatVal = 1.0f / inputSize.cx;
|
||||
constants[5].floatVal = 1.0f / inputSize.cy;
|
||||
constants[6].floatVal = 1.0f / outputSize.cx;
|
||||
constants[7].floatVal = 1.0f / outputSize.cy;
|
||||
constants[8].floatVal = outputSize.cx / (FLOAT)inputSize.cx;
|
||||
constants[9].floatVal = outputSize.cy / (FLOAT)inputSize.cy;
|
||||
|
||||
// PS 样式的通道需要的参数
|
||||
EffectHelper::Constant32* pCurParam = _constants.data() + builtinConstantCount;
|
||||
EffectHelper::Constant32* pCurParam = constants.data() + builtinConstantCount;
|
||||
if (psStylePassParams > 0) {
|
||||
for (UINT i = 0, end = (UINT)desc.passes.size() - 1; i < end; ++i) {
|
||||
if (desc.passes[i].flags & EffectPassFlags::PSStyle) {
|
||||
|
|
@ -335,7 +469,7 @@ bool EffectDrawer::_InitializeConstants(
|
|||
if (!isInlineParams) {
|
||||
for (UINT i = 0; i < desc.params.size(); ++i) {
|
||||
const auto& paramDesc = desc.params[i];
|
||||
auto it = option.parameters.find(StrHelper::UTF8ToUTF16(paramDesc.name));
|
||||
auto it = option.parameters.find(paramDesc.name);
|
||||
|
||||
if (paramDesc.constant.index() == 0) {
|
||||
const EffectConstant<float>& constant = std::get<0>(paramDesc.constant);
|
||||
|
|
@ -371,21 +505,128 @@ bool EffectDrawer::_InitializeConstants(
|
|||
}
|
||||
}
|
||||
|
||||
D3D11_BUFFER_DESC bd{
|
||||
.ByteWidth = 4 * (UINT)_constants.size(),
|
||||
.Usage = D3D11_USAGE_DEFAULT,
|
||||
.BindFlags = D3D11_BIND_CONSTANT_BUFFER
|
||||
};
|
||||
if (_constantBuffer) {
|
||||
// 更新缓冲区
|
||||
deviceResources.GetD3DDC()->UpdateSubresource1(
|
||||
_constantBuffer.get(), 0, nullptr, constants.data(), 0, 0, D3D11_COPY_DISCARD);
|
||||
} else {
|
||||
// 创建缓冲区
|
||||
D3D11_BUFFER_DESC bd{
|
||||
.ByteWidth = 4 * (UINT)constants.size(),
|
||||
.Usage = D3D11_USAGE_DEFAULT,
|
||||
.BindFlags = D3D11_BIND_CONSTANT_BUFFER
|
||||
};
|
||||
|
||||
D3D11_SUBRESOURCE_DATA initData{ .pSysMem = _constants.data() };
|
||||
D3D11_SUBRESOURCE_DATA initData{ .pSysMem = constants.data() };
|
||||
|
||||
HRESULT hr = deviceResources.GetD3DDevice()->CreateBuffer(&bd, &initData, _constantBuffer.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateBuffer 失败", hr);
|
||||
return false;
|
||||
HRESULT hr = deviceResources.GetD3DDevice()->CreateBuffer(&bd, &initData, _constantBuffer.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateBuffer 失败", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EffectDrawer::_DrawPass(uint32_t i) const noexcept {
|
||||
_d3dDC->CSSetShader(_shaders[i].get(), nullptr, 0);
|
||||
|
||||
_d3dDC->CSSetShaderResources(0, (UINT)_srvs[i].size(), _srvs[i].data());
|
||||
UINT uavCount = (UINT)_uavs[i].size() / 2;
|
||||
_d3dDC->CSSetUnorderedAccessViews(0, uavCount, _uavs[i].data(), nullptr);
|
||||
|
||||
_d3dDC->Dispatch(_dispatches[i].first, _dispatches[i].second, 1);
|
||||
|
||||
_d3dDC->CSSetUnorderedAccessViews(0, uavCount, _uavs[i].data() + uavCount, nullptr);
|
||||
}
|
||||
|
||||
static bool IsReadonlyTexture(const EffectDesc& desc, uint32_t texture) noexcept {
|
||||
return texture == 0 || !desc.textures[texture].source.empty();
|
||||
}
|
||||
|
||||
// 计算导出某个通道的输出时需要重新渲染的通道
|
||||
SmallVector<uint32_t> EffectDrawer::_CalcPassesToDrawForExport(
|
||||
const EffectDesc& desc,
|
||||
uint32_t passIdx
|
||||
) const noexcept {
|
||||
SmallVector<uint32_t> passesToDraw;
|
||||
passesToDraw.push_back(passIdx);
|
||||
|
||||
if (passIdx == 0) {
|
||||
return passesToDraw;
|
||||
}
|
||||
|
||||
const std::vector<EffectPassDesc>& passes = desc.passes;
|
||||
const uint32_t end = (uint32_t)passes.size() - 1;
|
||||
|
||||
// 用于记录该通道依赖的输入纹理,格式为 (passIdx, texture)
|
||||
SmallVector<std::pair<uint32_t, uint32_t>, 0> depTextures;
|
||||
|
||||
for (uint32_t input : passes[passIdx].inputs) {
|
||||
if (!IsReadonlyTexture(desc, input)) {
|
||||
depTextures.emplace_back(passIdx, input);
|
||||
}
|
||||
}
|
||||
|
||||
while (!depTextures.empty()) {
|
||||
const auto [curPass, curTexture] = depTextures.pop_back_val();
|
||||
|
||||
// 检查 curTexture 是否会被后面的通道修改
|
||||
{
|
||||
bool isOverwritten = false;
|
||||
for (uint32_t i = curPass + 1; i < end; ++i) {
|
||||
const SmallVector<uint32_t>& curOutputs = passes[i].outputs;
|
||||
if (std::find(curOutputs.begin(), curOutputs.end(), curTexture) != curOutputs.end()) {
|
||||
isOverwritten = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isOverwritten) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 需要重新渲染前一个输出 curTexture 的通道,并带来新的依赖
|
||||
for (int i = (int)curPass - 1; i >= 0; --i) {
|
||||
const SmallVector<uint32_t>& curOutputs = passes[i].outputs;
|
||||
if (std::find(curOutputs.begin(), curOutputs.end(), curTexture) != curOutputs.end()) {
|
||||
const uint32_t ui = (uint32_t)i;
|
||||
|
||||
if (std::find(passesToDraw.begin(), passesToDraw.end(), ui) == passesToDraw.end()) {
|
||||
passesToDraw.push_back(ui);
|
||||
|
||||
// 作为优化,如果之前的所有通道都需要重新渲染则提前返回
|
||||
if ((uint32_t)passesToDraw.size() == passIdx + 1) {
|
||||
for (uint32_t j = 0; j <= passIdx; ++j) {
|
||||
passesToDraw[j] = j;
|
||||
}
|
||||
return passesToDraw;
|
||||
}
|
||||
|
||||
for (uint32_t input : passes[ui].inputs) {
|
||||
if (!IsReadonlyTexture(desc, input)) {
|
||||
depTextures.emplace_back(ui, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(passesToDraw.begin(), passesToDraw.end());
|
||||
return passesToDraw;
|
||||
}
|
||||
|
||||
void EffectDrawer::_PrepareForDraw() const noexcept {
|
||||
{
|
||||
ID3D11Buffer* t = _constantBuffer.get();
|
||||
_d3dDC->CSSetConstantBuffers(0, 1, &t);
|
||||
}
|
||||
|
||||
_d3dDC->CSSetSamplers(0, (UINT)_samplers.size(), _samplers.data());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@
|
|||
#include "SmallVector.h"
|
||||
#include "EffectHelper.h"
|
||||
|
||||
#pragma push_macro("_UNICODE")
|
||||
// Conan 的 muparser 不含 UNICODE 支持
|
||||
#undef _UNICODE
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4310) // 类型强制转换截断常量值
|
||||
#include <muParser.h>
|
||||
#pragma warning(push)
|
||||
#pragma pop_macro("_UNICODE")
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct EffectOption;
|
||||
|
|
@ -16,6 +25,8 @@ public:
|
|||
EffectDrawer(const EffectDrawer&) = delete;
|
||||
EffectDrawer(EffectDrawer&&) = default;
|
||||
|
||||
~EffectDrawer();
|
||||
|
||||
bool Initialize(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
|
|
@ -26,8 +37,33 @@ public:
|
|||
|
||||
void Draw(EffectsProfiler& profiler) const noexcept;
|
||||
|
||||
void DrawForExport(const EffectDesc& desc, uint32_t passIdx) const noexcept;
|
||||
|
||||
bool ResizeTextures(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
DeviceResources& deviceResources,
|
||||
ID3D11Texture2D** inOutTexture
|
||||
) noexcept;
|
||||
|
||||
ID3D11Texture2D* GetOutputTexture() const noexcept {
|
||||
return _textures[1].get();
|
||||
}
|
||||
|
||||
ID3D11Texture2D* GetTexture(uint32_t idx) const noexcept {
|
||||
return _textures[idx].get();
|
||||
}
|
||||
|
||||
private:
|
||||
bool _InitializeConstants(
|
||||
SIZE _CalcOutputSize(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
SIZE inputSize
|
||||
) const noexcept;
|
||||
|
||||
bool _UpdatePassResources(const EffectDesc& desc) noexcept;
|
||||
|
||||
bool _UpdateConstants(
|
||||
const EffectDesc& desc,
|
||||
const EffectOption& option,
|
||||
DeviceResources& deviceResources,
|
||||
|
|
@ -35,9 +71,17 @@ private:
|
|||
SIZE outputSize
|
||||
) noexcept;
|
||||
|
||||
void _PrepareForDraw() const noexcept;
|
||||
|
||||
void _DrawPass(uint32_t i) const noexcept;
|
||||
|
||||
SmallVector<uint32_t> _CalcPassesToDrawForExport(
|
||||
const EffectDesc& desc,
|
||||
uint32_t passIdx
|
||||
) const noexcept;
|
||||
|
||||
ID3D11DeviceContext* _d3dDC = nullptr;
|
||||
BackendDescriptorStore* _descriptorStore = nullptr;
|
||||
|
||||
SmallVector<ID3D11SamplerState*> _samplers;
|
||||
SmallVector<winrt::com_ptr<ID3D11Texture2D>> _textures;
|
||||
|
|
@ -45,12 +89,13 @@ private:
|
|||
// 后半部分为空,用于解绑
|
||||
std::vector<SmallVector<ID3D11UnorderedAccessView*>> _uavs;
|
||||
|
||||
SmallVector<EffectHelper::Constant32, 32> _constants;
|
||||
winrt::com_ptr<ID3D11Buffer> _constantBuffer;
|
||||
|
||||
SmallVector<winrt::com_ptr<ID3D11ComputeShader>> _shaders;
|
||||
|
||||
SmallVector<std::pair<uint32_t, uint32_t>> _dispatches;
|
||||
|
||||
static inline mu::Parser _exprParser;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
void EffectsProfiler::Start(ID3D11Device* d3dDevice, uint32_t passCount) {
|
||||
assert(_passQueries.empty());
|
||||
void EffectsProfiler::Start(ID3D11Device* d3dDevice, uint32_t passCount) noexcept {
|
||||
assert(!IsProfiling() && passCount > 0);
|
||||
|
||||
_passQueries.resize(passCount);
|
||||
|
||||
D3D11_QUERY_DESC desc{ .Query = D3D11_QUERY_TIMESTAMP_DISJOINT };
|
||||
|
|
@ -18,14 +19,40 @@ void EffectsProfiler::Start(ID3D11Device* d3dDevice, uint32_t passCount) {
|
|||
}
|
||||
}
|
||||
|
||||
void EffectsProfiler::Stop() {
|
||||
void EffectsProfiler::Stop() noexcept {
|
||||
_disjointQuery = nullptr;
|
||||
_startQuery = nullptr;
|
||||
_passQueries.clear();
|
||||
}
|
||||
|
||||
void EffectsProfiler::OnBeginEffects(ID3D11DeviceContext* d3dDC) {
|
||||
if (_passQueries.empty()) {
|
||||
bool EffectsProfiler::IsProfiling() const noexcept {
|
||||
return (bool)_disjointQuery;
|
||||
}
|
||||
|
||||
void EffectsProfiler::SetPassCount(ID3D11Device* d3dDevice, uint32_t passCount) noexcept {
|
||||
if (!IsProfiling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(passCount > 0);
|
||||
const uint32_t oldPassCount = (uint32_t)_passQueries.size();
|
||||
|
||||
if (passCount == oldPassCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
_passQueries.resize(passCount);
|
||||
|
||||
if (passCount > oldPassCount) {
|
||||
D3D11_QUERY_DESC desc{ .Query = D3D11_QUERY_TIMESTAMP };
|
||||
for (uint32_t i = oldPassCount; i < passCount; ++i) {
|
||||
d3dDevice->CreateQuery(&desc, _passQueries[i].put());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EffectsProfiler::OnBeginEffects(ID3D11DeviceContext* d3dDC) noexcept {
|
||||
if (!IsProfiling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -35,16 +62,16 @@ void EffectsProfiler::OnBeginEffects(ID3D11DeviceContext* d3dDC) {
|
|||
_curPass = 0;
|
||||
}
|
||||
|
||||
void EffectsProfiler::OnEndPass(ID3D11DeviceContext* d3dDC) {
|
||||
if (_passQueries.empty()) {
|
||||
void EffectsProfiler::OnEndPass(ID3D11DeviceContext* d3dDC) noexcept {
|
||||
if (!IsProfiling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
d3dDC->End(_passQueries[_curPass++].get());
|
||||
}
|
||||
|
||||
void EffectsProfiler::OnEndEffects(ID3D11DeviceContext* d3dDC) {
|
||||
if (_passQueries.empty()) {
|
||||
void EffectsProfiler::OnEndEffects(ID3D11DeviceContext* d3dDC) noexcept {
|
||||
if (!IsProfiling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +88,7 @@ static T GetQueryData(ID3D11DeviceContext* d3dDC, ID3D11Query* query) noexcept {
|
|||
}
|
||||
|
||||
void EffectsProfiler::QueryTimings(ID3D11DeviceContext* d3dDC) noexcept {
|
||||
if (_passQueries.empty()) {
|
||||
if (!IsProfiling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,15 +13,19 @@ public:
|
|||
EffectsProfiler(const EffectsProfiler&) = delete;
|
||||
EffectsProfiler(EffectsProfiler&&) = delete;
|
||||
|
||||
void Start(ID3D11Device* d3dDevice, uint32_t passCount);
|
||||
void Start(ID3D11Device* d3dDevice, uint32_t passCount) noexcept;
|
||||
|
||||
void Stop();
|
||||
void Stop() noexcept;
|
||||
|
||||
void OnBeginEffects(ID3D11DeviceContext* d3dDC);
|
||||
bool IsProfiling() const noexcept;
|
||||
|
||||
void OnEndPass(ID3D11DeviceContext* d3dDC);
|
||||
void SetPassCount(ID3D11Device* d3dDevice, uint32_t passCount) noexcept;
|
||||
|
||||
void OnEndEffects(ID3D11DeviceContext* d3dDC);
|
||||
void OnBeginEffects(ID3D11DeviceContext* d3dDC) noexcept;
|
||||
|
||||
void OnEndPass(ID3D11DeviceContext* d3dDC) noexcept;
|
||||
|
||||
void OnEndEffects(ID3D11DeviceContext* d3dDC) noexcept;
|
||||
|
||||
void QueryTimings(ID3D11DeviceContext* d3dDC) noexcept;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ FrameSourceBase::FrameSourceBase() noexcept :
|
|||
_nextSkipCount(INITIAL_SKIP_COUNT), _framesLeft(INITIAL_CHECK_COUNT) {}
|
||||
|
||||
FrameSourceBase::~FrameSourceBase() noexcept {
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
|
||||
// 还原窗口圆角
|
||||
if (_roundCornerDisabled) {
|
||||
|
|
@ -36,65 +36,12 @@ FrameSourceBase::~FrameSourceBase() noexcept {
|
|||
Logger::Get().Info("已取消禁用窗口圆角");
|
||||
}
|
||||
}
|
||||
|
||||
// 还原窗口大小调整
|
||||
if (_windowResizingDisabled) {
|
||||
LONG_PTR style = GetWindowLongPtr(hwndSrc, GWL_STYLE);
|
||||
if (!(style & WS_THICKFRAME)) {
|
||||
if (SetWindowLongPtr(hwndSrc, GWL_STYLE, style | WS_THICKFRAME)) {
|
||||
if (!SetWindowPos(hwndSrc, 0, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED)) {
|
||||
Logger::Get().Win32Error("SetWindowPos 失败");
|
||||
}
|
||||
|
||||
Logger::Get().Info("已取消禁用窗口大小调整");
|
||||
} else {
|
||||
Logger::Get().Win32Error("取消禁用窗口大小调整失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameSourceBase::Initialize(DeviceResources& deviceResources, BackendDescriptorStore& descriptorStore) noexcept {
|
||||
_deviceResources = &deviceResources;
|
||||
_descriptorStore = &descriptorStore;
|
||||
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
|
||||
// 禁用窗口大小调整
|
||||
if (ScalingWindow::Get().Options().IsWindowResizingDisabled()) {
|
||||
LONG_PTR style = GetWindowLongPtr(hwndSrc, GWL_STYLE);
|
||||
if (style & WS_THICKFRAME) {
|
||||
if (SetWindowLongPtr(hwndSrc, GWL_STYLE, style ^ WS_THICKFRAME)) {
|
||||
// 不重绘边框,以防某些窗口状态不正确
|
||||
// if (!SetWindowPos(HwndSrc, 0, 0, 0, 0, 0,
|
||||
// SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED)) {
|
||||
// SPDLOG_LOGGER_ERROR(logger, MakeWin32ErrorMsg("SetWindowPos 失败"));
|
||||
// }
|
||||
|
||||
Logger::Get().Info("已禁用窗口大小调整");
|
||||
_windowResizingDisabled = true;
|
||||
} else {
|
||||
Logger::Get().Win32Error("禁用窗口大小调整失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用窗口圆角
|
||||
if (_HasRoundCornerInWin11()) {
|
||||
if (Win32Helper::GetOSVersion().IsWin11()) {
|
||||
INT attr = DWMWCP_DONOTROUND;
|
||||
HRESULT hr = DwmSetWindowAttribute(
|
||||
hwndSrc, DWMWA_WINDOW_CORNER_PREFERENCE, &attr, sizeof(attr));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("禁用窗口圆角失败", hr);
|
||||
} else {
|
||||
Logger::Get().Info("已禁用窗口圆角");
|
||||
_roundCornerDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_Initialize()) {
|
||||
Logger::Get().Error("_Initialize 失败");
|
||||
return false;
|
||||
|
|
@ -209,190 +156,22 @@ std::pair<uint32_t, uint32_t> FrameSourceBase::GetStatisticsForDynamicDetection(
|
|||
return _statistics.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
struct EnumChildWndParam {
|
||||
const wchar_t* clientWndClassName = nullptr;
|
||||
SmallVector<HWND, 1> childWindows;
|
||||
};
|
||||
|
||||
static BOOL CALLBACK EnumChildProc(
|
||||
_In_ HWND hwnd,
|
||||
_In_ LPARAM lParam
|
||||
) {
|
||||
std::wstring className = Win32Helper::GetWndClassName(hwnd);
|
||||
|
||||
EnumChildWndParam* param = (EnumChildWndParam*)lParam;
|
||||
if (className == param->clientWndClassName) {
|
||||
param->childWindows.push_back(hwnd);
|
||||
void FrameSourceBase::_DisableRoundCornerInWin11() noexcept {
|
||||
if (Win32Helper::GetOSVersion().IsWin10()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static HWND FindClientWindowOfUWP(HWND hwndSrc, const wchar_t* clientWndClassName) noexcept {
|
||||
// 查找所有窗口类名为 ApplicationFrameInputSinkWindow 的子窗口
|
||||
// 该子窗口一般为客户区
|
||||
EnumChildWndParam param{ .clientWndClassName = clientWndClassName };
|
||||
EnumChildWindows(hwndSrc, EnumChildProc, (LPARAM)¶m);
|
||||
|
||||
if (param.childWindows.empty()) {
|
||||
// 未找到符合条件的子窗口
|
||||
return hwndSrc;
|
||||
}
|
||||
|
||||
if (param.childWindows.size() == 1) {
|
||||
return param.childWindows[0];
|
||||
}
|
||||
|
||||
// 如果有多个匹配的子窗口,取最大的(一般不会出现)
|
||||
int maxSize = 0, maxIdx = 0;
|
||||
for (int i = 0; i < param.childWindows.size(); ++i) {
|
||||
RECT rect;
|
||||
if (!GetClientRect(param.childWindows[i], &rect)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int size = rect.right - rect.left + rect.bottom - rect.top;
|
||||
if (size > maxSize) {
|
||||
maxSize = size;
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return param.childWindows[maxIdx];
|
||||
}
|
||||
|
||||
static bool GetClientRectOfUWP(HWND hWnd, RECT& rect) noexcept {
|
||||
std::wstring className = Win32Helper::GetWndClassName(hWnd);
|
||||
if (className != L"ApplicationFrameWindow" && className != L"Windows.UI.Core.CoreWindow") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 客户区窗口类名为 ApplicationFrameInputSinkWindow
|
||||
HWND hwndClient = FindClientWindowOfUWP(hWnd, L"ApplicationFrameInputSinkWindow");
|
||||
if (!hwndClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Win32Helper::GetClientScreenRect(hwndClient, rect)) {
|
||||
Logger::Get().Win32Error("GetClientScreenRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取窗口上边框高度,不适用于最大化的窗口
|
||||
static uint32_t GetTopBorderHeight(HWND hWnd, const RECT& clientRect, const RECT& windowRect) noexcept {
|
||||
// 检查该窗口是否禁用了非客户区域的绘制
|
||||
BOOL hasBorder = TRUE;
|
||||
HRESULT hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
INT attr = DWMWCP_DONOTROUND;
|
||||
HRESULT hr = DwmSetWindowAttribute(
|
||||
hwndSrc, DWMWA_WINDOW_CORNER_PREFERENCE, &attr, sizeof(attr));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
|
||||
return 0;
|
||||
Logger::Get().ComError("禁用窗口圆角失败", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasBorder) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果左右下三边均存在边框,那么应视为存在上边框:
|
||||
// * Win10 中窗口很可能绘制了假的上边框,这是很常见的创建无边框窗口的方法
|
||||
// * Win11 中 DWM 会将上边框绘制到客户区
|
||||
if (windowRect.top == clientRect.top && (windowRect.left == clientRect.left ||
|
||||
windowRect.right == clientRect.right || windowRect.bottom == clientRect.bottom)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Win32Helper::GetOSVersion().IsWin11()) {
|
||||
uint32_t borderThickness = 0;
|
||||
hr = DwmGetWindowAttribute(hWnd, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &borderThickness, sizeof(borderThickness));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return borderThickness;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameSourceBase::_CalcSrcRect() noexcept {
|
||||
const ScalingOptions& options = ScalingWindow::Get().Options();
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
|
||||
if (options.IsCaptureTitleBar() && _CanCaptureTitleBar()) {
|
||||
if (!Win32Helper::GetWindowFrameRect(hwndSrc, _srcRect)) {
|
||||
Logger::Get().Error("GetWindowFrameRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(hwndSrc, clientRect)) {
|
||||
Logger::Get().Win32Error("GetClientScreenRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 左右下三边裁剪至客户区
|
||||
_srcRect.left = std::max(_srcRect.left, clientRect.left);
|
||||
_srcRect.right = std::min(_srcRect.right, clientRect.right);
|
||||
_srcRect.bottom = std::min(_srcRect.bottom, clientRect.bottom);
|
||||
|
||||
if (Win32Helper::GetWindowShowCmd(hwndSrc) == SW_SHOWNORMAL) {
|
||||
// 裁剪上边框
|
||||
RECT windowRect;
|
||||
if (!GetWindowRect(hwndSrc, &windowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
_srcRect.top += GetTopBorderHeight(hwndSrc, clientRect, windowRect);
|
||||
}
|
||||
} else {
|
||||
if (!GetClientRectOfUWP(hwndSrc, _srcRect)) {
|
||||
if (!Win32Helper::GetClientScreenRect(hwndSrc, _srcRect)) {
|
||||
Logger::Get().Error("GetClientScreenRect 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Win32Helper::GetWindowShowCmd(hwndSrc) == SW_SHOWMAXIMIZED) {
|
||||
// 最大化的窗口可能有一部分客户区在屏幕外,但只有屏幕内是有效区域,
|
||||
// 因此裁剪到屏幕边界
|
||||
HMONITOR hMon = MonitorFromWindow(hwndSrc, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi{ .cbSize = sizeof(mi) };
|
||||
if (!GetMonitorInfo(hMon, &mi)) {
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
IntersectRect(&_srcRect, &_srcRect, &mi.rcMonitor);
|
||||
} else {
|
||||
RECT windowRect;
|
||||
if (!GetWindowRect(hwndSrc, &windowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果上边框在客户区内,则裁剪上边框
|
||||
if (windowRect.top == _srcRect.top) {
|
||||
_srcRect.top += GetTopBorderHeight(hwndSrc, _srcRect, windowRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_srcRect = {
|
||||
std::lround(_srcRect.left + options.cropping.Left),
|
||||
std::lround(_srcRect.top + options.cropping.Top),
|
||||
std::lround(_srcRect.right - options.cropping.Right),
|
||||
std::lround(_srcRect.bottom - options.cropping.Bottom)
|
||||
};
|
||||
|
||||
if (_srcRect.right - _srcRect.left <= 0 || _srcRect.bottom - _srcRect.top <= 0) {
|
||||
Logger::Get().Error("裁剪窗口失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
Logger::Get().Info("已禁用窗口圆角");
|
||||
_roundCornerDisabled = true;
|
||||
}
|
||||
|
||||
bool FrameSourceBase::_GetMapToOriginDPI(HWND hWnd, double& a, double& bx, double& by) noexcept {
|
||||
|
|
@ -467,43 +246,6 @@ bool FrameSourceBase::_GetMapToOriginDPI(HWND hWnd, double& a, double& bx, doubl
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FrameSourceBase::_CenterWindowIfNecessary(HWND hWnd, const RECT& rcWork) noexcept {
|
||||
if (Win32Helper::GetWindowShowCmd(hWnd) == SW_SHOWMAXIMIZED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RECT srcRect;
|
||||
if (!Win32Helper::GetWindowFrameRect(hWnd, srcRect)) {
|
||||
Logger::Get().Error("GetWindowFrameRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (srcRect.left < rcWork.left || srcRect.top < rcWork.top
|
||||
|| srcRect.right > rcWork.right || srcRect.bottom > rcWork.bottom) {
|
||||
// 源窗口超越边界,将源窗口移到屏幕中央
|
||||
SIZE srcSize = { srcRect.right - srcRect.left, srcRect.bottom - srcRect.top };
|
||||
SIZE rcWorkSize = { rcWork.right - rcWork.left, rcWork.bottom - rcWork.top };
|
||||
if (srcSize.cx > rcWorkSize.cx || srcSize.cy > rcWorkSize.cy) {
|
||||
// 源窗口无法被当前屏幕容纳,因此无法捕获
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetWindowPos(
|
||||
hWnd,
|
||||
0,
|
||||
rcWork.left + (rcWorkSize.cx - srcSize.cx) / 2,
|
||||
rcWork.top + (rcWorkSize.cy - srcSize.cy) / 2,
|
||||
0,
|
||||
0,
|
||||
SWP_NOSIZE | SWP_NOZORDER
|
||||
)) {
|
||||
Logger::Get().Win32Error("SetWindowPos 失败");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FrameSourceBase::_InitCheckingForDuplicateFrame() {
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
|
||||
|
|
|
|||
|
|
@ -29,22 +29,18 @@ public:
|
|||
|
||||
bool Initialize(DeviceResources& deviceResources, BackendDescriptorStore& descriptorStore) noexcept;
|
||||
|
||||
virtual bool Start() noexcept { return true; }
|
||||
|
||||
FrameSourceState Update() noexcept;
|
||||
|
||||
ID3D11Texture2D* GetOutput() noexcept {
|
||||
return _output.get();
|
||||
}
|
||||
|
||||
// 注意: 返回源窗口作为输入部分的位置,但可能和 GetOutput 获取到的纹理尺寸不同,
|
||||
// 因为源窗口可能存在 DPI 缩放,而某些捕获方法无视 DPI 缩放
|
||||
const RECT& SrcRect() const noexcept { return _srcRect; }
|
||||
|
||||
std::pair<uint32_t, uint32_t> GetStatisticsForDynamicDetection() const noexcept;
|
||||
|
||||
virtual const char* Name() const noexcept = 0;
|
||||
|
||||
virtual bool IsScreenCapture() const noexcept = 0;
|
||||
|
||||
virtual FrameSourceWaitType WaitType() const noexcept = 0;
|
||||
|
||||
virtual void OnCursorVisibilityChanged(bool /*isVisible*/, bool /*onDestory*/) noexcept {};
|
||||
|
|
@ -54,11 +50,7 @@ protected:
|
|||
|
||||
virtual FrameSourceState _Update() noexcept = 0;
|
||||
|
||||
virtual bool _HasRoundCornerInWin11() noexcept = 0;
|
||||
|
||||
virtual bool _CanCaptureTitleBar() noexcept = 0;
|
||||
|
||||
bool _CalcSrcRect() noexcept;
|
||||
void _DisableRoundCornerInWin11() noexcept;
|
||||
|
||||
// 获取坐标系 1 到坐标系 2 的映射关系
|
||||
// 坐标系 1: 屏幕坐标系,即虚拟化后的坐标系。原点为屏幕左上角
|
||||
|
|
@ -69,10 +61,6 @@ protected:
|
|||
// 坐标系 1 中的 (x1, y1) 映射到 (x1 * a + bx, x2 * a + by)
|
||||
static bool _GetMapToOriginDPI(HWND hWnd, double& a, double& bx, double& by) noexcept;
|
||||
|
||||
static bool _CenterWindowIfNecessary(HWND hWnd, const RECT& rcWork) noexcept;
|
||||
|
||||
RECT _srcRect{};
|
||||
|
||||
DeviceResources* _deviceResources = nullptr;
|
||||
BackendDescriptorStore* _descriptorStore = nullptr;
|
||||
winrt::com_ptr<ID3D11Texture2D> _output;
|
||||
|
|
@ -85,7 +73,6 @@ protected:
|
|||
std::pair<uint32_t, uint32_t> _dispatchCount;
|
||||
|
||||
bool _roundCornerDisabled = false;
|
||||
bool _windowResizingDisabled = false;
|
||||
|
||||
private:
|
||||
bool _InitCheckingForDuplicateFrame();
|
||||
|
|
|
|||
|
|
@ -9,14 +9,10 @@
|
|||
namespace Magpie {
|
||||
|
||||
bool GDIFrameSource::_Initialize() noexcept {
|
||||
if (!_CalcSrcRect()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
const SrcTracker& srcTracker = ScalingWindow::Get().SrcTracker();
|
||||
|
||||
double a, bx, by;
|
||||
if (!_GetMapToOriginDPI(hwndSrc, a, bx, by)) {
|
||||
if (!_GetMapToOriginDPI(srcTracker.Handle(), a, bx, by)) {
|
||||
// 很可能是因为窗口没有重定向表面,这种情况下 GDI 捕获肯定失败
|
||||
Logger::Get().Error("_GetMapToOriginDPI 失败");
|
||||
return false;
|
||||
|
|
@ -25,10 +21,10 @@ bool GDIFrameSource::_Initialize() noexcept {
|
|||
Logger::Get().Info(fmt::format("源窗口 DPI 缩放为 {}", 1 / a));
|
||||
|
||||
_frameRect = {
|
||||
std::lround(_srcRect.left * a + bx),
|
||||
std::lround(_srcRect.top * a + by),
|
||||
std::lround(_srcRect.right * a + bx),
|
||||
std::lround(_srcRect.bottom * a + by)
|
||||
std::lround(srcTracker.SrcRect().left * a + bx),
|
||||
std::lround(srcTracker.SrcRect().top * a + by),
|
||||
std::lround(srcTracker.SrcRect().right * a + bx),
|
||||
std::lround(srcTracker.SrcRect().bottom * a + by)
|
||||
};
|
||||
|
||||
if (_frameRect.left < 0 || _frameRect.top < 0 || _frameRect.right < 0
|
||||
|
|
@ -75,7 +71,7 @@ FrameSourceState GDIFrameSource::_Update() noexcept {
|
|||
_dxgiSurface->ReleaseDC(nullptr);
|
||||
});
|
||||
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
wil::unique_hdc_window hdcSrc(wil::window_dc(GetDCEx(hwndSrc, NULL, DCX_WINDOW), hwndSrc));
|
||||
if (!hdcSrc) {
|
||||
Logger::Get().Win32Error("GetDC 失败");
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@ class GDIFrameSource final : public FrameSourceBase {
|
|||
public:
|
||||
virtual ~GDIFrameSource() {}
|
||||
|
||||
bool IsScreenCapture() const noexcept override {
|
||||
return false;
|
||||
}
|
||||
|
||||
FrameSourceWaitType WaitType() const noexcept override {
|
||||
return FrameSourceWaitType::NoWait;
|
||||
}
|
||||
|
|
@ -24,14 +20,6 @@ protected:
|
|||
|
||||
FrameSourceState _Update() noexcept override;
|
||||
|
||||
bool _HasRoundCornerInWin11() noexcept override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _CanCaptureTitleBar() noexcept override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
RECT _frameRect{};
|
||||
winrt::com_ptr<IDXGISurface1> _dxgiSurface;
|
||||
|
|
|
|||
|
|
@ -54,20 +54,9 @@ bool GraphicsCaptureFrameSource::_Initialize() noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!_CalcSrcRect()) {
|
||||
Logger::Get().Error("_CalcSrcRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_CaptureWindow(interop.get())) {
|
||||
Logger::Get().Info("窗口捕获失败,回落到屏幕捕获");
|
||||
|
||||
if (_CaptureMonitor(interop.get())) {
|
||||
_isScreenCapture = true;
|
||||
} else {
|
||||
Logger::Get().Error("屏幕捕获失败");
|
||||
return false;
|
||||
}
|
||||
Logger::Get().Error("窗口捕获失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
_output = DirectXHelper::CreateTexture2D(
|
||||
|
|
@ -82,15 +71,15 @@ bool GraphicsCaptureFrameSource::_Initialize() noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!_StartCapture()) {
|
||||
Logger::Get().Error("_StartCapture 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger::Get().Info("GraphicsCaptureFrameSource 初始化完成");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::Start() noexcept {
|
||||
_DisableRoundCornerInWin11();
|
||||
return _StartCapture();
|
||||
}
|
||||
|
||||
FrameSourceState GraphicsCaptureFrameSource::_Update() noexcept {
|
||||
if (!_captureSession) {
|
||||
return FrameSourceState::Waiting;
|
||||
|
|
@ -135,25 +124,25 @@ void GraphicsCaptureFrameSource::OnCursorVisibilityChanged(bool isVisible, bool
|
|||
}
|
||||
}
|
||||
|
||||
// Graphics Capture 的捕获区域没有文档记录,这里的计算是我实验了多种窗口后得出的,
|
||||
// 高度依赖实现细节,未来可能会失效
|
||||
static bool CalcWindowCapturedFrameBounds(HWND hWnd, RECT& rect) noexcept {
|
||||
// Win10 中捕获区域为 extended frame bounds;Win11 中 DwmGetWindowAttribute
|
||||
// 对最大化的窗口返回值和 Win10 不同,可能是 OS 的 bug,应进一步处理
|
||||
HRESULT hr = DwmGetWindowAttribute(hWnd,
|
||||
DWMWA_EXTENDED_FRAME_BOUNDS, &rect, sizeof(rect));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
|
||||
return false;
|
||||
}
|
||||
// Graphics Capture 的捕获区域没有文档记录,这里的计算是我实验了多种窗口后得出的,
|
||||
// 高度依赖实现细节,未来可能会失效。
|
||||
// Win10 和 Win11 24H2 开始捕获区域为 extended frame bounds;Win11 24H2 前
|
||||
// DwmGetWindowAttribute 对最大化的窗口返回值和 Win10 不同,可能是 OS 的 bug,
|
||||
// 应进一步处理。
|
||||
const auto& srcTracker = ScalingWindow::Get().SrcTracker();
|
||||
rect = srcTracker.WindowFrameRect();
|
||||
|
||||
if(!Win32Helper::GetOSVersion().IsWin11() || Win32Helper::GetWindowShowCmd(hWnd) != SW_SHOWMAXIMIZED) {
|
||||
if (!srcTracker.IsZoomed() ||
|
||||
Win32Helper::GetOSVersion().IsWin10() ||
|
||||
Win32Helper::GetOSVersion().Is24H2OrNewer())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果窗口禁用了非客户区域绘制则捕获区域为 extended frame bounds
|
||||
BOOL hasBorder = TRUE;
|
||||
hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
|
||||
HRESULT hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
|
||||
return false;
|
||||
|
|
@ -181,14 +170,16 @@ static bool CalcWindowCapturedFrameBounds(HWND hWnd, RECT& rect) noexcept {
|
|||
if (clientRect.top < mi.rcWork.top) {
|
||||
rect = clientRect;
|
||||
} else {
|
||||
IntersectRect(&rect, &rect, &mi.rcWork);
|
||||
Win32Helper::IntersectRect(rect, rect, mi.rcWork);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* interop) noexcept {
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
const SrcTracker& srcTracker = ScalingWindow::Get().SrcTracker();
|
||||
const HWND hwndSrc = srcTracker.Handle();
|
||||
const RECT& srcRect = srcTracker.SrcRect();
|
||||
|
||||
RECT frameBounds;
|
||||
if (!CalcWindowCapturedFrameBounds(hwndSrc, frameBounds)) {
|
||||
|
|
@ -196,7 +187,7 @@ bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* int
|
|||
return false;
|
||||
}
|
||||
|
||||
if (_srcRect.left < frameBounds.left || _srcRect.top < frameBounds.top) {
|
||||
if (srcRect.left < frameBounds.left || srcRect.top < frameBounds.top) {
|
||||
Logger::Get().Error("裁剪边框错误");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -204,11 +195,11 @@ bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* int
|
|||
// 在源窗口存在 DPI 缩放时有时会有一像素的偏移(取决于窗口在屏幕上的位置)
|
||||
// 可能是 DwmGetWindowAttribute 的 bug
|
||||
_frameBox = {
|
||||
UINT(_srcRect.left - frameBounds.left),
|
||||
UINT(_srcRect.top - frameBounds.top),
|
||||
UINT(srcRect.left - frameBounds.left),
|
||||
UINT(srcRect.top - frameBounds.top),
|
||||
0,
|
||||
UINT(_srcRect.right - frameBounds.left),
|
||||
UINT(_srcRect.bottom - frameBounds.top),
|
||||
UINT(srcRect.right - frameBounds.left),
|
||||
UINT(srcRect.bottom - frameBounds.top),
|
||||
1
|
||||
};
|
||||
|
||||
|
|
@ -275,7 +266,7 @@ bool GraphicsCaptureFrameSource::_CaptureWindow(IGraphicsCaptureItemInterop* int
|
|||
bool GraphicsCaptureFrameSource::_TryCreateGraphicsCaptureItem(IGraphicsCaptureItemInterop* interop) noexcept {
|
||||
try {
|
||||
HRESULT hr = interop->CreateForWindow(
|
||||
ScalingWindow::Get().HwndSrc(),
|
||||
ScalingWindow::Get().SrcTracker().Handle(),
|
||||
winrt::guid_of<winrt::GraphicsCaptureItem>(),
|
||||
winrt::put_abi(_captureItem)
|
||||
);
|
||||
|
|
@ -328,76 +319,6 @@ void GraphicsCaptureFrameSource::_RemoveOwnerFromAltTabList(HWND hwndSrc) noexce
|
|||
_originalOwnerExStyle = ownerExStyle;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_CaptureMonitor(IGraphicsCaptureItemInterop* interop) noexcept {
|
||||
// Win10 无法隐藏黄色边框,因此只在 Win11 中回落到屏幕捕获
|
||||
if (!Win32Helper::GetOSVersion().IsWin11()) {
|
||||
Logger::Get().Error("无法使用屏幕捕获");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使全屏窗口无法被捕获到
|
||||
// WDA_EXCLUDEFROMCAPTURE 只在 Win10 20H1 及更新版本中可用
|
||||
if (!SetWindowDisplayAffinity(ScalingWindow::Get().Handle(), WDA_EXCLUDEFROMCAPTURE)) {
|
||||
Logger::Get().Win32Error("SetWindowDisplayAffinity 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
HMONITOR hMonitor = MonitorFromWindow(hwndSrc, MONITOR_DEFAULTTONEAREST);
|
||||
if (!hMonitor) {
|
||||
Logger::Get().Win32Error("MonitorFromWindow 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
MONITORINFO mi{};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (!GetMonitorInfo(hMonitor, &mi)) {
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 最大化的窗口无需调整位置
|
||||
if (Win32Helper::GetWindowShowCmd(hwndSrc) != SW_SHOWMAXIMIZED) {
|
||||
// 放在屏幕左上角而不是中间可以提高帧率,这里是为了和 DesktopDuplication 保持一致
|
||||
if (!_CenterWindowIfNecessary(hwndSrc, mi.rcWork)) {
|
||||
Logger::Get().Error("居中源窗口失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重新计算捕获位置
|
||||
if (!_CalcSrcRect()) {
|
||||
Logger::Get().Error("_CalcSrcRect 失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_frameBox = {
|
||||
UINT(_srcRect.left - mi.rcMonitor.left),
|
||||
UINT(_srcRect.top - mi.rcMonitor.top),
|
||||
0,
|
||||
UINT(_srcRect.right - mi.rcMonitor.left),
|
||||
UINT(_srcRect.bottom - mi.rcMonitor.top),
|
||||
1
|
||||
};
|
||||
|
||||
try {
|
||||
HRESULT hr = interop->CreateForMonitor(
|
||||
hMonitor,
|
||||
winrt::guid_of<winrt::GraphicsCaptureItem>(),
|
||||
winrt::put_abi(_captureItem)
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("创建 GraphicsCaptureItem 失败", hr);
|
||||
return false;
|
||||
}
|
||||
} catch (const winrt::hresult_error& e) {
|
||||
Logger::Get().Info(StrHelper::Concat("捕获屏幕失败: ", StrHelper::UTF16ToUTF8(e.message())));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GraphicsCaptureFrameSource::_StartCapture() noexcept {
|
||||
if (_captureSession) {
|
||||
return true;
|
||||
|
|
@ -464,7 +385,7 @@ void GraphicsCaptureFrameSource::_StopCapture() noexcept {
|
|||
GraphicsCaptureFrameSource::~GraphicsCaptureFrameSource() {
|
||||
_StopCapture();
|
||||
|
||||
const HWND hwndSrc = ScalingWindow::Get().HwndSrc();
|
||||
const HWND hwndSrc = ScalingWindow::Get().SrcTracker().Handle();
|
||||
|
||||
if (_taskbarList) {
|
||||
_taskbarList->DeleteTab(hwndSrc);
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ class GraphicsCaptureFrameSource final : public FrameSourceBase {
|
|||
public:
|
||||
virtual ~GraphicsCaptureFrameSource();
|
||||
|
||||
bool IsScreenCapture() const noexcept override {
|
||||
return _isScreenCapture;
|
||||
}
|
||||
bool Start() noexcept override;
|
||||
|
||||
FrameSourceWaitType WaitType() const noexcept override {
|
||||
return FrameSourceWaitType::WaitForMessage;
|
||||
|
|
@ -27,14 +25,6 @@ public:
|
|||
void OnCursorVisibilityChanged(bool isVisible, bool onDestory) noexcept override;
|
||||
|
||||
protected:
|
||||
bool _HasRoundCornerInWin11() noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _CanCaptureTitleBar() noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _Initialize() noexcept override;
|
||||
|
||||
FrameSourceState _Update() noexcept override;
|
||||
|
|
@ -46,8 +36,6 @@ private:
|
|||
|
||||
bool _CaptureWindow(IGraphicsCaptureItemInterop* interop) noexcept;
|
||||
|
||||
bool _CaptureMonitor(IGraphicsCaptureItemInterop* interop) noexcept;
|
||||
|
||||
bool _TryCreateGraphicsCaptureItem(IGraphicsCaptureItemInterop* interop) noexcept;
|
||||
|
||||
void _RemoveOwnerFromAltTabList(HWND hwndSrc) noexcept;
|
||||
|
|
@ -58,12 +46,10 @@ private:
|
|||
|
||||
D3D11_BOX _frameBox{};
|
||||
|
||||
bool _isScreenCapture = false;
|
||||
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem _captureItem{ nullptr };
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool _captureFramePool{ nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession _captureSession{ nullptr };
|
||||
winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice _wrappedD3DDevice{ nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem _captureItem{ nullptr };
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureSession _captureSession{ nullptr };
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool _captureFramePool{ nullptr };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// 原始文件: https://github.com/ocornut/imgui/blob/e489e40a853426767de9ce0637bc0c9ceb431c1e/backends/imgui_impl_dx11.cpp
|
||||
|
||||
#include "pch.h"
|
||||
#include "ImGuiBackend.h"
|
||||
#include <d3dcompiler.h>
|
||||
|
|
@ -17,8 +15,8 @@ struct VERTEX_CONSTANT_BUFFER {
|
|||
float mvp[4][4];
|
||||
};
|
||||
|
||||
bool ImGuiBackend::Initialize(DeviceResources* deviceResources) noexcept {
|
||||
_deviceResources = deviceResources;
|
||||
bool ImGuiBackend::Initialize(DeviceResources& deviceResources) noexcept {
|
||||
_deviceResources = &deviceResources;
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.BackendRendererName = "Magpie";
|
||||
|
|
@ -33,10 +31,12 @@ bool ImGuiBackend::Initialize(DeviceResources* deviceResources) noexcept {
|
|||
return true;
|
||||
}
|
||||
|
||||
void ImGuiBackend::_SetupRenderState(const ImDrawData& drawData) noexcept {
|
||||
void ImGuiBackend::_SetupRenderState(const ImDrawData& drawData, POINT viewportOffset) noexcept {
|
||||
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
|
||||
|
||||
D3D11_VIEWPORT vp{
|
||||
.TopLeftX = (FLOAT)viewportOffset.x,
|
||||
.TopLeftY = (FLOAT)viewportOffset.y,
|
||||
.Width = drawData.DisplaySize.x,
|
||||
.Height = drawData.DisplaySize.y,
|
||||
.MinDepth = 0.0f,
|
||||
|
|
@ -75,7 +75,7 @@ void ImGuiBackend::_SetupRenderState(const ImDrawData& drawData) noexcept {
|
|||
d3dDC->RSSetState(_rasterizerState.get());
|
||||
}
|
||||
|
||||
void ImGuiBackend::RenderDrawData(const ImDrawData& drawData) noexcept {
|
||||
void ImGuiBackend::RenderDrawData(const ImDrawData& drawData, POINT viewportOffset) noexcept {
|
||||
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
|
||||
ID3D11Device5* d3dDevice = _deviceResources->GetD3DDevice();
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData) noexcept {
|
|||
d3dDC->Unmap(_vertexConstantBuffer.get(), 0);
|
||||
}
|
||||
|
||||
_SetupRenderState(drawData);
|
||||
_SetupRenderState(drawData, viewportOffset);
|
||||
|
||||
// Render command lists
|
||||
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||
|
|
@ -186,7 +186,7 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData) noexcept {
|
|||
// User callback, registered via ImDrawList::AddCallback()
|
||||
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
|
||||
if (drawCmd.UserCallback == ImDrawCallback_ResetRenderState) {
|
||||
_SetupRenderState(drawData);
|
||||
_SetupRenderState(drawData, viewportOffset);
|
||||
} else {
|
||||
drawCmd.UserCallback(cmdList, &drawCmd);
|
||||
}
|
||||
|
|
@ -198,7 +198,12 @@ void ImGuiBackend::RenderDrawData(const ImDrawData& drawData) noexcept {
|
|||
continue;
|
||||
|
||||
// Apply scissor/clipping rectangle
|
||||
const D3D11_RECT r = { (LONG)clipMin.x, (LONG)clipMin.y, (LONG)clipMax.x, (LONG)clipMax.y };
|
||||
const D3D11_RECT r = {
|
||||
(LONG)clipMin.x + viewportOffset.x,
|
||||
(LONG)clipMin.y + viewportOffset.y,
|
||||
(LONG)clipMax.x + viewportOffset.x,
|
||||
(LONG)clipMax.y + viewportOffset.y
|
||||
};
|
||||
d3dDC->RSSetScissorRects(1, &r);
|
||||
|
||||
// Bind texture, Draw
|
||||
|
|
@ -329,6 +334,10 @@ bool ImGuiBackend::BuildFonts() noexcept {
|
|||
|
||||
// 清理不再需要的数据降低内存占用
|
||||
io.Fonts->ClearTexData();
|
||||
// Debug 配置下保留 ConfigData 以方便调试
|
||||
#ifndef _DEBUG
|
||||
io.Fonts->ClearInputData();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ public:
|
|||
ImGuiBackend(const ImGuiBackend&) = delete;
|
||||
ImGuiBackend(ImGuiBackend&&) = delete;
|
||||
|
||||
bool Initialize(DeviceResources* deviceResources) noexcept;
|
||||
bool Initialize(DeviceResources& deviceResources) noexcept;
|
||||
|
||||
bool BuildFonts() noexcept;
|
||||
|
||||
void RenderDrawData(const ImDrawData& drawData) noexcept;
|
||||
void RenderDrawData(const ImDrawData& drawData, POINT viewportOffset) noexcept;
|
||||
|
||||
private:
|
||||
bool _CreateDeviceObjects() noexcept;
|
||||
|
||||
void _SetupRenderState(const ImDrawData& drawData) noexcept;
|
||||
void _SetupRenderState(const ImDrawData& drawData, POINT viewportOffset) noexcept;
|
||||
|
||||
DeviceResources* _deviceResources = nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -139,17 +139,17 @@ struct serializer<
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
// 缓存版本
|
||||
// 当缓存文件结构有更改时更新它,使旧缓存失效
|
||||
static constexpr uint32_t FONTS_CACHE_VERSION = 1;
|
||||
// 缓存版本号。当缓存文件结构有更改时更新它,使旧缓存失效
|
||||
static constexpr uint32_t FONTS_CACHE_VERSION = 3;
|
||||
|
||||
static std::wstring GetCacheFileName(const std::wstring_view& language, uint32_t dpi) noexcept {
|
||||
return fmt::format(L"{}fonts_{}_{}", CommonSharedConstants::CACHE_DIR, language, dpi);
|
||||
return fmt::format(L"{}\\fonts_{}_{}", CommonSharedConstants::CACHE_DIR, language, dpi);
|
||||
}
|
||||
|
||||
void ImGuiFontsCacheManager::Save(std::wstring_view language, uint32_t dpi, const ImFontAtlas& fontAltas) noexcept {
|
||||
std::vector<uint8_t>& buffer = _cacheMap[dpi];
|
||||
buffer.reserve(131072);
|
||||
buffer.clear();
|
||||
buffer.reserve(1024);
|
||||
|
||||
try {
|
||||
yas::vector_ostream os(buffer);
|
||||
|
|
@ -168,7 +168,7 @@ void ImGuiFontsCacheManager::Save(std::wstring_view language, uint32_t dpi, cons
|
|||
}
|
||||
|
||||
std::wstring cacheFileName = GetCacheFileName(language, dpi);
|
||||
if (!Win32Helper::WriteFile(cacheFileName.c_str(), buffer.data(), buffer.size())) {
|
||||
if (!Win32Helper::WriteFile(cacheFileName.c_str(), buffer)) {
|
||||
Logger::Get().Error("保存字体缓存失败");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,35 @@
|
|||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "CursorManager.h"
|
||||
#include <ranges>
|
||||
#include "StrHelper.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static bool operator==(const ImVec2& l, const ImVec2& r) noexcept {
|
||||
return l.x == r.x && l.y == r.y;
|
||||
}
|
||||
|
||||
static bool operator==(const ImVec4& l, const ImVec4& r) noexcept {
|
||||
return l.x == r.x && l.y == r.y && l.z == r.z && l.w == r.w;
|
||||
}
|
||||
|
||||
static const char* GetWindowIDFromName(const char* name) noexcept {
|
||||
size_t idPos = std::string_view(name).find("##");
|
||||
if (idPos == std::string_view::npos) {
|
||||
return name;
|
||||
} else {
|
||||
return name + idPos + 2;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiImpl::~ImGuiImpl() noexcept {
|
||||
if (ImGui::GetCurrentContext()) {
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGuiImpl::Initialize(DeviceResources* deviceResources) noexcept {
|
||||
bool ImGuiImpl::Initialize(DeviceResources& deviceResources) noexcept {
|
||||
#ifdef _DEBUG
|
||||
// 检查 ImGUI 版本是否匹配
|
||||
if (!IMGUI_CHECKVERSION()) {
|
||||
|
|
@ -30,10 +48,11 @@ bool ImGuiImpl::Initialize(DeviceResources* deviceResources) noexcept {
|
|||
|
||||
ImGui::CreateContext();
|
||||
|
||||
// Setup backend capabilities flags
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.BackendPlatformName = "Magpie";
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavNoCaptureKeyboard | ImGuiConfigFlags_NoMouseCursorChange;
|
||||
// 禁用 ini 配置文件
|
||||
io.IniFilename = nullptr;
|
||||
|
||||
if (!_backend.Initialize(deviceResources)) {
|
||||
Logger::Get().Error("初始化 ImGuiBackend 失败");
|
||||
|
|
@ -47,15 +66,24 @@ bool ImGuiImpl::BuildFonts() noexcept {
|
|||
return _backend.BuildFonts();
|
||||
}
|
||||
|
||||
void ImGuiImpl::NewFrame() noexcept {
|
||||
void ImGuiImpl::NewFrame(
|
||||
phmap::flat_hash_map<std::string, OverlayWindowOption>& windowOptions,
|
||||
float fittsLawAdjustment,
|
||||
float dpiScale
|
||||
) noexcept {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// Setup display size (every frame to accommodate for window resizing)
|
||||
const SIZE outputSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().Renderer().DestRect());
|
||||
io.DisplaySize = ImVec2((float)outputSize.cx, (float)outputSize.cy);
|
||||
{
|
||||
const SIZE destSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().Renderer().DestRect());
|
||||
ImVec2 newDisplaySize((float)destSize.cx, (float)destSize.cy);
|
||||
if (io.DisplaySize != newDisplaySize) {
|
||||
io.DisplaySize = newDisplaySize;
|
||||
// 调整缩放窗口尺寸时强制调整叠加层窗口位置
|
||||
_windowRects.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Update OS mouse position
|
||||
_UpdateMousePos();
|
||||
_UpdateMousePos(fittsLawAdjustment);
|
||||
|
||||
// 不接受键盘输入
|
||||
if (io.WantCaptureKeyboard) {
|
||||
|
|
@ -64,60 +92,166 @@ void ImGuiImpl::NewFrame() noexcept {
|
|||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
// 将所有 ImGUI 窗口限制在视口内
|
||||
|
||||
for (ImGuiWindow* window : ImGui::GetCurrentContext()->Windows) {
|
||||
if (window->Flags & ImGuiWindowFlags_Tooltip) {
|
||||
if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoMove)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 排除 Debug##Default 窗口和尚未初始化完成的窗口
|
||||
if (window->IsFallbackWindow || window->Appearing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImVec2 pos = window->Pos;
|
||||
|
||||
if (outputSize.cx > window->Size.x) {
|
||||
pos.x = std::clamp(pos.x, 0.0f, outputSize.cx - window->Size.x);
|
||||
// 将窗口限制在视口内
|
||||
if (io.DisplaySize.x > window->Size.x) {
|
||||
pos.x = std::clamp(pos.x, 0.0f, io.DisplaySize.x - window->Size.x);
|
||||
} else {
|
||||
pos.x = 0;
|
||||
}
|
||||
|
||||
if (outputSize.cy > window->Size.y) {
|
||||
pos.y = std::clamp(pos.y, 0.0f, outputSize.cy - window->Size.y);
|
||||
if (io.DisplaySize.y > window->Size.y) {
|
||||
pos.y = std::clamp(pos.y, 0.0f, io.DisplaySize.y - window->Size.y);
|
||||
} else {
|
||||
pos.y = 0;
|
||||
}
|
||||
|
||||
ImGui::SetWindowPos(window, pos);
|
||||
const char* windowId = GetWindowIDFromName(window->Name);
|
||||
if (auto it = windowOptions.find(windowId); it != windowOptions.end()) {
|
||||
OverlayWindowOption& option = it->second;
|
||||
|
||||
auto it1 = _windowRects.find(windowId);
|
||||
if (it1 == _windowRects.end()) {
|
||||
// 第一次显示或调整缩放窗口大小时叠加层窗口应根据规则调整位置
|
||||
|
||||
if (option.hArea == 0) {
|
||||
pos.x = option.hPos * dpiScale;
|
||||
} else if (option.hArea == 1) {
|
||||
pos.x = io.DisplaySize.x * option.hPos - window->Size.x / 2;
|
||||
} else if (option.hArea == 2) {
|
||||
pos.x = io.DisplaySize.x - option.hPos * dpiScale - window->Size.x;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
if (option.vArea == 0) {
|
||||
pos.y = option.vPos * dpiScale;
|
||||
} else if (option.vArea == 1) {
|
||||
pos.y = io.DisplaySize.y * option.vPos - window->Size.y / 2;
|
||||
} else if (option.vArea == 2) {
|
||||
pos.y = io.DisplaySize.y - option.vPos * dpiScale - window->Size.y;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// 再次将窗口限制在视口内
|
||||
if (io.DisplaySize.x > window->Size.x) {
|
||||
pos.x = std::clamp(pos.x, 0.0f, io.DisplaySize.x - window->Size.x);
|
||||
} else {
|
||||
pos.x = 0;
|
||||
}
|
||||
|
||||
if (io.DisplaySize.y > window->Size.y) {
|
||||
pos.y = std::clamp(pos.y, 0.0f, io.DisplaySize.y - window->Size.y);
|
||||
} else {
|
||||
pos.y = 0;
|
||||
}
|
||||
} else if (it1->second != ImVec4(pos.x, pos.y, window->Size.x, window->Size.y)) {
|
||||
// 当且仅当用户移动窗口或调整窗口大小后后重新计算贴靠的边,调整缩放窗口大小时应保持
|
||||
// 贴靠的边不变。我们根据两侧边距的比例决定贴靠哪边或者都不贴靠。
|
||||
|
||||
// 这些阈值决定是否贴靠在某一边上,它们不是定值,而是窗口尺寸和画面尺寸的比例。这个
|
||||
// 算法的效果出乎意料的好,因为窗口两侧边距较大时人对比例更敏感,较小时则对差值更敏
|
||||
// 感。
|
||||
const float thresholdX = std::max(window->Size.x / io.DisplaySize.x, 0.2f);
|
||||
const float thresholdY = std::max(window->Size.y / io.DisplaySize.y, 0.2f);
|
||||
|
||||
// 根据左右边距比例决定贴靠
|
||||
float ratio = pos.x / (io.DisplaySize.x - pos.x - window->Size.x);
|
||||
if (ratio < thresholdX) {
|
||||
option.hArea = 0;
|
||||
option.hPos = pos.x / dpiScale;
|
||||
} else if (ratio <= 1 / thresholdX) {
|
||||
option.hArea = 1;
|
||||
option.hPos = (pos.x + window->Size.x / 2) / io.DisplaySize.x;
|
||||
} else {
|
||||
option.hArea = 2;
|
||||
option.hPos = (io.DisplaySize.x - pos.x - window->Size.x) / dpiScale;
|
||||
}
|
||||
|
||||
// 根据上下边距比例决定贴靠
|
||||
ratio = pos.y / (io.DisplaySize.y - pos.y - window->Size.y);
|
||||
if (ratio < thresholdY) {
|
||||
option.vArea = 0;
|
||||
option.vPos = pos.y / dpiScale;
|
||||
} else if (ratio <= 1 / thresholdY) {
|
||||
option.vArea = 1;
|
||||
option.vPos = (pos.y + window->Size.y / 2) / io.DisplaySize.y;
|
||||
} else {
|
||||
option.vArea = 2;
|
||||
option.vPos = (io.DisplaySize.y - pos.y - window->Size.y) / dpiScale;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetWindowPos(window, pos);
|
||||
|
||||
// 此时 window->Pos 已更新,记录新的窗口位置
|
||||
_windowRects[windowId] = ImVec4(window->Pos.x, window->Pos.y, window->Size.x, window->Size.y);
|
||||
} else {
|
||||
ImGui::SetWindowPos(window, pos);
|
||||
}
|
||||
}
|
||||
|
||||
ScalingWindow::Get().CursorManager().IsCursorOnOverlay(io.WantCaptureMouse);
|
||||
// 调整缩放窗口大小或鼠标被前台窗口捕获时避免鼠标跳跃
|
||||
CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
if (!ScalingWindow::Get().IsResizingOrMoving() && !cursorManager.IsCursorCapturedOnForeground()) {
|
||||
cursorManager.IsCursorOnOverlay(io.WantCaptureMouse);
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiImpl::Draw() noexcept {
|
||||
const RECT& scalingRect = ScalingWindow::Get().WndRect();
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
|
||||
void ImGuiImpl::Draw(POINT drawOffset) noexcept {
|
||||
ImGui::Render();
|
||||
ImDrawData& drawData = *ImGui::GetDrawData();
|
||||
drawData.DisplayPos = ImVec2(
|
||||
float(scalingRect.left - destRect.left),
|
||||
float(scalingRect.top - destRect.top)
|
||||
);
|
||||
drawData.DisplaySize = ImVec2(
|
||||
float(destRect.right - scalingRect.left),
|
||||
float(destRect.bottom - scalingRect.top)
|
||||
);
|
||||
|
||||
_backend.RenderDrawData(drawData);
|
||||
const RECT& rendererRect = ScalingWindow::Get().RendererRect();
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
const POINT viewportOffset = {
|
||||
destRect.left - rendererRect.left + drawOffset.x,
|
||||
destRect.top - rendererRect.top + drawOffset.y
|
||||
};
|
||||
_backend.RenderDrawData(*ImGui::GetDrawData(), viewportOffset);
|
||||
}
|
||||
|
||||
void ImGuiImpl::Tooltip(const char* content, float maxWidth) noexcept {
|
||||
void ImGuiImpl::Tooltip(
|
||||
const char* content,
|
||||
float dpiScale,
|
||||
const char* description,
|
||||
float maxWidth
|
||||
) noexcept {
|
||||
static constexpr float DESCRIPTION_SCALE = 0.9f;
|
||||
|
||||
ImVec2 padding = ImGui::GetStyle().WindowPadding;
|
||||
ImVec2 contentSize = ImGui::CalcTextSize(content, nullptr, false, maxWidth - 2 * padding.x);
|
||||
ImVec2 windowSize(contentSize.x + 2 * padding.x, contentSize.y + 2 * padding.y);
|
||||
ImVec2 descriptionSize{};
|
||||
if (description) {
|
||||
float oldFontScale = ImGui::GetIO().FontGlobalScale;
|
||||
ImGui::GetIO().FontGlobalScale *= DESCRIPTION_SCALE;
|
||||
ImGui::PushFont(ImGui::GetFont());
|
||||
descriptionSize = ImGui::CalcTextSize(description, nullptr, false, maxWidth - 2 * padding.x);
|
||||
ImGui::GetIO().FontGlobalScale = oldFontScale;
|
||||
ImGui::PopFont();
|
||||
}
|
||||
// 稍微增加高度,否则下边框比上边框稍窄
|
||||
ImVec2 windowSize(
|
||||
std::max(contentSize.x, descriptionSize.x) + 2 * padding.x,
|
||||
contentSize.y + descriptionSize.y + 2 * padding.y + 1.5f * dpiScale
|
||||
);
|
||||
ImGui::SetNextWindowSize(windowSize);
|
||||
|
||||
ImVec2 windowPos = ImGui::GetMousePos();
|
||||
windowPos.x += 16 * ImGui::GetStyle().MouseCursorScale;
|
||||
windowPos.y += 8 * ImGui::GetStyle().MouseCursorScale;
|
||||
windowPos.x += 16.0f * dpiScale * ImGui::GetStyle().MouseCursorScale;
|
||||
windowPos.y += 8.0f * dpiScale * ImGui::GetStyle().MouseCursorScale;
|
||||
|
||||
SIZE outputSize = Win32Helper::GetSizeOfRect(ScalingWindow::Get().Renderer().DestRect());
|
||||
windowPos.x = std::clamp(windowPos.x, 0.0f, outputSize.cx - windowSize.x);
|
||||
|
|
@ -126,38 +260,53 @@ void ImGuiImpl::Tooltip(const char* content, float maxWidth) noexcept {
|
|||
ImGui::SetNextWindowPos(windowPos);
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(ImGui::GetStyle().Colors[ImGuiCol_PopupBg].w);
|
||||
ImGui::Begin("tooltip", NULL, ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||
ImGui::Begin("tooltip", NULL,
|
||||
ImGuiWindowFlags_NoInputs |
|
||||
ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing);
|
||||
|
||||
ImGui::PushTextWrapPos(maxWidth - padding.x);
|
||||
ImGui::TextUnformatted(content);
|
||||
if (description) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, { 1.0f,1.0f,1.0f,0.8f });
|
||||
float oldFontScale = ImGui::GetIO().FontGlobalScale;
|
||||
ImGui::GetIO().FontGlobalScale *= DESCRIPTION_SCALE;
|
||||
ImGui::PushFont(ImGui::GetFont());
|
||||
ImGui::TextUnformatted(description);
|
||||
ImGui::GetIO().FontGlobalScale = oldFontScale;
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ImGuiImpl::_UpdateMousePos() noexcept {
|
||||
void ImGuiImpl::_UpdateMousePos(float fittsLawAdjustment) noexcept {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||
|
||||
// 调整缩放窗口大小或鼠标被前台窗口捕获时不应和叠加层交互
|
||||
const CursorManager& cursorManager = ScalingWindow::Get().CursorManager();
|
||||
|
||||
if (cursorManager.IsCursorCapturedOnForeground()) {
|
||||
// 光标被前台窗口捕获时应避免造成光标跳跃
|
||||
if (ScalingWindow::Get().IsResizingOrMoving() || cursorManager.IsCursorCapturedOnForeground()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const POINT cursorPos = cursorManager.CursorPos();
|
||||
if (cursorPos.x == std::numeric_limits<LONG>::max()) {
|
||||
// 无光标
|
||||
return;
|
||||
}
|
||||
|
||||
const RECT& scalingRect = ScalingWindow::Get().WndRect();
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
|
||||
io.MousePos.x = float(cursorPos.x + scalingRect.left - destRect.left);
|
||||
io.MousePos.y = float(cursorPos.y + scalingRect.top - destRect.top);
|
||||
// 转换为目标矩形局部坐标
|
||||
const RECT& destRect = ScalingWindow::Get().Renderer().DestRect();
|
||||
io.MousePos.x = float(cursorPos.x - destRect.left);
|
||||
io.MousePos.y = float(cursorPos.y - destRect.top);
|
||||
|
||||
// 下移鼠标的逻辑位置使得在上边缘可以选中工具栏按钮
|
||||
if (io.MousePos.y >= 0 && io.MousePos.y < fittsLawAdjustment) {
|
||||
io.MousePos.y = fittsLawAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiImpl::ClearStates() noexcept {
|
||||
|
|
@ -182,12 +331,8 @@ void ImGuiImpl::ClearStates() noexcept {
|
|||
|
||||
void ImGuiImpl::MessageHandler(UINT msg, WPARAM wParam, LPARAM /*lParam*/) noexcept {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
|
||||
if (!io.WantCaptureMouse) {
|
||||
// 3D 游戏模式下显示叠加层会使缩放窗口不透明,这时点击非叠加层区域应关闭叠加层
|
||||
if (msg == WM_LBUTTONDOWN && ScalingWindow::Get().Options().Is3DGameMode()) {
|
||||
ScalingWindow::Get().Renderer().SetOverlayVisibility(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -227,4 +372,46 @@ void ImGuiImpl::MessageHandler(UINT msg, WPARAM wParam, LPARAM /*lParam*/) noexc
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<ImVec4> ImGuiImpl::GetWindowRect(const char* id) const noexcept {
|
||||
const std::string suffix = StrHelper::Concat("##", id);
|
||||
for (ImGuiWindow* window : ImGui::GetCurrentContext()->Windows) {
|
||||
if (std::string_view(window->Name).ends_with(suffix)) {
|
||||
return ImVec4(
|
||||
window->Pos.x,
|
||||
window->Pos.y,
|
||||
window->Pos.x + window->Size.x,
|
||||
window->Pos.y + window->Size.y
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const char* ImGuiImpl::GetHoveredWindowId() const noexcept {
|
||||
const ImVec2 mousePos = ImGui::GetIO().MousePos;
|
||||
// 自顶向下遍历
|
||||
for (ImGuiWindow* window : ImGui::GetCurrentContext()->Windows | std::views::reverse) {
|
||||
// 排除不接受鼠标输入的窗口,来自
|
||||
// https://github.com/ocornut/imgui/blob/77f1d3b317c400c34ee02fe9a5354d0d757b55ca/imgui.cpp#L5855
|
||||
if (!window->WasActive || window->Hidden) {
|
||||
continue;
|
||||
}
|
||||
if (window->Flags & ImGuiWindowFlags_NoMouseInputs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (window->Rect().Contains(mousePos)) {
|
||||
return GetWindowIDFromName(window->Name);
|
||||
}
|
||||
|
||||
// 弹窗会阻止和其他窗口交互
|
||||
if (window->Flags & ImGuiWindowFlags_Popup) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
#include "ImGuiBackend.h"
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
class DeviceResources;
|
||||
struct OverlayWindowOption;
|
||||
|
||||
class ImGuiImpl {
|
||||
public:
|
||||
|
|
@ -13,25 +15,40 @@ public:
|
|||
|
||||
~ImGuiImpl() noexcept;
|
||||
|
||||
bool Initialize(DeviceResources* deviceResource) noexcept;
|
||||
bool Initialize(DeviceResources& deviceResource) noexcept;
|
||||
|
||||
bool BuildFonts() noexcept;
|
||||
|
||||
void NewFrame() noexcept;
|
||||
void NewFrame(
|
||||
phmap::flat_hash_map<std::string, OverlayWindowOption>& windowOptions,
|
||||
float fittsLawAdjustment,
|
||||
float dpiScale
|
||||
) noexcept;
|
||||
|
||||
void Draw() noexcept;
|
||||
void Draw(POINT drawOffset) noexcept;
|
||||
|
||||
void ClearStates() noexcept;
|
||||
|
||||
void MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
|
||||
std::optional<ImVec4> GetWindowRect(const char* id) const noexcept;
|
||||
|
||||
const char* GetHoveredWindowId() const noexcept;
|
||||
|
||||
// 将提示窗口限制在屏幕内
|
||||
static void Tooltip(const char* content, float maxWidth = -1.0f) noexcept;
|
||||
void Tooltip(
|
||||
const char* content,
|
||||
float dpiScale,
|
||||
const char* description = nullptr,
|
||||
float maxWidth = -1.0f
|
||||
) noexcept;
|
||||
private:
|
||||
void _UpdateMousePos() noexcept;
|
||||
void _UpdateMousePos(float fittsLawAdjustment) noexcept;
|
||||
|
||||
ImGuiBackend _backend;
|
||||
|
||||
phmap::flat_hash_map<std::string, ImVec4> _windowRects;
|
||||
|
||||
uint32_t _handlerId = 0;
|
||||
|
||||
HANDLE _hHookThread = NULL;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?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.240405.15\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.250303.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Import Project="..\Common.Pre.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<CppWinRTFastAbi>true</CppWinRTFastAbi>
|
||||
|
|
@ -48,10 +48,11 @@
|
|||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="BackendDescriptorStore.h" />
|
||||
<ClInclude Include="CompSwapchainPresenter.h" />
|
||||
<ClInclude Include="CursorManager.h" />
|
||||
<ClInclude Include="CursorDrawer.h" />
|
||||
<ClInclude Include="DDS.h" />
|
||||
<ClInclude Include="DDSLoderHelpers.h" />
|
||||
<ClInclude Include="DDSHelper.h" />
|
||||
<ClInclude Include="DesktopDuplicationFrameSource.h" />
|
||||
<ClInclude Include="DeviceResources.h" />
|
||||
<ClInclude Include="DwmSharedSurfaceFrameSource.h" />
|
||||
|
|
@ -65,36 +66,36 @@
|
|||
<ClInclude Include="GraphicsCaptureFrameSource.h" />
|
||||
<ClInclude Include="ImGuiBackend.h" />
|
||||
<ClInclude Include="ImGuiFontsCacheManager.h" />
|
||||
<ClInclude Include="ImGuiHelper.h" />
|
||||
<ClInclude Include="OverlayHelper.h" />
|
||||
<ClInclude Include="ImGuiImpl.h" />
|
||||
<ClInclude Include="include\CommonDefines.h" />
|
||||
<ClInclude Include="include\CommonSharedConstants.h" />
|
||||
<ClInclude Include="include\DirectXHelper.h" />
|
||||
<ClInclude Include="include\EffectCompiler.h" />
|
||||
<ClInclude Include="include\EffectDesc.h" />
|
||||
<ClInclude Include="include\Logger.h" />
|
||||
<ClInclude Include="include\ScalingError.h" />
|
||||
<ClInclude Include="include\ScalingOptions.h" />
|
||||
<ClInclude Include="include\ScalingRuntime.h" />
|
||||
<ClInclude Include="include\SmallVector.h" />
|
||||
<ClInclude Include="include\StrHelper.h" />
|
||||
<ClInclude Include="include\Version.h" />
|
||||
<ClInclude Include="include\Win32Helper.h" />
|
||||
<ClInclude Include="include\WindowBase.h" />
|
||||
<ClInclude Include="include\WindowHelper.h" />
|
||||
<ClInclude Include="include\Event.h" />
|
||||
<ClInclude Include="OverlayDrawer.h" />
|
||||
<ClInclude Include="PresenterBase.h" />
|
||||
<ClInclude Include="Renderer.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ScalingWindow.h" />
|
||||
<ClInclude Include="ScreenshotHelper.h" />
|
||||
<ClInclude Include="SrcTracker.h" />
|
||||
<ClInclude Include="StepTimer.h" />
|
||||
<ClInclude Include="TextureLoader.h" />
|
||||
<ClInclude Include="AdaptivePresenter.h" />
|
||||
<ClInclude Include="TextureHelper.h" />
|
||||
<ClInclude Include="YasHelper.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="BackendDescriptorStore.cpp" />
|
||||
<ClCompile Include="CompSwapchainPresenter.cpp" />
|
||||
<ClCompile Include="CursorManager.cpp" />
|
||||
<ClCompile Include="CursorDrawer.cpp" />
|
||||
<ClCompile Include="DDSHelper.cpp" />
|
||||
<ClCompile Include="DesktopDuplicationFrameSource.cpp" />
|
||||
<ClCompile Include="DeviceResources.cpp" />
|
||||
<ClCompile Include="DirectXHelper.cpp" />
|
||||
|
|
@ -109,22 +110,22 @@
|
|||
<ClCompile Include="GraphicsCaptureFrameSource.cpp" />
|
||||
<ClCompile Include="ImGuiBackend.cpp" />
|
||||
<ClCompile Include="ImGuiFontsCacheManager.cpp" />
|
||||
<ClCompile Include="ImGuiHelper.cpp" />
|
||||
<ClCompile Include="OverlayHelper.cpp" />
|
||||
<ClCompile Include="ImGuiImpl.cpp" />
|
||||
<ClCompile Include="Logger.cpp" />
|
||||
<ClCompile Include="OverlayDrawer.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PresenterBase.cpp" />
|
||||
<ClCompile Include="Renderer.cpp" />
|
||||
<ClCompile Include="ScalingOptions.cpp" />
|
||||
<ClCompile Include="ScalingRuntime.cpp" />
|
||||
<ClCompile Include="ScalingWindow.cpp" />
|
||||
<ClCompile Include="SmallVector.cpp" />
|
||||
<ClCompile Include="ScreenshotHelper.cpp" />
|
||||
<ClCompile Include="SrcTracker.cpp" />
|
||||
<ClCompile Include="StepTimer.cpp" />
|
||||
<ClCompile Include="StrHelper.cpp" />
|
||||
<ClCompile Include="TextureLoader.cpp" />
|
||||
<ClCompile Include="Version.cpp" />
|
||||
<ClCompile Include="AdaptivePresenter.cpp" />
|
||||
<ClCompile Include="TextureHelper.cpp" />
|
||||
<ClCompile Include="Win32Helper.cpp" />
|
||||
<ClCompile Include="WindowHelper.cpp" />
|
||||
</ItemGroup>
|
||||
|
|
@ -156,15 +157,15 @@
|
|||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
<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')" />
|
||||
</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.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240405.15\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
<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'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,15 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Capture">
|
||||
<UniqueIdentifier>{a3c5f48a-b77b-4e7d-ab18-74308a1c0889}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Overlay">
|
||||
<UniqueIdentifier>{a50e8e49-7ac6-4977-98d8-013e82a19dad}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="TextureLoader">
|
||||
<UniqueIdentifier>{b0bbac79-0e76-436d-bcaa-b725b62d545f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Include">
|
||||
<UniqueIdentifier>{7ed1757c-3140-42a7-93b3-5bf0171cfa7b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
|
|
@ -19,37 +13,27 @@
|
|||
<Filter Include="Shaders">
|
||||
<UniqueIdentifier>{1956ae10-07ad-4b77-a37f-25f7fe10654b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Render">
|
||||
<UniqueIdentifier>{6cc4a9bb-b213-4722-b658-3b720317d9de}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Presenter">
|
||||
<UniqueIdentifier>{bf1f1896-560d-4f37-9c3a-891b7c9cd2af}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="EffectCacheManager.h" />
|
||||
<ClInclude Include="TextureLoader.h">
|
||||
<Filter>TextureLoader</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DDS.h">
|
||||
<Filter>TextureLoader</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DDSLoderHelpers.h">
|
||||
<Filter>TextureLoader</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EffectHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiHelper.h">
|
||||
<ClInclude Include="OverlayHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiFontsCacheManager.h">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="YasHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ScalingWindow.h" />
|
||||
<ClInclude Include="Renderer.h" />
|
||||
<ClInclude Include="DeviceResources.h" />
|
||||
<ClInclude Include="EffectDrawer.h" />
|
||||
<ClInclude Include="CursorManager.h" />
|
||||
<ClInclude Include="CursorDrawer.h" />
|
||||
<ClInclude Include="GraphicsCaptureFrameSource.h">
|
||||
<Filter>Capture</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -59,24 +43,12 @@
|
|||
<ClInclude Include="FrameSourceBase.h">
|
||||
<Filter>Capture</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="OverlayDrawer.h">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiImpl.h">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiBackend.h">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BackendDescriptorStore.h" />
|
||||
<ClInclude Include="EffectsProfiler.h" />
|
||||
<ClInclude Include="DwmSharedSurfaceFrameSource.h">
|
||||
<Filter>Capture</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DesktopDuplicationFrameSource.h">
|
||||
<Filter>Capture</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ExclModeHelper.h" />
|
||||
<ClInclude Include="include\EffectCompiler.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -92,9 +64,6 @@
|
|||
<ClInclude Include="include\WindowHelper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\SmallVector.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\StrHelper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -110,50 +79,87 @@
|
|||
<ClInclude Include="include\WindowBase.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StepTimer.h" />
|
||||
<ClInclude Include="include\ScalingError.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\CommonDefines.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\CommonSharedConstants.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\Logger.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\DirectXHelper.h">
|
||||
<Filter>Include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BackendDescriptorStore.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CursorDrawer.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DeviceResources.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EffectDrawer.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ExclModeHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Renderer.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StepTimer.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="EffectsProfiler.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="OverlayDrawer.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiBackend.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiFontsCacheManager.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ImGuiImpl.h">
|
||||
<Filter>Render</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AdaptivePresenter.h">
|
||||
<Filter>Presenter</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CompSwapchainPresenter.h">
|
||||
<Filter>Presenter</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PresenterBase.h">
|
||||
<Filter>Presenter</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ScreenshotHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TextureHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DDS.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DDSHelper.h">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SrcTracker.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ScalingRuntime.cpp" />
|
||||
<ClCompile Include="pch.cpp" />
|
||||
<ClCompile Include="EffectCacheManager.cpp" />
|
||||
<ClCompile Include="EffectCompiler.cpp" />
|
||||
<ClCompile Include="TextureLoader.cpp">
|
||||
<Filter>TextureLoader</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DirectXHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="WindowHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiHelper.cpp">
|
||||
<ClCompile Include="OverlayHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiFontsCacheManager.cpp">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ScalingWindow.cpp" />
|
||||
<ClCompile Include="Renderer.cpp" />
|
||||
<ClCompile Include="DeviceResources.cpp" />
|
||||
<ClCompile Include="EffectDrawer.cpp" />
|
||||
<ClCompile Include="CursorManager.cpp" />
|
||||
<ClCompile Include="CursorDrawer.cpp" />
|
||||
<ClCompile Include="StepTimer.cpp" />
|
||||
<ClCompile Include="GraphicsCaptureFrameSource.cpp">
|
||||
<Filter>Capture</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -163,34 +169,75 @@
|
|||
<ClCompile Include="FrameSourceBase.cpp">
|
||||
<Filter>Capture</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OverlayDrawer.cpp">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiImpl.cpp">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiBackend.cpp">
|
||||
<Filter>Overlay</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="BackendDescriptorStore.cpp" />
|
||||
<ClCompile Include="EffectsProfiler.cpp" />
|
||||
<ClCompile Include="DwmSharedSurfaceFrameSource.cpp">
|
||||
<Filter>Capture</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DesktopDuplicationFrameSource.cpp">
|
||||
<Filter>Capture</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ExclModeHelper.cpp" />
|
||||
<ClCompile Include="ScalingOptions.cpp" />
|
||||
<ClCompile Include="SmallVector.cpp" />
|
||||
<ClCompile Include="Version.cpp" />
|
||||
<ClCompile Include="Logger.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>
|
||||
<ClCompile Include="CursorDrawer.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DeviceResources.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EffectDrawer.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Renderer.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ExclModeHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StepTimer.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="EffectsProfiler.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OverlayDrawer.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiBackend.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiFontsCacheManager.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImGuiImpl.cpp">
|
||||
<Filter>Render</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AdaptivePresenter.cpp">
|
||||
<Filter>Presenter</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CompSwapchainPresenter.cpp">
|
||||
<Filter>Presenter</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PresenterBase.cpp">
|
||||
<Filter>Presenter</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ScreenshotHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TextureHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DDSHelper.cpp">
|
||||
<Filter>Helpers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SrcTracker.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<FxCompile Include="shaders\SimpleVS.hlsl">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,43 +1,52 @@
|
|||
#pragma once
|
||||
#include <deque>
|
||||
#include "SmallVector.h"
|
||||
#include <imgui.h>
|
||||
#include "SmallVector.h"
|
||||
#include "ImGuiImpl.h"
|
||||
#include "Renderer.h"
|
||||
#include "ScalingOptions.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct EffectDesc;
|
||||
|
||||
class OverlayDrawer {
|
||||
public:
|
||||
OverlayDrawer();
|
||||
OverlayDrawer() = default;
|
||||
OverlayDrawer(const OverlayDrawer&) = delete;
|
||||
OverlayDrawer(OverlayDrawer&&) = delete;
|
||||
|
||||
~OverlayDrawer();
|
||||
|
||||
bool Initialize(DeviceResources* deviceResources) noexcept;
|
||||
bool Initialize(DeviceResources& deviceResources, OverlayOptions& overlayOptions) noexcept;
|
||||
|
||||
void Draw(
|
||||
uint32_t count,
|
||||
uint32_t fps,
|
||||
const SmallVector<float>& effectTimings
|
||||
const SmallVector<float>& effectTimings,
|
||||
POINT drawOffset
|
||||
) noexcept;
|
||||
|
||||
bool IsUIVisible() const noexcept {
|
||||
return _isUIVisiable;
|
||||
}
|
||||
ToolbarState ToolbarState() const noexcept;
|
||||
|
||||
void SetUIVisibility(bool value, bool noSetForeground = false) noexcept;
|
||||
void ToolbarState(Magpie::ToolbarState value) noexcept;
|
||||
|
||||
bool AnyVisibleWindow() const noexcept;
|
||||
|
||||
void MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
|
||||
bool NeedRedraw(uint32_t fps) const noexcept;
|
||||
|
||||
void UpdateAfterActiveEffectsChanged() noexcept;
|
||||
|
||||
bool IsCursorOnCaptionArea() const noexcept {
|
||||
return _isCursorOnCaptionArea;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _BuildFonts() noexcept;
|
||||
void _BuildFontUI(std::wstring_view language, const std::vector<uint8_t>& fontData, std::vector<ImWchar>& uiRanges) noexcept;
|
||||
void _BuildFontFPS(const std::vector<uint8_t>& fontData) noexcept;
|
||||
SmallVector<ImWchar> _BuildFontUI(std::wstring_view language, const std::vector<uint8_t>& fontData) noexcept;
|
||||
void _BuildFontIcons(const char* fontPath) noexcept;
|
||||
|
||||
struct _EffectDrawInfo {
|
||||
const Renderer::EffectInfo* info = nullptr;
|
||||
const EffectDesc* desc = nullptr;
|
||||
std::span<const float> passTimings;
|
||||
float totalTime = 0.0f;
|
||||
};
|
||||
|
|
@ -68,17 +77,22 @@ private:
|
|||
bool selected = false
|
||||
);
|
||||
|
||||
void _DrawFPS(uint32_t fps) noexcept;
|
||||
bool _DrawToolbar(uint32_t fps) noexcept;
|
||||
|
||||
bool _DrawUI(const SmallVector<float>& effectTimings, uint32_t fps) noexcept;
|
||||
bool _DrawProfiler(const SmallVector<float>& effectTimings, uint32_t fps) noexcept;
|
||||
|
||||
const std::string& _GetResourceString(const std::wstring_view& key) noexcept;
|
||||
|
||||
float _CalcToolbarAlpha() const noexcept;
|
||||
|
||||
void _ClearStatesIfNoVisibleWindow() noexcept;
|
||||
|
||||
OverlayOptions* _overlayOptions = nullptr;
|
||||
float _dpiScale = 1.0f;
|
||||
|
||||
ImFont* _fontUI = nullptr; // 普通 UI 文字
|
||||
ImFont* _fontMonoNumbers = nullptr; // 普通 UI 文字,但数字部分是等宽的,只支持 ASCII
|
||||
ImFont* _fontFPS = nullptr; // FPS
|
||||
ImFont* _fontUI = nullptr; // 普通 UI 文字
|
||||
ImFont* _fontMonoNumbers = nullptr; // 普通 UI 文字,但数字部分是等宽的,只支持 ASCII
|
||||
ImFont* _fontIcons = nullptr; // 图标字体
|
||||
|
||||
std::chrono::steady_clock::time_point _lastUpdateTime{};
|
||||
// (总计时间, 帧数)
|
||||
|
|
@ -93,10 +107,18 @@ private:
|
|||
|
||||
ImGuiImpl _imguiImpl;
|
||||
|
||||
winrt::ResourceLoader _resourceLoader{ nullptr };
|
||||
uint32_t _lastFPS = std::numeric_limits<uint32_t>::max();
|
||||
float _lastToolbarAlpha = -1.0f;
|
||||
|
||||
bool _isUIVisiable = false;
|
||||
bool _isToolbarVisible = false;
|
||||
bool _isFirstFrame = true;
|
||||
bool _isToolbarPinned = false;
|
||||
bool _isCursorOnCaptionArea = false;
|
||||
bool _isToolbarItemActive = false;
|
||||
bool _isProfilerVisible = false;
|
||||
#ifdef _DEBUG
|
||||
bool _isDemoWindowVisible = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#include "pch.h"
|
||||
#include "ImGuiHelper.h"
|
||||
#include "OverlayHelper.h"
|
||||
#include "EffectDesc.h"
|
||||
#include <random>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static void UnpackAccumulativeOffsetsIntoRanges(
|
||||
int baseCodepoint,
|
||||
|
|
@ -14,7 +18,7 @@ static void UnpackAccumulativeOffsetsIntoRanges(
|
|||
outRanges[0] = 0;
|
||||
}
|
||||
|
||||
const ImWchar* Magpie::ImGuiHelper::GetGlyphRangesChineseSimplifiedOfficial() noexcept {
|
||||
const ImWchar* OverlayHelper::GetGlyphRangesChineseSimplifiedOfficial() noexcept {
|
||||
// 存储了通用规范汉字表中的一级字表(3500字)以及其他一些常用字。
|
||||
// 来自 https://zh.wiktionary.org/wiki/Appendix:%E9%80%9A%E7%94%A8%E8%A7%84%E8%8C%83%E6%B1%89%E5%AD%97%E8%A1%A8
|
||||
// 由 CJKCharacterSetForImGui 生成,它位于 tools 文件夹中。
|
||||
|
|
@ -93,7 +97,7 @@ const ImWchar* Magpie::ImGuiHelper::GetGlyphRangesChineseSimplifiedOfficial() no
|
|||
}
|
||||
|
||||
// 来自 https://github.com/flyinghead/flycast/blob/541544292a3d051839672ffa7bd4524a3e1c1c51/core/rend/gui_util.cpp#L523
|
||||
const ImWchar* Magpie::ImGuiHelper::GetGlyphRangesChineseTraditionalOfficial() noexcept {
|
||||
const ImWchar* OverlayHelper::GetGlyphRangesChineseTraditionalOfficial() noexcept {
|
||||
// Store all official characters for Traditional Chinese.
|
||||
// Sourced from https://https://en.wikipedia.org/wiki/List_of_Graphemes_of_Commonly-Used_Chinese_Characters
|
||||
// (Stored as accumulative offsets from the initial unicode codepoint 0x4E00. This encoding is designed to helps us compact the source code size.)
|
||||
|
|
@ -188,3 +192,128 @@ const ImWchar* Magpie::ImGuiHelper::GetGlyphRangesChineseTraditionalOfficial() n
|
|||
}
|
||||
return &fullRanges[0];
|
||||
}
|
||||
|
||||
static uint32_t GetSeed(const std::vector<const EffectDesc*>& effectDescs) noexcept {
|
||||
uint32_t result = 0;
|
||||
for (const EffectDesc* effectDesc : effectDescs) {
|
||||
result ^= (uint32_t)std::hash<std::string>()(effectDesc->name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
SmallVector<uint32_t> OverlayHelper::GenerateTimelineColors(const std::vector<const EffectDesc*>& effectDescs) noexcept {
|
||||
const uint32_t nEffect = (uint32_t)effectDescs.size();
|
||||
uint32_t totalColors = nEffect > 1 ? nEffect : 0;
|
||||
for (uint32_t i = 0; i < nEffect; ++i) {
|
||||
uint32_t nPass = (uint32_t)effectDescs[i]->passes.size();
|
||||
if (nPass > 1) {
|
||||
totalColors += nPass;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalColors == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
constexpr uint32_t nColors = (uint32_t)std::size(TIMELINE_COLORS);
|
||||
|
||||
std::default_random_engine randomEngine(GetSeed(effectDescs));
|
||||
SmallVector<uint32_t> result;
|
||||
|
||||
if (totalColors <= nColors) {
|
||||
result.resize(nColors);
|
||||
for (uint32_t i = 0; i < nColors; ++i) {
|
||||
result[i] = i;
|
||||
}
|
||||
std::shuffle(result.begin(), result.end(), randomEngine);
|
||||
|
||||
result.resize(totalColors);
|
||||
} else {
|
||||
// 相邻通道颜色不同,相邻效果颜色不同
|
||||
result.resize(totalColors);
|
||||
std::uniform_int_distribution<uint32_t> uniformDst(0, nColors - 1);
|
||||
|
||||
if (nEffect <= nColors) {
|
||||
if (nEffect > 1) {
|
||||
// 确保效果的颜色不重复
|
||||
std::array<uint32_t, nColors> effectColors{};
|
||||
for (uint32_t i = 0; i < nColors; ++i) {
|
||||
effectColors[i] = i;
|
||||
}
|
||||
std::shuffle(effectColors.begin(), effectColors.end(), randomEngine);
|
||||
|
||||
uint32_t i = 0;
|
||||
for (uint32_t j = 0; j < nEffect; ++j) {
|
||||
result[i] = effectColors[j];
|
||||
++i;
|
||||
|
||||
uint32_t nPass = (uint32_t)effectDescs[j]->passes.size();
|
||||
if (nPass > 1) {
|
||||
i += nPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 仅确保与前一个效果颜色不同
|
||||
uint32_t prevColor = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t i = 0;
|
||||
for (uint32_t j = 0; j < nEffect; ++j) {
|
||||
uint32_t c = uniformDst(randomEngine);
|
||||
while (c == prevColor) {
|
||||
c = uniformDst(randomEngine);
|
||||
}
|
||||
|
||||
result[i] = c;
|
||||
prevColor = c;
|
||||
++i;
|
||||
|
||||
uint32_t nPass = (uint32_t)effectDescs[j]->passes.size();
|
||||
if (nPass > 1) {
|
||||
i += nPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成通道的颜色
|
||||
size_t idx = 0;
|
||||
for (uint32_t i = 0; i < nEffect; ++i) {
|
||||
uint32_t nPass = (uint32_t)effectDescs[i]->passes.size();
|
||||
|
||||
if (nEffect > 1) {
|
||||
++idx;
|
||||
|
||||
if (nPass == 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < nPass; ++j) {
|
||||
uint32_t c = uniformDst(randomEngine);
|
||||
|
||||
if (i > 0 || j > 0) {
|
||||
uint32_t prevColor = (i > 0 && j == 0) ? result[idx - 2] : result[idx - 1];
|
||||
|
||||
if (j + 1 == nPass && i + 1 != nEffect && effectDescs[(size_t)i + 1]->passes.size() == 1) {
|
||||
// 当前效果的最后一个通道且下一个效果只有一个通道
|
||||
uint32_t nextColor = result[idx + 1];
|
||||
while (c == prevColor || c == nextColor) {
|
||||
c = uniformDst(randomEngine);
|
||||
}
|
||||
} else {
|
||||
while (c == prevColor) {
|
||||
c = uniformDst(randomEngine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result[idx] = c;
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include "SmallVector.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct ImGuiHelper {
|
||||
struct EffectDesc;
|
||||
|
||||
struct OverlayHelper {
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
// 下面用于默认字体,不以 0 结尾
|
||||
|
|
@ -12,12 +15,9 @@ struct ImGuiHelper {
|
|||
|
||||
// Basic Latin
|
||||
static constexpr ImWchar BASIC_LATIN_RANGES[] = { 0x20, 0x7E };
|
||||
// Basic Latin + Latin-1 Supplement + Latin Extended-A,用于土耳其语、匈牙利语等。
|
||||
// Basic Latin + Latin-1 Supplement + Latin Extended-A,用于土耳其语、波兰语等。
|
||||
// 参见 https://en.wikipedia.org/wiki/Latin_Extended-A
|
||||
static constexpr ImWchar EXTENDED_LATIN_RANGES[] = { 0x20, 0x17F };
|
||||
// Basic Latin + Georgian + Georgian Supplement + Georgian Extended,用于格鲁吉亚语。
|
||||
// https://en.wikipedia.org/wiki/Georgian_scripts
|
||||
static constexpr ImWchar GEORGIAN_RANGES[] = { 0x20, 0x7E, 0x10A0, 0x10FF, 0x2D00, 0x2D2F, 0x1C90, 0x1CBF };
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -42,6 +42,47 @@ struct ImGuiHelper {
|
|||
|
||||
static constexpr ImWchar NUMBER_RANGES[] = { L'0', L'9', 0 };
|
||||
static constexpr ImWchar NOT_NUMBER_RANGES[] = { BASIC_LATIN_RANGES[0], L'0' - 1, L'9' + 1, BASIC_LATIN_RANGES[1], 0};
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//
|
||||
// 下面用于等宽字体
|
||||
//
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
struct SegoeIcons {
|
||||
static const ImWchar Cancel = 0xE711;
|
||||
static const ImWchar Camera = 0xE722;
|
||||
static const ImWchar BackToWindow = 0xE73F;
|
||||
static const ImWchar Pinned = 0xE840;
|
||||
static const ImWchar Diagnostic = 0xE9D9;
|
||||
#ifdef _DEBUG
|
||||
static const ImWchar Design = 0xEB3C;
|
||||
#endif
|
||||
};
|
||||
|
||||
static constexpr ImWchar ICON_RANGES[] = {
|
||||
SegoeIcons::Cancel, SegoeIcons::Cancel,
|
||||
SegoeIcons::Camera, SegoeIcons::Camera,
|
||||
SegoeIcons::BackToWindow, SegoeIcons::BackToWindow,
|
||||
SegoeIcons::Pinned, SegoeIcons::Pinned,
|
||||
SegoeIcons::Diagnostic, SegoeIcons::Diagnostic,
|
||||
#ifdef _DEBUG
|
||||
SegoeIcons::Design, SegoeIcons::Design,
|
||||
#endif
|
||||
0
|
||||
};
|
||||
|
||||
static constexpr const ImColor TIMELINE_COLORS[] = {
|
||||
{229,57,53,255},
|
||||
{156,39,176,255},
|
||||
{63,81,181,255},
|
||||
{30,136,229,255},
|
||||
{0,137,123,255},
|
||||
{121,85,72,255},
|
||||
{117,117,117,255}
|
||||
};
|
||||
|
||||
static SmallVector<uint32_t> GenerateTimelineColors(const std::vector<const EffectDesc*>& effectDescs) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
111
src/Magpie.Core/PresenterBase.cpp
Normal file
111
src/Magpie.Core/PresenterBase.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include "pch.h"
|
||||
#include "PresenterBase.h"
|
||||
#include "DeviceResources.h"
|
||||
#include "Logger.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include <dwmapi.h>
|
||||
#include <dcomp.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
bool PresenterBase::Initialize(HWND hwndAttach, const DeviceResources& deviceResources) noexcept {
|
||||
_deviceResources = &deviceResources;
|
||||
|
||||
HRESULT hr = deviceResources.GetD3DDevice()->CreateFence(
|
||||
_fenceValue, D3D11_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));
|
||||
if (FAILED(hr)) {
|
||||
// GH#979
|
||||
// 这个错误会在某些很旧的显卡上出现,似乎是驱动的 bug。文档中提到 ID3D11Device5::CreateFence
|
||||
// 和 ID3D12Device::CreateFence 等价,但支持 DX12 的显卡也有失败的可能,如 GH#1013
|
||||
Logger::Get().ComError("CreateFence 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_fenceEvent.try_create(wil::EventOptions::None, nullptr)) {
|
||||
Logger::Get().Win32Error("CreateEvent 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {
|
||||
ID3D11DeviceContext4* d3dDC = _deviceResources->GetD3DDC();
|
||||
|
||||
// 等待渲染完成
|
||||
HRESULT hr = d3dDC->Signal(_fence.get(), ++_fenceValue);
|
||||
if (FAILED(hr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
hr = _fence->SetEventOnCompletion(_fenceValue, _fenceEvent.get());
|
||||
if (FAILED(hr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
d3dDC->Flush();
|
||||
|
||||
WaitForSingleObject(_fenceEvent.get(), 1000);
|
||||
}
|
||||
|
||||
}
|
||||
45
src/Magpie.Core/PresenterBase.h
Normal file
45
src/Magpie.Core/PresenterBase.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
class DeviceResources;
|
||||
|
||||
class PresenterBase {
|
||||
public:
|
||||
virtual ~PresenterBase() noexcept {}
|
||||
|
||||
bool Initialize(HWND hwndAttach, const DeviceResources& deviceResources) noexcept;
|
||||
|
||||
virtual bool BeginFrame(
|
||||
winrt::com_ptr<ID3D11Texture2D>& frameTex,
|
||||
winrt::com_ptr<ID3D11RenderTargetView>& frameRtv,
|
||||
POINT& drawOffset
|
||||
) noexcept = 0;
|
||||
|
||||
virtual void EndFrame(bool waitForRenderComplete = false) noexcept = 0;
|
||||
|
||||
virtual bool OnResize() noexcept = 0;
|
||||
|
||||
virtual void OnEndResize(bool& shouldRedraw) noexcept {
|
||||
shouldRedraw = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool _Initialize(HWND hwndAttach) noexcept = 0;
|
||||
|
||||
void _WaitForRenderComplete() noexcept;
|
||||
|
||||
// 和 DwmFlush 效果相同但更准确
|
||||
static void _WaitForDwmComposition() noexcept;
|
||||
|
||||
static uint32_t _CalcBufferCount() noexcept;
|
||||
|
||||
const DeviceResources* _deviceResources = nullptr;
|
||||
|
||||
private:
|
||||
winrt::com_ptr<ID3D11Fence> _fence;
|
||||
uint64_t _fenceValue = 0;
|
||||
wil::unique_event_nothrow _fenceEvent;
|
||||
};
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,8 @@
|
|||
#include "StepTimer.h"
|
||||
#include "EffectsProfiler.h"
|
||||
#include "ScalingError.h"
|
||||
#include "PresenterBase.h"
|
||||
#include "OverlayDrawer.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -20,17 +22,19 @@ public:
|
|||
Renderer(const Renderer&) = delete;
|
||||
Renderer(Renderer&&) = delete;
|
||||
|
||||
ScalingError Initialize() noexcept;
|
||||
ScalingError Initialize(HWND hwndAttach, OverlayOptions& overlayOptions) noexcept;
|
||||
|
||||
bool Render() noexcept;
|
||||
bool Render(bool force = false, bool waitForRenderComplete = false) noexcept;
|
||||
|
||||
bool IsOverlayVisible() noexcept;
|
||||
bool OnResize() noexcept;
|
||||
|
||||
void SetOverlayVisibility(bool value, bool noSetForeground = false) noexcept;
|
||||
void OnEndResize() noexcept;
|
||||
|
||||
const RECT& SrcRect() const noexcept {
|
||||
return _srcRect;
|
||||
}
|
||||
void OnMove() noexcept;
|
||||
|
||||
void ToggleToolbarState() noexcept;
|
||||
|
||||
const RECT& SrcRect() const noexcept;
|
||||
|
||||
// 屏幕坐标而不是窗口局部坐标
|
||||
const RECT& DestRect() const noexcept {
|
||||
|
|
@ -45,53 +49,71 @@ public:
|
|||
|
||||
void MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
|
||||
struct EffectInfo {
|
||||
std::string name;
|
||||
std::vector<std::string> passNames;
|
||||
bool isFP16 = false;
|
||||
};
|
||||
const std::vector<EffectInfo>& EffectInfos() const noexcept {
|
||||
return _effectInfos;
|
||||
const std::vector<const EffectDesc*>& ActiveEffectDescs() const noexcept {
|
||||
return _activeEffectDescs;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _CreateSwapChain() noexcept;
|
||||
void StartProfile() noexcept;
|
||||
|
||||
void _FrontendRender() noexcept;
|
||||
void StopProfile() noexcept;
|
||||
|
||||
bool IsCursorOnOverlayCaptionArea() const noexcept {
|
||||
return _overlayDrawer.IsCursorOnCaptionArea();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TakeScreenshot(
|
||||
uint32_t effectIdx,
|
||||
uint32_t passIdx = std::numeric_limits<uint32_t>::max(),
|
||||
uint32_t outputIdx = std::numeric_limits<uint32_t>::max()
|
||||
) noexcept;
|
||||
|
||||
private:
|
||||
void _FrontendRender(bool waitForRenderComplete = false) noexcept;
|
||||
|
||||
void _BackendThreadProc() noexcept;
|
||||
|
||||
ID3D11Texture2D* _InitBackend() noexcept;
|
||||
HANDLE _InitBackend() noexcept;
|
||||
|
||||
bool _InitFrameSource() noexcept;
|
||||
|
||||
ID3D11Texture2D* _BuildEffects() noexcept;
|
||||
|
||||
void _UpdateActiveEffectDescs() noexcept;
|
||||
|
||||
bool _ShouldAppendBicubic(ID3D11Texture2D* outTexture) noexcept;
|
||||
|
||||
bool _AppendBicubic(ID3D11Texture2D** inOutTexture) noexcept;
|
||||
|
||||
ID3D11Texture2D* _ResizeEffects() noexcept;
|
||||
|
||||
void _UpdateDestRect() noexcept;
|
||||
|
||||
HANDLE _CreateSharedTexture(ID3D11Texture2D* effectsOutput) noexcept;
|
||||
|
||||
void _BackendRender(ID3D11Texture2D* effectsOutput) noexcept;
|
||||
|
||||
bool _UpdateDynamicConstants() const noexcept;
|
||||
|
||||
winrt::IAsyncAction _UpdateNextScreenshotNum(const wchar_t* imgFormat) noexcept;
|
||||
|
||||
winrt::IAsyncOperation<bool> _TakeScreenshotImpl(
|
||||
uint32_t effectIdx,
|
||||
uint32_t passIdx,
|
||||
uint32_t outputIdx
|
||||
) noexcept;
|
||||
|
||||
static LRESULT CALLBACK _LowLevelKeyboardHook(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
// 只能由前台线程访问
|
||||
DeviceResources _frontendResources;
|
||||
winrt::com_ptr<IDXGISwapChain4> _swapChain;
|
||||
wil::unique_event_nothrow _frameLatencyWaitableObject;
|
||||
winrt::com_ptr<ID3D11Texture2D> _backBuffer;
|
||||
winrt::com_ptr<ID3D11RenderTargetView> _backBufferRtv;
|
||||
uint64_t _lastAccessMutexKey = 0;
|
||||
|
||||
std::unique_ptr<PresenterBase> _presenter;
|
||||
|
||||
CursorDrawer _cursorDrawer;
|
||||
std::unique_ptr<class OverlayDrawer> _overlayDrawer;
|
||||
|
||||
HCURSOR _lastCursorHandle = NULL;
|
||||
POINT _lastCursorPos{ std::numeric_limits<LONG>::max(), std::numeric_limits<LONG>::max() };
|
||||
uint32_t _lastFPS = std::numeric_limits<uint32_t>::max();
|
||||
OverlayDrawer _overlayDrawer;
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> _frontendSharedTexture;
|
||||
winrt::com_ptr<IDXGIKeyedMutex> _frontendSharedTextureMutex;
|
||||
uint64_t _lastAccessMutexKey = 0;
|
||||
RECT _destRect{};
|
||||
|
||||
std::thread _backendThread;
|
||||
|
|
@ -116,19 +138,19 @@ private:
|
|||
|
||||
winrt::com_ptr<ID3D11Buffer> _dynamicCB;
|
||||
|
||||
uint32_t _screenshotNum = 0;
|
||||
|
||||
// 可由所有线程访问
|
||||
std::atomic<uint64_t> _sharedTextureMutexKey = 0;
|
||||
|
||||
// INVALID_HANDLE_VALUE 表示后端初始化失败
|
||||
std::atomic<HANDLE> _sharedTextureHandle{ NULL };
|
||||
// 下面三个成员由 _sharedTextureHandle 同步
|
||||
// 下面四个成员由 _sharedTextureHandle 同步
|
||||
winrt::Windows::System::DispatcherQueue _backendThreadDispatcher{ nullptr };
|
||||
RECT _srcRect{};
|
||||
ScalingError _backendInitError = ScalingError::NoError;
|
||||
|
||||
// 供游戏内叠加层使用
|
||||
// 由于要跨线程访问,初始化之后不能更改
|
||||
std::vector<EffectInfo> _effectInfos;
|
||||
std::vector<EffectDesc> _effectDescs;
|
||||
// 包含追加的 Bicubic
|
||||
std::vector<const EffectDesc*> _activeEffectDescs;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
static std::string LogParameters(const phmap::flat_hash_map<std::wstring, float>& params) noexcept {
|
||||
static std::string LogParameters(const phmap::flat_hash_map<std::string, float>& params) noexcept {
|
||||
std::string result;
|
||||
|
||||
if (params.empty()) {
|
||||
result = "无";
|
||||
} else {
|
||||
for (const auto& pair : params) {
|
||||
result.append(fmt::format("\n\t\t\t\t{}: {}", StrHelper::UTF16ToUTF8(pair.first), pair.second));
|
||||
result.append(fmt::format("\n\t\t\t\t{}: {}", pair.first, pair.second));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ static std::string LogEffects(const std::vector<EffectOption>& effects) noexcept
|
|||
scalingType: {}
|
||||
scale: {},{}
|
||||
parameters: {})",
|
||||
StrHelper::UTF16ToUTF8(effect.name),
|
||||
effect.name,
|
||||
(int)effect.scalingType,
|
||||
effect.scale.first, effect.scale.second,
|
||||
LogParameters(effect.parameters)
|
||||
|
|
@ -36,8 +36,44 @@ 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: {}
|
||||
IsFP16Disabled: {}
|
||||
|
|
@ -51,11 +87,8 @@ void ScalingOptions::Log() const noexcept {
|
|||
IsAllowScalingMaximized: {}
|
||||
IsSimulateExclusiveFullscreen: {}
|
||||
Is3DGameMode: {}
|
||||
IsShowFPS: {}
|
||||
IsWindowResizingDisabled: {}
|
||||
IsCaptureTitleBar: {}
|
||||
IsAdjustCursorSpeed: {}
|
||||
IsDrawCursor: {}
|
||||
IsDirectFlipDisabled: {}
|
||||
cropping: {},{},{},{}
|
||||
graphicsCardId:
|
||||
|
|
@ -69,7 +102,10 @@ void ScalingOptions::Log() const noexcept {
|
|||
multiMonitorUsage: {}
|
||||
cursorInterpolationMode: {}
|
||||
duplicateFrameDetectionMode: {}
|
||||
initialToolbarState: {}
|
||||
screenshotsDir: {}
|
||||
effects: {})",
|
||||
IsWindowedMode(),
|
||||
IsDebugMode(),
|
||||
IsBenchmarkMode(),
|
||||
IsFP16Disabled(),
|
||||
|
|
@ -83,11 +119,8 @@ void ScalingOptions::Log() const noexcept {
|
|||
IsAllowScalingMaximized(),
|
||||
IsSimulateExclusiveFullscreen(),
|
||||
Is3DGameMode(),
|
||||
IsShowFPS(),
|
||||
IsWindowResizingDisabled(),
|
||||
IsCaptureTitleBar(),
|
||||
IsAdjustCursorSpeed(),
|
||||
IsDrawCursor(),
|
||||
IsDirectFlipDisabled(),
|
||||
cropping.Left, cropping.Top, cropping.Right, cropping.Bottom,
|
||||
graphicsCardId.idx,
|
||||
|
|
@ -100,6 +133,8 @@ void ScalingOptions::Log() const noexcept {
|
|||
(int)multiMonitorUsage,
|
||||
(int)cursorInterpolationMode,
|
||||
(int)duplicateFrameDetectionMode,
|
||||
(int)initialToolbarState,
|
||||
StrHelper::UTF16ToUTF8(screenshotsDir.native()),
|
||||
LogEffects(effects)
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
#include "pch.h"
|
||||
#include "ScalingRuntime.h"
|
||||
#include <dispatcherqueue.h>
|
||||
#include "Logger.h"
|
||||
#include "ScalingWindow.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <dispatcherqueue.h>
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
ScalingRuntime::ScalingRuntime() :
|
||||
_scalingThread(std::bind_front(&ScalingRuntime::_ScalingThreadProc, this)) {
|
||||
ScalingRuntime::ScalingRuntime() : _scalingThread(&ScalingRuntime::_ScalingThreadProc, this) {
|
||||
}
|
||||
|
||||
ScalingRuntime::~ScalingRuntime() {
|
||||
|
|
@ -47,17 +50,23 @@ ScalingRuntime::~ScalingRuntime() {
|
|||
}
|
||||
}
|
||||
|
||||
void ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
|
||||
bool ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
|
||||
if (!options.Prepare()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_State expected = _State::Idle;
|
||||
if (!_state.compare_exchange_strong(
|
||||
expected, _State::Initializing, std::memory_order_relaxed)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
IsRunningChanged.Invoke(true, ScalingError::NoError);
|
||||
|
||||
_Dispatcher().TryEnqueue([this, dispatcher(_Dispatcher()), hwndSrc, options(std::move(options))]() mutable {
|
||||
ScalingError error = ScalingWindow::Get().Create(dispatcher, hwndSrc, std::move(options));
|
||||
_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 {
|
||||
|
|
@ -66,16 +75,18 @@ void ScalingRuntime::Start(HWND hwndSrc, ScalingOptions&& options) {
|
|||
IsRunningChanged.Invoke(false, error);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScalingRuntime::ToggleOverlay() {
|
||||
void ScalingRuntime::ToggleToolbarState() {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_Dispatcher().TryEnqueue([]() {
|
||||
if (ScalingWindow& scalingWindow = ScalingWindow::Get()) {
|
||||
scalingWindow.ToggleOverlay();
|
||||
scalingWindow.ToggleToolbarState();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
@ -112,9 +123,7 @@ static int GetSrcRepositionState(HWND hwndSrc, bool allowScalingMaximized) noexc
|
|||
}
|
||||
|
||||
// 检查源窗口是否正在调整大小或移动
|
||||
GUITHREADINFO guiThreadInfo{
|
||||
.cbSize = sizeof(GUITHREADINFO)
|
||||
};
|
||||
GUITHREADINFO guiThreadInfo{ .cbSize = sizeof(GUITHREADINFO) };
|
||||
if (!GetGUIThreadInfo(GetWindowThreadProcessId(hwndSrc, nullptr), &guiThreadInfo)) {
|
||||
Logger::Get().Win32Error("GetGUIThreadInfo 失败");
|
||||
return -1;
|
||||
|
|
@ -125,7 +134,7 @@ static int GetSrcRepositionState(HWND hwndSrc, bool allowScalingMaximized) noexc
|
|||
|
||||
void ScalingRuntime::_ScalingThreadProc() noexcept {
|
||||
#ifdef _DEBUG
|
||||
SetThreadDescription(GetCurrentThread(), L"Magpie 缩放线程");
|
||||
SetThreadDescription(GetCurrentThread(), L"Magpie-缩放线程");
|
||||
#endif
|
||||
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
|
|
@ -151,6 +160,10 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
}
|
||||
|
||||
ScalingWindow& scalingWindow = ScalingWindow::Get();
|
||||
ScalingWindow::Dispatcher(_dispatcher);
|
||||
|
||||
time_point<steady_clock> lastRenderTime;
|
||||
const milliseconds timeout(scalingWindow.Options().Is3DGameMode() ? 8 : 2);
|
||||
|
||||
MSG msg;
|
||||
while (true) {
|
||||
|
|
@ -163,6 +176,9 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
}
|
||||
|
||||
return;
|
||||
} else if (msg.message == CommonSharedConstants::WM_FRONTEND_RENDER && msg.hwnd == scalingWindow.Handle()) {
|
||||
// 缩放窗口收到 WM_FRONTEND_RENDER 将执行渲染
|
||||
lastRenderTime = steady_clock::now();
|
||||
}
|
||||
|
||||
DispatchMessage(&msg);
|
||||
|
|
@ -174,11 +190,23 @@ void ScalingRuntime::_ScalingThreadProc() noexcept {
|
|||
}
|
||||
|
||||
if (scalingWindow) {
|
||||
scalingWindow.Render();
|
||||
MsgWaitForMultipleObjectsEx(0, nullptr, 1, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||
const auto now = steady_clock::now();
|
||||
// 限制检测光标移动的频率
|
||||
nanoseconds rest = timeout - (now - lastRenderTime);
|
||||
if (rest.count() <= 0) {
|
||||
lastRenderTime = now;
|
||||
rest = timeout;
|
||||
scalingWindow.Render();
|
||||
}
|
||||
|
||||
// 值为 1000000
|
||||
constexpr auto ratio = std::ratio_divide<std::milli, std::nano>().num;
|
||||
// 向上取整
|
||||
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.HwndSrc(),
|
||||
scalingWindow.SrcTracker().Handle(),
|
||||
scalingWindow.Options().IsAllowScalingMaximized()
|
||||
);
|
||||
if (state == 0) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
#include "WindowBase.h"
|
||||
#include "ScalingOptions.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "ScalingError.h"
|
||||
#include "SrcTracker.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -18,26 +18,35 @@ public:
|
|||
return instance;
|
||||
}
|
||||
|
||||
ScalingError Create(
|
||||
const winrt::DispatcherQueue& dispatcher,
|
||||
HWND hwndSrc,
|
||||
ScalingOptions&& options
|
||||
) noexcept;
|
||||
// 用于检查当前缩放是否结束
|
||||
static uint32_t RunId() noexcept {
|
||||
return _runId.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static void Dispatcher(const winrt::DispatcherQueue& value) noexcept {
|
||||
_dispatcher = value;
|
||||
}
|
||||
|
||||
static const winrt::DispatcherQueue& Dispatcher() noexcept {
|
||||
return _dispatcher;
|
||||
}
|
||||
|
||||
ScalingError Create(HWND hwndSrc, ScalingOptions options) noexcept;
|
||||
|
||||
void Render() noexcept;
|
||||
|
||||
void ToggleOverlay() noexcept;
|
||||
void ToggleToolbarState() noexcept;
|
||||
|
||||
const RECT& WndRect() const noexcept {
|
||||
return _wndRect;
|
||||
const RECT& RendererRect() const noexcept {
|
||||
return _rendererRect;
|
||||
}
|
||||
|
||||
const ScalingOptions& Options() const noexcept {
|
||||
return _options;
|
||||
}
|
||||
|
||||
HWND HwndSrc() const noexcept {
|
||||
return _hwndSrc;
|
||||
SrcTracker& SrcTracker() noexcept {
|
||||
return _srcTracker;
|
||||
}
|
||||
|
||||
class Renderer& Renderer() noexcept {
|
||||
|
|
@ -48,10 +57,6 @@ public:
|
|||
return *_cursorManager;
|
||||
}
|
||||
|
||||
const winrt::DispatcherQueue& Dispatcher() const noexcept {
|
||||
return _dispatcher;
|
||||
}
|
||||
|
||||
bool IsSrcRepositioning() const noexcept {
|
||||
return _isSrcRepositioning;
|
||||
}
|
||||
|
|
@ -60,6 +65,12 @@ public:
|
|||
|
||||
void CleanAfterSrcRepositioned() noexcept;
|
||||
|
||||
bool IsResizingOrMoving() const noexcept {
|
||||
return _isResizingOrMoving;
|
||||
}
|
||||
|
||||
winrt::hstring GetLocalizedString(std::wstring_view resName) const;
|
||||
|
||||
// 缩放过程中出现的错误
|
||||
ScalingError RuntimeError() const noexcept {
|
||||
return _runtimeError;
|
||||
|
|
@ -69,6 +80,10 @@ public:
|
|||
_runtimeError = value;
|
||||
}
|
||||
|
||||
void ShowToast(std::wstring_view msg) const noexcept {
|
||||
_options.showToast(Handle(), msg);
|
||||
}
|
||||
|
||||
protected:
|
||||
LRESULT _MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
|
||||
|
|
@ -76,38 +91,99 @@ private:
|
|||
ScalingWindow() noexcept;
|
||||
~ScalingWindow() noexcept;
|
||||
|
||||
int _CheckSrcState() const noexcept;
|
||||
// 确保渲染窗口长宽比不变,且限制最小和最大尺寸。必须提供 width 和 height 之一,另一个
|
||||
// 应为 0。如果 isRendererSize 为真,传入的 width 和 height 为渲染矩形尺寸,否则为缩
|
||||
// 放窗口尺寸。返回时 width 和 height 是新的缩放窗口尺寸。
|
||||
bool _CalcWindowedScalingWindowSize(int& width, int& height, bool isRendererSize, uint32_t dpi = 0) const noexcept;
|
||||
|
||||
bool _CheckForeground(HWND hwndForeground) const noexcept;
|
||||
RECT _CalcWindowedRendererRect() const noexcept;
|
||||
|
||||
bool _DisableDirectFlip() noexcept;
|
||||
ScalingError _CalcFullscreenRendererRect(uint32_t& monitorCount) noexcept;
|
||||
|
||||
SIZE _AdjustFullscreenWindowSize(SIZE size, uint32_t dpi = 0) const noexcept;
|
||||
|
||||
ScalingError _InitialMoveSrcWindowInFullscreen() noexcept;
|
||||
|
||||
void _Show() noexcept;
|
||||
|
||||
bool _UpdateSrcState() noexcept;
|
||||
|
||||
bool _CheckForegroundFor3DGameMode(HWND hwndFore) const noexcept;
|
||||
|
||||
void _SetWindowProps() const noexcept;
|
||||
|
||||
void _UpdateWindowProps() const noexcept;
|
||||
|
||||
void _UpdateTouchProps(const RECT& srcRect) const noexcept;
|
||||
|
||||
void _RemoveWindowProps() const noexcept;
|
||||
|
||||
void _CreateTouchHoleWindows() noexcept;
|
||||
static LRESULT CALLBACK _RendererWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
winrt::DispatcherQueue _dispatcher{ nullptr };
|
||||
void _ResizeRenderer() noexcept;
|
||||
|
||||
RECT _wndRect{};
|
||||
void _MoveRenderer() noexcept;
|
||||
|
||||
static LRESULT CALLBACK _BorderHelperWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
void _CreateBorderHelperWindows() noexcept;
|
||||
|
||||
void _RepostionBorderHelperWindows() noexcept;
|
||||
|
||||
RECT _CalcSrcTouchRect() const noexcept;
|
||||
|
||||
void _UpdateTouchHoleWindows(bool onInit) noexcept;
|
||||
|
||||
void _UpdateFrameMargins() const noexcept;
|
||||
|
||||
winrt::fire_and_forget _UpdateFocusStateAsync(bool onShow = false) const noexcept;
|
||||
|
||||
bool _IsBorderless() const noexcept;
|
||||
|
||||
void _UpdateRendererRect() noexcept;
|
||||
|
||||
bool _EnsureCaptionVisibleOnScreen() noexcept;
|
||||
|
||||
void _UpdateWindowRectFromWindowPos(const WINDOWPOS& windowPos) noexcept;
|
||||
|
||||
void _DelayedDestroy(bool onSrcHung = false) const noexcept;
|
||||
|
||||
static inline std::atomic<uint32_t> _runId = 0;
|
||||
static inline winrt::DispatcherQueue _dispatcher{ nullptr };
|
||||
|
||||
RECT _windowRect{};
|
||||
RECT _rendererRect{};
|
||||
HWND _hwndRenderer = NULL;
|
||||
|
||||
uint32_t _currentDpi = USER_DEFAULT_SCREEN_DPI;
|
||||
uint32_t _topBorderThicknessInClient = 0;
|
||||
// Win11 中“无边框”窗口的边框在客户区内
|
||||
uint32_t _nonTopBorderThicknessInClient = 0;
|
||||
|
||||
ScalingOptions _options;
|
||||
std::unique_ptr<class Renderer> _renderer;
|
||||
std::unique_ptr<class CursorManager> _cursorManager;
|
||||
|
||||
HWND _hwndSrc = NULL;
|
||||
RECT _srcWndRect{};
|
||||
class SrcTracker _srcTracker;
|
||||
|
||||
winrt::ResourceLoader _resourceLoader{ nullptr };
|
||||
|
||||
wil::unique_hwnd _hwndDDF;
|
||||
wil::unique_mutex_nothrow _exclModeMutex;
|
||||
|
||||
std::array<wil::unique_hwnd, 4> _hwndResizeHelpers{};
|
||||
std::array<wil::unique_hwnd, 4> _hwndTouchHoles{};
|
||||
|
||||
ScalingError _runtimeError = ScalingError::NoError;
|
||||
|
||||
// 第一帧渲染完成后再显示
|
||||
bool _isFirstFrame = false;
|
||||
bool _isResizingOrMoving = false;
|
||||
// 用于区分调整大小和移动
|
||||
bool _isPreparingForResizing = false;
|
||||
bool _isMovingDueToSrcMoved = false;
|
||||
bool _shouldWaitForRender = false;
|
||||
bool _areResizeHelperWindowsVisible = false;
|
||||
bool _isSrcRepositioning = false;
|
||||
bool _isDDFWindowShown = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
94
src/Magpie.Core/ScreenshotHelper.cpp
Normal file
94
src/Magpie.Core/ScreenshotHelper.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#include "pch.h"
|
||||
#include "ScreenshotHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include <wincodec.h>
|
||||
#include <charconv>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static bool ExtractNumber(std::wstring_view fileName, std::string& numStr, uint32_t& curNum) noexcept {
|
||||
const size_t dotPos = fileName.find_last_of(L'.');
|
||||
if (dotPos == std::wstring_view::npos || dotPos < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// "Magpie_" 共 7 个字符
|
||||
size_t len = dotPos - 7;
|
||||
numStr.resize(len);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
wchar_t c = fileName[i + 7];
|
||||
if (c >= L'0' && c <= L'9') {
|
||||
numStr[i] = (char)c;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return std::from_chars(numStr.c_str(), numStr.c_str() + numStr.size(), curNum).ec == std::errc{};
|
||||
}
|
||||
|
||||
uint32_t ScreenshotHelper::FindUnusedScreenshotNum(const std::filesystem::path& screenshotsDir) noexcept {
|
||||
WIN32_FIND_DATA findData{};
|
||||
const std::wstring pattern = StrHelper::Concat(screenshotsDir.native(), L"\\Magpie_*");
|
||||
wil::unique_hfind hFind(FindFirstFileEx(
|
||||
pattern.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH));
|
||||
if (!hFind) {
|
||||
Logger::Get().Win32Error("FindFirstFileEx 失败");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 新截图应在所有现有截图之后,因此查找最大序号。如果最大序号是 UINT_MAX 则回落
|
||||
// 到查找最小的可用序号,不过这种数据除了特意构造不可能出现
|
||||
uint32_t result = 0;
|
||||
|
||||
std::string numStr;
|
||||
std::vector<uint32_t> nums;
|
||||
bool shouldFallback = false;
|
||||
do {
|
||||
uint32_t curNum;
|
||||
if (!ExtractNumber(findData.cFileName, numStr, curNum) || curNum == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nums.push_back(curNum);
|
||||
|
||||
if (shouldFallback) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (curNum == std::numeric_limits<uint32_t>::max()) {
|
||||
// 回落到查找最小的可用序号
|
||||
shouldFallback = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
result = std::max(result, curNum + 1);
|
||||
} while (FindNextFile(hFind.get(), &findData));
|
||||
|
||||
if (!shouldFallback) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 查找最小的可用序号
|
||||
std::sort(nums.begin(), nums.end());
|
||||
assert(nums.back() == std::numeric_limits<uint32_t>::max());
|
||||
|
||||
if (nums[0] != 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t size = nums.size();
|
||||
for (size_t i = 1; i < size; ++i) {
|
||||
result = nums[i - 1] + 1;
|
||||
if (nums[i] > result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// 不可能执行到这里
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
11
src/Magpie.Core/ScreenshotHelper.h
Normal file
11
src/Magpie.Core/ScreenshotHelper.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct ScreenshotHelper {
|
||||
// 失败则返回 0
|
||||
static uint32_t FindUnusedScreenshotNum(const std::filesystem::path& screenshotsDir) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
472
src/Magpie.Core/SrcTracker.cpp
Normal file
472
src/Magpie.Core/SrcTracker.cpp
Normal file
|
|
@ -0,0 +1,472 @@
|
|||
#include "pch.h"
|
||||
#include "SrcTracker.h"
|
||||
#include "Win32Helper.h"
|
||||
#include "Logger.h"
|
||||
#ifdef _DEBUG
|
||||
#include "WindowHelper.h"
|
||||
#endif
|
||||
#include <dwmapi.h>
|
||||
#include "SmallVector.h"
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static bool GetWindowIntegrityLevel(HWND hWnd, DWORD& integrityLevel) noexcept {
|
||||
wil::unique_process_handle hProc = Win32Helper::GetWindowProcessHandle(hWnd);
|
||||
if (!hProc) {
|
||||
Logger::Get().Error("GetWindowProcessHandle 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::unique_handle hQueryToken;
|
||||
if (!OpenProcessToken(hProc.get(), TOKEN_QUERY, hQueryToken.put())) {
|
||||
Logger::Get().Win32Error("OpenProcessToken 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return Win32Helper::GetProcessIntegrityLevel(hQueryToken.get(), integrityLevel);
|
||||
}
|
||||
|
||||
static bool CheckIL(HWND hwndSrc) noexcept {
|
||||
static DWORD thisIL = []() -> DWORD {
|
||||
DWORD il;
|
||||
return Win32Helper::GetProcessIntegrityLevel(NULL, il) ? il : 0;
|
||||
}();
|
||||
|
||||
DWORD windowIL;
|
||||
return GetWindowIntegrityLevel(hwndSrc, windowIL) && windowIL <= thisIL;
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::Set(HWND hWnd, const ScalingOptions& options) noexcept {
|
||||
_hWnd = hWnd;
|
||||
|
||||
// 这里不检查源窗口是否挂起,将在创建缩放窗口前检查
|
||||
|
||||
if (!IsWindow(_hWnd)) {
|
||||
Logger::Get().Error("源窗口句柄非法");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
if (!IsWindowVisible(_hWnd)) {
|
||||
Logger::Get().Error("不支持缩放隐藏的窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
if (Win32Helper::GetWindowClassName(hWnd) == L"Ghost") {
|
||||
Logger::Get().Error("不支持缩放幽灵窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
if (!CheckIL(hWnd)) {
|
||||
Logger::Get().Error("不支持缩放 IL 更高的窗口");
|
||||
return ScalingError::LowIntegrityLevel;
|
||||
}
|
||||
|
||||
// 已在 ScalingService 中阻止
|
||||
assert(!WindowHelper::IsForbiddenSystemWindow(hWnd));
|
||||
|
||||
if (GetWindowLongPtr(hWnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT) {
|
||||
Logger::Get().Error("不支持缩放透明的窗口");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
const HMONITOR hMon = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL);
|
||||
if (!hMon) {
|
||||
Logger::Get().Error("源窗口不在任何屏幕上");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
_isFocused = GetForegroundWindow() == hWnd;
|
||||
|
||||
if (!GetWindowRect(hWnd, &_windowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
HRESULT hr = DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||
&_windowFrameRect, sizeof(_windowFrameRect));
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("DwmGetWindowAttribute 失败", hr);
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(hWnd, clientRect)) {
|
||||
Logger::Get().Win32Error("GetClientScreenRect 失败");
|
||||
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;
|
||||
hr = DwmGetWindowAttribute(hWnd, DWMWA_NCRENDERING_ENABLED, &hasBorder, sizeof(hasBorder));
|
||||
if (FAILED(hr) || !hasBorder) {
|
||||
// 凡是没有原生框架的窗口都视为 NoDecoration,这类窗口可能没有 WS_CAPTION 和 WS_THICKFRAME 样式,
|
||||
// 或者禁用了原生窗口框架以自绘标题栏和边框。
|
||||
_windowKind = SrcWindowKind::NoDecoration;
|
||||
} else {
|
||||
// 最大化窗口的上边框很可能存在非客户区,这使得 NoTitleBar 类型的窗口最大化时会被归类到 Native。
|
||||
// 技术上说这很合理,上边框处的非客户区当然可以视为标题栏,对后续计算也没有影响。
|
||||
if (_windowRect.top == clientRect.top) {
|
||||
if (_windowRect.left != clientRect.left && _windowRect.right != clientRect.right && _windowRect.bottom != clientRect.bottom) {
|
||||
// 如果左右下三边均存在边框,那么应视为存在上边框:
|
||||
// * Win10 中窗口很可能绘制了假的上边框,这是很常见的创建无边框窗口的方法
|
||||
// * Win11 中 DWM 会将上边框绘制到客户区
|
||||
_windowKind = SrcWindowKind::NoTitleBar;
|
||||
} else {
|
||||
// 一个窗口要么有边框,要么没有。只要左右下三边中有一条边没有边框,我们就将它视为无边框窗口。
|
||||
//
|
||||
// FIXME: 有的窗口(如微信)通过 WM_NCCALCSIZE 移除边框,但不使用 DwmExtendFrameIntoClientArea
|
||||
// 还原阴影,这种窗口实际上是 NoDecoration 类型。遗憾的是没有办法获知窗口是否调用了
|
||||
// DwmExtendFrameIntoClientArea,因此我们假设所有使用 WM_NCCALCSIZE 移除边框的窗口都有阴影,
|
||||
// 一方面有阴影的情况更多,比如基于 electron 的窗口,另一方面如果假设没有阴影会使得 Win11 中
|
||||
// 不能正确裁剪边框导致黑边,而如果假设有阴影,猜错的后果相对较轻。
|
||||
_windowKind = SrcWindowKind::NoBorder;
|
||||
}
|
||||
} else {
|
||||
const DWORD windowStyle = GetWindowStyle(hWnd);
|
||||
if (!(windowStyle & WS_CAPTION) && (windowStyle & WS_THICKFRAME)) {
|
||||
// 若无 WS_CAPTION 样式则系统边框仍存在但上边框较粗
|
||||
_windowKind = SrcWindowKind::OnlyThickFrame;
|
||||
} else {
|
||||
_windowKind = SrcWindowKind::Native;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// * 最大化窗口、NoDecoration 窗口和 Win10 中 NoBorder 窗口不存在边框
|
||||
LONG borderThicknessInFrame = 0;
|
||||
const bool isWin11 = Win32Helper::GetOSVersion().IsWin11();
|
||||
if (!_isMaximized &&
|
||||
_windowKind != SrcWindowKind::NoDecoration &&
|
||||
(_windowKind != SrcWindowKind::NoBorder || isWin11))
|
||||
{
|
||||
// 使用屏幕而非窗口的 DPI 来计算边框宽度
|
||||
UINT dpi = USER_DEFAULT_SCREEN_DPI;
|
||||
GetDpiForMonitor(hMon, MDT_EFFECTIVE_DPI, &dpi, &dpi);
|
||||
|
||||
borderThicknessInFrame = (LONG)Win32Helper::GetNativeWindowBorderThickness(dpi);
|
||||
}
|
||||
|
||||
return _CalcSrcRect(options, borderThicknessInFrame);
|
||||
}
|
||||
|
||||
static bool IsPrimaryMouseButtonDown() noexcept {
|
||||
const bool isSwapped = GetSystemMetrics(SM_SWAPBUTTON);
|
||||
const int vkPrimary = isSwapped ? VK_RBUTTON : VK_LBUTTON;
|
||||
return GetAsyncKeyState(vkPrimary) & 0x8000;
|
||||
}
|
||||
|
||||
bool SrcTracker::UpdateState(
|
||||
HWND hwndFore,
|
||||
bool isWindowedMode,
|
||||
bool isResizingOrMoving,
|
||||
bool& srcRectChanged,
|
||||
bool& srcSizeChanged,
|
||||
bool& srcMovingChanged
|
||||
) noexcept {
|
||||
assert(!srcRectChanged && !srcSizeChanged && !srcMovingChanged);
|
||||
|
||||
if (!IsWindow(_hWnd)) {
|
||||
Logger::Get().Error("源窗口已销毁");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWindowVisible(_hWnd)) {
|
||||
Logger::Get().Error("源窗口已隐藏");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Win32Helper::IsWindowHung 更准确,但它会向源窗口发送消息,比较耗时。
|
||||
// IsHungAppWindow 的另一个好处是它不如 Win32Helper::IsWindowHung 严
|
||||
// 格,因此即使源窗口挂起一段时间,只要用户不做额外的操作就不会结束缩放,
|
||||
// 直到源窗口被替换为幽灵窗口。
|
||||
if (IsHungAppWindow(_hWnd)) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
return false;
|
||||
}
|
||||
|
||||
_isFocused = hwndFore == _hWnd;
|
||||
|
||||
const bool oldMaximized = _isMaximized;
|
||||
UINT showCmd = Win32Helper::GetWindowShowCmd(_hWnd);
|
||||
if (showCmd == SW_SHOWMINIMIZED) {
|
||||
Logger::Get().Error("源窗口处于最小化状态");
|
||||
return false;
|
||||
}
|
||||
_isMaximized = showCmd == SW_SHOWMAXIMIZED;
|
||||
|
||||
RECT curWindowRect;
|
||||
if (!GetWindowRect(_hWnd, &curWindowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
srcSizeChanged = oldMaximized != _isMaximized ||
|
||||
Win32Helper::GetSizeOfRect(curWindowRect) != Win32Helper::GetSizeOfRect(_windowRect);
|
||||
|
||||
// 缩放窗口正在调整大小或被拖动时源窗口的移动是异步的,暂时不检查源窗口是否移动
|
||||
if (isResizingOrMoving) {
|
||||
srcRectChanged = oldMaximized != _isMaximized;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 最大化状态改变视为尺寸发生变化
|
||||
srcRectChanged = 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) {
|
||||
const LONG offsetX = curWindowRect.left - _windowRect.left;
|
||||
const LONG offsetY = curWindowRect.top - _windowRect.top;
|
||||
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_srcRect, offsetX, offsetY);
|
||||
}
|
||||
|
||||
if (_isMoving != isMoving) {
|
||||
srcMovingChanged = true;
|
||||
_isMoving = isMoving;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcRectChanged) {
|
||||
_windowRect = curWindowRect;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SrcTracker::Move(int offsetX, int offsetY, bool async) noexcept {
|
||||
assert(!_isMaximized);
|
||||
|
||||
if (offsetX == 0 && offsetY == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!async) {
|
||||
// 同步移动
|
||||
Win32Helper::OffsetRect(_windowRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_srcRect, offsetX, offsetY);
|
||||
return MoveOnEndResizeMove();
|
||||
}
|
||||
|
||||
// 异步移动源窗口
|
||||
if (!SetWindowPos(
|
||||
_hWnd,
|
||||
NULL,
|
||||
_windowRect.left + offsetX,
|
||||
_windowRect.top + offsetY,
|
||||
0,
|
||||
0,
|
||||
SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER | SWP_ASYNCWINDOWPOS
|
||||
)) {
|
||||
Logger::Get().Win32Error("SetWindowPos 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 暂时设为理想值,缩放窗口调整大小或移动结束后将在 MoveOnEndResizeMove
|
||||
// 更新为实际位置。
|
||||
Win32Helper::OffsetRect(_windowRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_srcRect, offsetX, offsetY);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SrcTracker::MoveOnEndResizeMove() noexcept {
|
||||
assert(!_isMaximized);
|
||||
|
||||
if (Win32Helper::IsWindowHung(_hWnd)) {
|
||||
Logger::Get().Error("源窗口已挂起");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 同步移动源窗口,这确保所有异步操作都已完成
|
||||
if (!SetWindowPos(
|
||||
_hWnd,
|
||||
NULL,
|
||||
_windowRect.left,
|
||||
_windowRect.top,
|
||||
0,
|
||||
0,
|
||||
SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOSIZE | SWP_NOZORDER
|
||||
)) {
|
||||
Logger::Get().Win32Error("SetWindowPos 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 需要重新检索窗口矩形,因为 SetWindowPos 不保证准确设置。常见的情况是源窗口
|
||||
// 被 DPI 虚拟化时经常有轻微偏移,此外技术上说源窗口可以在 WM_WINDOWPOSCHANGING
|
||||
// 中随意改变尺寸和位置。
|
||||
const RECT oldWindowRect = _windowRect;
|
||||
|
||||
if (!GetWindowRect(_hWnd, &_windowRect)) {
|
||||
Logger::Get().Win32Error("GetWindowRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Win32Helper::GetSizeOfRect(oldWindowRect) != Win32Helper::GetSizeOfRect(_windowRect)) {
|
||||
Logger::Get().Error("源窗口意外出现尺寸变化");
|
||||
return false;
|
||||
}
|
||||
|
||||
const int offsetX = _windowRect.left - oldWindowRect.left;
|
||||
const int offsetY = _windowRect.top - oldWindowRect.top;
|
||||
if (offsetX != 0 || offsetY != 0) {
|
||||
Win32Helper::OffsetRect(_windowFrameRect, offsetX, offsetY);
|
||||
Win32Helper::OffsetRect(_srcRect, offsetX, offsetY);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static HWND FindClientWindowOfUWP(HWND hwndSrc, const wchar_t* clientWndClassName) noexcept {
|
||||
// 查找所有窗口类名为 ApplicationFrameInputSinkWindow 的子窗口,
|
||||
// 该窗口一般为客户区。
|
||||
struct EnumData {
|
||||
const wchar_t* clientWndClassName;
|
||||
SmallVector<HWND, 1> childWindows;
|
||||
} data{ clientWndClassName };
|
||||
|
||||
static const auto enumChildProc = [](HWND hWnd, LPARAM lParam) {
|
||||
std::wstring className = Win32Helper::GetWindowClassName(hWnd);
|
||||
|
||||
EnumData& data = *(EnumData*)lParam;
|
||||
if (className == data.clientWndClassName) {
|
||||
data.childWindows.push_back(hWnd);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
};
|
||||
|
||||
EnumChildWindows(hwndSrc, enumChildProc, (LPARAM)&data);
|
||||
|
||||
if (data.childWindows.empty()) {
|
||||
// 未找到符合条件的子窗口
|
||||
return hwndSrc;
|
||||
}
|
||||
|
||||
if (data.childWindows.size() == 1) {
|
||||
return data.childWindows[0];
|
||||
}
|
||||
|
||||
// 如果有多个匹配的子窗口,取最大的(一般不会出现)
|
||||
int maxSize = 0, maxIdx = 0;
|
||||
for (int i = 0; i < data.childWindows.size(); ++i) {
|
||||
RECT rect;
|
||||
if (!GetClientRect(data.childWindows[i], &rect)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int size = rect.right - rect.left + rect.bottom - rect.top;
|
||||
if (size > maxSize) {
|
||||
maxSize = size;
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
return data.childWindows[maxIdx];
|
||||
}
|
||||
|
||||
static bool GetClientRectOfUWP(HWND hWnd, RECT& rect) noexcept {
|
||||
std::wstring className = Win32Helper::GetWindowClassName(hWnd);
|
||||
if (className != L"ApplicationFrameWindow" && className != L"Windows.UI.Core.CoreWindow") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 客户区窗口类名为 ApplicationFrameInputSinkWindow
|
||||
HWND hwndClient = FindClientWindowOfUWP(hWnd, L"ApplicationFrameInputSinkWindow");
|
||||
if (!hwndClient) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Win32Helper::GetClientScreenRect(hwndClient, rect)) {
|
||||
Logger::Get().Win32Error("GetClientScreenRect 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ScalingError SrcTracker::_CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept {
|
||||
if (_windowKind == SrcWindowKind::NoDecoration) {
|
||||
// NoDecoration 类型的窗口不裁剪非客户区。它们要么没有非客户区,要么非客户区不是由
|
||||
// DWM 绘制,前者无需裁剪,后者不能裁剪。
|
||||
_srcRect = _windowRect;
|
||||
} else {
|
||||
// UWP 窗口都是 NoTitleBar 类型,但可能使用子窗口作为“客户区”
|
||||
if (_windowKind == SrcWindowKind::NoTitleBar && !options.IsCaptureTitleBar() && GetClientRectOfUWP(_hWnd, _srcRect)) {
|
||||
_srcRect.top = std::max(_srcRect.top, _windowFrameRect.top + borderThicknessInFrame);
|
||||
} else {
|
||||
_srcRect.left = _windowFrameRect.left + borderThicknessInFrame;
|
||||
_srcRect.top = _windowFrameRect.top + borderThicknessInFrame;
|
||||
_srcRect.right = _windowFrameRect.right - borderThicknessInFrame;
|
||||
_srcRect.bottom = _windowFrameRect.bottom - borderThicknessInFrame;
|
||||
|
||||
if (!options.IsCaptureTitleBar() || _windowKind == SrcWindowKind::OnlyThickFrame) {
|
||||
RECT clientRect;
|
||||
if (!Win32Helper::GetClientScreenRect(_hWnd, clientRect)) {
|
||||
Logger::Get().Error("GetClientScreenRect 失败");
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
_srcRect.top = std::max(_srcRect.top, clientRect.top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_isMaximized) {
|
||||
// 最大化的窗口只有屏幕内是有效区域
|
||||
HMONITOR hMon = MonitorFromWindow(_hWnd, MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi{ .cbSize = sizeof(mi) };
|
||||
if (!GetMonitorInfo(hMon, &mi)) {
|
||||
Logger::Get().Win32Error("GetMonitorInfo 失败");
|
||||
return ScalingError::ScalingFailedGeneral;
|
||||
}
|
||||
|
||||
Win32Helper::IntersectRect(_srcRect, _srcRect, mi.rcMonitor);
|
||||
}
|
||||
|
||||
static constexpr int MIN_SRC_SIZE = 64;
|
||||
|
||||
if (_srcRect.right - _srcRect.left < MIN_SRC_SIZE || _srcRect.bottom - _srcRect.top < MIN_SRC_SIZE) {
|
||||
Logger::Get().Error("源窗口太小");
|
||||
return ScalingError::InvalidSourceWindow;
|
||||
}
|
||||
|
||||
_srcRect = {
|
||||
std::lround(_srcRect.left + options.cropping.Left),
|
||||
std::lround(_srcRect.top + options.cropping.Top),
|
||||
std::lround(_srcRect.right - options.cropping.Right),
|
||||
std::lround(_srcRect.bottom - options.cropping.Bottom)
|
||||
};
|
||||
|
||||
if (_srcRect.right - _srcRect.left < MIN_SRC_SIZE || _srcRect.bottom - _srcRect.top < MIN_SRC_SIZE) {
|
||||
Logger::Get().Error("裁剪窗口失败");
|
||||
return ScalingError::InvalidCropping;
|
||||
}
|
||||
|
||||
return ScalingError::NoError;
|
||||
}
|
||||
|
||||
}
|
||||
94
src/Magpie.Core/SrcTracker.h
Normal file
94
src/Magpie.Core/SrcTracker.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
#include "ScalingOptions.h"
|
||||
#include "ScalingError.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
// 详细信息参见 https://github.com/Blinue/Magpie/pull/1071#issuecomment-2668398139
|
||||
enum class SrcWindowKind {
|
||||
// 系统标题栏和边框,有阴影,左右下三边在窗口外调整大小,Win11 中有圆角
|
||||
Native,
|
||||
// 无标题栏,系统边框(上边框可能自绘),有阴影,左右下三边在窗口外调整大
|
||||
// 小,Win11 中有圆角
|
||||
NoTitleBar,
|
||||
// 无标题栏,是否有边框取决于 OS 版本(Win10 中不存在边框,Win11 中边框
|
||||
// 被绘制到客户区内),有阴影,在窗口内调整大小,Win11 中有圆角
|
||||
NoBorder,
|
||||
// 无标题栏、边框和阴影,在窗口内调整大小,Win11 中无圆角
|
||||
NoDecoration,
|
||||
// 无标题栏,系统边框但上边框较粗,有阴影,左右下三边在窗口外调整大小,Win11 中有圆角
|
||||
OnlyThickFrame
|
||||
};
|
||||
|
||||
class SrcTracker {
|
||||
public:
|
||||
SrcTracker() = default;
|
||||
|
||||
// 防止意外复制
|
||||
SrcTracker(const SrcTracker&) = delete;
|
||||
SrcTracker(SrcTracker&&) = delete;
|
||||
|
||||
ScalingError Set(HWND hWnd, const ScalingOptions& options) noexcept;
|
||||
|
||||
bool UpdateState(
|
||||
HWND hwndFore,
|
||||
bool isWindowedMode,
|
||||
bool isResizingOrMoving,
|
||||
bool& srcRectChanged,
|
||||
bool& srcSizeChanged,
|
||||
bool& srcMovingChanged
|
||||
) noexcept;
|
||||
|
||||
bool Move(int offsetX, int offsetY, bool async) noexcept;
|
||||
|
||||
bool MoveOnEndResizeMove() noexcept;
|
||||
|
||||
HWND Handle() const noexcept {
|
||||
return _hWnd;
|
||||
}
|
||||
|
||||
const RECT& WindowRect() const noexcept {
|
||||
return _windowRect;
|
||||
}
|
||||
|
||||
const RECT& WindowFrameRect() const noexcept {
|
||||
return _windowFrameRect;
|
||||
}
|
||||
|
||||
const RECT& SrcRect() const noexcept {
|
||||
return _srcRect;
|
||||
}
|
||||
|
||||
bool IsFocused() const noexcept {
|
||||
return _isFocused;
|
||||
}
|
||||
|
||||
// IsMaximized 已定义为宏
|
||||
bool IsZoomed() const noexcept {
|
||||
return _isMaximized;
|
||||
}
|
||||
|
||||
SrcWindowKind WindowKind() const noexcept {
|
||||
return _windowKind;
|
||||
}
|
||||
|
||||
// 只在窗口模式缩放时有效
|
||||
bool IsMoving() const noexcept {
|
||||
return _isMoving;
|
||||
}
|
||||
|
||||
private:
|
||||
ScalingError _CalcSrcRect(const ScalingOptions& options, LONG borderThicknessInFrame) noexcept;
|
||||
|
||||
HWND _hWnd = NULL;
|
||||
RECT _windowRect{};
|
||||
RECT _windowFrameRect{};
|
||||
RECT _srcRect{};
|
||||
SrcWindowKind _windowKind = SrcWindowKind::Native;
|
||||
|
||||
bool _isFocused = false;
|
||||
bool _isMaximized = false;
|
||||
bool _isMoving = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -32,7 +32,9 @@ void StepTimer::Initialize(float minFrameRate, std::optional<float> maxFrameRate
|
|||
// ────────▼─────────┬────────┬──────▼─────────
|
||||
// wait │ capture │ render │ wait │ capture
|
||||
//
|
||||
StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame) noexcept {
|
||||
StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame, bool& fpsUpdated) noexcept {
|
||||
fpsUpdated = false;
|
||||
|
||||
// 不断更新 _nextFrameStartTime 直到新帧到达
|
||||
_nextFrameStartTime = steady_clock::now();
|
||||
|
||||
|
|
@ -53,7 +55,7 @@ StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame) noexcept {
|
|||
}
|
||||
|
||||
// 没有新帧也应更新 FPS。作为性能优化,强制帧无需更新,因为 PrepareForRender 必定会执行
|
||||
_UpdateFPS(_nextFrameStartTime);
|
||||
fpsUpdated = _UpdateFPS(_nextFrameStartTime);
|
||||
|
||||
if (delta < _minInterval) {
|
||||
_WaitForMsgAndTimer(_minInterval - delta);
|
||||
|
|
@ -65,8 +67,8 @@ StepTimerStatus StepTimer::WaitForNextFrame(bool waitMsgForNewFrame) noexcept {
|
|||
if (_HasMaxInterval()) {
|
||||
_WaitForMsgAndTimer(_maxInterval - delta);
|
||||
} else {
|
||||
// 没有最小帧率限制则只需等待消息
|
||||
WaitMessage();
|
||||
// 没有最小帧率限制则只需等待消息。为了及时更新 FPS,每次等待 500ms
|
||||
MsgWaitForMultipleObjectsEx(0, nullptr, 500, QS_ALLINPUT, MWMO_INPUTAVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,21 +114,25 @@ void StepTimer::_WaitForMsgAndTimer(std::chrono::nanoseconds time) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
void StepTimer::_UpdateFPS(time_point<steady_clock> now) noexcept {
|
||||
// FPS 更新时返回 true
|
||||
bool StepTimer::_UpdateFPS(time_point<steady_clock> now) noexcept {
|
||||
if (_lastSecondTime == time_point<steady_clock>{}) {
|
||||
// 第一帧
|
||||
_lastSecondTime = now;
|
||||
} else {
|
||||
const nanoseconds delta = now - _lastSecondTime;
|
||||
if (delta < 1s) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastSecondTime = now - delta % 1s;
|
||||
}
|
||||
|
||||
_framesPerSecond.store(_framesThisSecond, std::memory_order_relaxed);
|
||||
const uint32_t oldFPS = _framesPerSecond.exchange(_framesThisSecond, std::memory_order_relaxed);
|
||||
const bool changed = oldFPS != _framesThisSecond;
|
||||
_framesThisSecond = 0;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool StepTimer::_HasMinInterval() const noexcept {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public:
|
|||
|
||||
void Initialize(float minFrameRate, std::optional<float> maxFrameRate) noexcept;
|
||||
|
||||
StepTimerStatus WaitForNextFrame(bool waitMsgForNewFrame) noexcept;
|
||||
StepTimerStatus WaitForNextFrame(bool waitMsgForNewFrame, bool& fpsUpdated) noexcept;
|
||||
|
||||
void PrepareForRender() noexcept;
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ private:
|
|||
|
||||
void _WaitForMsgAndTimer(std::chrono::nanoseconds time) noexcept;
|
||||
|
||||
void _UpdateFPS(std::chrono::time_point<std::chrono::steady_clock> now) noexcept;
|
||||
bool _UpdateFPS(std::chrono::time_point<std::chrono::steady_clock> now) noexcept;
|
||||
|
||||
std::chrono::nanoseconds _minInterval{};
|
||||
std::chrono::nanoseconds _maxInterval{ std::numeric_limits<std::chrono::nanoseconds::rep>::max() };
|
||||
|
|
|
|||
313
src/Magpie.Core/TextureHelper.cpp
Normal file
313
src/Magpie.Core/TextureHelper.cpp
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
#include "pch.h"
|
||||
#include "TextureHelper.h"
|
||||
#include "Logger.h"
|
||||
#include "DDSHelper.h"
|
||||
#include "DirectXHelper.h"
|
||||
#include "EffectHelper.h"
|
||||
#include <wincodec.h>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static winrt::com_ptr<ID3D11Texture2D> LoadImg(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept {
|
||||
winrt::com_ptr<IWICImagingFactory2> wicImgFactory =
|
||||
winrt::try_create_instance<IWICImagingFactory2>(CLSID_WICImagingFactory);
|
||||
if (!wicImgFactory) {
|
||||
Logger::Get().Error("创建 WICImagingFactory 失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 读取图像文件
|
||||
winrt::com_ptr<IWICBitmapDecoder> decoder;
|
||||
HRESULT hr = wicImgFactory->CreateDecoderFromFilename(fileName, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateDecoderFromFilename 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICBitmapFrameDecode> frame;
|
||||
hr = decoder->GetFrame(0, frame.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameDecode::GetFrame 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool useFloatFormat = false;
|
||||
{
|
||||
WICPixelFormatGUID sourceFormat;
|
||||
hr = frame->GetPixelFormat(&sourceFormat);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetPixelFormat 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICComponentInfo> cInfo;
|
||||
hr = wicImgFactory->CreateComponentInfo(sourceFormat, cInfo.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateComponentInfo", hr);
|
||||
return nullptr;
|
||||
}
|
||||
winrt::com_ptr<IWICPixelFormatInfo2> formatInfo = cInfo.try_as<IWICPixelFormatInfo2>();
|
||||
if (!formatInfo) {
|
||||
Logger::Get().Error("IWICComponentInfo 转换为 IWICPixelFormatInfo2 时失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UINT bitsPerPixel;
|
||||
WICPixelFormatNumericRepresentation type;
|
||||
hr = formatInfo->GetBitsPerPixel(&bitsPerPixel);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetBitsPerPixel", hr);
|
||||
return nullptr;
|
||||
}
|
||||
hr = formatInfo->GetNumericRepresentation(&type);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetNumericRepresentation", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
useFloatFormat = bitsPerPixel > 32 || type == WICPixelFormatNumericRepresentationFixed || type == WICPixelFormatNumericRepresentationFloat;
|
||||
}
|
||||
|
||||
// 转换格式
|
||||
winrt::com_ptr<IWICFormatConverter> formatConverter;
|
||||
hr = wicImgFactory->CreateFormatConverter(formatConverter.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateFormatConverter 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WICPixelFormatGUID targetFormat = useFloatFormat ? GUID_WICPixelFormat64bppRGBAHalf : GUID_WICPixelFormat32bppRGBA;
|
||||
hr = formatConverter->Initialize(frame.get(), targetFormat, WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICFormatConverter::Initialize 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查 D3D 纹理尺寸限制
|
||||
UINT width, height;
|
||||
hr = formatConverter->GetSize(&width, &height);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetSize 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION || height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION) {
|
||||
Logger::Get().Error("图像尺寸超出限制");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UINT stride = width * (useFloatFormat ? 8 : 4);
|
||||
UINT size = stride * height;
|
||||
std::unique_ptr<BYTE[]> buf(new BYTE[size]);
|
||||
|
||||
hr = formatConverter->CopyPixels(nullptr, stride, size, buf.get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CopyPixels 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
D3D11_SUBRESOURCE_DATA initData{
|
||||
.pSysMem = buf.get(),
|
||||
.SysMemPitch = stride
|
||||
};
|
||||
winrt::com_ptr<ID3D11Texture2D> result = DirectXHelper::CreateTexture2D(
|
||||
d3dDevice,
|
||||
useFloatFormat ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
width,
|
||||
height,
|
||||
D3D11_BIND_SHADER_RESOURCE,
|
||||
D3D11_USAGE_IMMUTABLE,
|
||||
0,
|
||||
&initData
|
||||
);
|
||||
if (!result) {
|
||||
Logger::Get().Error("创建纹理失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> TextureHelper::LoadTexture(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept {
|
||||
std::wstring_view sv(fileName);
|
||||
size_t npos = sv.find_last_of(L'.');
|
||||
if (npos == std::wstring_view::npos) {
|
||||
Logger::Get().Error("文件名无后缀名");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::wstring_view suffix = sv.substr(npos + 1);
|
||||
|
||||
if (suffix == L"dds") {
|
||||
return DDSHelper::Load(fileName, d3dDevice);
|
||||
}
|
||||
|
||||
if (suffix == L"bmp" || suffix == L"jpg" || suffix == L"jpeg"
|
||||
|| suffix == L"png" || suffix == L"tif" || suffix == L"tiff"
|
||||
) {
|
||||
return LoadImg(fileName, d3dDevice);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool SavePng(
|
||||
const wchar_t* fileName,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
std::span<uint8_t> pixelData,
|
||||
uint32_t rowPitch
|
||||
) {
|
||||
// 初始化 WIC
|
||||
winrt::com_ptr<IWICImagingFactory2> wicFactory =
|
||||
winrt::try_create_instance<IWICImagingFactory2>(CLSID_WICImagingFactory);
|
||||
if (!wicFactory) {
|
||||
Logger::Get().Error("创建 WICImagingFactory2 失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICBitmapEncoder> imgEncoder;
|
||||
HRESULT hr = wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, imgEncoder.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICImagingFactory::CreateEncoder 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
winrt::com_ptr<IWICStream> wicStream;
|
||||
hr = wicFactory->CreateStream(wicStream.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICImagingFactory::CreateStream 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = wicStream->InitializeFromFilename(fileName, GENERIC_WRITE);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICStream::InitializeFromFilename 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = imgEncoder->Initialize(wicStream.get(), WICBitmapEncoderNoCache);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapEncoder::Initialize 失败", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICBitmapFrameEncode> frameEncoder;
|
||||
hr = imgEncoder->CreateNewFrame(frameEncoder.put(), nullptr);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapEncoder::CreateNewFrame 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frameEncoder->Initialize(nullptr);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameEncode::Initialize 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frameEncoder->SetSize(width, height);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameEncode::SetSize 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const WICPixelFormatGUID& srcFormat = GUID_WICPixelFormat32bppRGBA;
|
||||
WICPixelFormatGUID destFormat = srcFormat;
|
||||
hr = frameEncoder->SetPixelFormat(&destFormat);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameEncode::SetPixelFormat 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 写入像素数据
|
||||
if (destFormat == srcFormat) {
|
||||
hr = frameEncoder->WritePixels(
|
||||
height,
|
||||
rowPitch,
|
||||
(UINT)pixelData.size(),
|
||||
pixelData.data()
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameEncode::WritePixels 失败", hr);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 需要转换格式
|
||||
winrt::com_ptr<IWICBitmap> memBmp;
|
||||
hr = wicFactory->CreateBitmapFromMemory(
|
||||
width,
|
||||
height,
|
||||
srcFormat,
|
||||
rowPitch,
|
||||
(UINT)pixelData.size(),
|
||||
pixelData.data(),
|
||||
memBmp.put()
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICImagingFactory::CreateBitmapFromMemory 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICFormatConverter> formatConverter;
|
||||
hr = wicFactory->CreateFormatConverter(formatConverter.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICImagingFactory::CreateFormatConverter 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = formatConverter->Initialize(
|
||||
memBmp.get(),
|
||||
destFormat,
|
||||
WICBitmapDitherTypeNone,
|
||||
nullptr,
|
||||
0.0,
|
||||
WICBitmapPaletteTypeMedianCut
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICFormatConverter::Initialize 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = frameEncoder->WriteSource(formatConverter.get(), nullptr);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameEncode::WriteSource 失败", hr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
hr = frameEncoder->Commit();
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameEncode::Commit 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = imgEncoder->Commit();
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapEncoder::Commit 失败", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureHelper::SaveTexture(
|
||||
const wchar_t* fileName,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
EffectIntermediateTextureFormat format,
|
||||
std::span<uint8_t> pixelData,
|
||||
uint32_t rowPitch
|
||||
) noexcept {
|
||||
if (std::wstring_view(fileName).ends_with(L".dds")) {
|
||||
DXGI_FORMAT dxgiFormat = EffectHelper::FORMAT_DESCS[(uint32_t)format].dxgiFormat;
|
||||
return DDSHelper::Save(fileName, width, height, dxgiFormat, pixelData, rowPitch);
|
||||
} else {
|
||||
assert(std::wstring_view(fileName).ends_with(L".png"));
|
||||
assert(format == EffectIntermediateTextureFormat::R8G8B8A8_UNORM);
|
||||
return SavePng(fileName, width, height, pixelData, rowPitch);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
src/Magpie.Core/TextureHelper.h
Normal file
22
src/Magpie.Core/TextureHelper.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#include "EffectDesc.h"
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
class TextureHelper {
|
||||
public:
|
||||
// 支持 dds、bmp、jpg、png 和 tiff
|
||||
static winrt::com_ptr<ID3D11Texture2D> LoadTexture(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept;
|
||||
|
||||
// 支持 dds 和 png
|
||||
static bool SaveTexture(
|
||||
const wchar_t* fileName,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
EffectIntermediateTextureFormat format,
|
||||
std::span<uint8_t> pixelData,
|
||||
uint32_t rowPitch
|
||||
) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,682 +0,0 @@
|
|||
#include "pch.h"
|
||||
#include "TextureLoader.h"
|
||||
#include "Logger.h"
|
||||
#include "DDS.h"
|
||||
#include "DDSLoderHelpers.h"
|
||||
#include <wincodec.h>
|
||||
#include "DirectXHelper.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// 读取 DDS 文件的代码取自 https://github.com/microsoft/DirectXTK //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
static HRESULT CreateD3DResources(
|
||||
_In_ ID3D11Device* d3dDevice,
|
||||
_In_ uint32_t resDim,
|
||||
_In_ size_t width,
|
||||
_In_ size_t height,
|
||||
_In_ size_t depth,
|
||||
_In_ size_t mipCount,
|
||||
_In_ size_t arraySize,
|
||||
_In_ DXGI_FORMAT format,
|
||||
_In_ D3D11_USAGE usage,
|
||||
_In_ unsigned int bindFlags,
|
||||
_In_ unsigned int cpuAccessFlags,
|
||||
_In_ unsigned int miscFlags,
|
||||
_In_ bool forceSRGB,
|
||||
_In_ bool isCubeMap,
|
||||
_In_reads_opt_(mipCount* arraySize) const D3D11_SUBRESOURCE_DATA* initData,
|
||||
_Outptr_opt_ ID3D11Resource** texture) noexcept {
|
||||
if (!d3dDevice)
|
||||
return E_POINTER;
|
||||
|
||||
HRESULT hr = E_FAIL;
|
||||
|
||||
if (forceSRGB) {
|
||||
format = MakeSRGB(format);
|
||||
}
|
||||
|
||||
switch (resDim) {
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
{
|
||||
D3D11_TEXTURE1D_DESC desc = {};
|
||||
desc.Width = static_cast<UINT>(width);
|
||||
desc.MipLevels = static_cast<UINT>(mipCount);
|
||||
desc.ArraySize = static_cast<UINT>(arraySize);
|
||||
desc.Format = format;
|
||||
desc.Usage = usage;
|
||||
desc.BindFlags = bindFlags;
|
||||
desc.CPUAccessFlags = cpuAccessFlags;
|
||||
desc.MiscFlags = miscFlags & ~static_cast<unsigned int>(D3D11_RESOURCE_MISC_TEXTURECUBE);
|
||||
|
||||
ID3D11Texture1D* tex = nullptr;
|
||||
hr = d3dDevice->CreateTexture1D(&desc,
|
||||
initData,
|
||||
&tex
|
||||
);
|
||||
if (SUCCEEDED(hr) && tex) {
|
||||
if (texture) {
|
||||
*texture = tex;
|
||||
} else {
|
||||
tex->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
{
|
||||
D3D11_TEXTURE2D_DESC desc = {
|
||||
.Width = static_cast<UINT>(width),
|
||||
.Height = static_cast<UINT>(height),
|
||||
.MipLevels = static_cast<UINT>(mipCount),
|
||||
.ArraySize = static_cast<UINT>(arraySize),
|
||||
.Format = format,
|
||||
.SampleDesc = {
|
||||
.Count = 1
|
||||
},
|
||||
.Usage = usage,
|
||||
.BindFlags = bindFlags,
|
||||
.CPUAccessFlags = cpuAccessFlags,
|
||||
};
|
||||
if (isCubeMap) {
|
||||
desc.MiscFlags = miscFlags | D3D11_RESOURCE_MISC_TEXTURECUBE;
|
||||
} else {
|
||||
desc.MiscFlags = miscFlags & ~static_cast<unsigned int>(D3D11_RESOURCE_MISC_TEXTURECUBE);
|
||||
}
|
||||
|
||||
ID3D11Texture2D* tex = nullptr;
|
||||
hr = d3dDevice->CreateTexture2D(&desc,
|
||||
initData,
|
||||
&tex
|
||||
);
|
||||
if (SUCCEEDED(hr) && tex) {
|
||||
if (texture) {
|
||||
*texture = tex;
|
||||
} else {
|
||||
tex->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
{
|
||||
D3D11_TEXTURE3D_DESC desc = {
|
||||
.Width = static_cast<UINT>(width),
|
||||
.Height = static_cast<UINT>(height),
|
||||
.Depth = static_cast<UINT>(depth),
|
||||
.MipLevels = static_cast<UINT>(mipCount),
|
||||
.Format = format,
|
||||
.Usage = usage,
|
||||
.BindFlags = bindFlags,
|
||||
.CPUAccessFlags = cpuAccessFlags,
|
||||
.MiscFlags = miscFlags & ~UINT(D3D11_RESOURCE_MISC_TEXTURECUBE)
|
||||
};
|
||||
|
||||
ID3D11Texture3D* tex = nullptr;
|
||||
hr = d3dDevice->CreateTexture3D(&desc,
|
||||
initData,
|
||||
&tex
|
||||
);
|
||||
if (SUCCEEDED(hr) && tex) {
|
||||
if (texture) {
|
||||
*texture = tex;
|
||||
} else {
|
||||
tex->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT FillInitData(
|
||||
_In_ size_t width,
|
||||
_In_ size_t height,
|
||||
_In_ size_t depth,
|
||||
_In_ size_t mipCount,
|
||||
_In_ size_t arraySize,
|
||||
_In_ DXGI_FORMAT format,
|
||||
_In_ size_t maxsize,
|
||||
_In_ size_t bitSize,
|
||||
_In_reads_bytes_(bitSize) const uint8_t* bitData,
|
||||
_Out_ size_t& twidth,
|
||||
_Out_ size_t& theight,
|
||||
_Out_ size_t& tdepth,
|
||||
_Out_ size_t& skipMip,
|
||||
_Out_writes_(mipCount* arraySize) D3D11_SUBRESOURCE_DATA* initData) noexcept {
|
||||
if (!bitData || !initData) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
skipMip = 0;
|
||||
twidth = 0;
|
||||
theight = 0;
|
||||
tdepth = 0;
|
||||
|
||||
size_t NumBytes = 0;
|
||||
size_t RowBytes = 0;
|
||||
const uint8_t* pSrcBits = bitData;
|
||||
const uint8_t* pEndBits = bitData + bitSize;
|
||||
|
||||
size_t index = 0;
|
||||
for (size_t j = 0; j < arraySize; j++) {
|
||||
size_t w = width;
|
||||
size_t h = height;
|
||||
size_t d = depth;
|
||||
for (size_t i = 0; i < mipCount; i++) {
|
||||
HRESULT hr = GetSurfaceInfo(w, h, format, &NumBytes, &RowBytes, nullptr);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
|
||||
if (NumBytes > UINT32_MAX || RowBytes > UINT32_MAX)
|
||||
return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW);
|
||||
|
||||
if ((mipCount <= 1) || !maxsize || (w <= maxsize && h <= maxsize && d <= maxsize)) {
|
||||
if (!twidth) {
|
||||
twidth = w;
|
||||
theight = h;
|
||||
tdepth = d;
|
||||
}
|
||||
|
||||
assert(index < mipCount* arraySize);
|
||||
_Analysis_assume_(index < mipCount* arraySize);
|
||||
initData[index].pSysMem = pSrcBits;
|
||||
initData[index].SysMemPitch = static_cast<UINT>(RowBytes);
|
||||
initData[index].SysMemSlicePitch = static_cast<UINT>(NumBytes);
|
||||
++index;
|
||||
} else if (!j) {
|
||||
// Count number of skipped mipmaps (first item only)
|
||||
++skipMip;
|
||||
}
|
||||
|
||||
if (pSrcBits + (NumBytes * d) > pEndBits) {
|
||||
return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
|
||||
}
|
||||
|
||||
pSrcBits += NumBytes * d;
|
||||
|
||||
w = w >> 1;
|
||||
h = h >> 1;
|
||||
d = d >> 1;
|
||||
if (w == 0) {
|
||||
w = 1;
|
||||
}
|
||||
if (h == 0) {
|
||||
h = 1;
|
||||
}
|
||||
if (d == 0) {
|
||||
d = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (index > 0) ? S_OK : E_FAIL;
|
||||
}
|
||||
|
||||
static HRESULT CreateTextureFromDDS(
|
||||
_In_ ID3D11Device* d3dDevice,
|
||||
_In_ const DDS_HEADER* header,
|
||||
_In_reads_bytes_(bitSize) const uint8_t* bitData,
|
||||
_In_ size_t bitSize,
|
||||
_In_ size_t maxsize,
|
||||
_In_ D3D11_USAGE usage,
|
||||
_In_ unsigned int bindFlags,
|
||||
_In_ unsigned int cpuAccessFlags,
|
||||
_In_ unsigned int miscFlags,
|
||||
_In_ bool forceSRGB,
|
||||
_Outptr_opt_ ID3D11Resource** texture) noexcept {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
const UINT width = header->width;
|
||||
UINT height = header->height;
|
||||
UINT depth = header->depth;
|
||||
|
||||
uint32_t resDim = D3D11_RESOURCE_DIMENSION_UNKNOWN;
|
||||
UINT arraySize = 1;
|
||||
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
|
||||
bool isCubeMap = false;
|
||||
|
||||
size_t mipCount = header->mipMapCount;
|
||||
if (0 == mipCount) {
|
||||
mipCount = 1;
|
||||
}
|
||||
|
||||
if ((header->ddspf.flags & DDS_FOURCC) &&
|
||||
(MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) {
|
||||
auto d3d10ext = reinterpret_cast<const DDS_HEADER_DXT10*>(reinterpret_cast<const char*>(header) + sizeof(DDS_HEADER));
|
||||
|
||||
arraySize = d3d10ext->arraySize;
|
||||
if (arraySize == 0) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
||||
}
|
||||
|
||||
switch (d3d10ext->dxgiFormat) {
|
||||
case DXGI_FORMAT_AI44:
|
||||
case DXGI_FORMAT_IA44:
|
||||
case DXGI_FORMAT_P8:
|
||||
case DXGI_FORMAT_A8P8:
|
||||
Logger::Get().Error("ERROR: DDSTextureLoader does not support video textures. Consider using DirectXTex instead.\n");
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
|
||||
default:
|
||||
if (BitsPerPixel(d3d10ext->dxgiFormat) == 0) {
|
||||
Logger::Get().Error(fmt::format("ERROR: Unknown DXGI format ({})\n", static_cast<uint32_t>(d3d10ext->dxgiFormat)));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
format = d3d10ext->dxgiFormat;
|
||||
|
||||
switch (d3d10ext->resourceDimension) {
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
// D3DX writes 1D textures with a fixed Height of 1
|
||||
if ((header->flags & DDS_HEIGHT) && height != 1) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
||||
}
|
||||
height = depth = 1;
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (d3d10ext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE) {
|
||||
arraySize *= 6;
|
||||
isCubeMap = true;
|
||||
}
|
||||
depth = 1;
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
if (!(header->flags & DDS_HEADER_FLAGS_VOLUME)) {
|
||||
return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);
|
||||
}
|
||||
|
||||
if (arraySize > 1) {
|
||||
Logger::Get().Error("ERROR: Volume textures are not texture arrays\n");
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_BUFFER:
|
||||
Logger::Get().Error("ERROR: Resource dimension buffer type not supported for textures\n");
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_UNKNOWN:
|
||||
default:
|
||||
Logger::Get().Error(fmt::format("ERROR: Unknown resource dimension ({})\n", static_cast<uint32_t>(d3d10ext->resourceDimension)));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
resDim = d3d10ext->resourceDimension;
|
||||
} else {
|
||||
format = GetDXGIFormat(header->ddspf);
|
||||
|
||||
if (format == DXGI_FORMAT_UNKNOWN) {
|
||||
Logger::Get().Error("ERROR: DDSTextureLoader does not support all legacy DDS formats. Consider using DirectXTex.\n");
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
if (header->flags & DDS_HEADER_FLAGS_VOLUME) {
|
||||
resDim = D3D11_RESOURCE_DIMENSION_TEXTURE3D;
|
||||
} else {
|
||||
if (header->caps2 & DDS_CUBEMAP) {
|
||||
// We require all six faces to be defined
|
||||
if ((header->caps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES) {
|
||||
Logger::Get().Error("ERROR: DirectX 11 does not support partial cubemaps\n");
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
arraySize = 6;
|
||||
isCubeMap = true;
|
||||
}
|
||||
|
||||
depth = 1;
|
||||
resDim = D3D11_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
|
||||
// Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture
|
||||
}
|
||||
|
||||
assert(BitsPerPixel(format) != 0);
|
||||
}
|
||||
|
||||
if ((miscFlags & D3D11_RESOURCE_MISC_TEXTURECUBE)
|
||||
&& (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE2D)
|
||||
&& ((arraySize % 6) == 0)) {
|
||||
isCubeMap = true;
|
||||
}
|
||||
|
||||
// Bound sizes (for security purposes we don't trust DDS file metadata larger than the Direct3D hardware requirements)
|
||||
if (mipCount > D3D11_REQ_MIP_LEVELS) {
|
||||
Logger::Get().Error(fmt::format("ERROR: Too many mipmap levels defined for DirectX 11 ({}).\n", mipCount));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
switch (resDim) {
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
|
||||
if ((arraySize > D3D11_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION) ||
|
||||
(width > D3D11_REQ_TEXTURE1D_U_DIMENSION)) {
|
||||
Logger::Get().Error(fmt::format("ERROR: Resource dimensions too large for DirectX 11 (1D: array {}, size {})\n", arraySize, width));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
|
||||
if (isCubeMap) {
|
||||
// This is the right bound because we set arraySize to (NumCubes*6) above
|
||||
if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
|
||||
(width > D3D11_REQ_TEXTURECUBE_DIMENSION) ||
|
||||
(height > D3D11_REQ_TEXTURECUBE_DIMENSION)) {
|
||||
Logger::Get().Error(fmt::format("ERROR: Resource dimensions too large for DirectX 11 (2D cubemap: array {}, size {} by {})\n", arraySize, width, height));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
} else if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION) ||
|
||||
(width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION) ||
|
||||
(height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION)) {
|
||||
Logger::Get().Error(fmt::format("ERROR: Resource dimensions too large for DirectX 11 (2D: array {}, size {} by {})\n", arraySize, width, height));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
|
||||
if ((arraySize > 1) ||
|
||||
(width > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
|
||||
(height > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION) ||
|
||||
(depth > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)) {
|
||||
Logger::Get().Error(fmt::format("ERROR: Resource dimensions too large for DirectX 11 (3D: array {}, size {} by {} by {})\n", arraySize, width, height, depth));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_BUFFER:
|
||||
Logger::Get().Error("ERROR: Resource dimension buffer type not supported for textures\n");
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
|
||||
case D3D11_RESOURCE_DIMENSION_UNKNOWN:
|
||||
default:
|
||||
Logger::Get().Error(fmt::format("ERROR: Unknown resource dimension ({})\n", static_cast<uint32_t>(resDim)));
|
||||
return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
// Create the texture
|
||||
std::unique_ptr<D3D11_SUBRESOURCE_DATA[]> initData(new (std::nothrow) D3D11_SUBRESOURCE_DATA[mipCount * arraySize]);
|
||||
if (!initData) {
|
||||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
size_t skipMip = 0;
|
||||
size_t twidth = 0;
|
||||
size_t theight = 0;
|
||||
size_t tdepth = 0;
|
||||
hr = FillInitData(width, height, depth, mipCount, arraySize, format,
|
||||
maxsize, bitSize, bitData,
|
||||
twidth, theight, tdepth, skipMip, initData.get());
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = CreateD3DResources(d3dDevice,
|
||||
resDim, twidth, theight, tdepth, mipCount - skipMip, arraySize,
|
||||
format,
|
||||
usage, bindFlags, cpuAccessFlags, miscFlags,
|
||||
forceSRGB,
|
||||
isCubeMap,
|
||||
initData.get(),
|
||||
texture
|
||||
);
|
||||
|
||||
if (FAILED(hr) && !maxsize && (mipCount > 1)) {
|
||||
// Retry with a maxsize determined by feature level
|
||||
maxsize = (resDim == D3D11_RESOURCE_DIMENSION_TEXTURE3D)
|
||||
? D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION
|
||||
: D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
|
||||
|
||||
hr = FillInitData(width, height, depth, mipCount, arraySize, format,
|
||||
maxsize, bitSize, bitData,
|
||||
twidth, theight, tdepth, skipMip, initData.get());
|
||||
if (SUCCEEDED(hr)) {
|
||||
hr = CreateD3DResources(d3dDevice,
|
||||
resDim, twidth, theight, tdepth, mipCount - skipMip, arraySize,
|
||||
format,
|
||||
usage, bindFlags, cpuAccessFlags, miscFlags,
|
||||
forceSRGB,
|
||||
isCubeMap,
|
||||
initData.get(),
|
||||
texture
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static HRESULT CreateDDSTextureFromFileEx(
|
||||
ID3D11Device* d3dDevice,
|
||||
const wchar_t* fileName,
|
||||
size_t maxsize,
|
||||
D3D11_USAGE usage,
|
||||
unsigned int bindFlags,
|
||||
unsigned int cpuAccessFlags,
|
||||
unsigned int miscFlags,
|
||||
bool forceSRGB,
|
||||
ID3D11Resource** texture,
|
||||
DDS_ALPHA_MODE* alphaMode) noexcept {
|
||||
if (texture) {
|
||||
*texture = nullptr;
|
||||
}
|
||||
if (alphaMode) {
|
||||
*alphaMode = DDS_ALPHA_MODE_UNKNOWN;
|
||||
}
|
||||
|
||||
if (!d3dDevice || !fileName || !texture) {
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
const DDS_HEADER* header = nullptr;
|
||||
const uint8_t* bitData = nullptr;
|
||||
size_t bitSize = 0;
|
||||
|
||||
std::unique_ptr<uint8_t[]> ddsData;
|
||||
HRESULT hr = LoadTextureDataFromFile(fileName,
|
||||
ddsData,
|
||||
&header,
|
||||
&bitData,
|
||||
&bitSize
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = CreateTextureFromDDS(d3dDevice,
|
||||
header, bitData, bitSize,
|
||||
maxsize,
|
||||
usage, bindFlags, cpuAccessFlags, miscFlags,
|
||||
forceSRGB,
|
||||
texture
|
||||
);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
if (alphaMode)
|
||||
*alphaMode = GetAlphaMode(header);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
static winrt::com_ptr<ID3D11Texture2D> LoadImg(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept {
|
||||
winrt::com_ptr<IWICImagingFactory2> wicImgFactory =
|
||||
winrt::try_create_instance<IWICImagingFactory2>(CLSID_WICImagingFactory);
|
||||
if (!wicImgFactory) {
|
||||
Logger::Get().Error("创建 WICImagingFactory 失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 读取图像文件
|
||||
winrt::com_ptr<IWICBitmapDecoder> decoder;
|
||||
HRESULT hr = wicImgFactory->CreateDecoderFromFilename(fileName, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateDecoderFromFilename 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICBitmapFrameDecode> frame;
|
||||
hr = decoder->GetFrame(0, frame.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICBitmapFrameDecode::GetFrame 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool useFloatFormat = false;
|
||||
{
|
||||
WICPixelFormatGUID sourceFormat;
|
||||
hr = frame->GetPixelFormat(&sourceFormat);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetPixelFormat 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<IWICComponentInfo> cInfo;
|
||||
hr = wicImgFactory->CreateComponentInfo(sourceFormat, cInfo.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateComponentInfo", hr);
|
||||
return nullptr;
|
||||
}
|
||||
winrt::com_ptr<IWICPixelFormatInfo2> formatInfo = cInfo.try_as<IWICPixelFormatInfo2>();
|
||||
if (!formatInfo) {
|
||||
Logger::Get().Error("IWICComponentInfo 转换为 IWICPixelFormatInfo2 时失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UINT bitsPerPixel;
|
||||
WICPixelFormatNumericRepresentation type;
|
||||
hr = formatInfo->GetBitsPerPixel(&bitsPerPixel);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetBitsPerPixel", hr);
|
||||
return nullptr;
|
||||
}
|
||||
hr = formatInfo->GetNumericRepresentation(&type);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetNumericRepresentation", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
useFloatFormat = bitsPerPixel > 32 || type == WICPixelFormatNumericRepresentationFixed || type == WICPixelFormatNumericRepresentationFloat;
|
||||
}
|
||||
|
||||
// 转换格式
|
||||
winrt::com_ptr<IWICFormatConverter> formatConverter;
|
||||
hr = wicImgFactory->CreateFormatConverter(formatConverter.put());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateFormatConverter 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WICPixelFormatGUID targetFormat = useFloatFormat ? GUID_WICPixelFormat64bppRGBAHalf : GUID_WICPixelFormat32bppRGBA;
|
||||
hr = formatConverter->Initialize(frame.get(), targetFormat, WICBitmapDitherTypeNone, nullptr, 0, WICBitmapPaletteTypeCustom);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("IWICFormatConverter::Initialize 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 检查 D3D 纹理尺寸限制
|
||||
UINT width, height;
|
||||
hr = formatConverter->GetSize(&width, &height);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetSize 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (width > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION || height > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION) {
|
||||
Logger::Get().Error("图像尺寸超出限制");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UINT stride = width * (useFloatFormat ? 8 : 4);
|
||||
UINT size = stride * height;
|
||||
std::unique_ptr<BYTE[]> buf(new BYTE[size]);
|
||||
|
||||
hr = formatConverter->CopyPixels(nullptr, stride, size, buf.get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CopyPixels 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
D3D11_SUBRESOURCE_DATA initData{
|
||||
.pSysMem = buf.get(),
|
||||
.SysMemPitch = stride
|
||||
};
|
||||
winrt::com_ptr<ID3D11Texture2D> result = DirectXHelper::CreateTexture2D(
|
||||
d3dDevice,
|
||||
useFloatFormat ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
width,
|
||||
height,
|
||||
D3D11_BIND_SHADER_RESOURCE,
|
||||
D3D11_USAGE_IMMUTABLE,
|
||||
0,
|
||||
&initData
|
||||
);
|
||||
if (!result) {
|
||||
Logger::Get().Error("创建纹理失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static winrt::com_ptr<ID3D11Texture2D> LoadDDS(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept {
|
||||
winrt::com_ptr<ID3D11Resource> result;
|
||||
|
||||
DDS_ALPHA_MODE alphaMode = DDS_ALPHA_MODE_STRAIGHT;
|
||||
HRESULT hr = CreateDDSTextureFromFileEx(
|
||||
d3dDevice,
|
||||
fileName,
|
||||
0,
|
||||
D3D11_USAGE_IMMUTABLE,
|
||||
D3D11_BIND_SHADER_RESOURCE,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
result.put(),
|
||||
&alphaMode
|
||||
);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("CreateDDSTextureFromFile 失败", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> tex = result.try_as<ID3D11Texture2D>();
|
||||
if (!tex) {
|
||||
Logger::Get().Error("从 ID3D11Resource 获取 ID3D11Texture2D 失败");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
winrt::com_ptr<ID3D11Texture2D> TextureLoader::Load(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept {
|
||||
std::wstring_view sv(fileName);
|
||||
size_t npos = sv.find_last_of(L'.');
|
||||
if (npos == std::wstring_view::npos) {
|
||||
Logger::Get().Error("文件名无后缀名");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::wstring_view suffix = sv.substr(npos + 1);
|
||||
|
||||
if (suffix == L"dds") {
|
||||
return LoadDDS(fileName, d3dDevice);
|
||||
}
|
||||
|
||||
if (suffix == L"bmp" || suffix == L"jpg" || suffix == L"jpeg"
|
||||
|| suffix == L"png" || suffix == L"tif" || suffix == L"tiff"
|
||||
) {
|
||||
return LoadImg(fileName, d3dDevice);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
class TextureLoader {
|
||||
public:
|
||||
static winrt::com_ptr<ID3D11Texture2D> Load(const wchar_t* fileName, ID3D11Device* d3dDevice) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
std::wstring Win32Helper::GetWndClassName(HWND hWnd) noexcept {
|
||||
std::wstring Win32Helper::GetWindowClassName(HWND hWnd) noexcept {
|
||||
// 窗口类名最多 256 个字符
|
||||
std::wstring className(256, 0);
|
||||
int num = GetClassName(hWnd, &className[0], (int)className.size() + 1);
|
||||
|
|
@ -26,7 +26,7 @@ std::wstring Win32Helper::GetWndClassName(HWND hWnd) noexcept {
|
|||
return className;
|
||||
}
|
||||
|
||||
std::wstring Win32Helper::GetWndTitle(HWND hWnd) noexcept {
|
||||
std::wstring Win32Helper::GetWindowTitle(HWND hWnd) noexcept {
|
||||
int len = GetWindowTextLength(hWnd);
|
||||
if (len == 0) {
|
||||
return {};
|
||||
|
|
@ -38,7 +38,7 @@ std::wstring Win32Helper::GetWndTitle(HWND hWnd) noexcept {
|
|||
return title;
|
||||
}
|
||||
|
||||
wil::unique_process_handle Win32Helper::GetWndProcessHandle(HWND hWnd) noexcept {
|
||||
wil::unique_process_handle Win32Helper::GetWindowProcessHandle(HWND hWnd) noexcept {
|
||||
wil::unique_process_handle hProc;
|
||||
|
||||
if (DWORD dwProcId = 0; GetWindowThreadProcessId(hWnd, &dwProcId)) {
|
||||
|
|
@ -67,10 +67,10 @@ wil::unique_process_handle Win32Helper::GetWndProcessHandle(HWND hWnd) noexcept
|
|||
return hProc;
|
||||
}
|
||||
|
||||
std::wstring Win32Helper::GetPathOfWnd(HWND hWnd) noexcept {
|
||||
wil::unique_process_handle hProc = GetWndProcessHandle(hWnd);
|
||||
std::wstring Win32Helper::GetWindowPath(HWND hWnd) noexcept {
|
||||
wil::unique_process_handle hProc = GetWindowProcessHandle(hWnd);
|
||||
if (!hProc) {
|
||||
Logger::Get().Error("GetWndProcessHandle 失败");
|
||||
Logger::Get().Error("GetWindowProcessHandle 失败");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +84,17 @@ std::wstring Win32Helper::GetPathOfWnd(HWND hWnd) noexcept {
|
|||
return fileName;
|
||||
}
|
||||
|
||||
std::wstring Win32Helper::GetWindowExeName(HWND hWnd) noexcept {
|
||||
std::wstring path = GetWindowPath(hWnd);
|
||||
|
||||
const size_t delimPos = path.find_last_of(L'\\');
|
||||
if (delimPos != std::wstring::npos) {
|
||||
path.erase(path.begin(), path.begin() + delimPos + 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
UINT Win32Helper::GetWindowShowCmd(HWND hWnd) noexcept {
|
||||
assert(hWnd != NULL);
|
||||
|
||||
|
|
@ -139,7 +150,7 @@ bool Win32Helper::GetWindowFrameRect(HWND hWnd, RECT& rect) noexcept {
|
|||
// 一个屏幕上。
|
||||
// 注意 Win11 中最大化窗口的 extended frame bounds 包含了下边框,但对我们
|
||||
// 没有影响,因为缩放时下边框始终会被裁剪掉。
|
||||
IntersectRect(&rect, &rect, &mi.rcMonitor);
|
||||
Win32Helper::IntersectRect(rect, rect, mi.rcMonitor);
|
||||
}
|
||||
|
||||
// 对于使用 SetWindowRgn 自定义形状的窗口,裁剪到最小矩形边框
|
||||
|
|
@ -153,26 +164,146 @@ bool Win32Helper::GetWindowFrameRect(HWND hWnd, RECT& rect) noexcept {
|
|||
}
|
||||
|
||||
// 转换为屏幕坐标
|
||||
OffsetRect(&rgnRect, windowRect.left, windowRect.top);
|
||||
OffsetRect(rgnRect, windowRect.left, windowRect.top);
|
||||
|
||||
IntersectRect(&rect, &rect, &rgnRect);
|
||||
Win32Helper::IntersectRect(rect, rect, rgnRect);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Win32Helper::GetNativeWindowBorderThickness(uint32_t dpi) noexcept {
|
||||
if (GetOSVersion().IsWin11()) {
|
||||
// 这里的计算方式是通过实验总结出来的。DwmGetWindowAttribute 有两个问题:
|
||||
// 1. 它要求窗口存在,而有些时候我们需要在创建窗口前计算窗口尺寸。
|
||||
// 2. 如果窗口被 DPI 虚拟化,它返回的结果是错误的。
|
||||
return (dpi + USER_DEFAULT_SCREEN_DPI / 2) / USER_DEFAULT_SCREEN_DPI;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int16_t Win32Helper::AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeout, HWND* hwndInvolve) noexcept {
|
||||
HWND hwndCur = hWnd;
|
||||
int16_t hittest = HTNOWHERE;
|
||||
while (true) {
|
||||
// ChildWindowFromPointEx 不检查命中测试,稍后手动检查
|
||||
POINT ptClient = ptScreen;
|
||||
ScreenToClient(hwndCur, &ptClient);
|
||||
const HWND hwndChild = ChildWindowFromPointEx(hwndCur, ptClient,
|
||||
CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT);
|
||||
if (!hwndChild || hwndChild == hwndCur) {
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD_PTR area = HTNOWHERE;
|
||||
SendMessageTimeout(hwndChild, WM_NCHITTEST, 0, MAKELPARAM(ptScreen.x, ptScreen.y),
|
||||
SMTO_NORMAL, timeout, &area);
|
||||
if (area != HTTRANSPARENT) {
|
||||
hwndCur = hwndChild;
|
||||
hittest = (int16_t)area;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 必须遍历子窗口手动执行命中测试。这个路径较慢,但基本没机会执行。已知
|
||||
// Office 2021 会触发此路径。
|
||||
struct EnumData {
|
||||
HWND hwndParent;
|
||||
HWND hwndChild;
|
||||
POINT ptScreen;
|
||||
UINT timeout;
|
||||
int16_t& hittest;
|
||||
} data{ hwndCur, NULL, ptScreen, timeout, hittest };
|
||||
|
||||
EnumChildWindows(hwndCur, [](HWND hWnd, LPARAM lParam) {
|
||||
// 跳过不可见和被禁用的窗口
|
||||
if (!IsWindowVisible(hWnd) || !IsWindowEnabled(hWnd)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
EnumData* data = (EnumData*)lParam;
|
||||
|
||||
// 只检查直接子窗口
|
||||
if (GetParent(hWnd) != data->hwndParent) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 检查是否在窗口内
|
||||
RECT windowRect;
|
||||
if (!GetWindowRect(hWnd, &windowRect) || !PtInRect(&windowRect, data->ptScreen)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 检查窗口是否对鼠标透明
|
||||
if (GetWindowExStyle(hWnd) & WS_EX_TRANSPARENT) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 使用 ChildWindowFromPointEx 检查分层窗口和自定义形状,见 PtInWindow
|
||||
SetLastError(0);
|
||||
POINT ptClient = data->ptScreen;
|
||||
ScreenToClient(hWnd, &ptClient);
|
||||
if (!ChildWindowFromPointEx(hWnd, ptClient,
|
||||
CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT
|
||||
)) {
|
||||
// 如果因权限不足等原因失败则视为不透明
|
||||
if (GetLastError() == 0) {
|
||||
// 命中了透明像素
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查命中测试是否透明。这是我们不得不遍历子窗口的原因:ChildWindowFromPoint 的所有
|
||||
// 变体都不支持检查命中测试。(RealChildWindowFromPoint 只检查标准控件,因此也无用。)
|
||||
DWORD_PTR area = HTNOWHERE;
|
||||
SendMessageTimeout(hWnd, WM_NCHITTEST, 0,
|
||||
MAKELPARAM(data->ptScreen.x, data->ptScreen.y), SMTO_NORMAL, data->timeout, &area);
|
||||
if (area == HTTRANSPARENT) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
data->hwndChild = hWnd;
|
||||
data->hittest = (int16_t)area;
|
||||
return FALSE;
|
||||
}, (LPARAM)&data);
|
||||
|
||||
if (!data.hwndChild) {
|
||||
break;
|
||||
}
|
||||
|
||||
hwndCur = data.hwndChild;
|
||||
}
|
||||
|
||||
if (hwndInvolve) {
|
||||
*hwndInvolve = hwndCur;
|
||||
}
|
||||
|
||||
if (hwndCur == hWnd) {
|
||||
// 没落到子窗口上
|
||||
DWORD_PTR area = HTNOWHERE;
|
||||
SendMessageTimeout(hWnd, WM_NCHITTEST, 0, MAKELPARAM(ptScreen.x, ptScreen.y),
|
||||
SMTO_NORMAL, timeout, &area);
|
||||
return (int16_t)area;
|
||||
} else {
|
||||
return hittest;
|
||||
}
|
||||
}
|
||||
|
||||
bool Win32Helper::IsWindowHung(HWND hWnd) noexcept {
|
||||
return 0 == SendMessageTimeout(hWnd, WM_NULL, 0, 0,
|
||||
SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT, 500, nullptr);
|
||||
}
|
||||
|
||||
bool Win32Helper::ReadFile(const wchar_t* fileName, std::vector<uint8_t>& result) noexcept {
|
||||
Logger::Get().Info(StrHelper::Concat("读取文件: ", StrHelper::UTF16ToUTF8(fileName)));
|
||||
|
||||
CREATEFILE2_EXTENDED_PARAMETERS extendedParams{
|
||||
.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
|
||||
.dwFileAttributes = FILE_ATTRIBUTE_NORMAL,
|
||||
.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN,
|
||||
.dwSecurityQosFlags = SECURITY_ANONYMOUS
|
||||
.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN
|
||||
};
|
||||
|
||||
wil::unique_hfile hFile(CreateFile2(fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, &extendedParams));
|
||||
|
||||
if (!hFile) {
|
||||
Logger::Get().Error("打开文件失败");
|
||||
return false;
|
||||
|
|
@ -190,7 +321,33 @@ bool Win32Helper::ReadFile(const wchar_t* fileName, std::vector<uint8_t>& result
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Win32Helper::WriteFile(const wchar_t* fileName, std::span<uint8_t> buffer) noexcept {
|
||||
Logger::Get().Info(StrHelper::Concat("写入文件: ", StrHelper::UTF16ToUTF8(fileName)));
|
||||
|
||||
CREATEFILE2_EXTENDED_PARAMETERS extendedParams{
|
||||
.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS),
|
||||
.dwFileAttributes = FILE_ATTRIBUTE_NORMAL
|
||||
};
|
||||
|
||||
wil::unique_hfile hFile(CreateFile2(fileName, GENERIC_WRITE, 0, CREATE_ALWAYS, &extendedParams));
|
||||
if (!hFile) {
|
||||
Logger::Get().Error("打开文件失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD written;
|
||||
if (!::WriteFile(hFile.get(), buffer.data(), (DWORD)buffer.size(), &written, nullptr)
|
||||
|| buffer.size() != written) {
|
||||
Logger::Get().Error("写入文件失败");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32Helper::ReadTextFile(const wchar_t* fileName, std::string& result) noexcept {
|
||||
Logger::Get().Info(StrHelper::Concat("读取文本文件: ", StrHelper::UTF16ToUTF8(fileName)));
|
||||
|
||||
wil::unique_file hFile;
|
||||
if (_wfopen_s(hFile.put(), fileName, L"rt") || !hFile) {
|
||||
Logger::Get().Error(StrHelper::Concat("打开文件 ", StrHelper::UTF16ToUTF8(fileName), " 失败"));
|
||||
|
|
@ -210,22 +367,9 @@ bool Win32Helper::ReadTextFile(const wchar_t* fileName, std::string& result) noe
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Win32Helper::WriteFile(const wchar_t* fileName, const void* buffer, size_t bufferSize) noexcept {
|
||||
wil::unique_file hFile;
|
||||
if (_wfopen_s(hFile.put(), fileName, L"wb") || !hFile) {
|
||||
Logger::Get().Error(StrHelper::Concat("打开文件 ", StrHelper::UTF16ToUTF8(fileName), " 失败"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bufferSize > 0) {
|
||||
[[maybe_unused]] size_t writed = fwrite(buffer, 1, bufferSize, hFile.get());
|
||||
assert(writed == bufferSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Win32Helper::WriteTextFile(const wchar_t* fileName, std::string_view text) noexcept {
|
||||
Logger::Get().Info(StrHelper::Concat("写入文本文件: ", StrHelper::UTF16ToUTF8(fileName)));
|
||||
|
||||
wil::unique_file hFile;
|
||||
if (_wfopen_s(hFile.put(), fileName, L"wt") || !hFile) {
|
||||
Logger::Get().Error(StrHelper::Concat("打开文件 ", StrHelper::UTF16ToUTF8(fileName), " 失败"));
|
||||
|
|
@ -348,32 +492,6 @@ void Win32Helper::RunParallel(std::function<void(uint32_t)> func, uint32_t times
|
|||
#endif // _DEBUG
|
||||
}
|
||||
|
||||
bool Win32Helper::SetForegroundWindow(HWND hWnd) noexcept {
|
||||
if (::SetForegroundWindow(hWnd)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 有多种原因会导致 SetForegroundWindow 失败,因此使用一个 trick 强制切换前台窗口
|
||||
// 来自 https://pinvoke.net/default.aspx/user32.SetForegroundWindow
|
||||
DWORD foreThreadId = GetWindowThreadProcessId(GetForegroundWindow(), nullptr);
|
||||
DWORD curThreadId = GetCurrentThreadId();
|
||||
|
||||
if (foreThreadId != curThreadId) {
|
||||
if (!AttachThreadInput(foreThreadId, curThreadId, TRUE)) {
|
||||
Logger::Get().Win32Error("AttachThreadInput 失败");
|
||||
return false;
|
||||
}
|
||||
BringWindowToTop(hWnd);
|
||||
ShowWindow(hWnd, SW_SHOW);
|
||||
AttachThreadInput(foreThreadId, curThreadId, FALSE);
|
||||
} else {
|
||||
BringWindowToTop(hWnd);
|
||||
ShowWindow(hWnd, SW_SHOW);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool MapKeycodeToUnicode(
|
||||
const int vCode,
|
||||
HKL layout,
|
||||
|
|
@ -731,27 +849,13 @@ bool Win32Helper::OpenFolderAndSelectFile(const wchar_t* fileName) noexcept {
|
|||
return true;
|
||||
}
|
||||
|
||||
const std::wstring& Win32Helper::GetExePath() noexcept {
|
||||
const std::filesystem::path& Win32Helper::GetExePath() noexcept {
|
||||
// 会在日志初始化前调用
|
||||
static std::wstring result = []() -> std::wstring {
|
||||
static std::filesystem::path result = [] {
|
||||
std::wstring exePath;
|
||||
FAIL_FAST_IF_FAILED(wil::GetModuleFileNameW(NULL, exePath));
|
||||
|
||||
if (!wil::is_extended_length_path(exePath.c_str())) {
|
||||
return exePath;
|
||||
}
|
||||
|
||||
// 去除 \\?\ 前缀
|
||||
wil::unique_hlocal_string canonicalPath;
|
||||
FAIL_FAST_IF_FAILED(PathAllocCanonicalize(
|
||||
exePath.c_str(),
|
||||
PATHCCH_ALLOW_LONG_PATHS,
|
||||
canonicalPath.put()
|
||||
));
|
||||
|
||||
return canonicalPath.get();
|
||||
return std::filesystem::path(std::move(exePath));
|
||||
}();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,23 +6,19 @@
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
static std::wstring GetExeName(HWND hWnd) noexcept {
|
||||
std::wstring exeName = Win32Helper::GetPathOfWnd(hWnd);
|
||||
exeName = exeName.substr(exeName.find_last_of(L'\\') + 1);
|
||||
StrHelper::ToLowerCase(exeName);
|
||||
return exeName;
|
||||
}
|
||||
static const wchar_t* CoreWindowClassName = L"Windows.UI.Core.CoreWindow";
|
||||
|
||||
bool WindowHelper::IsStartMenu(HWND hWnd) noexcept {
|
||||
// 作为优化,首先检查窗口类
|
||||
std::wstring className = Win32Helper::GetWndClassName(hWnd);
|
||||
std::wstring className = Win32Helper::GetWindowClassName(hWnd);
|
||||
|
||||
if (className != L"Windows.UI.Core.CoreWindow") {
|
||||
if (className != CoreWindowClassName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查可执行文件名称
|
||||
std::wstring exeName = GetExeName(hWnd);
|
||||
std::wstring exeName = Win32Helper::GetWindowExeName(hWnd);
|
||||
StrHelper::ToLowerCase(exeName);
|
||||
// win10: searchapp.exe 和 startmenuexperiencehost.exe
|
||||
// win11: searchhost.exe 和 startmenuexperiencehost.exe
|
||||
return exeName == L"searchapp.exe" || exeName == L"searchhost.exe" ||
|
||||
|
|
@ -30,25 +26,27 @@ bool WindowHelper::IsStartMenu(HWND hWnd) noexcept {
|
|||
}
|
||||
|
||||
bool WindowHelper::IsForbiddenSystemWindow(HWND hwndSrc) noexcept {
|
||||
// 禁止缩放的系统窗口
|
||||
// (可执行文件名, 类名)
|
||||
static const phmap::flat_hash_set<std::pair<std::wstring_view, std::wstring_view>> systemWindows{
|
||||
{ L"explorer.exe", L"Shell_TrayWnd" }, // 任务栏
|
||||
{ L"explorer.exe", L"Shell_TrayWnd" }, // 任务栏
|
||||
{ L"explorer.exe", L"NotifyIconOverflowWindow" }, // 任务栏通知区域溢出菜单
|
||||
{ L"explorer.exe", L"TopLevelWindowForOverflowXamlIsland" }, // 任务栏通知区域溢出菜单 (Win11)
|
||||
{ L"explorer.exe", L"WorkerW" }, // 桌面窗口
|
||||
{ L"explorer.exe", L"Progman" }, // 桌面窗口
|
||||
{ L"explorer.exe", L"ForegroundStaging" }, // 任务视图
|
||||
{ L"explorer.exe", L"XamlExplorerHostIslandWindow" }, // 任务视图 (Win11)
|
||||
{ L"explorer.exe", L"MultitaskingViewFrame" }, // 任务视图 (Win10)
|
||||
{ L"startmenuexperiencehost.exe", L"Windows.UI.Core.CoreWindow" }, // 开始菜单
|
||||
{ L"searchapp.exe", L"Windows.UI.Core.CoreWindow" }, // 开始菜单 (Win10)
|
||||
{ L"searchhost.exe", L"Windows.UI.Core.CoreWindow" }, // 开始菜单 (Win11)
|
||||
{ L"shellexperiencehost.exe", L"Windows.UI.Core.CoreWindow" } // 任务中心
|
||||
{ L"explorer.exe", L"WorkerW" }, // 桌面窗口
|
||||
{ L"explorer.exe", L"Progman" }, // 桌面窗口
|
||||
{ L"explorer.exe", L"ForegroundStaging" }, // 任务视图
|
||||
{ L"explorer.exe", L"MultitaskingViewFrame" }, // 任务视图 (Win10)
|
||||
{ L"explorer.exe", L"XamlExplorerHostIslandWindow" }, // 任务视图 (Win11)
|
||||
{ L"startmenuexperiencehost.exe", CoreWindowClassName }, // 开始菜单
|
||||
{ L"searchapp.exe", CoreWindowClassName }, // 开始菜单 (Win10)
|
||||
{ L"searchhost.exe", CoreWindowClassName }, // 开始菜单 (Win11)
|
||||
{ L"shellexperiencehost.exe", CoreWindowClassName } // 任务中心
|
||||
};
|
||||
|
||||
std::wstring exeName = Win32Helper::GetWindowExeName(hwndSrc);
|
||||
StrHelper::ToLowerCase(exeName);
|
||||
|
||||
return systemWindows.contains(std::make_pair(
|
||||
GetExeName(hwndSrc), Win32Helper::GetWndClassName(hwndSrc)));
|
||||
exeName, Win32Helper::GetWindowClassName(hwndSrc)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,15 +50,15 @@ struct serializer<
|
|||
type_prop::not_a_fundamental,
|
||||
ser_case::use_internal_serializer,
|
||||
F,
|
||||
Magpie::SmallVector<T, N>
|
||||
SmallVector<T, N>
|
||||
> {
|
||||
template <typename Archive>
|
||||
static Archive& save(Archive& ar, const Magpie::SmallVector<T, N>& vector) noexcept {
|
||||
static Archive& save(Archive& ar, const SmallVectorImpl<T>& vector) noexcept {
|
||||
return concepts::array::save<F>(ar, vector);
|
||||
}
|
||||
|
||||
template <typename Archive>
|
||||
static Archive& load(Archive& ar, Magpie::SmallVector<T, N>& vector) noexcept {
|
||||
static Archive& load(Archive& ar, SmallVectorImpl<T>& vector) noexcept {
|
||||
return concepts::array::load<F>(ar, vector);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
#define DEFINE_FLAG_ACCESSOR(Name, FlagBit, FlagsVar) \
|
||||
bool Name() const noexcept { return WI_IsFlagSet(FlagsVar, FlagBit); } \
|
||||
void Name(bool value) noexcept { WI_UpdateFlag(FlagsVar, FlagBit, value); }
|
||||
|
||||
#define _WIDEN_HELPER(x) L ## x
|
||||
#define WIDEN(x) _WIDEN_HELPER(x)
|
||||
#define _STRING_HELPER(x) #x
|
||||
#define STRING(x) _STRING_HELPER(x)
|
||||
|
||||
struct Ignore {
|
||||
constexpr Ignore() noexcept = default;
|
||||
|
||||
template <typename T>
|
||||
constexpr Ignore(const T&) noexcept {}
|
||||
|
||||
template <typename T>
|
||||
constexpr const Ignore& operator=(const T&) const noexcept {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
|
@ -20,7 +20,7 @@ struct EffectCompiler {
|
|||
static uint32_t Compile(
|
||||
struct EffectDesc& desc,
|
||||
uint32_t flags, // EffectCompilerFlags
|
||||
const phmap::flat_hash_map<std::wstring, float>* inlineParams = nullptr
|
||||
const phmap::flat_hash_map<std::string, float>* inlineParams = nullptr
|
||||
) noexcept;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,18 @@ enum class ScalingError {
|
|||
InvalidScalingMode,
|
||||
// 启用触控支持失败
|
||||
TouchSupport,
|
||||
// 3D 游戏模式下不支持窗口模式缩放
|
||||
Windowed3DGameMode,
|
||||
// 通用的不支持缩放错误
|
||||
InvalidSourceWindow,
|
||||
// 不支持缩放系统窗口,这个错误无需显示消息
|
||||
SystemWindow,
|
||||
// 因窗口已最大化或全屏而无法缩放,可通过更改设置强制缩放
|
||||
Maximized,
|
||||
// 因窗口的 IL 更高而无法缩放
|
||||
LowIntegrityLevel,
|
||||
// 应用自定义裁剪后尺寸太小或为负
|
||||
InvalidCropping,
|
||||
// 窗口不符合窗口模式缩放的条件,如已最大化
|
||||
BannedInWindowedMode,
|
||||
|
||||
/////////////////////////////////////
|
||||
//
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ enum class CaptureMethod {
|
|||
DesktopDuplication,
|
||||
GDI,
|
||||
DwmSharedSurface,
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum class MultiMonitorUsage {
|
||||
Closest,
|
||||
Intersected,
|
||||
All,
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum class CursorInterpolationMode {
|
||||
|
|
@ -40,27 +42,25 @@ struct GraphicsCardId {
|
|||
};
|
||||
|
||||
struct ScalingFlags {
|
||||
static constexpr uint32_t DisableWindowResizing = 1;
|
||||
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 ShowFPS = 1 << 7;
|
||||
static constexpr uint32_t CaptureTitleBar = 1 << 10;
|
||||
static constexpr uint32_t AdjustCursorSpeed = 1 << 11;
|
||||
static constexpr uint32_t DrawCursor = 1 << 12;
|
||||
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,指定此标志会使 Magpie.Core 创建辅助窗口以拦截
|
||||
// 黑边上的触控输入。
|
||||
// 只影响缩放行为,Magpie.Core 不负责启动 TouchHelper.exe
|
||||
static constexpr uint32_t IsTouchSupportEnabled = 1 << 17;
|
||||
static constexpr uint32_t InlineParams = 1 << 18;
|
||||
static constexpr uint32_t IsFP16Disabled = 1 << 19;
|
||||
static constexpr uint32_t BenchmarkMode = 1 << 20;
|
||||
static constexpr uint32_t DeveloperMode = 1 << 21;
|
||||
};
|
||||
|
||||
enum class ScalingType {
|
||||
|
|
@ -71,14 +71,14 @@ enum class ScalingType {
|
|||
};
|
||||
|
||||
struct EffectOption {
|
||||
std::wstring name;
|
||||
phmap::flat_hash_map<std::wstring, float> parameters;
|
||||
std::string name;
|
||||
phmap::flat_hash_map<std::string, float> parameters;
|
||||
ScalingType scalingType = ScalingType::Normal;
|
||||
std::pair<float, float> scale = { 1.0f,1.0f };
|
||||
|
||||
bool HasScale() const noexcept {
|
||||
return scalingType != ScalingType::Normal ||
|
||||
std::abs(scale.first - 1.0f) > 1e-5 || std::abs(scale.second - 1.0f) > 1e-5;
|
||||
!IsApprox(scale.first, 1.0f) || !IsApprox(scale.second, 1.0f);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -88,7 +88,33 @@ enum class DuplicateFrameDetectionMode {
|
|||
Never
|
||||
};
|
||||
|
||||
enum class ToolbarState {
|
||||
Off,
|
||||
AlwaysShow,
|
||||
AutoHide,
|
||||
COUNT
|
||||
};
|
||||
|
||||
struct OverlayWindowOption {
|
||||
// 0: 位于左侧,hPos 是窗口左边界和画面左边界距离(所有距离都是应用 DPI 缩放前的值)
|
||||
// 1: 位于中侧,hPos 是窗口中心点和画面左边界距离与画面宽度之比
|
||||
// 2: 位于右侧,hPos 是窗口右边界和画面右边界距离
|
||||
uint16_t hArea = 0;
|
||||
// 0: 位于上侧,vPos 是窗口上边界和画面上边界距离
|
||||
// 1: 位于中侧,vPos 是窗口中心点和画面上边界距离与画面高度之比
|
||||
// 3: 位于下侧,vPos 是窗口下边界和画面下边界距离
|
||||
uint16_t vArea = 0;
|
||||
float hPos = 0.0f;
|
||||
float vPos = 0.0f;
|
||||
};
|
||||
|
||||
struct OverlayOptions {
|
||||
phmap::flat_hash_map<std::string, OverlayWindowOption> windows;
|
||||
};
|
||||
|
||||
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)
|
||||
|
|
@ -102,15 +128,16 @@ struct ScalingOptions {
|
|||
DEFINE_FLAG_ACCESSOR(IsAllowScalingMaximized, ScalingFlags::AllowScalingMaximized, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsSimulateExclusiveFullscreen, ScalingFlags::SimulateExclusiveFullscreen, flags)
|
||||
DEFINE_FLAG_ACCESSOR(Is3DGameMode, ScalingFlags::Is3DGameMode, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsShowFPS, ScalingFlags::ShowFPS, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsWindowResizingDisabled, ScalingFlags::DisableWindowResizing, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsCaptureTitleBar, ScalingFlags::CaptureTitleBar, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsAdjustCursorSpeed, ScalingFlags::AdjustCursorSpeed, flags)
|
||||
DEFINE_FLAG_ACCESSOR(IsDrawCursor, ScalingFlags::DrawCursor, 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{};
|
||||
uint32_t flags = ScalingFlags::AdjustCursorSpeed | ScalingFlags::DrawCursor; // ScalingFlags
|
||||
GraphicsCardId graphicsCardId;
|
||||
float minFrameRate = 0.0f;
|
||||
std::optional<float> maxFrameRate;
|
||||
|
|
@ -118,12 +145,16 @@ struct ScalingOptions {
|
|||
CaptureMethod captureMethod = CaptureMethod::GraphicsCapture;
|
||||
MultiMonitorUsage multiMonitorUsage = MultiMonitorUsage::Closest;
|
||||
CursorInterpolationMode cursorInterpolationMode = CursorInterpolationMode::NearestNeighbor;
|
||||
|
||||
std::vector<EffectOption> effects;
|
||||
|
||||
DuplicateFrameDetectionMode duplicateFrameDetectionMode = DuplicateFrameDetectionMode::Dynamic;
|
||||
ToolbarState initialToolbarState = ToolbarState::AutoHide;
|
||||
float initialWindowedScaleFactor = 0.0f;
|
||||
std::filesystem::path screenshotsDir;
|
||||
|
||||
void Log() const noexcept;
|
||||
// 下面的成员支持在缩放时修改
|
||||
OverlayOptions overlayOptions;
|
||||
|
||||
void (*showToast)(HWND hwndTarget, std::wstring_view msg) = nullptr;
|
||||
void (*save)(const ScalingOptions& options, HWND hwndScaling) = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ public:
|
|||
ScalingRuntime();
|
||||
~ScalingRuntime();
|
||||
|
||||
void Start(HWND hwndSrc, struct ScalingOptions&& options);
|
||||
bool Start(HWND hwndSrc, struct ScalingOptions&& options);
|
||||
|
||||
void ToggleOverlay();
|
||||
void ToggleToolbarState();
|
||||
|
||||
void Stop();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
#include <compare>
|
||||
#include <tuple>
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
struct Version {
|
||||
constexpr Version() {}
|
||||
constexpr Version(uint32_t major, uint32_t minor, uint32_t patch)
|
||||
: major(major), minor(minor), patch(patch) {}
|
||||
|
||||
std::strong_ordering operator<=>(const Version& other) const noexcept {
|
||||
return std::make_tuple(major, minor, patch) <=> std::make_tuple(other.major, other.minor, other.patch);
|
||||
}
|
||||
|
||||
bool Parse(std::string_view str);
|
||||
|
||||
std::wstring ToString() const noexcept {
|
||||
return fmt::format(L"{}.{}.{}", major, minor, patch);
|
||||
}
|
||||
|
||||
uint32_t major = 0;
|
||||
uint32_t minor = 0;
|
||||
uint32_t patch = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -8,17 +8,38 @@ struct Win32Helper {
|
|||
return { rect.right - rect.left, rect.bottom - rect.top };
|
||||
}
|
||||
|
||||
static bool CheckOverlap(const RECT& r1, const RECT& r2) noexcept {
|
||||
static bool IsRectOverlap(const RECT& r1, const RECT& r2) noexcept {
|
||||
return r1.right > r2.left && r1.bottom > r2.top && r1.left < r2.right && r1.top < r2.bottom;
|
||||
}
|
||||
|
||||
static std::wstring GetWndClassName(HWND hWnd) noexcept;
|
||||
// Win32 API 不能内联,所以我们自己实现
|
||||
static void OffsetRect(RECT& rect, LONG offsetX, LONG offsetY) noexcept {
|
||||
rect.left += offsetX;
|
||||
rect.top += offsetY;
|
||||
rect.right += offsetX;
|
||||
rect.bottom += offsetY;
|
||||
}
|
||||
|
||||
static std::wstring GetWndTitle(HWND hWnd) noexcept;
|
||||
static bool IntersectRect(RECT& result, const RECT& r1, const RECT& r2) noexcept {
|
||||
// 计算重叠部分
|
||||
result.left = std::max(r1.left, r2.left);
|
||||
result.top = std::max(r1.top, r2.top);
|
||||
result.right = std::min(r1.right, r2.right);
|
||||
result.bottom = std::min(r1.bottom, r2.bottom);
|
||||
|
||||
static wil::unique_process_handle GetWndProcessHandle(HWND hWnd) noexcept;
|
||||
// 判断重叠部分是否是正面积
|
||||
return result.left < result.right && result.top < result.bottom;
|
||||
}
|
||||
|
||||
static std::wstring GetPathOfWnd(HWND hWnd) noexcept;
|
||||
static std::wstring GetWindowClassName(HWND hWnd) noexcept;
|
||||
|
||||
static std::wstring GetWindowTitle(HWND hWnd) noexcept;
|
||||
|
||||
static wil::unique_process_handle GetWindowProcessHandle(HWND hWnd) noexcept;
|
||||
|
||||
static std::wstring GetWindowPath(HWND hWnd) noexcept;
|
||||
|
||||
static std::wstring GetWindowExeName(HWND hWnd) noexcept;
|
||||
|
||||
static UINT GetWindowShowCmd(HWND hWnd) noexcept;
|
||||
|
||||
|
|
@ -26,11 +47,18 @@ struct Win32Helper {
|
|||
|
||||
static bool GetWindowFrameRect(HWND hWnd, RECT& rect) noexcept;
|
||||
|
||||
static uint32_t GetNativeWindowBorderThickness(uint32_t dpi) noexcept;
|
||||
|
||||
// 模拟 OS 命中测试的逻辑,检查所有层级的子窗口
|
||||
static int16_t AdvancedWindowHitTest(HWND hWnd, POINT ptScreen, UINT timeout, HWND* hwndInvolve = nullptr) noexcept;
|
||||
|
||||
static bool IsWindowHung(HWND hWnd) noexcept;
|
||||
|
||||
static bool ReadFile(const wchar_t* fileName, std::vector<uint8_t>& result) noexcept;
|
||||
|
||||
static bool ReadTextFile(const wchar_t* fileName, std::string& result) noexcept;
|
||||
static bool WriteFile(const wchar_t* fileName, std::span<uint8_t> buffer) noexcept;
|
||||
|
||||
static bool WriteFile(const wchar_t* fileName, const void* buffer, size_t bufferSize) noexcept;
|
||||
static bool ReadTextFile(const wchar_t* fileName, std::string& result) noexcept;
|
||||
|
||||
static bool WriteTextFile(const wchar_t* fileName, std::string_view text) noexcept;
|
||||
|
||||
|
|
@ -46,6 +74,14 @@ struct Win32Helper {
|
|||
constexpr OSVersion(uint32_t major, uint32_t minor, uint32_t patch)
|
||||
: Version(major, minor, patch) {}
|
||||
|
||||
bool IsWin10() const noexcept {
|
||||
return !IsWin11();
|
||||
}
|
||||
|
||||
bool IsWin11() const noexcept {
|
||||
return Is21H2OrNewer();
|
||||
}
|
||||
|
||||
bool Is20H1OrNewer() const noexcept {
|
||||
return *this >= Version(10, 0, 19041);
|
||||
}
|
||||
|
|
@ -53,10 +89,6 @@ struct Win32Helper {
|
|||
// 下面为 Win11
|
||||
// 不考虑代号相同的 Win10
|
||||
|
||||
bool IsWin11() const noexcept {
|
||||
return Is21H2OrNewer();
|
||||
}
|
||||
|
||||
bool Is21H2OrNewer() const noexcept {
|
||||
return *this >= Version(10, 0, 22000);
|
||||
}
|
||||
|
|
@ -64,6 +96,10 @@ struct Win32Helper {
|
|||
bool Is22H2OrNewer() const noexcept {
|
||||
return *this >= Version(10, 0, 22621);
|
||||
}
|
||||
|
||||
bool Is24H2OrNewer() const noexcept {
|
||||
return *this >= Version(10, 0, 26100);
|
||||
}
|
||||
};
|
||||
|
||||
static const OSVersion& GetOSVersion() noexcept;
|
||||
|
|
@ -72,9 +108,6 @@ struct Win32Helper {
|
|||
// 执行完毕后返回
|
||||
static void RunParallel(std::function<void(uint32_t)> func, uint32_t times) noexcept;
|
||||
|
||||
// 强制切换前台窗口
|
||||
static bool SetForegroundWindow(HWND hWnd) noexcept;
|
||||
|
||||
// 获取 Virtual Key 的名字
|
||||
static const std::wstring& GetKeyName(uint8_t key) noexcept;
|
||||
|
||||
|
|
@ -141,7 +174,7 @@ struct Win32Helper {
|
|||
// 不应在主线程调用
|
||||
static bool OpenFolderAndSelectFile(const wchar_t* fileName) noexcept;
|
||||
|
||||
static const std::wstring& GetExePath() noexcept;
|
||||
static const std::filesystem::path& GetExePath() noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240405.15" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.250303.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.250325.1" targetFramework="native" />
|
||||
</packages>
|
||||
|
|
@ -20,14 +20,18 @@
|
|||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
|
||||
// WIL
|
||||
#include <wil/resource.h>
|
||||
#include <wil/win32_helpers.h>
|
||||
#include <wil/filesystem.h>
|
||||
// wil::string_maker<std::wstring> 需要启用异常,应最后包含
|
||||
#define WIL_ENABLE_EXCEPTIONS
|
||||
// 防止编译失败
|
||||
#define RESOURCE_SUPPRESS_STL
|
||||
#include <wil/stl.h>
|
||||
#undef RESOURCE_SUPPRESS_STL
|
||||
#undef WIL_ENABLE_EXCEPTIONS
|
||||
|
||||
// C++/WinRT
|
||||
|
|
@ -56,12 +60,3 @@ using namespace Windows::System;
|
|||
#include <fmt/xchar.h>
|
||||
|
||||
#include "CommonDefines.h"
|
||||
|
||||
|
||||
using namespace std::string_literals;
|
||||
using namespace std::string_view_literals;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
// 导入 winrt 命名空间的 co_await 重载
|
||||
// https://devblogs.microsoft.com/oldnewthing/20191219-00/?p=103230
|
||||
using winrt::operator co_await;
|
||||
|
|
|
|||
|
|
@ -22,15 +22,15 @@ void AboutPage::VersionTextBlock_DoubleTapped(IInspectable const&, Input::Double
|
|||
}
|
||||
}
|
||||
|
||||
void AboutPage::BugReportButton_Click(IInspectable const&, RoutedEventArgs const&) {
|
||||
void AboutPage::BugReport_Click(IInspectable const&, RoutedEventArgs const&) {
|
||||
Win32Helper::ShellOpen(L"https://github.com/Blinue/Magpie/issues/new?assignees=&labels=bug&template=01_bug.yaml");
|
||||
}
|
||||
|
||||
void AboutPage::FeatureRequestButton_Click(IInspectable const&, RoutedEventArgs const&) {
|
||||
void AboutPage::FeatureRequest_Click(IInspectable const&, RoutedEventArgs const&) {
|
||||
Win32Helper::ShellOpen(L"https://github.com/Blinue/Magpie/issues/new?assignees=&labels=enhancement&template=03_request.yaml");
|
||||
}
|
||||
|
||||
void AboutPage::DiscussionsButton_Click(IInspectable const&, RoutedEventArgs const&) {
|
||||
void AboutPage::Discussions_Click(IInspectable const&, RoutedEventArgs const&) {
|
||||
Win32Helper::ShellOpen(L"https://github.com/Blinue/Magpie/discussions");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ struct AboutPage : AboutPageT<AboutPage> {
|
|||
|
||||
void VersionTextBlock_DoubleTapped(IInspectable const&, Input::DoubleTappedRoutedEventArgs const&);
|
||||
|
||||
void BugReportButton_Click(IInspectable const&, RoutedEventArgs const&);
|
||||
void FeatureRequestButton_Click(IInspectable const&, RoutedEventArgs const&);
|
||||
void DiscussionsButton_Click(IInspectable const&, RoutedEventArgs const&);
|
||||
void BugReport_Click(IInspectable const&, RoutedEventArgs const&);
|
||||
void FeatureRequest_Click(IInspectable const&, RoutedEventArgs const&);
|
||||
void Discussions_Click(IInspectable const&, RoutedEventArgs const&);
|
||||
|
||||
private:
|
||||
winrt::com_ptr<AboutViewModel> _viewModel = make_self<AboutViewModel>();
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@
|
|||
</local:SettingsGroup>
|
||||
<local:SettingsGroup x:Uid="About_Feedback">
|
||||
<local:SettingsCard x:Uid="About_Feedback_ReportBug"
|
||||
Click="BugReportButton_Click"
|
||||
Click="BugReport_Click"
|
||||
IsClickEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
</local:SettingsCard.ActionIcon>
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="About_Feedback_RequestFeature"
|
||||
Click="FeatureRequestButton_Click"
|
||||
Click="FeatureRequest_Click"
|
||||
IsClickEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
</local:SettingsCard.ActionIcon>
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="About_Feedback_Discussion"
|
||||
Click="DiscussionsButton_Click"
|
||||
Click="Discussions_Click"
|
||||
IsClickEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ bool AboutViewModel::IsNoDownloadProgress() const noexcept {
|
|||
UpdateService& service = UpdateService::Get();
|
||||
switch (service.Status()) {
|
||||
case UpdateStatus::Downloading:
|
||||
return service.DownloadProgress() < 1e-6;
|
||||
return service.DownloadProgress() < FLOAT_EPSILON<double>;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@ void AdaptersService::Uninitialize() noexcept {
|
|||
return;
|
||||
}
|
||||
|
||||
const HANDLE hToastThread = _monitorThread.native_handle();
|
||||
if (!wil::handle_wait(hToastThread, 0)) {
|
||||
const DWORD threadId = GetThreadId(hToastThread);
|
||||
const HANDLE hMonitorThread = _monitorThread.native_handle();
|
||||
if (!wil::handle_wait(hMonitorThread, 0)) {
|
||||
const DWORD threadId = GetThreadId(hMonitorThread);
|
||||
|
||||
// 持续尝试直到 _monitorThread 创建了消息队列
|
||||
while (!PostThreadMessage(threadId, WM_QUIT, 0, 0)) {
|
||||
if (wil::handle_wait(hToastThread, 1)) {
|
||||
if (wil::handle_wait(hMonitorThread, 1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ void AdaptersService::Uninitialize() noexcept {
|
|||
}
|
||||
|
||||
void AdaptersService::StartMonitor() noexcept {
|
||||
_monitorThread = std::thread(std::bind_front(&AdaptersService::_MonitorThreadProc, this));
|
||||
_monitorThread = std::thread(&AdaptersService::_MonitorThreadProc, this);
|
||||
}
|
||||
|
||||
bool AdaptersService::_GatherAdapterInfos(
|
||||
|
|
@ -138,7 +138,7 @@ bool AdaptersService::_GatherAdapterInfos(
|
|||
|
||||
void AdaptersService::_MonitorThreadProc() noexcept {
|
||||
#ifdef _DEBUG
|
||||
SetThreadDescription(GetCurrentThread(), L"AdaptersService 线程");
|
||||
SetThreadDescription(GetCurrentThread(), L"Magpie-AdaptersService 线程");
|
||||
#endif
|
||||
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2021 - present, Liu Xu
|
||||
// Copyright (c) Xu
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
|
|
@ -129,7 +129,7 @@ bool App::Initialize(const wchar_t* arguments) {
|
|||
|
||||
if (CoreWindow coreWindow = CoreWindow::GetForCurrentThread()) {
|
||||
// Win10 中隐藏 DesktopWindowXamlSource 窗口
|
||||
if (!Win32Helper::GetOSVersion().IsWin11()) {
|
||||
if (Win32Helper::GetOSVersion().IsWin10()) {
|
||||
HWND hwndDWXS;
|
||||
coreWindow.as<ICoreWindowInterop>()->get_WindowHandle(&hwndDWXS);
|
||||
ShowWindow(hwndDWXS, SW_HIDE);
|
||||
|
|
@ -280,6 +280,7 @@ INumberFormatter2 App::DoubleFormatter() {
|
|||
|
||||
void App::_Uninitialize() {
|
||||
NotifyIconService::Get().Uninitialize();
|
||||
UpdateService::Get().Uninitialize();
|
||||
ScalingService::Get().Uninitialize();
|
||||
// 提前取消热键注册,这样关闭 Magpie 后立即重新打开不会注册热键失败
|
||||
ShortcutService::Get().Uninitialize();
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -6,20 +6,20 @@
|
|||
#include "ShortcutHelper.h"
|
||||
#include "Profile.h"
|
||||
#include "CommonSharedConstants.h"
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include "AutoStartHelper.h"
|
||||
#include "ScalingModesService.h"
|
||||
#include "JsonHelper.h"
|
||||
#include "ScalingMode.h"
|
||||
#include "LocalizationService.h"
|
||||
#include <ShellScalingApi.h>
|
||||
#include "resource.h"
|
||||
#include "App.h"
|
||||
#include "MainWindow.h"
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <ShlObj.h>
|
||||
#include <ShellScalingApi.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Magpie;
|
||||
using namespace winrt::Magpie;
|
||||
|
||||
namespace Magpie {
|
||||
|
||||
|
|
@ -73,9 +73,9 @@ static void WriteProfile(rapidjson::PrettyWriter<rapidjson::StringBuffer>& write
|
|||
writer.Key("classNameRule");
|
||||
writer.String(StrHelper::UTF16ToUTF8(profile.classNameRule).c_str());
|
||||
writer.Key("launcherPath");
|
||||
writer.String(StrHelper::UTF16ToUTF8(profile.launcherPath).c_str());
|
||||
writer.String(StrHelper::UTF16ToUTF8(profile.launcherPath.native()).c_str());
|
||||
writer.Key("autoScale");
|
||||
writer.Bool(profile.isAutoScale);
|
||||
writer.Uint((uint32_t)profile.autoScale);
|
||||
writer.Key("launchParameters");
|
||||
writer.String(StrHelper::UTF16ToUTF8(profile.launchParameters).c_str());
|
||||
}
|
||||
|
|
@ -87,6 +87,11 @@ static void WriteProfile(rapidjson::PrettyWriter<rapidjson::StringBuffer>& write
|
|||
writer.Key("multiMonitorUsage");
|
||||
writer.Uint((uint32_t)profile.multiMonitorUsage);
|
||||
|
||||
writer.Key("initialWindowedScaleFactor");
|
||||
writer.Uint((uint32_t)profile.initialWindowedScaleFactor);
|
||||
writer.Key("customInitialWindowedScaleFactor");
|
||||
writer.Double(profile.customInitialWindowedScaleFactor);
|
||||
|
||||
writer.Key("graphicsCardId");
|
||||
writer.StartObject();
|
||||
writer.Key("idx");
|
||||
|
|
@ -101,18 +106,12 @@ static void WriteProfile(rapidjson::PrettyWriter<rapidjson::StringBuffer>& write
|
|||
writer.Key("maxFrameRate");
|
||||
writer.Double(profile.maxFrameRate);
|
||||
|
||||
writer.Key("disableWindowResizing");
|
||||
writer.Bool(profile.IsWindowResizingDisabled());
|
||||
writer.Key("3DGameMode");
|
||||
writer.Bool(profile.Is3DGameMode());
|
||||
writer.Key("showFPS");
|
||||
writer.Bool(profile.IsShowFPS());
|
||||
writer.Key("captureTitleBar");
|
||||
writer.Bool(profile.IsCaptureTitleBar());
|
||||
writer.Key("adjustCursorSpeed");
|
||||
writer.Bool(profile.IsAdjustCursorSpeed());
|
||||
writer.Key("drawCursor");
|
||||
writer.Bool(profile.IsDrawCursor());
|
||||
writer.Key("disableDirectFlip");
|
||||
writer.Bool(profile.IsDirectFlipDisabled());
|
||||
|
||||
|
|
@ -199,9 +198,9 @@ bool AppSettings::Initialize() noexcept {
|
|||
|
||||
// 若程序所在目录存在配置文件则为便携模式
|
||||
_isPortableMode = Win32Helper::FileExists(StrHelper::Concat(
|
||||
CommonSharedConstants::CONFIG_DIR, CommonSharedConstants::CONFIG_FILENAME).c_str());
|
||||
CommonSharedConstants::CONFIG_DIR, L"\\", CommonSharedConstants::CONFIG_FILENAME).c_str());
|
||||
|
||||
std::wstring existingConfigPath;
|
||||
std::filesystem::path existingConfigPath;
|
||||
if (!_UpdateConfigPath(&existingConfigPath)) {
|
||||
logger.Error("_UpdateConfigPath 失败");
|
||||
return false;
|
||||
|
|
@ -227,7 +226,7 @@ bool AppSettings::Initialize() noexcept {
|
|||
hstring title = resourceLoader.GetString(L"AppSettings_ErrorDialog_ReadFailed");
|
||||
hstring content = resourceLoader.GetString(L"AppSettings_ErrorDialog_ConfigLocation");
|
||||
ShowErrorMessage(title.c_str(),
|
||||
fmt::format(fmt::runtime(std::wstring_view(content)), existingConfigPath).c_str());
|
||||
fmt::format(fmt::runtime(std::wstring_view(content)), existingConfigPath.native()).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +247,7 @@ bool AppSettings::Initialize() noexcept {
|
|||
hstring title = resourceLoader.GetString(L"AppSettings_ErrorDialog_NotValidJson");
|
||||
hstring content = resourceLoader.GetString(L"AppSettings_ErrorDialog_ConfigLocation");
|
||||
ShowErrorMessage(title.c_str(),
|
||||
fmt::format(fmt::runtime(std::wstring_view(content)), existingConfigPath).c_str());
|
||||
fmt::format(fmt::runtime(std::wstring_view(content)), existingConfigPath.native()).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -259,7 +258,7 @@ bool AppSettings::Initialize() noexcept {
|
|||
hstring title = resourceLoader.GetString(L"AppSettings_ErrorDialog_ParseFailed");
|
||||
hstring content = resourceLoader.GetString(L"AppSettings_ErrorDialog_ConfigLocation");
|
||||
ShowErrorMessage(title.c_str(),
|
||||
fmt::format(fmt::runtime(std::wstring_view(content)), existingConfigPath).c_str());
|
||||
fmt::format(fmt::runtime(std::wstring_view(content)), existingConfigPath.native()).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -295,7 +294,7 @@ void AppSettings::IsPortableMode(bool value) noexcept {
|
|||
|
||||
if (!value) {
|
||||
// 关闭便携模式需删除本地配置文件
|
||||
if (!DeleteFile(StrHelper::Concat(_configDir, CommonSharedConstants::CONFIG_FILENAME).c_str())) {
|
||||
if (!DeleteFile((_configDir / CommonSharedConstants::CONFIG_FILENAME).c_str())) {
|
||||
if (GetLastError() != ERROR_FILE_NOT_FOUND) {
|
||||
Logger::Get().Win32Error("删除本地配置文件失败");
|
||||
return;
|
||||
|
|
@ -346,17 +345,6 @@ void AppSettings::SetShortcut(ShortcutAction action, const Shortcut& value) {
|
|||
SaveAsync();
|
||||
}
|
||||
|
||||
void AppSettings::IsAutoRestore(bool value) noexcept {
|
||||
if (_isAutoRestore == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isAutoRestore = value;
|
||||
IsAutoRestoreChanged.Invoke(value);
|
||||
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
void AppSettings::CountdownSeconds(uint32_t value) noexcept {
|
||||
if (_countdownSeconds == value) {
|
||||
return;
|
||||
|
|
@ -412,6 +400,103 @@ void AppSettings::IsShowNotifyIcon(bool value) noexcept {
|
|||
SaveAsync();
|
||||
}
|
||||
|
||||
static std::filesystem::path GetSystemScreenshotsDir() noexcept {
|
||||
// 如果 Screenshots 文件夹不存在将失败
|
||||
wil::unique_cotaskmem_string folder;
|
||||
HRESULT hr = SHGetKnownFolderPath(
|
||||
FOLDERID_Screenshots, KF_FLAG_DEFAULT, NULL, folder.put());
|
||||
if (SUCCEEDED(hr)) {
|
||||
return folder.get();
|
||||
}
|
||||
|
||||
// 屏幕截图文件夹默认路径是 %USERPROFILE%\Pictures\Screenshots
|
||||
|
||||
hr = SHGetKnownFolderPath(
|
||||
FOLDERID_Pictures, KF_FLAG_DEFAULT, NULL, folder.put());
|
||||
if (SUCCEEDED(hr)) {
|
||||
return StrHelper::Concat(folder.get(), L"\\Screenshots");
|
||||
}
|
||||
|
||||
hr = SHGetKnownFolderPath(
|
||||
FOLDERID_Profile, KF_FLAG_DEFAULT, NULL, folder.put());
|
||||
if (SUCCEEDED(hr)) {
|
||||
return StrHelper::Concat(folder.get(), L"\\Pictures\\Screenshots");
|
||||
}
|
||||
|
||||
Logger::Get().ComError("SHGetKnownFolderPath 失败", hr);
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool IsSubfolder(const std::wstring& sub, const std::wstring& parent) noexcept {
|
||||
if (!sub.starts_with(parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent.size() == sub.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return sub[parent.size()] == L'\\';
|
||||
}
|
||||
|
||||
// 失败时返回空字符串
|
||||
std::filesystem::path AppSettings::ScreenshotsDir() const noexcept {
|
||||
if (_screenshotsDir.empty()) {
|
||||
// 系统“屏幕截图”文件夹
|
||||
return GetSystemScreenshotsDir();
|
||||
} else if (_screenshotsDir.is_relative()) {
|
||||
// 相对路径
|
||||
std::wstring workingDir;
|
||||
HRESULT hr = wil::GetCurrentDirectoryW(workingDir);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("wil::GetCurrentDirectoryW 失败", hr);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (_screenshotsDir == L".") {
|
||||
return std::filesystem::path(std::move(workingDir));
|
||||
} else {
|
||||
return (std::filesystem::path(std::move(workingDir)) / _screenshotsDir).lexically_normal();
|
||||
}
|
||||
} else {
|
||||
// 绝对路径
|
||||
return _screenshotsDir;
|
||||
}
|
||||
}
|
||||
|
||||
void AppSettings::ScreenshotsDir(const std::filesystem::path& value) noexcept {
|
||||
assert(!value.empty());
|
||||
|
||||
if (value == GetSystemScreenshotsDir()) {
|
||||
// 系统“屏幕截图”文件夹
|
||||
_screenshotsDir.clear();
|
||||
} else {
|
||||
std::wstring workingDir;
|
||||
HRESULT hr = wil::GetCurrentDirectoryW(workingDir);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("wil::GetCurrentDirectoryW 失败", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSubfolder(value, workingDir)) {
|
||||
// 保存位置在工作文件夹内则转换为相对路径
|
||||
if (value.native().size() == workingDir.size()) {
|
||||
_screenshotsDir = L".";
|
||||
} else {
|
||||
_screenshotsDir = StrHelper::Concat(
|
||||
L".",
|
||||
std::wstring(value.native().begin() + workingDir.size(), value.native().end())
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 绝对路径
|
||||
_screenshotsDir = value;
|
||||
}
|
||||
}
|
||||
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
void AppSettings::_UpdateWindowPlacement() noexcept {
|
||||
const HWND hwndMain = implementation::App::Get().MainWindow().Handle();;
|
||||
if (!hwndMain) {
|
||||
|
|
@ -439,7 +524,7 @@ void AppSettings::_UpdateWindowPlacement() noexcept {
|
|||
}
|
||||
|
||||
bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
|
||||
if (!Win32Helper::CreateDir(data._configDir, true)) {
|
||||
if (!Win32Helper::CreateDir(data._configDir.native(), true)) {
|
||||
Logger::Get().Win32Error("创建配置文件夹失败");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -477,12 +562,12 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
|
|||
writer.StartObject();
|
||||
writer.Key("scale");
|
||||
writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::Scale]));
|
||||
writer.Key("overlay");
|
||||
writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::Overlay]));
|
||||
writer.Key("windowedModeScale");
|
||||
writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::WindowedModeScale]));
|
||||
writer.Key("toolbar");
|
||||
writer.Uint(EncodeShortcut(data._shortcuts[(size_t)ShortcutAction::Toolbar]));
|
||||
writer.EndObject();
|
||||
|
||||
writer.Key("autoRestore");
|
||||
writer.Bool(data._isAutoRestore);
|
||||
writer.Key("countdownSeconds");
|
||||
writer.Uint(data._countdownSeconds);
|
||||
writer.Key("developerMode");
|
||||
|
|
@ -534,6 +619,30 @@ bool AppSettings::_Save(const _AppSettingsData& data) noexcept {
|
|||
}
|
||||
writer.EndArray();
|
||||
|
||||
writer.Key("overlay");
|
||||
writer.StartObject();
|
||||
writer.Key("initialToolbarState");
|
||||
writer.Uint((uint32_t)_initialToolbarState);
|
||||
writer.Key("screenshotsDir");
|
||||
writer.String(StrHelper::UTF16ToUTF8(_screenshotsDir.native()).c_str());
|
||||
writer.Key("windows");
|
||||
writer.StartObject();
|
||||
for (const auto& [name, windowOption] : _overlayOptions.windows) {
|
||||
writer.Key(name.c_str());
|
||||
writer.StartObject();
|
||||
writer.Key("hArea");
|
||||
writer.Uint(windowOption.hArea);
|
||||
writer.Key("vArea");
|
||||
writer.Uint(windowOption.vArea);
|
||||
writer.Key("hPos");
|
||||
writer.Double(windowOption.hPos);
|
||||
writer.Key("vPos");
|
||||
writer.Double(windowOption.vPos);
|
||||
writer.EndObject();
|
||||
}
|
||||
writer.EndObject();
|
||||
writer.EndObject();
|
||||
|
||||
writer.EndObject();
|
||||
|
||||
// 防止并行写入
|
||||
|
|
@ -578,7 +687,7 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
|
|||
|
||||
auto windowPosNode = root.FindMember("windowPos");
|
||||
if (windowPosNode != root.MemberEnd() && windowPosNode->value.IsObject()) {
|
||||
const auto& windowPosObj = windowPosNode->value.GetObj();
|
||||
auto windowPosObj = windowPosNode->value.GetObj();
|
||||
|
||||
Point center{};
|
||||
Size size{};
|
||||
|
|
@ -629,20 +738,29 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
|
|||
shortcutsNode= root.FindMember("hotkeys");
|
||||
}
|
||||
if (shortcutsNode != root.MemberEnd() && shortcutsNode->value.IsObject()) {
|
||||
const auto& shortcutsObj = shortcutsNode->value.GetObj();
|
||||
auto shortcutsObj = shortcutsNode->value.GetObj();
|
||||
|
||||
auto scaleNode = shortcutsObj.FindMember("scale");
|
||||
if (scaleNode != shortcutsObj.MemberEnd() && scaleNode->value.IsUint()) {
|
||||
DecodeShortcut(scaleNode->value.GetUint(), _shortcuts[(size_t)ShortcutAction::Scale]);
|
||||
}
|
||||
|
||||
auto overlayNode = shortcutsObj.FindMember("overlay");
|
||||
if (overlayNode != shortcutsObj.MemberEnd() && overlayNode->value.IsUint()) {
|
||||
DecodeShortcut(overlayNode->value.GetUint(), _shortcuts[(size_t)ShortcutAction::Overlay]);
|
||||
auto windowedModeScaleNode = shortcutsObj.FindMember("windowedModeScale");
|
||||
if (windowedModeScaleNode != shortcutsObj.MemberEnd() && windowedModeScaleNode->value.IsUint()) {
|
||||
DecodeShortcut(windowedModeScaleNode->value.GetUint(), _shortcuts[(size_t)ShortcutAction::WindowedModeScale]);
|
||||
}
|
||||
|
||||
auto toolbarNode = shortcutsObj.FindMember("toolbar");
|
||||
if (toolbarNode == shortcutsObj.MemberEnd()) {
|
||||
// v0.12 前使用 overlay
|
||||
toolbarNode = shortcutsObj.FindMember("overlay");
|
||||
}
|
||||
|
||||
if (toolbarNode != shortcutsObj.MemberEnd() && toolbarNode->value.IsUint()) {
|
||||
DecodeShortcut(toolbarNode->value.GetUint(), _shortcuts[(size_t)ShortcutAction::Toolbar]);
|
||||
}
|
||||
}
|
||||
|
||||
JsonHelper::ReadBool(root, "autoRestore", _isAutoRestore);
|
||||
if (!JsonHelper::ReadUInt(root, "countdownSeconds", _countdownSeconds, true)) {
|
||||
// v0.10.0-preview1 使用 downCount
|
||||
JsonHelper::ReadUInt(root, "downCount", _countdownSeconds);
|
||||
|
|
@ -698,7 +816,7 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
|
|||
scaleProfilesNode = root.FindMember("scalingProfiles");
|
||||
}
|
||||
if (scaleProfilesNode != root.MemberEnd() && scaleProfilesNode->value.IsArray()) {
|
||||
const auto& scaleProfilesArray = scaleProfilesNode->value.GetArray();
|
||||
auto scaleProfilesArray = scaleProfilesNode->value.GetArray();
|
||||
|
||||
const rapidjson::SizeType size = scaleProfilesArray.Size();
|
||||
if (size > 0) {
|
||||
|
|
@ -723,6 +841,48 @@ void AppSettings::_LoadSettings(const rapidjson::GenericObject<true, rapidjson::
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto overlayNode = root.FindMember("overlay");
|
||||
if (overlayNode != root.MemberEnd() && overlayNode->value.IsObject()) {
|
||||
auto overlayObj = overlayNode->value.GetObj();
|
||||
|
||||
uint32_t initialToolbarState = (uint32_t)ToolbarState::AutoHide;
|
||||
JsonHelper::ReadUInt(overlayObj, "initialToolbarState", initialToolbarState);
|
||||
if (initialToolbarState >= (uint32_t)ToolbarState::COUNT) {
|
||||
initialToolbarState = (uint32_t)ToolbarState::AutoHide;
|
||||
}
|
||||
_initialToolbarState = (ToolbarState)initialToolbarState;
|
||||
|
||||
{
|
||||
std::wstring value;
|
||||
JsonHelper::ReadString(overlayObj, "screenshotsDir", value);
|
||||
_screenshotsDir = std::move(value);
|
||||
}
|
||||
|
||||
auto windowsNode = overlayObj.FindMember("windows");
|
||||
if (windowsNode != overlayObj.MemberEnd() && windowsNode->value.IsObject()) {
|
||||
auto windowsObj = windowsNode->value.GetObj();
|
||||
|
||||
const rapidjson::SizeType size = windowsObj.MemberCount();
|
||||
if (size > 0) {
|
||||
_overlayOptions.windows.reserve(size);
|
||||
|
||||
for (const auto& windowOptionPair : windowsObj) {
|
||||
if (!windowOptionPair.value.IsObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto windowOptionObj = windowOptionPair.value.GetObj();
|
||||
|
||||
OverlayWindowOption& windowOption = _overlayOptions.windows[windowOptionPair.name.GetString()];
|
||||
JsonHelper::ReadUInt16(windowOptionObj, "hArea", windowOption.hArea);
|
||||
JsonHelper::ReadUInt16(windowOptionObj, "vArea", windowOption.vArea);
|
||||
JsonHelper::ReadFloat(windowOptionObj, "hPos", windowOption.hPos);
|
||||
JsonHelper::ReadFloat(windowOptionObj, "vPos", windowOption.vPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AppSettings::_LoadProfile(
|
||||
|
|
@ -757,24 +917,35 @@ bool AppSettings::_LoadProfile(
|
|||
return false;
|
||||
}
|
||||
|
||||
JsonHelper::ReadString(profileObj, "launcherPath", profile.launcherPath);
|
||||
{
|
||||
std::wstring value;
|
||||
JsonHelper::ReadString(profileObj, "launcherPath", value);
|
||||
profile.launcherPath = std::move(value);
|
||||
}
|
||||
|
||||
// 将旧版本的相对路径转换为绝对路径
|
||||
if (!profile.launcherPath.empty() && PathIsRelative(profile.launcherPath.c_str())) {
|
||||
size_t delimPos = profile.pathRule.find_last_of(L'\\');
|
||||
if (delimPos != std::wstring::npos) {
|
||||
wil::unique_hlocal_string combinedPath;
|
||||
if (SUCCEEDED(PathAllocCombine(
|
||||
profile.pathRule.substr(0, delimPos).c_str(),
|
||||
profile.launcherPath.c_str(),
|
||||
PATHCCH_ALLOW_LONG_PATHS,
|
||||
combinedPath.put()
|
||||
))) {
|
||||
profile.launcherPath.assign(combinedPath.get());
|
||||
if (!profile.launcherPath.empty() && profile.launcherPath.is_relative()) {
|
||||
std::filesystem::path exePath(profile.pathRule);
|
||||
profile.launcherPath = (exePath.parent_path() / profile.launcherPath).lexically_normal();
|
||||
}
|
||||
|
||||
{
|
||||
auto autoScaleNode = profileObj.FindMember("autoScale");
|
||||
if (autoScaleNode != profileObj.MemberEnd()) {
|
||||
if (autoScaleNode->value.IsUint()) {
|
||||
uint32_t value = autoScaleNode->value.GetUint();
|
||||
if (value >= (uint32_t)AutoScale::COUNT) {
|
||||
value = (uint32_t)AutoScale::Disabled;
|
||||
}
|
||||
profile.autoScale = (AutoScale)value;
|
||||
} else if (autoScaleNode->value.IsBool()) {
|
||||
// v0.12 前为布尔值
|
||||
profile.autoScale = autoScaleNode->value.GetBool() ?
|
||||
AutoScale::Fullscreen : AutoScale::Disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonHelper::ReadBool(profileObj, "autoScale", profile.isAutoScale);
|
||||
|
||||
JsonHelper::ReadString(profileObj, "launchParameters", profile.launchParameters);
|
||||
}
|
||||
|
||||
|
|
@ -790,7 +961,7 @@ bool AppSettings::_LoadProfile(
|
|||
JsonHelper::ReadUInt(profileObj, "captureMode", captureMethod);
|
||||
}
|
||||
|
||||
if (captureMethod > 3) {
|
||||
if (captureMethod >= (uint32_t)CaptureMethod::COUNT) {
|
||||
captureMethod = (uint32_t)CaptureMethod::GraphicsCapture;
|
||||
} else if (captureMethod == (uint32_t)CaptureMethod::DesktopDuplication) {
|
||||
// Desktop Duplication 捕获模式要求 Win10 20H1+
|
||||
|
|
@ -804,11 +975,26 @@ bool AppSettings::_LoadProfile(
|
|||
{
|
||||
uint32_t multiMonitorUsage = (uint32_t)MultiMonitorUsage::Closest;
|
||||
JsonHelper::ReadUInt(profileObj, "multiMonitorUsage", multiMonitorUsage);
|
||||
if (multiMonitorUsage > 2) {
|
||||
if (multiMonitorUsage >= (uint32_t)MultiMonitorUsage::COUNT) {
|
||||
multiMonitorUsage = (uint32_t)MultiMonitorUsage::Closest;
|
||||
}
|
||||
profile.multiMonitorUsage = (MultiMonitorUsage)multiMonitorUsage;
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t factor = (uint32_t)InitialWindowedScaleFactor::Auto;
|
||||
JsonHelper::ReadUInt(profileObj, "initialWindowedScaleFactor", factor);
|
||||
if (factor >= (uint32_t)InitialWindowedScaleFactor::COUNT) {
|
||||
factor = (uint32_t)InitialWindowedScaleFactor::Auto;
|
||||
}
|
||||
profile.initialWindowedScaleFactor = (InitialWindowedScaleFactor)factor;
|
||||
}
|
||||
|
||||
JsonHelper::ReadFloat(profileObj, "customInitialWindowedScaleFactor",
|
||||
profile.customInitialWindowedScaleFactor);
|
||||
if (profile.customInitialWindowedScaleFactor < 1.0f) {
|
||||
profile.customInitialWindowedScaleFactor = 1.0f;
|
||||
}
|
||||
|
||||
{
|
||||
auto graphicsCardIdNode = profileObj.FindMember("graphicsCardId");
|
||||
|
|
@ -850,21 +1036,18 @@ bool AppSettings::_LoadProfile(
|
|||
profile.maxFrameRate = 60.0f;
|
||||
}
|
||||
|
||||
JsonHelper::ReadBoolFlag(profileObj, "disableWindowResizing", ScalingFlags::DisableWindowResizing, profile.scalingFlags);
|
||||
JsonHelper::ReadBoolFlag(profileObj, "3DGameMode", ScalingFlags::Is3DGameMode, profile.scalingFlags);
|
||||
JsonHelper::ReadBoolFlag(profileObj, "showFPS", ScalingFlags::ShowFPS, profile.scalingFlags);
|
||||
if (!JsonHelper::ReadBoolFlag(profileObj, "captureTitleBar", ScalingFlags::CaptureTitleBar, profile.scalingFlags, true)) {
|
||||
// v0.10.0-preview1 使用 reserveTitleBar
|
||||
JsonHelper::ReadBoolFlag(profileObj, "reserveTitleBar", ScalingFlags::CaptureTitleBar, profile.scalingFlags);
|
||||
}
|
||||
JsonHelper::ReadBoolFlag(profileObj, "adjustCursorSpeed", ScalingFlags::AdjustCursorSpeed, profile.scalingFlags);
|
||||
JsonHelper::ReadBoolFlag(profileObj, "drawCursor", ScalingFlags::DrawCursor, profile.scalingFlags);
|
||||
JsonHelper::ReadBoolFlag(profileObj, "disableDirectFlip", ScalingFlags::DisableDirectFlip, profile.scalingFlags);
|
||||
|
||||
{
|
||||
uint32_t cursorScaling = (uint32_t)CursorScaling::NoScaling;
|
||||
JsonHelper::ReadUInt(profileObj, "cursorScaling", cursorScaling);
|
||||
if (cursorScaling > 7) {
|
||||
if (cursorScaling >= (uint32_t)CursorScaling::COUNT) {
|
||||
cursorScaling = (uint32_t)CursorScaling::NoScaling;
|
||||
}
|
||||
profile.cursorScaling = (CursorScaling)cursorScaling;
|
||||
|
|
@ -888,7 +1071,7 @@ bool AppSettings::_LoadProfile(
|
|||
|
||||
auto croppingNode = profileObj.FindMember("cropping");
|
||||
if (croppingNode != profileObj.MemberEnd() && croppingNode->value.IsObject()) {
|
||||
const auto& croppingObj = croppingNode->value.GetObj();
|
||||
auto croppingObj = croppingNode->value.GetObj();
|
||||
|
||||
if (!JsonHelper::ReadFloat(croppingObj, "left", profile.cropping.Left, true)
|
||||
|| profile.cropping.Left < 0
|
||||
|
|
@ -911,16 +1094,25 @@ bool AppSettings::_SetDefaultShortcuts() noexcept {
|
|||
|
||||
Shortcut& scaleShortcut = _shortcuts[(size_t)ShortcutAction::Scale];
|
||||
if (scaleShortcut.IsEmpty()) {
|
||||
scaleShortcut.win = true;
|
||||
scaleShortcut.alt = true;
|
||||
scaleShortcut.shift = true;
|
||||
scaleShortcut.code = 'A';
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Shortcut& overlayShortcut = _shortcuts[(size_t)ShortcutAction::Overlay];
|
||||
Shortcut& windowedModeScaleShortcut = _shortcuts[(size_t)ShortcutAction::WindowedModeScale];
|
||||
if (windowedModeScaleShortcut.IsEmpty()) {
|
||||
windowedModeScaleShortcut.alt = true;
|
||||
windowedModeScaleShortcut.shift = true;
|
||||
windowedModeScaleShortcut.code = 'Q';
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Shortcut& overlayShortcut = _shortcuts[(size_t)ShortcutAction::Toolbar];
|
||||
if (overlayShortcut.IsEmpty()) {
|
||||
overlayShortcut.win = true;
|
||||
overlayShortcut.alt = true;
|
||||
overlayShortcut.shift = true;
|
||||
overlayShortcut.code = 'D';
|
||||
|
||||
|
|
@ -1005,7 +1197,7 @@ void AppSettings::_SetDefaultScalingModes() noexcept {
|
|||
static std::wstring FindOldConfig(const wchar_t* localAppDataDir) noexcept {
|
||||
for (uint32_t version = CONFIG_VERSION - 1; version >= 2; --version) {
|
||||
std::wstring oldConfigPath = fmt::format(
|
||||
L"{}\\Magpie\\{}v{}\\{}",
|
||||
L"{}\\Magpie\\{}\\v{}\\{}",
|
||||
localAppDataDir,
|
||||
CommonSharedConstants::CONFIG_DIR,
|
||||
version,
|
||||
|
|
@ -1022,6 +1214,7 @@ static std::wstring FindOldConfig(const wchar_t* localAppDataDir) noexcept {
|
|||
localAppDataDir,
|
||||
L"\\Magpie\\",
|
||||
CommonSharedConstants::CONFIG_DIR,
|
||||
L"\\",
|
||||
CommonSharedConstants::CONFIG_FILENAME
|
||||
);
|
||||
|
||||
|
|
@ -1032,15 +1225,17 @@ static std::wstring FindOldConfig(const wchar_t* localAppDataDir) noexcept {
|
|||
return {};
|
||||
}
|
||||
|
||||
bool AppSettings::_UpdateConfigPath(std::wstring* existingConfigPath) noexcept {
|
||||
bool AppSettings::_UpdateConfigPath(std::filesystem::path* existingConfigPath) noexcept {
|
||||
if (_isPortableMode) {
|
||||
HRESULT hr = wil::GetFullPathNameW(CommonSharedConstants::CONFIG_DIR, _configDir);
|
||||
std::wstring value;
|
||||
HRESULT hr = wil::GetFullPathNameW(CommonSharedConstants::CONFIG_DIR, value);
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("GetFullPathNameW 失败", hr);
|
||||
return false;
|
||||
}
|
||||
_configDir = std::move(value);
|
||||
|
||||
_configPath = _configDir + CommonSharedConstants::CONFIG_FILENAME;
|
||||
_configPath = _configDir / CommonSharedConstants::CONFIG_FILENAME;
|
||||
|
||||
if (existingConfigPath) {
|
||||
if (Win32Helper::FileExists(_configPath.c_str())) {
|
||||
|
|
@ -1056,9 +1251,9 @@ bool AppSettings::_UpdateConfigPath(std::wstring* existingConfigPath) noexcept {
|
|||
return false;
|
||||
}
|
||||
|
||||
_configDir = fmt::format(L"{}\\Magpie\\{}v{}\\",
|
||||
_configDir = fmt::format(L"{}\\Magpie\\{}\\v{}\\",
|
||||
localAppDataDir.get(), CommonSharedConstants::CONFIG_DIR, CONFIG_VERSION);
|
||||
_configPath = _configDir + CommonSharedConstants::CONFIG_FILENAME;
|
||||
_configPath = _configDir / CommonSharedConstants::CONFIG_FILENAME;
|
||||
|
||||
if (existingConfigPath) {
|
||||
if (Win32Helper::FileExists(_configPath.c_str())) {
|
||||
|
|
@ -1071,7 +1266,7 @@ bool AppSettings::_UpdateConfigPath(std::wstring* existingConfigPath) noexcept {
|
|||
}
|
||||
|
||||
// 确保配置文件夹存在
|
||||
if (!Win32Helper::CreateDir(_configDir, true)) {
|
||||
if (!Win32Helper::CreateDir(_configDir.native(), true)) {
|
||||
Logger::Get().Win32Error("创建配置文件夹失败");
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ struct _AppSettingsData {
|
|||
Profile _defaultProfile;
|
||||
std::vector<Profile> _profiles;
|
||||
|
||||
std::wstring _configDir;
|
||||
std::wstring _configPath;
|
||||
std::filesystem::path _configDir;
|
||||
std::filesystem::path _configPath;
|
||||
|
||||
// LocalizationService::SupportedLanguages 索引
|
||||
// -1 表示使用系统设置
|
||||
|
|
@ -50,6 +50,12 @@ struct _AppSettingsData {
|
|||
DuplicateFrameDetectionMode::Dynamic;
|
||||
|
||||
float _minFrameRate = 10.0f;
|
||||
|
||||
ToolbarState _initialToolbarState = ToolbarState::AutoHide;
|
||||
// 为空表示 FOLDERID_Screenshots,支持绝对路径和相对路径
|
||||
std::filesystem::path _screenshotsDir;
|
||||
|
||||
OverlayOptions _overlayOptions;
|
||||
|
||||
bool _isPortableMode = false;
|
||||
bool _isAlwaysRunAsAdmin = false;
|
||||
|
|
@ -64,7 +70,6 @@ struct _AppSettingsData {
|
|||
bool _isSimulateExclusiveFullscreen = false;
|
||||
bool _isInlineParams = false;
|
||||
bool _isShowNotifyIcon = true;
|
||||
bool _isAutoRestore = false;
|
||||
bool _isMainWindowMaximized = false;
|
||||
bool _isAutoCheckForUpdates = true;
|
||||
bool _isCheckForPreviewUpdates = false;
|
||||
|
|
@ -87,7 +92,7 @@ public:
|
|||
|
||||
winrt::fire_and_forget SaveAsync() noexcept;
|
||||
|
||||
const std::wstring& ConfigDir() const noexcept {
|
||||
const std::filesystem::path& ConfigDir() const noexcept {
|
||||
return _configDir;
|
||||
}
|
||||
|
||||
|
|
@ -126,12 +131,6 @@ public:
|
|||
|
||||
void SetShortcut(winrt::Magpie::ShortcutAction action, const Shortcut& value);
|
||||
|
||||
bool IsAutoRestore() const noexcept {
|
||||
return _isAutoRestore;
|
||||
}
|
||||
|
||||
void IsAutoRestore(bool value) noexcept;
|
||||
|
||||
uint32_t CountdownSeconds() const noexcept {
|
||||
return _countdownSeconds;
|
||||
}
|
||||
|
|
@ -312,9 +311,25 @@ public:
|
|||
SaveAsync();
|
||||
}
|
||||
|
||||
ToolbarState InitialToolbarState() const noexcept {
|
||||
return _initialToolbarState;
|
||||
}
|
||||
|
||||
void InitialToolbarState(ToolbarState value) noexcept {
|
||||
_initialToolbarState = value;
|
||||
SaveAsync();
|
||||
}
|
||||
|
||||
std::filesystem::path ScreenshotsDir() const noexcept;
|
||||
|
||||
void ScreenshotsDir(const std::filesystem::path& value) noexcept;
|
||||
|
||||
OverlayOptions& OverlayOptions() noexcept {
|
||||
return _overlayOptions;
|
||||
}
|
||||
|
||||
Event<AppTheme> ThemeChanged;
|
||||
Event<winrt::Magpie::ShortcutAction> ShortcutChanged;
|
||||
Event<bool> IsAutoRestoreChanged;
|
||||
Event<uint32_t> CountdownSecondsChanged;
|
||||
Event<bool> IsShowNotifyIconChanged;
|
||||
Event<bool> IsAutoCheckForUpdatesChanged;
|
||||
|
|
@ -337,7 +352,7 @@ private:
|
|||
bool _SetDefaultShortcuts() noexcept;
|
||||
void _SetDefaultScalingModes() noexcept;
|
||||
|
||||
bool _UpdateConfigPath(std::wstring* existingConfigPath = nullptr) noexcept;
|
||||
bool _UpdateConfigPath(std::filesystem::path* existingConfigPath = nullptr) noexcept;
|
||||
|
||||
// 用于同步保存
|
||||
wil::srwlock _saveLock;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include <AppxPackaging.h>
|
||||
#include <propsys.h>
|
||||
#include <winrt/Windows.Graphics.Imaging.h>
|
||||
#include <Shlwapi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Graphics::Imaging;
|
||||
|
|
@ -97,13 +99,13 @@ static std::wstring ResourceFromPri(std::wstring_view packageFullName, std::wstr
|
|||
}
|
||||
|
||||
bool AppXReader::Initialize(HWND hWnd) noexcept {
|
||||
if (Win32Helper::GetWndClassName(hWnd) == L"ApplicationFrameWindow") {
|
||||
if (Win32Helper::GetWindowClassName(hWnd) == L"ApplicationFrameWindow") {
|
||||
// UWP 应用被托管在 ApplicationFrameHost 进程中
|
||||
HWND childHwnd = NULL;
|
||||
EnumChildWindows(
|
||||
hWnd,
|
||||
[](HWND hWnd, LPARAM lParam) {
|
||||
if (Win32Helper::GetWndClassName(hWnd) != L"Windows.UI.Core.CoreWindow") {
|
||||
if (Win32Helper::GetWindowClassName(hWnd) != L"Windows.UI.Core.CoreWindow") {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
@ -599,13 +601,13 @@ static SoftwareBitmap AutoFillBackground(const std::wstring& iconPath, bool isLi
|
|||
const uint8_t* origin = buf.get();
|
||||
for (UINT i = 0; i < height; ++i) {
|
||||
for (UINT j = 0; j < width; ++j, origin += 4, pixels += 4) {
|
||||
float alpha = origin[3] / 255.0f;
|
||||
if (alpha < 1e-5) {
|
||||
const float alpha = origin[3] / 255.0f;
|
||||
if (alpha < FLOAT_EPSILON<float>) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float reverseAlpha = 1 - alpha;
|
||||
if (reverseAlpha < 1e-5) {
|
||||
const float reverseAlpha = 1 - alpha;
|
||||
if (reverseAlpha < FLOAT_EPSILON<float>) {
|
||||
pixels[0] = origin[0];
|
||||
pixels[1] = origin[1];
|
||||
pixels[2] = origin[2];
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
#include "pch.h"
|
||||
#include "AutoStartHelper.h"
|
||||
#include <taskschd.h>
|
||||
#include <Lmcons.h>
|
||||
#include "Logger.h"
|
||||
#include "StrHelper.h"
|
||||
#include "Win32Helper.h"
|
||||
#include <propkey.h>
|
||||
#include <ShlObj.h>
|
||||
|
||||
#include <taskschd.h>
|
||||
#include <Lmcons.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
|
@ -210,7 +209,7 @@ static bool CreateAutoStartTask(bool runElevated, const wchar_t* arguments) noex
|
|||
}
|
||||
|
||||
// Set the path of the executable to Magpie (passed as CustomActionData).
|
||||
const std::wstring& exePath = Win32Helper::GetExePath();
|
||||
const std::filesystem::path& exePath = Win32Helper::GetExePath();
|
||||
hr = execAction->put_Path(wil::make_bstr_nothrow(exePath.c_str()).get());
|
||||
if (FAILED(hr)) {
|
||||
Logger::Get().ComError("设置可执行文件路径失败", hr);
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ using namespace Windows::Graphics::Display;
|
|||
namespace winrt::Magpie::implementation {
|
||||
|
||||
static std::wstring GetProcessDesc(HWND hWnd) {
|
||||
if (Win32Helper::GetWndClassName(hWnd) == L"ApplicationFrameWindow") {
|
||||
if (Win32Helper::GetWindowClassName(hWnd) == L"ApplicationFrameWindow") {
|
||||
// 跳过 UWP 窗口
|
||||
return {};
|
||||
}
|
||||
|
||||
// 移植自 https://github.com/dotnet/runtime/blob/4a63cb28b69e1c48bccf592150be7ba297b67950/src/libraries/System.Diagnostics.FileVersionInfo/src/System/Diagnostics/FileVersionInfo.Windows.cs
|
||||
std::wstring fileName = Win32Helper::GetPathOfWnd(hWnd);
|
||||
std::wstring fileName = Win32Helper::GetWindowPath(hWnd);
|
||||
if (fileName.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -62,11 +62,11 @@ static std::wstring GetProcessDesc(HWND hWnd) {
|
|||
}
|
||||
|
||||
CandidateWindowItem::CandidateWindowItem(HWND hWnd) {
|
||||
_title = Win32Helper::GetWndTitle(hWnd);
|
||||
_title = Win32Helper::GetWindowTitle(hWnd);
|
||||
_defaultProfileName = _title;
|
||||
|
||||
_className = Win32Helper::GetWndClassName(hWnd);
|
||||
_path = Win32Helper::GetPathOfWnd(hWnd);
|
||||
_className = Win32Helper::GetWindowClassName(hWnd);
|
||||
_path = Win32Helper::GetWindowPath(hWnd);
|
||||
|
||||
MUXC::ImageIcon placeholder;
|
||||
placeholder.Width(16);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ EffectParametersViewModel::EffectParametersViewModel(uint32_t scalingModeIdx, ui
|
|||
auto boolParamItem = make_self<ScalingModeBoolParameter>(
|
||||
i,
|
||||
hstring(StrHelper::UTF8ToUTF16(param.label.empty() ? param.name : param.label)),
|
||||
paramValue.has_value() ? std::abs(*paramValue) > 1e-6 : (bool)constant.defaultValue
|
||||
paramValue.has_value() ? std::abs(*paramValue) > FLOAT_EPSILON<float> : (bool)constant.defaultValue
|
||||
);
|
||||
boolParamItem->PropertyChanged({ this, &EffectParametersViewModel::_ScalingModeBoolParameter_PropertyChanged });
|
||||
boolParams.push_back(*boolParamItem);
|
||||
|
|
|
|||
|
|
@ -21,29 +21,31 @@ static void ListEffects(std::vector<std::wstring>& result, std::wstring_view pre
|
|||
|
||||
WIN32_FIND_DATA findData{};
|
||||
wil::unique_hfind hFind(FindFirstFileEx(
|
||||
StrHelper::Concat(CommonSharedConstants::EFFECTS_DIR, prefix, L"*").c_str(),
|
||||
StrHelper::Concat(CommonSharedConstants::EFFECTS_DIR, L"\\", prefix, L"*").c_str(),
|
||||
FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH));
|
||||
if (hFind) {
|
||||
do {
|
||||
std::wstring_view fileName(findData.cFileName);
|
||||
if (fileName == L"." || fileName == L"..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Win32Helper::DirExists(StrHelper::Concat(CommonSharedConstants::EFFECTS_DIR, prefix, fileName).c_str())) {
|
||||
ListEffects(result, StrHelper::Concat(prefix, fileName, L"\\"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fileName.ends_with(L".hlsl")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.emplace_back(StrHelper::Concat(prefix, fileName.substr(0, fileName.size() - 5)));
|
||||
} while (FindNextFile(hFind.get(), &findData));
|
||||
} else {
|
||||
Logger::Get().Win32Error("查找缓存文件失败");
|
||||
if (!hFind) {
|
||||
Logger::Get().Win32Error("FindFirstFileEx 失败");
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::wstring_view fileName(findData.cFileName);
|
||||
if (fileName == L"." || fileName == L"..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Win32Helper::DirExists(StrHelper::Concat(
|
||||
CommonSharedConstants::EFFECTS_DIR, L"\\", prefix, fileName).c_str())) {
|
||||
ListEffects(result, StrHelper::Concat(prefix, fileName, L"\\"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fileName.ends_with(L".hlsl")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.emplace_back(StrHelper::Concat(prefix, fileName.substr(0, fileName.size() - 5)));
|
||||
} while (FindNextFile(hFind.get(), &findData));
|
||||
}
|
||||
|
||||
fire_and_forget EffectsService::Initialize() {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@
|
|||
#include <parallel_hashmap/phmap.h>
|
||||
|
||||
namespace Magpie {
|
||||
struct EffectParameterDesc;
|
||||
}
|
||||
|
||||
namespace Magpie {
|
||||
struct EffectParameterDesc;
|
||||
|
||||
struct EffectInfoFlags {
|
||||
static constexpr uint32_t CanScale = 1;
|
||||
|
|
@ -17,7 +15,7 @@ struct EffectInfo {
|
|||
|
||||
std::wstring name;
|
||||
std::wstring sortName;
|
||||
std::vector<::Magpie::EffectParameterDesc> params;
|
||||
std::vector<EffectParameterDesc> params;
|
||||
uint32_t flags = 0; // EffectInfoFlags
|
||||
|
||||
bool CanScale() const noexcept {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ using namespace winrt;
|
|||
|
||||
namespace Magpie {
|
||||
|
||||
// 出错返回空,取消返回空字符串
|
||||
std::optional<std::wstring> FileDialogHelper::OpenFileDialog(
|
||||
// 出错返回 nullopt,取消返回空字符串
|
||||
std::optional<std::filesystem::path> FileDialogHelper::OpenFileDialog(
|
||||
IFileDialog* fileDialog,
|
||||
FILEOPENDIALOGOPTIONS options
|
||||
) noexcept {
|
||||
|
|
@ -21,7 +21,7 @@ std::optional<std::wstring> FileDialogHelper::OpenFileDialog(
|
|||
|
||||
if (fileDialog->Show(App::Get().MainWindow().Handle()) != S_OK) {
|
||||
// 被用户取消
|
||||
return std::wstring();
|
||||
return std::filesystem::path{};
|
||||
}
|
||||
|
||||
com_ptr<IShellItem> file;
|
||||
|
|
@ -38,7 +38,7 @@ std::optional<std::wstring> FileDialogHelper::OpenFileDialog(
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::wstring(fileName.get());
|
||||
return std::filesystem::path(fileName.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
namespace Magpie {
|
||||
|
||||
struct FileDialogHelper {
|
||||
static std::optional<std::wstring> OpenFileDialog(
|
||||
static std::optional<std::filesystem::path> OpenFileDialog(
|
||||
IFileDialog* fileDialog,
|
||||
FILEOPENDIALOGOPTIONS options = 0
|
||||
) noexcept;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
#endif
|
||||
#include "XamlHelper.h"
|
||||
#include "ControlHelper.h"
|
||||
#include "App.h"
|
||||
|
||||
using namespace ::Magpie;
|
||||
|
||||
|
|
@ -20,25 +19,4 @@ void HomePage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable
|
|||
ControlHelper::ComboBox_DropDownOpened(sender);
|
||||
}
|
||||
|
||||
void HomePage::SimulateExclusiveFullscreenToggleSwitch_Toggled(IInspectable const& sender, RoutedEventArgs const&) {
|
||||
// 如果没有启用开发者模式,模拟独占全屏选项位于页面底部,用户可能注意不到警告。
|
||||
// 为了解决这个问题,打开模拟独占全屏选项时自动滚动页面。
|
||||
if (_viewModel->IsDeveloperMode() || !sender.as<ToggleSwitch>().IsOn() || !IsLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 这个回调被触发时 UI 还没有更新,需要异步处理
|
||||
App::Get().Dispatcher().RunAsync(CoreDispatcherPriority::Low, [weakThis(get_weak())]() {
|
||||
auto strongThis = weakThis.get();
|
||||
if (!strongThis) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto scrollViewer = strongThis->PageFrame().ScrollViewer();
|
||||
scrollViewer.UpdateLayout();
|
||||
// 滚动到底部
|
||||
scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ struct HomePage : HomePageT<HomePage> {
|
|||
|
||||
void ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const;
|
||||
|
||||
void SimulateExclusiveFullscreenToggleSwitch_Toggled(IInspectable const& sender, RoutedEventArgs const&);
|
||||
|
||||
private:
|
||||
com_ptr<HomeViewModel> _viewModel = make_self<HomeViewModel>();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,31 +35,29 @@
|
|||
</local:WrapPanel>
|
||||
</local:SimpleStackPanel>
|
||||
</muxc:InfoBar>
|
||||
<local:SettingsGroup x:Uid="Home_Shortcuts">
|
||||
<local:SettingsCard x:Uid="Home_Shortcuts_Scale"
|
||||
<local:SettingsGroup x:Uid="Home_Activation">
|
||||
<local:SettingsCard x:Uid="Home_Activation_FullscreenScaling"
|
||||
IsWrapEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<local:ShortcutControl x:Uid="Home_Shortcuts_Scale_ShortcutControl">
|
||||
<local:ShortcutControl x:Uid="Home_Activation_FullscreenScaling_ShortcutControl">
|
||||
<local:ShortcutControl.Action>
|
||||
<local:ShortcutAction>Scale</local:ShortcutAction>
|
||||
</local:ShortcutControl.Action>
|
||||
</local:ShortcutControl>
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="Home_Shortcuts_Overlay"
|
||||
<local:SettingsCard x:Uid="Home_Activation_WindowedScaling"
|
||||
IsWrapEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<local:ShortcutControl x:Uid="Home_Shortcuts_Overlay_ShortcutControl">
|
||||
<local:ShortcutControl x:Uid="Home_Activation_WindowedScaling_ShortcutControl">
|
||||
<local:ShortcutControl.Action>
|
||||
<local:ShortcutAction>Overlay</local:ShortcutAction>
|
||||
<local:ShortcutAction>WindowedModeScale</local:ShortcutAction>
|
||||
</local:ShortcutControl.Action>
|
||||
</local:ShortcutControl>
|
||||
</local:SettingsCard>
|
||||
</local:SettingsGroup>
|
||||
<local:SettingsGroup x:Uid="Home_Activation">
|
||||
<local:SettingsExpander x:Uid="Home_Activation_Timer">
|
||||
<local:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
|
|
@ -96,42 +94,41 @@
|
|||
</local:SettingsCard>
|
||||
</local:SettingsExpander.Items>
|
||||
</local:SettingsExpander>
|
||||
<Grid>
|
||||
<local:SettingsCard x:Uid="Home_Activation_AutoRestore"
|
||||
Visibility="{x:Bind ViewModel.IsWndToRestore, Mode=OneWay, Converter={StaticResource NegativeVisibilityConverter}}">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsAutoRestore, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<local:SettingsExpander x:Uid="Home_Activation_AutoRestore"
|
||||
IsExpanded="{x:Bind ViewModel.IsWndToRestore, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.IsWndToRestore, Mode=OneWay}">
|
||||
<local:SettingsExpander.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsExpander.HeaderIcon>
|
||||
<local:SettingsExpander.Content>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsAutoRestore, Mode=TwoWay}" />
|
||||
</local:SettingsExpander.Content>
|
||||
<local:SettingsExpander.Items>
|
||||
<local:SettingsCard Header="{x:Bind ViewModel.RestoreWndDesc, Mode=OneWay}">
|
||||
<local:SimpleStackPanel Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<Button x:Uid="Home_Activation_AutoRestore_Activate"
|
||||
Click="{x:Bind ViewModel.ActivateRestore}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<Button x:Uid="Home_Activation_AutoRestore_Clear"
|
||||
Click="{x:Bind ViewModel.ClearRestore}" />
|
||||
</local:SimpleStackPanel>
|
||||
</local:SettingsCard>
|
||||
</local:SettingsExpander.Items>
|
||||
</local:SettingsExpander>
|
||||
</Grid>
|
||||
<local:SettingsCard x:Uid="Home_Activation_AllowScalingMaximized">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsAllowScalingMaximized, Mode=TwoWay}" />
|
||||
</local:SettingsGroup>
|
||||
<local:SettingsGroup x:Uid="Home_Toolbar">
|
||||
<local:SettingsCard x:Uid="Home_Toolbar_StateToggle"
|
||||
IsWrapEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<local:ShortcutControl x:Uid="Home_Toolbar_StateToggle_ShortcutControl">
|
||||
<local:ShortcutControl.Action>
|
||||
<local:ShortcutAction>Toolbar</local:ShortcutAction>
|
||||
</local:ShortcutControl.Action>
|
||||
</local:ShortcutControl>
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="Home_Toolbar_InitialState">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.InitialToolbarState, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="Home_Toolbar_InitialState_Off" />
|
||||
<ComboBoxItem x:Uid="Home_Toolbar_InitialState_AlwaysShow" />
|
||||
<ComboBoxItem x:Uid="Home_Toolbar_InitialState_AutoHide" />
|
||||
</ComboBox>
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="Home_Toolbar_ScreenshotSaveDirectory"
|
||||
Click="{x:Bind ViewModel.OpenScreenshotSaveDirectory}"
|
||||
Description="{x:Bind ViewModel.ScreenshotSaveDirectory, Mode=OneWay}"
|
||||
IsClickEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<local:SettingsCard.ActionIcon>
|
||||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.ActionIcon>
|
||||
<Button x:Uid="Home_Toolbar_ScreenshotSaveDirectory_Change"
|
||||
Click="{x:Bind ViewModel.ChangeScreenshotSaveDirectory}" />
|
||||
</local:SettingsCard>
|
||||
</local:SettingsGroup>
|
||||
<local:SettingsGroup x:Uid="Home_TouchSupport">
|
||||
|
|
@ -149,7 +146,7 @@
|
|||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsTouchSupportEnabled, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<muxc:InfoBar x:Uid="Home_TouchSupport_InfoBar"
|
||||
<muxc:InfoBar x:Uid="Home_TouchSupport_Info"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsShowTouchSupportInfoBar, Mode=OneWay}"
|
||||
Severity="Informational">
|
||||
|
|
@ -157,8 +154,24 @@
|
|||
<ResourceDictionary Source="ms-appx:///Magpie/BlueInfoBarStyle.xaml" />
|
||||
</muxc:InfoBar.Resources>
|
||||
</muxc:InfoBar>
|
||||
<muxc:InfoBar x:Uid="Home_TouchSupport_Warn"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsShowTouchSupportInfoBar, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
</local:SettingsGroup>
|
||||
<local:SettingsGroup x:Uid="Home_Advanced">
|
||||
<local:SettingsCard x:Uid="Home_Advanced_AllowScalingMaximized">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph=""
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<ScaleTransform ScaleX="-1" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsAllowScalingMaximized, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<local:SettingsCard x:Uid="Home_Advanced_InlineParams">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
<FontIcon Glyph="" />
|
||||
|
|
@ -171,9 +184,12 @@
|
|||
<FontIcon Glyph="" />
|
||||
</local:SettingsCard.HeaderIcon>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.IsSimulateExclusiveFullscreen, Mode=TwoWay}"
|
||||
Toggled="SimulateExclusiveFullscreenToggleSwitch_Toggled" />
|
||||
IsOn="{x:Bind ViewModel.IsSimulateExclusiveFullscreen, Mode=TwoWay}" />
|
||||
</local:SettingsCard>
|
||||
<muxc:InfoBar x:Uid="Home_Advanced_SimulateExclusiveFullscreen_InfoBar"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsSimulateExclusiveFullscreen, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
<local:SettingsCard x:Uid="Home_Advanced_MinFrameRate"
|
||||
IsWrapEnabled="True">
|
||||
<local:SettingsCard.HeaderIcon>
|
||||
|
|
@ -196,10 +212,6 @@
|
|||
Text="FPS" />
|
||||
</Grid>
|
||||
</local:SettingsCard>
|
||||
<muxc:InfoBar x:Uid="Home_Advanced_SimulateExclusiveFullscreen_InfoBar"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsSimulateExclusiveFullscreen, Mode=OneWay}"
|
||||
Severity="Warning" />
|
||||
<local:SettingsExpander x:Name="DeveloperModeExpander"
|
||||
x:Uid="Home_Advanced_DeveloperOptions"
|
||||
x:Load="{x:Bind ViewModel.IsDeveloperMode, Mode=OneWay}"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue