|
10 | 10 | #include <Windows.h> |
11 | 11 |
|
12 | 12 | #include <algorithm> |
| 13 | +#include <array> |
13 | 14 | #include <atomic> |
14 | 15 | #include <cassert> |
15 | 16 | #include <chrono> |
@@ -135,6 +136,96 @@ void QueueSnapshotCapture(reshade::api::device* device) { |
135 | 136 | } |
136 | 137 | std::atomic_uint32_t device_data_index = 0; |
137 | 138 |
|
| 139 | +std::atomic_uint32_t snapshot_hotkey = 0; |
| 140 | +std::atomic_bool snapshot_hotkey_input_active = false; |
| 141 | + |
| 142 | +struct KeyMapping { |
| 143 | + ImGuiKey imgui_key; |
| 144 | + int vk_code; |
| 145 | +}; |
| 146 | + |
| 147 | +constexpr KeyMapping Key(ImGuiKey imgui_key, int vk_code) { |
| 148 | + return {.imgui_key = imgui_key, .vk_code = vk_code}; |
| 149 | +} |
| 150 | + |
| 151 | +// ImGuiKey <-> VK code mappings |
| 152 | +// https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes |
| 153 | +constexpr auto BuildKeyMappings() { |
| 154 | + // Handle special keys |
| 155 | + constexpr auto special = std::to_array<KeyMapping>({ |
| 156 | + Key(ImGuiKey_Tab, VK_TAB), |
| 157 | + Key(ImGuiKey_Backspace, VK_BACK), |
| 158 | + Key(ImGuiKey_Space, VK_SPACE), |
| 159 | + Key(ImGuiKey_Enter, VK_RETURN), |
| 160 | + Key(ImGuiKey_Escape, VK_ESCAPE), |
| 161 | + Key(ImGuiKey_LeftArrow, VK_LEFT), |
| 162 | + Key(ImGuiKey_RightArrow, VK_RIGHT), |
| 163 | + Key(ImGuiKey_UpArrow, VK_UP), |
| 164 | + Key(ImGuiKey_DownArrow, VK_DOWN), |
| 165 | + Key(ImGuiKey_PageUp, VK_PRIOR), |
| 166 | + Key(ImGuiKey_PageDown, VK_NEXT), |
| 167 | + Key(ImGuiKey_Home, VK_HOME), |
| 168 | + Key(ImGuiKey_End, VK_END), |
| 169 | + Key(ImGuiKey_Insert, VK_INSERT), |
| 170 | + Key(ImGuiKey_Delete, VK_DELETE), |
| 171 | + Key(ImGuiKey_Pause, VK_PAUSE), |
| 172 | + Key(ImGuiKey_ScrollLock, VK_SCROLL), |
| 173 | + Key(ImGuiKey_PrintScreen, VK_SNAPSHOT), |
| 174 | + Key(ImGuiKey_KeypadDecimal, VK_DECIMAL), |
| 175 | + Key(ImGuiKey_KeypadDivide, VK_DIVIDE), |
| 176 | + Key(ImGuiKey_KeypadMultiply, VK_MULTIPLY), |
| 177 | + Key(ImGuiKey_KeypadSubtract, VK_SUBTRACT), |
| 178 | + Key(ImGuiKey_KeypadAdd, VK_ADD), |
| 179 | + Key(ImGuiKey_KeypadEnter, VK_RETURN), |
| 180 | + Key(ImGuiKey_GraveAccent, VK_OEM_3), |
| 181 | + Key(ImGuiKey_Minus, VK_OEM_MINUS), |
| 182 | + Key(ImGuiKey_Equal, VK_OEM_PLUS), |
| 183 | + Key(ImGuiKey_LeftBracket, VK_OEM_4), |
| 184 | + Key(ImGuiKey_RightBracket, VK_OEM_6), |
| 185 | + Key(ImGuiKey_Backslash, VK_OEM_5), |
| 186 | + Key(ImGuiKey_Semicolon, VK_OEM_1), |
| 187 | + Key(ImGuiKey_Apostrophe, VK_OEM_7), |
| 188 | + Key(ImGuiKey_Comma, VK_OEM_COMMA), |
| 189 | + Key(ImGuiKey_Period, VK_OEM_PERIOD), |
| 190 | + Key(ImGuiKey_Slash, VK_OEM_2), |
| 191 | + }); |
| 192 | + |
| 193 | + // Handle contiguous keys |
| 194 | + std::array<KeyMapping, special.size() + 12 + 10 + 10 + 26> result{}; |
| 195 | + size_t i = 0; |
| 196 | + for (const auto& k : special) result[i++] = k; |
| 197 | + for (int c = 0; c < 12; ++c) result[i++] = Key(static_cast<ImGuiKey>(ImGuiKey_F1 + c), VK_F1 + c); |
| 198 | + for (int c = 0; c < 10; ++c) result[i++] = Key(static_cast<ImGuiKey>(ImGuiKey_Keypad0 + c), VK_NUMPAD0 + c); |
| 199 | + for (int c = 0; c < 10; ++c) result[i++] = Key(static_cast<ImGuiKey>(ImGuiKey_0 + c), '0' + c); |
| 200 | + for (int c = 0; c < 26; ++c) result[i++] = Key(static_cast<ImGuiKey>(ImGuiKey_A + c), 'A' + c); |
| 201 | + |
| 202 | + return result; |
| 203 | +} |
| 204 | + |
| 205 | +constexpr auto KEY_MAPPINGS = BuildKeyMappings(); |
| 206 | + |
| 207 | +// Returns a display name for a VK code via ImGui. |
| 208 | +const char* GetVirtualKeyName(uint32_t vk_code) { |
| 209 | + for (const auto& [imgui_key, vk] : KEY_MAPPINGS) { |
| 210 | + if (vk == static_cast<int>(vk_code)) { |
| 211 | + return ImGui::GetKeyName(imgui_key); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + return ""; |
| 216 | +} |
| 217 | + |
| 218 | +// Returns the VK code of the key pressed this frame, or 0 if none. |
| 219 | +int GetLastKeyPressedImGui() { |
| 220 | + for (const auto& [imgui_key, vk_code] : KEY_MAPPINGS) { |
| 221 | + if (ImGui::IsKeyPressed(imgui_key, false)) { |
| 222 | + return vk_code; |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + return 0; |
| 227 | +} |
| 228 | + |
138 | 229 | struct ResourceBind { |
139 | 230 | enum class BindType : std::uint8_t { |
140 | 231 | SRV = 0, |
@@ -5990,6 +6081,50 @@ struct SettingsDeviceOption { |
5990 | 6081 | if (BeginSettingsSection("Other")) { |
5991 | 6082 | DrawSettingDecimalTextbox("FPS Limit", "FPSLimit", &renodx::utils::swapchain::fps_limit); |
5992 | 6083 |
|
| 6084 | + { |
| 6085 | + static bool key_was_pressed = false; |
| 6086 | + |
| 6087 | + const uint32_t current_hotkey = snapshot_hotkey.load(); |
| 6088 | + const char* key_name = GetVirtualKeyName(current_hotkey); |
| 6089 | + |
| 6090 | + std::array<char, 64> buf = {}; |
| 6091 | + if (current_hotkey != 0 && key_name[0] != '\0') { |
| 6092 | + strncpy_s(buf.data(), buf.size(), key_name, _TRUNCATE); |
| 6093 | + } |
| 6094 | + |
| 6095 | + ImGui::InputTextWithHint( |
| 6096 | + "Snapshot Hotkey", |
| 6097 | + "Press to bind...", |
| 6098 | + buf.data(), |
| 6099 | + buf.size(), |
| 6100 | + ImGuiInputTextFlags_ReadOnly); |
| 6101 | + |
| 6102 | + if (ImGui::IsItemActive()) { |
| 6103 | + snapshot_hotkey_input_active = true; |
| 6104 | + int key_pressed = GetLastKeyPressedImGui(); |
| 6105 | + |
| 6106 | + if (key_pressed != 0 && !key_was_pressed) { |
| 6107 | + if (key_pressed == VK_BACK || key_pressed == VK_DELETE) { |
| 6108 | + snapshot_hotkey = 0; |
| 6109 | + } else if (key_pressed != VK_ESCAPE) { |
| 6110 | + snapshot_hotkey = static_cast<uint32_t>(key_pressed); |
| 6111 | + } |
| 6112 | + |
| 6113 | + reshade::set_config_value(nullptr, "renodx-dev", "SnapshotHotkey", snapshot_hotkey.load()); |
| 6114 | + key_was_pressed = true; |
| 6115 | + } else if (key_pressed == 0) { |
| 6116 | + key_was_pressed = false; |
| 6117 | + } |
| 6118 | + } else { |
| 6119 | + snapshot_hotkey_input_active = false; |
| 6120 | + key_was_pressed = false; |
| 6121 | + } |
| 6122 | + |
| 6123 | + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) { |
| 6124 | + ImGui::SetTooltip("Click and press a key to set.\nBackspace/Delete to clear. Escape to cancel."); |
| 6125 | + } |
| 6126 | + } |
| 6127 | + |
5993 | 6128 | EndSettingsSection(); |
5994 | 6129 | } |
5995 | 6130 |
|
@@ -6958,6 +7093,7 @@ void InitializeUserSettings() { |
6958 | 7093 | } |
6959 | 7094 | for (const auto& [key, value] : std::vector<std::pair<const char*, std::atomic_uint32_t*>>({ |
6960 | 7095 | {"TraceInitialFrameCount", &renodx::utils::trace::trace_initial_frame_count}, |
| 7096 | + {"SnapshotHotkey", &snapshot_hotkey}, |
6961 | 7097 | })) { |
6962 | 7098 | uint32_t temp = *value; |
6963 | 7099 | if (reshade::get_config_value(nullptr, "renodx-dev", key, temp)) { |
@@ -7282,6 +7418,34 @@ void OnPresent( |
7282 | 7418 | renodx::utils::shader::dump::DumpAllPending(); |
7283 | 7419 | } |
7284 | 7420 |
|
| 7421 | + // If the snapshot hotkey was pressed, queue a capture. |
| 7422 | + // Only act on the device selected in the devkit, and skip while a |
| 7423 | + // snapshot is already active or queued (matches menu gating). |
| 7424 | + if (const auto hotkey = snapshot_hotkey.load(); |
| 7425 | + hotkey != 0 |
| 7426 | + && !snapshot_hotkey_input_active |
| 7427 | + && snapshot_device == nullptr |
| 7428 | + && snapshot_queued_device == nullptr) { |
| 7429 | + const auto is_selected_device = [&device]() { |
| 7430 | + std::shared_lock list_lock(device_data_list_mutex); |
| 7431 | + const auto size = device_data_list.size(); |
| 7432 | + if (size == 0) return false; |
| 7433 | + |
| 7434 | + const auto selected_index = std::min<uint32_t>( |
| 7435 | + device_data_index.load(std::memory_order_relaxed), |
| 7436 | + static_cast<uint32_t>(size - 1u)); |
| 7437 | + return device_data_list[selected_index]->device == device; |
| 7438 | + }(); |
| 7439 | + |
| 7440 | + if (is_selected_device) { |
| 7441 | + auto* device_data = get_data(); |
| 7442 | + if (device_data != nullptr && device_data->runtime != nullptr |
| 7443 | + && device_data->runtime->is_key_pressed(hotkey)) { |
| 7444 | + QueueSnapshotCapture(device); |
| 7445 | + } |
| 7446 | + } |
| 7447 | + } |
| 7448 | + |
7285 | 7449 | reshade::api::device* active_snapshot_device = snapshot_device; |
7286 | 7450 | if (active_snapshot_device == nullptr) { |
7287 | 7451 | if (snapshot_queued_device == device) { |
|
0 commit comments