Skip to content

Commit 30108bc

Browse files
committed
feat(devkit): add snapshot hotkey
1 parent d7487b4 commit 30108bc

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

src/addons/devkit/addon.cpp

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <Windows.h>
1111

1212
#include <algorithm>
13+
#include <array>
1314
#include <atomic>
1415
#include <cassert>
1516
#include <chrono>
@@ -135,6 +136,96 @@ void QueueSnapshotCapture(reshade::api::device* device) {
135136
}
136137
std::atomic_uint32_t device_data_index = 0;
137138

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+
138229
struct ResourceBind {
139230
enum class BindType : std::uint8_t {
140231
SRV = 0,
@@ -5990,6 +6081,50 @@ struct SettingsDeviceOption {
59906081
if (BeginSettingsSection("Other")) {
59916082
DrawSettingDecimalTextbox("FPS Limit", "FPSLimit", &renodx::utils::swapchain::fps_limit);
59926083

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+
59936128
EndSettingsSection();
59946129
}
59956130

@@ -6958,6 +7093,7 @@ void InitializeUserSettings() {
69587093
}
69597094
for (const auto& [key, value] : std::vector<std::pair<const char*, std::atomic_uint32_t*>>({
69607095
{"TraceInitialFrameCount", &renodx::utils::trace::trace_initial_frame_count},
7096+
{"SnapshotHotkey", &snapshot_hotkey},
69617097
})) {
69627098
uint32_t temp = *value;
69637099
if (reshade::get_config_value(nullptr, "renodx-dev", key, temp)) {
@@ -7282,6 +7418,34 @@ void OnPresent(
72827418
renodx::utils::shader::dump::DumpAllPending();
72837419
}
72847420

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+
72857449
reshade::api::device* active_snapshot_device = snapshot_device;
72867450
if (active_snapshot_device == nullptr) {
72877451
if (snapshot_queued_device == device) {

0 commit comments

Comments
 (0)