diff --git a/CMakeLists.txt b/CMakeLists.txt index a7dc732..d91e680 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,6 +287,9 @@ set(RENDER_CADENCE_APP_SOURCES "${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h" "${APP_DIR}/gl/renderer/GLExtensions.cpp" "${APP_DIR}/gl/renderer/GLExtensions.h" + "${APP_DIR}/shader/ShaderCompiler.cpp" + "${APP_DIR}/shader/ShaderCompiler.h" + "${APP_DIR}/shader/ShaderTypes.h" "${APP_DIR}/videoio/VideoIOFormat.cpp" "${APP_DIR}/videoio/VideoIOFormat.h" "${APP_DIR}/videoio/VideoIOTypes.h" @@ -310,8 +313,12 @@ set(RENDER_CADENCE_APP_SOURCES "${RENDER_CADENCE_APP_DIR}/render/RenderCadenceClock.h" "${RENDER_CADENCE_APP_DIR}/render/RenderThread.cpp" "${RENDER_CADENCE_APP_DIR}/render/RenderThread.h" + "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderRenderer.cpp" + "${RENDER_CADENCE_APP_DIR}/render/RuntimeShaderRenderer.h" "${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.cpp" "${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h" + "${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.cpp" + "${RENDER_CADENCE_APP_DIR}/runtime/RuntimeSlangShaderCompiler.h" "${RENDER_CADENCE_APP_DIR}/telemetry/CadenceTelemetry.h" "${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryPrinter.h" "${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.cpp" @@ -324,6 +331,8 @@ add_executable(RenderCadenceCompositor ${RENDER_CADENCE_APP_SOURCES}) target_include_directories(RenderCadenceCompositor PRIVATE "${APP_DIR}" "${APP_DIR}/gl/renderer" + "${APP_DIR}/platform" + "${APP_DIR}/shader" "${APP_DIR}/videoio" "${APP_DIR}/videoio/decklink" "${RENDER_CADENCE_APP_DIR}" @@ -331,6 +340,7 @@ target_include_directories(RenderCadenceCompositor PRIVATE "${RENDER_CADENCE_APP_DIR}/frames" "${RENDER_CADENCE_APP_DIR}/platform" "${RENDER_CADENCE_APP_DIR}/render" + "${RENDER_CADENCE_APP_DIR}/runtime" "${RENDER_CADENCE_APP_DIR}/telemetry" "${RENDER_CADENCE_APP_DIR}/video" ) diff --git a/apps/RenderCadenceCompositor/README.md b/apps/RenderCadenceCompositor/README.md index cb70484..5a58821 100644 --- a/apps/RenderCadenceCompositor/README.md +++ b/apps/RenderCadenceCompositor/README.md @@ -37,6 +37,8 @@ Included now: - async PBO readback - latest-N system-memory frame exchange - rendered-frame warmup +- background Slang compile of `shaders/happy-accident` +- render-thread-only GL commit once compiled shader source is ready - compact telemetry - non-GL frame-exchange tests @@ -85,7 +87,7 @@ Press Enter to stop. The app prints one line per second: ```text -renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 completedPollMisses=0 scheduleFailures=0 completions=119 late=0 dropped=0 decklinkBuffered=4 scheduleCallMs=0.0 +renderFps=59.9 scheduleFps=59.9 free=7 completed=1 scheduled=4 completedPollMisses=0 scheduleFailures=0 completions=119 late=0 dropped=0 shaderCommitted=1 shaderFailures=0 decklinkBuffered=4 scheduleCallMs=0.0 ``` Healthy first-run signs: @@ -97,9 +99,25 @@ Healthy first-run signs: - `decklinkBuffered` stays near 4 when available - `late` and `dropped` do not increase continuously - `scheduleFailures` does not increase +- `shaderCommitted` becomes `1` after the background Happy Accident compile completes +- `shaderFailures` remains `0` `completedPollMisses` means the DeckLink scheduling thread woke up before a completed frame was available. It is not a DeckLink playout underrun by itself. Treat it as healthy polling noise when `scheduled`, `decklinkBuffered`, `late`, `dropped`, and `scheduleFailures` remain stable. +## Runtime Slang Shader Test + +On startup the app begins compiling `shaders/happy-accident` on a background thread. + +The render thread keeps drawing the simple motion renderer while Slang compiles. It only attempts the OpenGL shader compile/link once a complete GLSL fragment shader is ready. If either the Slang build or GL commit fails, the app keeps rendering the simple motion fallback. + +Successful handoff signs: + +- console prints `Runtime shader committed: happy-accident` +- telemetry shows `shaderCommitted=1` +- output changes from the simple motion pattern to the Happy Accident shader +- render/schedule cadence remains near 60 fps during and after the handoff +- DeckLink buffer remains stable + ## Baseline Result Date: 2026-05-12 diff --git a/apps/RenderCadenceCompositor/app/RenderCadenceApp.h b/apps/RenderCadenceCompositor/app/RenderCadenceApp.h index 7f026a0..9a7c3d1 100644 --- a/apps/RenderCadenceCompositor/app/RenderCadenceApp.h +++ b/apps/RenderCadenceCompositor/app/RenderCadenceApp.h @@ -106,7 +106,7 @@ public: return false; } - mTelemetry.Start(mFrameExchange, mOutput, mOutputThread); + mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread); mStarted = true; return true; } diff --git a/apps/RenderCadenceCompositor/render/RenderThread.cpp b/apps/RenderCadenceCompositor/render/RenderThread.cpp index bbddcbf..6765104 100644 --- a/apps/RenderCadenceCompositor/render/RenderThread.cpp +++ b/apps/RenderCadenceCompositor/render/RenderThread.cpp @@ -5,10 +5,13 @@ #include "../platform/HiddenGlWindow.h" #include "Bgra8ReadbackPipeline.h" #include "GLExtensions.h" +#include "RuntimeShaderRenderer.h" #include "SimpleMotionRenderer.h" #include +#include #include +#include RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) : mFrameExchange(frameExchange), @@ -83,12 +86,14 @@ void RenderThread::ThreadMain() } SimpleMotionRenderer renderer; + RuntimeShaderRenderer runtimeShaderRenderer; Bgra8ReadbackPipeline readback; if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth)) { SignalStartupFailure("Render pipeline initialization failed."); return; } + mSlangCompiler.StartHappyAccidentBuild(); RenderCadenceClock clock(mConfig.frameDurationMilliseconds); uint64_t frameIndex = 0; @@ -114,7 +119,13 @@ void RenderThread::ThreadMain() continue; } - if (!readback.RenderAndQueue(frameIndex, [&renderer](uint64_t index) { renderer.RenderFrame(index); })) + TryCommitReadyRuntimeShader(runtimeShaderRenderer); + if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeShaderRenderer](uint64_t index) { + if (runtimeShaderRenderer.HasProgram()) + runtimeShaderRenderer.RenderFrame(index, mConfig.width, mConfig.height); + else + renderer.RenderFrame(index); + })) { std::lock_guard lock(mMetricsMutex); ++mMetrics.pboQueueMisses; @@ -143,7 +154,9 @@ void RenderThread::ThreadMain() } readback.Shutdown(); + runtimeShaderRenderer.ShutdownGl(); renderer.ShutdownGl(); + mSlangCompiler.Stop(); window.ClearCurrent(); mRunning.store(false, std::memory_order_release); } @@ -179,3 +192,34 @@ void RenderThread::CountAcquireMiss() std::lock_guard lock(mMetricsMutex); ++mMetrics.acquireMisses; } + +void RenderThread::TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer) +{ + RuntimeSlangShaderBuild build; + if (!mSlangCompiler.TryConsume(build)) + return; + + if (!build.succeeded) + { + std::cout << "Runtime Slang build failed: " << build.message << "\n"; + OutputDebugStringA(("Runtime Slang build failed: " + build.message + "\n").c_str()); + std::lock_guard lock(mMetricsMutex); + ++mMetrics.shaderBuildFailures; + return; + } + + std::string commitError; + if (!runtimeShaderRenderer.CommitFragmentShader(build.fragmentShaderSource, commitError)) + { + std::cout << "Runtime shader GL commit failed: " << commitError << "\n"; + OutputDebugStringA(("Runtime shader GL commit failed: " + commitError + "\n").c_str()); + std::lock_guard lock(mMetricsMutex); + ++mMetrics.shaderBuildFailures; + return; + } + + std::cout << "Runtime shader committed: " << build.shaderId << ". " << build.message << "\n"; + OutputDebugStringA(("Runtime shader committed: " + build.shaderId + ". " + build.message + "\n").c_str()); + std::lock_guard lock(mMetricsMutex); + ++mMetrics.shaderBuildsCommitted; +} diff --git a/apps/RenderCadenceCompositor/render/RenderThread.h b/apps/RenderCadenceCompositor/render/RenderThread.h index b92fac5..6e49d07 100644 --- a/apps/RenderCadenceCompositor/render/RenderThread.h +++ b/apps/RenderCadenceCompositor/render/RenderThread.h @@ -1,6 +1,8 @@ #pragma once #include "RenderCadenceClock.h" +#include "../runtime/RuntimeSlangShaderCompiler.h" +#include "RuntimeShaderRenderer.h" #include #include @@ -31,6 +33,8 @@ public: uint64_t pboQueueMisses = 0; uint64_t clockOverruns = 0; uint64_t skippedFrames = 0; + uint64_t shaderBuildsCommitted = 0; + uint64_t shaderBuildFailures = 0; }; RenderThread(SystemFrameExchange& frameExchange, Config config); @@ -51,9 +55,11 @@ private: void CountRendered(); void CountCompleted(); void CountAcquireMiss(); + void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer); SystemFrameExchange& mFrameExchange; Config mConfig; + RuntimeSlangShaderCompiler mSlangCompiler; std::thread mThread; std::atomic mStopping{ false }; std::atomic mRunning{ false }; diff --git a/apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.cpp b/apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.cpp new file mode 100644 index 0000000..c881983 --- /dev/null +++ b/apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.cpp @@ -0,0 +1,214 @@ +#include "RuntimeShaderRenderer.h" + +#include +#include +#include + +namespace +{ +constexpr GLuint kGlobalParamsBindingPoint = 0; + +const char* kVertexShaderSource = R"GLSL( +#version 430 core +out vec2 vTexCoord; +void main() +{ + vec2 positions[3] = vec2[3]( + vec2(-1.0, -1.0), + vec2( 3.0, -1.0), + vec2(-1.0, 3.0)); + vec2 texCoords[3] = vec2[3]( + vec2(0.0, 0.0), + vec2(2.0, 0.0), + vec2(0.0, 2.0)); + gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); + vTexCoord = texCoords[gl_VertexID]; +} +)GLSL"; + +struct GlobalParamsStd140 +{ + float time = 0.0f; + float pad0 = 0.0f; + float inputResolution[2] = {}; + float outputResolution[2] = {}; + float utcTimeSeconds = 0.0f; + float utcOffsetSeconds = 0.0f; + float startupRandom = 0.37f; + float frameCount = 0.0f; + float mixAmount = 1.0f; + float bypass = 0.0f; + int sourceHistoryLength = 0; + int temporalHistoryLength = 0; + int feedbackAvailable = 0; + float speed = 1.0f; + float scale = 1.0f; + float raySteps = 77.0f; + float intensity = 1.0f; + float sourceMix = 0.0f; + float pad1[3] = {}; +}; +} + +RuntimeShaderRenderer::~RuntimeShaderRenderer() +{ + ShutdownGl(); +} + +bool RuntimeShaderRenderer::CommitFragmentShader(const std::string& fragmentShaderSource, std::string& error) +{ + if (fragmentShaderSource.empty()) + { + error = "Cannot commit an empty fragment shader."; + return false; + } + + if (!EnsureStaticGlResources(error)) + return false; + + GLuint vertexShader = 0; + GLuint fragmentShader = 0; + GLuint program = 0; + if (!CompileShader(GL_VERTEX_SHADER, kVertexShaderSource, vertexShader, error)) + return false; + if (!CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str(), fragmentShader, error)) + { + glDeleteShader(vertexShader); + return false; + } + + program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + + GLint linkResult = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkResult); + if (linkResult == GL_FALSE) + { + std::array log = {}; + GLsizei length = 0; + glGetProgramInfoLog(program, static_cast(log.size()), &length, log.data()); + error = std::string(log.data(), static_cast(length)); + glDeleteProgram(program); + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + return false; + } + + const GLuint globalParamsIndex = glGetUniformBlockIndex(program, "GlobalParams"); + if (globalParamsIndex != GL_INVALID_INDEX) + glUniformBlockBinding(program, globalParamsIndex, kGlobalParamsBindingPoint); + + glUseProgram(program); + const GLint videoInputLocation = glGetUniformLocation(program, "gVideoInput"); + if (videoInputLocation >= 0) + glUniform1i(videoInputLocation, 0); + const GLint layerInputLocation = glGetUniformLocation(program, "gLayerInput"); + if (layerInputLocation >= 0) + glUniform1i(layerInputLocation, 0); + glUseProgram(0); + + DestroyProgram(); + mProgram = program; + mVertexShader = vertexShader; + mFragmentShader = fragmentShader; + return true; +} + +void RuntimeShaderRenderer::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height) +{ + if (mProgram == 0) + return; + + GlobalParamsStd140 params; + params.time = static_cast(frameIndex) / 60.0f; + params.inputResolution[0] = static_cast(width); + params.inputResolution[1] = static_cast(height); + params.outputResolution[0] = static_cast(width); + params.outputResolution[1] = static_cast(height); + params.frameCount = static_cast(frameIndex); + + glViewport(0, 0, static_cast(width), static_cast(height)); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(params), ¶ms); + glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsBuffer); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + glBindVertexArray(mVertexArray); + glUseProgram(mProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + glUseProgram(0); + glBindVertexArray(0); +} + +void RuntimeShaderRenderer::ShutdownGl() +{ + DestroyProgram(); + DestroyStaticGlResources(); +} + +bool RuntimeShaderRenderer::EnsureStaticGlResources(std::string& error) +{ + if (mVertexArray == 0) + glGenVertexArrays(1, &mVertexArray); + if (mGlobalParamsBuffer == 0) + { + glGenBuffers(1, &mGlobalParamsBuffer); + glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer); + glBufferData(GL_UNIFORM_BUFFER, static_cast(sizeof(GlobalParamsStd140)), nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + } + + if (mVertexArray == 0 || mGlobalParamsBuffer == 0) + { + error = "Failed to create runtime shader GL resources."; + return false; + } + return true; +} + +bool RuntimeShaderRenderer::CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error) const +{ + shader = glCreateShader(shaderType); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + + GLint compileResult = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult); + if (compileResult != GL_FALSE) + return true; + + std::array log = {}; + GLsizei length = 0; + glGetShaderInfoLog(shader, static_cast(log.size()), &length, log.data()); + error = std::string(log.data(), static_cast(length)); + glDeleteShader(shader); + shader = 0; + return false; +} + +void RuntimeShaderRenderer::DestroyProgram() +{ + if (mProgram != 0) + glDeleteProgram(mProgram); + if (mVertexShader != 0) + glDeleteShader(mVertexShader); + if (mFragmentShader != 0) + glDeleteShader(mFragmentShader); + mProgram = 0; + mVertexShader = 0; + mFragmentShader = 0; +} + +void RuntimeShaderRenderer::DestroyStaticGlResources() +{ + if (mGlobalParamsBuffer != 0) + glDeleteBuffers(1, &mGlobalParamsBuffer); + if (mVertexArray != 0) + glDeleteVertexArrays(1, &mVertexArray); + mGlobalParamsBuffer = 0; + mVertexArray = 0; +} diff --git a/apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.h b/apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.h new file mode 100644 index 0000000..0fb0584 --- /dev/null +++ b/apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.h @@ -0,0 +1,32 @@ +#pragma once + +#include "GLExtensions.h" + +#include +#include + +class RuntimeShaderRenderer +{ +public: + RuntimeShaderRenderer() = default; + RuntimeShaderRenderer(const RuntimeShaderRenderer&) = delete; + RuntimeShaderRenderer& operator=(const RuntimeShaderRenderer&) = delete; + ~RuntimeShaderRenderer(); + + bool CommitFragmentShader(const std::string& fragmentShaderSource, std::string& error); + bool HasProgram() const { return mProgram != 0; } + void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height); + void ShutdownGl(); + +private: + bool EnsureStaticGlResources(std::string& error); + bool CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error) const; + void DestroyProgram(); + void DestroyStaticGlResources(); + + GLuint mProgram = 0; + GLuint mVertexShader = 0; + GLuint mFragmentShader = 0; + GLuint mVertexArray = 0; + GLuint mGlobalParamsBuffer = 0; +}; diff --git a/apps/RenderCadenceCompositor/runtime/RuntimeSlangShaderCompiler.cpp b/apps/RenderCadenceCompositor/runtime/RuntimeSlangShaderCompiler.cpp new file mode 100644 index 0000000..cc1a992 --- /dev/null +++ b/apps/RenderCadenceCompositor/runtime/RuntimeSlangShaderCompiler.cpp @@ -0,0 +1,148 @@ +#include "RuntimeSlangShaderCompiler.h" + +#include "ShaderCompiler.h" +#include "ShaderTypes.h" + +#include +#include +#include + +namespace +{ +ShaderParameterDefinition FloatParam(const std::string& id, double defaultValue) +{ + ShaderParameterDefinition parameter; + parameter.id = id; + parameter.label = id; + parameter.type = ShaderParameterType::Float; + parameter.defaultNumbers.push_back(defaultValue); + return parameter; +} + +std::filesystem::path FindRepoRoot() +{ + std::filesystem::path current = std::filesystem::current_path(); + for (;;) + { + if (std::filesystem::exists(current / "shaders" / "happy-accident" / "shader.slang") && + std::filesystem::exists(current / "runtime" / "templates" / "shader_wrapper.slang.in")) + { + return current; + } + + const std::filesystem::path parent = current.parent_path(); + if (parent.empty() || parent == current) + return std::filesystem::current_path(); + current = parent; + } +} +} + +RuntimeSlangShaderCompiler::~RuntimeSlangShaderCompiler() +{ + Stop(); +} + +void RuntimeSlangShaderCompiler::StartHappyAccidentBuild() +{ + if (mRunning.load(std::memory_order_acquire)) + return; + + if (mThread.joinable()) + mThread.join(); + + { + std::lock_guard lock(mMutex); + mReadyBuild = RuntimeSlangShaderBuild(); + } + + mRunning.store(true, std::memory_order_release); + mThread = std::thread([this]() { + RuntimeSlangShaderBuild build = BuildHappyAccident(); + { + std::lock_guard lock(mMutex); + mReadyBuild = std::move(build); + mReadyBuild.available = true; + } + mRunning.store(false, std::memory_order_release); + }); +} + +void RuntimeSlangShaderCompiler::Stop() +{ + if (mThread.joinable()) + mThread.join(); + mRunning.store(false, std::memory_order_release); +} + +bool RuntimeSlangShaderCompiler::TryConsume(RuntimeSlangShaderBuild& build) +{ + std::lock_guard lock(mMutex); + if (!mReadyBuild.available) + return false; + + build = std::move(mReadyBuild); + mReadyBuild = RuntimeSlangShaderBuild(); + return true; +} + +RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildHappyAccident() const +{ + RuntimeSlangShaderBuild build; + build.shaderId = "happy-accident"; + + try + { + const std::filesystem::path repoRoot = FindRepoRoot(); + const std::filesystem::path shaderDir = repoRoot / "shaders" / "happy-accident"; + const std::filesystem::path runtimeBuildDir = repoRoot / "runtime" / "generated" / "render-cadence-compositor"; + + ShaderPackage shaderPackage; + shaderPackage.id = "happy-accident"; + shaderPackage.displayName = "Happy Accident"; + shaderPackage.entryPoint = "shadeVideo"; + shaderPackage.directoryPath = shaderDir; + shaderPackage.shaderPath = shaderDir / "shader.slang"; + shaderPackage.manifestPath = shaderDir / "shader.json"; + shaderPackage.parameters.push_back(FloatParam("speed", 1.0)); + shaderPackage.parameters.push_back(FloatParam("scale", 1.0)); + shaderPackage.parameters.push_back(FloatParam("raySteps", 77.0)); + shaderPackage.parameters.push_back(FloatParam("intensity", 1.0)); + shaderPackage.parameters.push_back(FloatParam("sourceMix", 0.0)); + + ShaderPassDefinition pass; + pass.id = "main"; + pass.entryPoint = shaderPackage.entryPoint; + pass.sourcePath = shaderPackage.shaderPath; + pass.outputName = "output"; + shaderPackage.passes.push_back(pass); + + ShaderCompiler compiler( + repoRoot, + runtimeBuildDir / "happy_accident_wrapper.slang", + runtimeBuildDir / "happy_accident.generated.glsl", + runtimeBuildDir / "happy_accident.patched.glsl", + 0); + + std::string error; + const auto start = std::chrono::steady_clock::now(); + if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, build.fragmentShaderSource, error)) + { + build.succeeded = false; + build.message = error.empty() ? "Happy Accident Slang compile failed." : error; + return build; + } + + const auto end = std::chrono::steady_clock::now(); + const double milliseconds = std::chrono::duration_cast>(end - start).count(); + build.succeeded = true; + build.message = "Happy Accident Slang compile completed in " + std::to_string(milliseconds) + " ms."; + return build; + } + catch (const std::exception& exception) + { + build.succeeded = false; + build.message = exception.what(); + return build; + } +} diff --git a/apps/RenderCadenceCompositor/runtime/RuntimeSlangShaderCompiler.h b/apps/RenderCadenceCompositor/runtime/RuntimeSlangShaderCompiler.h new file mode 100644 index 0000000..202eb97 --- /dev/null +++ b/apps/RenderCadenceCompositor/runtime/RuntimeSlangShaderCompiler.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +struct RuntimeSlangShaderBuild +{ + bool available = false; + bool succeeded = false; + std::string shaderId; + std::string fragmentShaderSource; + std::string message; +}; + +class RuntimeSlangShaderCompiler +{ +public: + RuntimeSlangShaderCompiler() = default; + RuntimeSlangShaderCompiler(const RuntimeSlangShaderCompiler&) = delete; + RuntimeSlangShaderCompiler& operator=(const RuntimeSlangShaderCompiler&) = delete; + ~RuntimeSlangShaderCompiler(); + + void StartHappyAccidentBuild(); + void Stop(); + bool TryConsume(RuntimeSlangShaderBuild& build); + bool Running() const { return mRunning.load(std::memory_order_acquire); } + +private: + RuntimeSlangShaderBuild BuildHappyAccident() const; + + std::thread mThread; + std::atomic mRunning{ false }; + std::mutex mMutex; + RuntimeSlangShaderBuild mReadyBuild; +}; diff --git a/apps/RenderCadenceCompositor/telemetry/CadenceTelemetry.h b/apps/RenderCadenceCompositor/telemetry/CadenceTelemetry.h index 0bca22d..0b2ee34 100644 --- a/apps/RenderCadenceCompositor/telemetry/CadenceTelemetry.h +++ b/apps/RenderCadenceCompositor/telemetry/CadenceTelemetry.h @@ -24,6 +24,8 @@ struct CadenceTelemetrySnapshot uint64_t completions = 0; uint64_t displayedLate = 0; uint64_t dropped = 0; + uint64_t shaderBuildsCommitted = 0; + uint64_t shaderBuildFailures = 0; bool deckLinkBufferedAvailable = false; uint64_t deckLinkBuffered = 0; double deckLinkScheduleCallMilliseconds = 0.0; @@ -78,6 +80,20 @@ public: return snapshot; } + template + CadenceTelemetrySnapshot Sample( + const SystemFrameExchange& exchange, + const DeckLinkOutput& output, + const OutputThread& outputThread, + const RenderThread& renderThread) + { + CadenceTelemetrySnapshot snapshot = Sample(exchange, output, outputThread); + const auto renderMetrics = renderThread.GetMetrics(); + snapshot.shaderBuildsCommitted = renderMetrics.shaderBuildsCommitted; + snapshot.shaderBuildFailures = renderMetrics.shaderBuildFailures; + return snapshot; + } + private: using Clock = std::chrono::steady_clock; diff --git a/apps/RenderCadenceCompositor/telemetry/TelemetryPrinter.h b/apps/RenderCadenceCompositor/telemetry/TelemetryPrinter.h index 51ab4af..6582f24 100644 --- a/apps/RenderCadenceCompositor/telemetry/TelemetryPrinter.h +++ b/apps/RenderCadenceCompositor/telemetry/TelemetryPrinter.h @@ -31,18 +31,18 @@ public: Stop(); } - template - void Start(const SystemFrameExchange& exchange, const DeckLinkOutput& output, const OutputThread& outputThread) + template + void Start(const SystemFrameExchange& exchange, const DeckLinkOutput& output, const OutputThread& outputThread, const RenderThread& renderThread) { if (mRunning) return; mStopping = false; - mThread = std::thread([this, &exchange, &output, &outputThread]() { + mThread = std::thread([this, &exchange, &output, &outputThread, &renderThread]() { CadenceTelemetry telemetry; while (!mStopping) { std::this_thread::sleep_for(mConfig.interval); - Print(telemetry.Sample(exchange, output, outputThread)); + Print(telemetry.Sample(exchange, output, outputThread, renderThread)); } }); mRunning = true; @@ -70,6 +70,8 @@ private: << " completions=" << snapshot.completions << " late=" << snapshot.displayedLate << " dropped=" << snapshot.dropped + << " shaderCommitted=" << snapshot.shaderBuildsCommitted + << " shaderFailures=" << snapshot.shaderBuildFailures << " decklinkBuffered="; if (snapshot.deckLinkBufferedAvailable) std::cout << snapshot.deckLinkBuffered;