diff --git a/src/Features/RemoteControl.cpp b/src/Features/RemoteControl.cpp index 186c926345..bb512ab538 100644 --- a/src/Features/RemoteControl.cpp +++ b/src/Features/RemoteControl.cpp @@ -12,6 +12,7 @@ #include "Features/RenderDoc.h" #include "Features/ScreenshotFeature.h" #include "Globals.h" +#include "ShaderCompileStatus.h" #include "State.h" #include @@ -449,6 +450,34 @@ static mcp::json EngineStateBlob() }); } +// Helper used by inspect(kind="shadercache"): runtime shader (re)compile +// status, built entirely from existing thread-safe ShaderCache accessors (no +// added state). Hot-reloading an .hlsl clears its variants and requeues them, +// so completedTasks advances once the recompile lands — poll it against a +// pre-deploy snapshot to know the new shader is live. A rising failedTasks / +// currentFailedCount means a (re)compile failed (otherwise invisible). +static mcp::json ShaderCacheBlob() +{ + const uint frames = globals::state ? globals::state->frameCountAtomic.load(std::memory_order_relaxed) : 0u; + const SIE::ShaderCompileStatus s = SIE::GetShaderCompileStatus(); + if (!s.valid) { + return mcp::json({ + { "plugin", "CommunityShaders" }, + { "frame_count", frames }, + { "error", "shaderCache unavailable" }, + }); + } + return mcp::json({ + { "plugin", "CommunityShaders" }, + { "frame_count", frames }, + { "compiling", s.compiling }, + { "completedTasks", s.completedTasks }, + { "totalTasks", s.totalTasks }, + { "failedTasks", s.failedTasks }, + { "currentFailedCount", s.currentFailedCount }, + }); +} + // Helper used by feature(action="list") to build one entry per feature. static mcp::json FeatureEntry(Feature* f) { @@ -510,11 +539,17 @@ void RemoteControl::RegisterInspectTool() " state — { plugin, frame_count, vr }. Frame counter " "monotonically increases each render tick; use as a " "ground truth for verifying that deferred operations " - "(see `console`) have had time to run.\n\n" + "(see `console`) have had time to run.\n" + " shadercache — { plugin, compiling, completedTasks, " + "totalTasks, failedTasks, currentFailedCount, " + "frame_count }. Poll completedTasks against a " + "pre-deploy snapshot to confirm a hot-reloaded " + "shader finished recompiling; a rising failedTasks " + "/ currentFailedCount means a compile failed.\n\n" "For feature reads (enumerate / settings), use the " "`feature` tool with action='list' or 'get'.") .with_string_param("kind", - "Currently 'state'. New kinds will be added here " + "'state' or 'shadercache'. New kinds will be added here " "rather than as new tools.") .build(); server->register_tool(tool, @@ -527,9 +562,12 @@ void RemoteControl::RegisterInspectTool() if (kind == "state") { return TextResult(EngineStateBlob().dump()); } + if (kind == "shadercache") { + return TextResult(ShaderCacheBlob().dump()); + } return ErrorResult("unknown kind", { { "kind", kind }, - { "supported", mcp::json::array({ "state" }) } }); + { "supported", mcp::json::array({ "state", "shadercache" }) } }); }); } diff --git a/src/ShaderCache.cpp b/src/ShaderCache.cpp index a8d27b4b4c..7c6408490a 100644 --- a/src/ShaderCache.cpp +++ b/src/ShaderCache.cpp @@ -1,5 +1,6 @@ #include "ShaderCache.h" #include "Globals.h" +#include "ShaderCompileStatus.h" #include "ShaderFileWatcher.h" #include "Util.h" @@ -14,6 +15,28 @@ namespace SIE { + // Chrono-free snapshot of compile counters for consumers that can't include + // ShaderCache.h (see ShaderCompileStatus.h). Thread-safe: atomics + the + // shader-map lock inside GetCurrentFailedCount. + ShaderCompileStatus GetShaderCompileStatus() + { + auto* cache = globals::shaderCache; + if (!cache) + return {}; + // Read the task counters once and derive `compiling` from the same + // snapshot, so callers never observe compiling=false with work still + // outstanding. Named-field init avoids depending on member order. + const uint64_t completed = cache->GetCompletedTasks(); + const uint64_t total = cache->GetTotalTasks(); + ShaderCompileStatus status{}; + status.valid = true; + status.compiling = completed < total; + status.completedTasks = completed; + status.totalTasks = total; + status.failedTasks = cache->GetFailedTasks(); + status.currentFailedCount = cache->GetCurrentFailedCount(); + return status; + } // Custom include handler to track all includes during shader compilation class TrackingIncludeHandler : public ID3DInclude @@ -1488,6 +1511,17 @@ namespace SIE shaderBlob->Release(); } +#ifdef TRACY_ENABLE + { + // Timeline annotation: a (re)compile failed. Pairs with the + // MCP shadercache status (failedTasks) for build-agnostic + // detection; this gives the exact frame for perf correlation. + const auto tracyMsg = std::format("Shader compile FAILED: {} {} {:X}", + magic_enum::enum_name(type), magic_enum::enum_name(shaderClass), descriptor); + TracyMessageC(tracyMsg.c_str(), tracyMsg.size(), 0xFF4444); + } +#endif + cache.AddCompletedShader(shaderClass, shader, descriptor, nullptr); return nullptr; } @@ -1495,6 +1529,17 @@ namespace SIE logger::debug("Shader logs:\n{}", static_cast(errorBlob->GetBufferPointer())); logger::debug("Compiled shader {}:{}:{:X}", magic_enum::enum_name(type), magic_enum::enum_name(shaderClass), descriptor); +#ifdef TRACY_ENABLE + { + // Timeline annotation: a shader (re)compiled successfully. During + // a hot-reload this marks the exact frame the new shader went + // live, so A/B perf windows split precisely on it. + const auto tracyMsg = std::format("Shader compiled: {} {} {:X}", + magic_enum::enum_name(type), magic_enum::enum_name(shaderClass), descriptor); + TracyMessage(tracyMsg.c_str(), tracyMsg.size()); + } +#endif + // strip debug info if (!globals::state->IsDeveloperMode()) { ID3DBlob* strippedShaderBlob = nullptr; diff --git a/src/ShaderCompileStatus.h b/src/ShaderCompileStatus.h new file mode 100644 index 0000000000..361e7cd7dc --- /dev/null +++ b/src/ShaderCompileStatus.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +// Minimal, dependency-free view of the shader (re)compile state for consumers +// that must NOT pull in ShaderCache.h — notably RemoteControl.cpp, where +// ShaderCache.h's global-scope `using namespace std::chrono` would leak chrono +// names (e.g. `last`) into the vendored cpp-mcp headers (httplib/base64) and +// trip warning-as-error. Defined in ShaderCache.cpp against the live cache. +namespace SIE +{ + struct ShaderCompileStatus + { + bool valid = false; ///< false when the shader cache singleton is unavailable + bool compiling = false; + uint64_t completedTasks = 0; + uint64_t totalTasks = 0; + uint64_t failedTasks = 0; + uint64_t currentFailedCount = 0; + }; + + /// Snapshot the current shader-cache compile counters. Thread-safe: reads + /// atomics and takes the shader-map lock internally. Callable from the + /// RemoteControl listener thread. + ShaderCompileStatus GetShaderCompileStatus(); +}