Shader test past
This commit is contained in:
@@ -287,6 +287,9 @@ set(RENDER_CADENCE_APP_SOURCES
|
|||||||
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
|
"${APP_DIR}/videoio/decklink/DeckLinkVideoIOFormat.h"
|
||||||
"${APP_DIR}/gl/renderer/GLExtensions.cpp"
|
"${APP_DIR}/gl/renderer/GLExtensions.cpp"
|
||||||
"${APP_DIR}/gl/renderer/GLExtensions.h"
|
"${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.cpp"
|
||||||
"${APP_DIR}/videoio/VideoIOFormat.h"
|
"${APP_DIR}/videoio/VideoIOFormat.h"
|
||||||
"${APP_DIR}/videoio/VideoIOTypes.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/RenderCadenceClock.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render/RenderThread.cpp"
|
"${RENDER_CADENCE_APP_DIR}/render/RenderThread.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render/RenderThread.h"
|
"${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.cpp"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render/SimpleMotionRenderer.h"
|
"${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/CadenceTelemetry.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryPrinter.h"
|
"${RENDER_CADENCE_APP_DIR}/telemetry/TelemetryPrinter.h"
|
||||||
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.cpp"
|
"${RENDER_CADENCE_APP_DIR}/video/DeckLinkOutput.cpp"
|
||||||
@@ -324,6 +331,8 @@ add_executable(RenderCadenceCompositor ${RENDER_CADENCE_APP_SOURCES})
|
|||||||
target_include_directories(RenderCadenceCompositor PRIVATE
|
target_include_directories(RenderCadenceCompositor PRIVATE
|
||||||
"${APP_DIR}"
|
"${APP_DIR}"
|
||||||
"${APP_DIR}/gl/renderer"
|
"${APP_DIR}/gl/renderer"
|
||||||
|
"${APP_DIR}/platform"
|
||||||
|
"${APP_DIR}/shader"
|
||||||
"${APP_DIR}/videoio"
|
"${APP_DIR}/videoio"
|
||||||
"${APP_DIR}/videoio/decklink"
|
"${APP_DIR}/videoio/decklink"
|
||||||
"${RENDER_CADENCE_APP_DIR}"
|
"${RENDER_CADENCE_APP_DIR}"
|
||||||
@@ -331,6 +340,7 @@ target_include_directories(RenderCadenceCompositor PRIVATE
|
|||||||
"${RENDER_CADENCE_APP_DIR}/frames"
|
"${RENDER_CADENCE_APP_DIR}/frames"
|
||||||
"${RENDER_CADENCE_APP_DIR}/platform"
|
"${RENDER_CADENCE_APP_DIR}/platform"
|
||||||
"${RENDER_CADENCE_APP_DIR}/render"
|
"${RENDER_CADENCE_APP_DIR}/render"
|
||||||
|
"${RENDER_CADENCE_APP_DIR}/runtime"
|
||||||
"${RENDER_CADENCE_APP_DIR}/telemetry"
|
"${RENDER_CADENCE_APP_DIR}/telemetry"
|
||||||
"${RENDER_CADENCE_APP_DIR}/video"
|
"${RENDER_CADENCE_APP_DIR}/video"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ Included now:
|
|||||||
- async PBO readback
|
- async PBO readback
|
||||||
- latest-N system-memory frame exchange
|
- latest-N system-memory frame exchange
|
||||||
- rendered-frame warmup
|
- rendered-frame warmup
|
||||||
|
- background Slang compile of `shaders/happy-accident`
|
||||||
|
- render-thread-only GL commit once compiled shader source is ready
|
||||||
- compact telemetry
|
- compact telemetry
|
||||||
- non-GL frame-exchange tests
|
- non-GL frame-exchange tests
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ Press Enter to stop.
|
|||||||
The app prints one line per second:
|
The app prints one line per second:
|
||||||
|
|
||||||
```text
|
```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:
|
Healthy first-run signs:
|
||||||
@@ -97,9 +99,25 @@ Healthy first-run signs:
|
|||||||
- `decklinkBuffered` stays near 4 when available
|
- `decklinkBuffered` stays near 4 when available
|
||||||
- `late` and `dropped` do not increase continuously
|
- `late` and `dropped` do not increase continuously
|
||||||
- `scheduleFailures` does not increase
|
- `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.
|
`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
|
## Baseline Result
|
||||||
|
|
||||||
Date: 2026-05-12
|
Date: 2026-05-12
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread);
|
mTelemetry.Start(mFrameExchange, mOutput, mOutputThread, mRenderThread);
|
||||||
mStarted = true;
|
mStarted = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
#include "../platform/HiddenGlWindow.h"
|
#include "../platform/HiddenGlWindow.h"
|
||||||
#include "Bgra8ReadbackPipeline.h"
|
#include "Bgra8ReadbackPipeline.h"
|
||||||
#include "GLExtensions.h"
|
#include "GLExtensions.h"
|
||||||
|
#include "RuntimeShaderRenderer.h"
|
||||||
#include "SimpleMotionRenderer.h"
|
#include "SimpleMotionRenderer.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
|
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
|
||||||
mFrameExchange(frameExchange),
|
mFrameExchange(frameExchange),
|
||||||
@@ -83,12 +86,14 @@ void RenderThread::ThreadMain()
|
|||||||
}
|
}
|
||||||
|
|
||||||
SimpleMotionRenderer renderer;
|
SimpleMotionRenderer renderer;
|
||||||
|
RuntimeShaderRenderer runtimeShaderRenderer;
|
||||||
Bgra8ReadbackPipeline readback;
|
Bgra8ReadbackPipeline readback;
|
||||||
if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
|
if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
|
||||||
{
|
{
|
||||||
SignalStartupFailure("Render pipeline initialization failed.");
|
SignalStartupFailure("Render pipeline initialization failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
mSlangCompiler.StartHappyAccidentBuild();
|
||||||
|
|
||||||
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
||||||
uint64_t frameIndex = 0;
|
uint64_t frameIndex = 0;
|
||||||
@@ -114,7 +119,13 @@ void RenderThread::ThreadMain()
|
|||||||
continue;
|
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<std::mutex> lock(mMetricsMutex);
|
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||||
++mMetrics.pboQueueMisses;
|
++mMetrics.pboQueueMisses;
|
||||||
@@ -143,7 +154,9 @@ void RenderThread::ThreadMain()
|
|||||||
}
|
}
|
||||||
|
|
||||||
readback.Shutdown();
|
readback.Shutdown();
|
||||||
|
runtimeShaderRenderer.ShutdownGl();
|
||||||
renderer.ShutdownGl();
|
renderer.ShutdownGl();
|
||||||
|
mSlangCompiler.Stop();
|
||||||
window.ClearCurrent();
|
window.ClearCurrent();
|
||||||
mRunning.store(false, std::memory_order_release);
|
mRunning.store(false, std::memory_order_release);
|
||||||
}
|
}
|
||||||
@@ -179,3 +192,34 @@ void RenderThread::CountAcquireMiss()
|
|||||||
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
std::lock_guard<std::mutex> lock(mMetricsMutex);
|
||||||
++mMetrics.acquireMisses;
|
++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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mMetricsMutex);
|
||||||
|
++mMetrics.shaderBuildsCommitted;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RenderCadenceClock.h"
|
#include "RenderCadenceClock.h"
|
||||||
|
#include "../runtime/RuntimeSlangShaderCompiler.h"
|
||||||
|
#include "RuntimeShaderRenderer.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -31,6 +33,8 @@ public:
|
|||||||
uint64_t pboQueueMisses = 0;
|
uint64_t pboQueueMisses = 0;
|
||||||
uint64_t clockOverruns = 0;
|
uint64_t clockOverruns = 0;
|
||||||
uint64_t skippedFrames = 0;
|
uint64_t skippedFrames = 0;
|
||||||
|
uint64_t shaderBuildsCommitted = 0;
|
||||||
|
uint64_t shaderBuildFailures = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
RenderThread(SystemFrameExchange& frameExchange, Config config);
|
RenderThread(SystemFrameExchange& frameExchange, Config config);
|
||||||
@@ -51,9 +55,11 @@ private:
|
|||||||
void CountRendered();
|
void CountRendered();
|
||||||
void CountCompleted();
|
void CountCompleted();
|
||||||
void CountAcquireMiss();
|
void CountAcquireMiss();
|
||||||
|
void TryCommitReadyRuntimeShader(RuntimeShaderRenderer& runtimeShaderRenderer);
|
||||||
|
|
||||||
SystemFrameExchange& mFrameExchange;
|
SystemFrameExchange& mFrameExchange;
|
||||||
Config mConfig;
|
Config mConfig;
|
||||||
|
RuntimeSlangShaderCompiler mSlangCompiler;
|
||||||
std::thread mThread;
|
std::thread mThread;
|
||||||
std::atomic<bool> mStopping{ false };
|
std::atomic<bool> mStopping{ false };
|
||||||
std::atomic<bool> mRunning{ false };
|
std::atomic<bool> mRunning{ false };
|
||||||
|
|||||||
214
apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.cpp
Normal file
214
apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.cpp
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#include "RuntimeShaderRenderer.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<char, 4096> log = {};
|
||||||
|
GLsizei length = 0;
|
||||||
|
glGetProgramInfoLog(program, static_cast<GLsizei>(log.size()), &length, log.data());
|
||||||
|
error = std::string(log.data(), static_cast<std::size_t>(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<float>(frameIndex) / 60.0f;
|
||||||
|
params.inputResolution[0] = static_cast<float>(width);
|
||||||
|
params.inputResolution[1] = static_cast<float>(height);
|
||||||
|
params.outputResolution[0] = static_cast<float>(width);
|
||||||
|
params.outputResolution[1] = static_cast<float>(height);
|
||||||
|
params.frameCount = static_cast<float>(frameIndex);
|
||||||
|
|
||||||
|
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(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<GLsizeiptr>(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<char, 4096> log = {};
|
||||||
|
GLsizei length = 0;
|
||||||
|
glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), &length, log.data());
|
||||||
|
error = std::string(log.data(), static_cast<std::size_t>(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;
|
||||||
|
}
|
||||||
32
apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.h
Normal file
32
apps/RenderCadenceCompositor/render/RuntimeShaderRenderer.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GLExtensions.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
#include "RuntimeSlangShaderCompiler.h"
|
||||||
|
|
||||||
|
#include "ShaderCompiler.h"
|
||||||
|
#include "ShaderTypes.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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<std::mutex> lock(mMutex);
|
||||||
|
mReadyBuild = RuntimeSlangShaderBuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
mRunning.store(true, std::memory_order_release);
|
||||||
|
mThread = std::thread([this]() {
|
||||||
|
RuntimeSlangShaderBuild build = BuildHappyAccident();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::chrono::duration<double, std::milli>>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
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<bool> mRunning{ false };
|
||||||
|
std::mutex mMutex;
|
||||||
|
RuntimeSlangShaderBuild mReadyBuild;
|
||||||
|
};
|
||||||
@@ -24,6 +24,8 @@ struct CadenceTelemetrySnapshot
|
|||||||
uint64_t completions = 0;
|
uint64_t completions = 0;
|
||||||
uint64_t displayedLate = 0;
|
uint64_t displayedLate = 0;
|
||||||
uint64_t dropped = 0;
|
uint64_t dropped = 0;
|
||||||
|
uint64_t shaderBuildsCommitted = 0;
|
||||||
|
uint64_t shaderBuildFailures = 0;
|
||||||
bool deckLinkBufferedAvailable = false;
|
bool deckLinkBufferedAvailable = false;
|
||||||
uint64_t deckLinkBuffered = 0;
|
uint64_t deckLinkBuffered = 0;
|
||||||
double deckLinkScheduleCallMilliseconds = 0.0;
|
double deckLinkScheduleCallMilliseconds = 0.0;
|
||||||
@@ -78,6 +80,20 @@ public:
|
|||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename SystemFrameExchange, typename OutputThread, typename RenderThread>
|
||||||
|
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:
|
private:
|
||||||
using Clock = std::chrono::steady_clock;
|
using Clock = std::chrono::steady_clock;
|
||||||
|
|
||||||
|
|||||||
@@ -31,18 +31,18 @@ public:
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename SystemFrameExchange, typename OutputThread>
|
template <typename SystemFrameExchange, typename OutputThread, typename RenderThread>
|
||||||
void Start(const SystemFrameExchange& exchange, const DeckLinkOutput& output, const OutputThread& outputThread)
|
void Start(const SystemFrameExchange& exchange, const DeckLinkOutput& output, const OutputThread& outputThread, const RenderThread& renderThread)
|
||||||
{
|
{
|
||||||
if (mRunning)
|
if (mRunning)
|
||||||
return;
|
return;
|
||||||
mStopping = false;
|
mStopping = false;
|
||||||
mThread = std::thread([this, &exchange, &output, &outputThread]() {
|
mThread = std::thread([this, &exchange, &output, &outputThread, &renderThread]() {
|
||||||
CadenceTelemetry telemetry;
|
CadenceTelemetry telemetry;
|
||||||
while (!mStopping)
|
while (!mStopping)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(mConfig.interval);
|
std::this_thread::sleep_for(mConfig.interval);
|
||||||
Print(telemetry.Sample(exchange, output, outputThread));
|
Print(telemetry.Sample(exchange, output, outputThread, renderThread));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mRunning = true;
|
mRunning = true;
|
||||||
@@ -70,6 +70,8 @@ private:
|
|||||||
<< " completions=" << snapshot.completions
|
<< " completions=" << snapshot.completions
|
||||||
<< " late=" << snapshot.displayedLate
|
<< " late=" << snapshot.displayedLate
|
||||||
<< " dropped=" << snapshot.dropped
|
<< " dropped=" << snapshot.dropped
|
||||||
|
<< " shaderCommitted=" << snapshot.shaderBuildsCommitted
|
||||||
|
<< " shaderFailures=" << snapshot.shaderBuildFailures
|
||||||
<< " decklinkBuffered=";
|
<< " decklinkBuffered=";
|
||||||
if (snapshot.deckLinkBufferedAvailable)
|
if (snapshot.deckLinkBufferedAvailable)
|
||||||
std::cout << snapshot.deckLinkBuffered;
|
std::cout << snapshot.deckLinkBuffered;
|
||||||
|
|||||||
Reference in New Issue
Block a user