Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion features/RenderDoc/Shaders/Features/RenderDoc.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[Info]
Version = 1-0-1
Version = 1-1-0

[Nexus]
autoupload = false
142 changes: 131 additions & 11 deletions src/Features/RenderDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ void RenderDoc::Load()
}

renderDocApi->MaskOverlayBits(eRENDERDOC_Overlay_None, eRENDERDOC_Overlay_None);
// Menu input owns capture hotkeys so they respect the configured frame count.
renderDocApi->SetCaptureKeys(nullptr, 0);

// Initialize capture count tracking
lastCaptureCount = renderDocApi->GetNumCaptures();
Expand Down Expand Up @@ -188,18 +190,16 @@ void RenderDoc::DrawSettings()
ImGui::InputTextWithHint("##CaptureComments", "Additional comments for next capture (optional)", commentsBuffer, sizeof(commentsBuffer));
Util::AddTooltip("Additional comments will be appended to automatic metadata and embedded in the .rdc file");

int captureFrameCountUI = static_cast<int>(GetCaptureFrameCount());
if (ImGui::SliderInt("Capture Frames", &captureFrameCountUI, static_cast<int>(kMinCaptureFrameCount), static_cast<int>(kMaxCaptureFrameCount), "%d", ImGuiSliderFlags_AlwaysClamp)) {
SetCaptureFrameCount(static_cast<uint32_t>(captureFrameCountUI));
}
Util::AddTooltip("Number of consecutive frames to capture. 1 uses a normal RenderDoc capture; higher values use TriggerMultiFrameCapture.");

if (ImGui::Button("Create Capture")) {
// Check available disk space before allowing capture
try {
auto capturesDir = GetCapturesDirectory();
std::error_code ec;
uint64_t freeSpace = std::filesystem::space(capturesDir, ec).available;
if (ec) {
logger::warn("[RenderDoc] Failed to check available space in '{}': {}", capturesDir, ec.message());
freeSpace = 0;
}

if (freeSpace < kMinCaptureSpaceBytes) {
if (!HasSufficientDiskSpaceForConfiguredCapture()) {
ImGui::OpenPopup("Not enough disk space##RenderDoc");
} else {
// Set comments if provided
Expand All @@ -215,7 +215,7 @@ void RenderDoc::DrawSettings()

// Actual capture logic
logger::info("[RenderDoc] Manual capture triggered by user");
TriggerCapture();
TriggerConfiguredCapture(false);
}
} catch (const std::exception& e) {
logger::error("[RenderDoc] Exception during capture logic: {}", e.what());
Expand All @@ -224,7 +224,7 @@ void RenderDoc::DrawSettings()

if (ImGui::BeginPopup("Not enough disk space##RenderDoc")) {
ImGui::Text("Not enough free disk space to create a capture.");
ImGui::Text("At least {} MB of free space is required.", kMinCaptureSpaceBytes / (1024 * 1024));
ImGui::Text("At least {} MB of free space is required.", GetRequiredCaptureSpaceBytes() / (1024 * 1024));
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();
}
Expand Down Expand Up @@ -540,18 +540,35 @@ void RenderDoc::SetupResources()
void RenderDoc::SaveSettings(json& o_json)
{
o_json["Enable RenderDoc Capture"] = enableRenderDocCapture;
o_json["Capture Frame Count"] = GetCaptureFrameCount();
}

void RenderDoc::LoadSettings(json& o_json)
{
if (o_json.contains("Enable RenderDoc Capture") && o_json["Enable RenderDoc Capture"].is_boolean()) {
enableRenderDocCapture = o_json["Enable RenderDoc Capture"];
}
if (!o_json.contains("Capture Frame Count")) {
return;
}

const auto& frameCountJson = o_json["Capture Frame Count"];
if (frameCountJson.is_number_unsigned()) {
const auto frameCount = std::min(frameCountJson.get<uint64_t>(), static_cast<uint64_t>(kMaxCaptureFrameCount));
SetCaptureFrameCount(static_cast<uint32_t>(frameCount));
} else if (frameCountJson.is_number_integer()) {
const auto frameCount = std::clamp(
frameCountJson.get<int64_t>(),
static_cast<int64_t>(kMinCaptureFrameCount),
static_cast<int64_t>(kMaxCaptureFrameCount));
SetCaptureFrameCount(static_cast<uint32_t>(frameCount));
}
}

void RenderDoc::RestoreDefaultSettings()
{
enableRenderDocCapture = false;
SetCaptureFrameCount(1);
}

void RenderDoc::ClearShaderCache()
Expand Down Expand Up @@ -643,6 +660,109 @@ void RenderDoc::TriggerCapture()
InvalidateCaptureCache();
}

void RenderDoc::TriggerMultiFrameCapture(uint32_t a_frameCount)
{
if (!renderDocApi) {
logger::warn("[RenderDoc] Cannot trigger multi-frame capture - RenderDoc API not available");
return;
}

const uint32_t frameCount = std::clamp(a_frameCount, kMinCaptureFrameCount, kMaxCaptureFrameCount);
logger::info("[RenderDoc] Triggering multi-frame capture for {} frame(s)", frameCount);
renderDocApi->TriggerMultiFrameCapture(frameCount);

// Invalidate cache so it refreshes when next accessed (capture should appear in list)
InvalidateCaptureCache();
}

bool RenderDoc::TriggerConfiguredCapture(bool a_checkDiskSpace)
{
if (!renderDocApi) {
logger::warn("[RenderDoc] Cannot trigger configured capture - RenderDoc API not available");
return false;
}

if (a_checkDiskSpace) {
uint64_t requiredSpace = 0;
if (!HasSufficientDiskSpaceForConfiguredCapture(&requiredSpace)) {
logger::warn("[RenderDoc] Not enough free disk space to create a capture. At least {} MB required.", requiredSpace / (1024 * 1024));
return false;
}
}

const uint32_t frameCount = GetCaptureFrameCount();
if (frameCount > 1) {
TriggerMultiFrameCapture(frameCount);
} else {
TriggerCapture();
}

return true;
}

bool RenderDoc::HandleCaptureHotkey(uint32_t a_vkKey)
{
if (a_vkKey != VK_F12 && a_vkKey != VK_SNAPSHOT) {
return false;
}

constexpr int kKeyPressedMask = 0x8000;
const bool modifierHeld =
(GetAsyncKeyState(VK_CONTROL) & kKeyPressedMask) != 0 ||
(GetAsyncKeyState(VK_SHIFT) & kKeyPressedMask) != 0 ||
(GetAsyncKeyState(VK_MENU) & kKeyPressedMask) != 0;
if (modifierHeld) {
return false;
}

if (!IsAvailable()) {
return false;
}

logger::info("[RenderDoc] Capture hotkey triggered");
TriggerConfiguredCapture();
return true;
}

uint32_t RenderDoc::GetCaptureFrameCount() const
{
return std::clamp(captureFrameCount, kMinCaptureFrameCount, kMaxCaptureFrameCount);
}

void RenderDoc::SetCaptureFrameCount(uint32_t a_frameCount)
{
captureFrameCount = std::clamp(a_frameCount, kMinCaptureFrameCount, kMaxCaptureFrameCount);
}

uint64_t RenderDoc::GetRequiredCaptureSpaceBytes() const
{
const uint64_t estimated = kObservedPerFrameBytes * GetCaptureFrameCount();
return std::max(estimated, kMinCaptureSpaceBytes);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

bool RenderDoc::HasSufficientDiskSpaceForConfiguredCapture(uint64_t* a_requiredSpaceBytes) const
{
const uint64_t requiredSpace = GetRequiredCaptureSpaceBytes();
if (a_requiredSpaceBytes) {
*a_requiredSpaceBytes = requiredSpace;
}

try {
auto capturesDir = GetCapturesDirectory();
std::error_code ec;
const uint64_t freeSpace = std::filesystem::space(capturesDir, ec).available;
if (ec) {
logger::warn("[RenderDoc] Failed to check available space in '{}': {}", capturesDir, ec.message());
return false;
}

return freeSpace >= requiredSpace;
} catch (const std::exception& e) {
logger::error("[RenderDoc] Exception while checking capture disk space: {}", e.what());
return false;
}
}

void RenderDoc::SetCaptureFilePathTemplate(const std::string& a_template)
{
if (!renderDocApi) {
Expand Down
18 changes: 15 additions & 3 deletions src/Features/RenderDoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class RenderDoc : public Feature
// Core RenderDoc functionality
bool IsAvailable() const { return renderDocApi != nullptr; }
void TriggerCapture();
void TriggerMultiFrameCapture(uint32_t a_frameCount);
bool HandleCaptureHotkey(uint32_t a_vkKey);
void SetCaptureFilePathTemplate(const std::string& a_template);
std::string GetCapturesDirectory() const;
bool IsCapturing() const;
Expand Down Expand Up @@ -87,6 +89,12 @@ class RenderDoc : public Feature
std::string GetOverlayWarningMessage() const;

private:
bool TriggerConfiguredCapture(bool a_checkDiskSpace = true);
uint32_t GetCaptureFrameCount() const;
void SetCaptureFrameCount(uint32_t a_frameCount);
uint64_t GetRequiredCaptureSpaceBytes() const;
bool HasSufficientDiskSpaceForConfiguredCapture(uint64_t* a_requiredSpaceBytes = nullptr) const;

// Cache management for capture files
void RefreshCaptureFileCache();
const std::vector<CaptureFileInfo>& GetCachedCaptureFiles();
Expand All @@ -112,6 +120,7 @@ class RenderDoc : public Feature

// RenderDoc capture enable setting
bool enableRenderDocCapture = false;
uint32_t captureFrameCount = 1;

// Track the last capture count we've processed for automatic comments
uint32_t lastCaptureCount = 0;
Expand All @@ -125,7 +134,10 @@ class RenderDoc : public Feature
// Track files that failed to delete for UI feedback
std::unordered_map<std::filesystem::path, std::string> failedDeletions;

static constexpr uint64_t kMinCaptureSpaceBytes = 100ULL * 1024ULL * 1024ULL; // 100 MB minimum free space
static constexpr uint32_t kCacheRefreshIntervalSeconds = 5; // Cache refresh interval
static constexpr size_t kCommentsBufferSize = 1024; // Size of comments input buffer
static constexpr uint64_t kMinCaptureSpaceBytes = 100ULL * 1024ULL * 1024ULL; // 100 MB minimum free space floor
static constexpr uint64_t kObservedPerFrameBytes = 256ULL * 1024ULL * 1024ULL; // ~256 MB per frame observed for multi-frame captures
static constexpr uint32_t kCacheRefreshIntervalSeconds = 5; // Cache refresh interval
static constexpr size_t kCommentsBufferSize = 1024; // Size of comments input buffer
static constexpr uint32_t kMinCaptureFrameCount = 1;
static constexpr uint32_t kMaxCaptureFrameCount = 120;
};
23 changes: 13 additions & 10 deletions src/Menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "Feature.h"
#include "FeatureIssues.h"
#include "FeatureVersions.h"
#include "Features/RenderDoc.h"
#include "Features/Upscaling.h"
#include "Menu/AdvancedSettingsRenderer.h"
#include "Menu/BackgroundBlur.h"
Expand Down Expand Up @@ -894,7 +895,7 @@ static std::vector<InputCombo> DeriveWeatherEditorKey(const std::vector<InputCom
if (vk == VK_SHIFT || vk == VK_LSHIFT || vk == VK_RSHIFT) {
hasShift = true;
} else if (vk != VK_CONTROL && vk != VK_LCONTROL && vk != VK_RCONTROL &&
vk != VK_MENU && vk != VK_LMENU && vk != VK_RMENU) {
vk != VK_MENU && vk != VK_LMENU && vk != VK_RMENU) {
baseKey = vk;
}
}
Expand Down Expand Up @@ -977,11 +978,11 @@ void Menu::ProcessInputEventQueue()
auto shaderCache = globals::shaderCache;
HotkeyAction hotkeyActions[] = {
{ &settings.ToggleKey, &settingToggleKey, [this](std::vector<InputCombo> keys) {
settings.ToggleKey = keys;
settingToggleKey = false;
if (!settings.FirstTimeSetupCompleted)
settings.WeatherEditorToggleKey = DeriveWeatherEditorKey(keys);
} },
settings.ToggleKey = keys;
settingToggleKey = false;
if (!settings.FirstTimeSetupCompleted)
settings.WeatherEditorToggleKey = DeriveWeatherEditorKey(keys);
} },
{ &settings.SkipCompilationKey, &settingSkipCompilationKey, [this](std::vector<InputCombo> keys) { settings.SkipCompilationKey = keys; settingSkipCompilationKey = false; } },
{ &settings.EffectToggleKey, &settingsEffectsToggle, [this](std::vector<InputCombo> keys) { settings.EffectToggleKey = keys; settingsEffectsToggle = false; } },
{ &settings.OverlayToggleKey, &settingOverlayToggleKey, [this](std::vector<InputCombo> keys) { settings.OverlayToggleKey = keys; settingOverlayToggleKey = false; } },
Expand Down Expand Up @@ -1072,10 +1073,12 @@ void Menu::ProcessInputEventQueue()
globals::features::screenshotFeature.captureRequested = true;
} },
};
for (const auto& ka : keyActions) {
if (InputCombo::MatchesKeyboardCombo(ka.settingKey, key)) {
ka.action();
break;
if (!globals::features::renderDoc.HandleCaptureHotkey(key)) {
for (const auto& ka : keyActions) {
if (InputCombo::MatchesKeyboardCombo(ka.settingKey, key)) {
ka.action();
break;
}
}
}
}
Expand Down
Loading