From 70be7312b88d4ade959d62eeb1c96c5e0b109f38 Mon Sep 17 00:00:00 2001 From: Aiden Date: Wed, 6 May 2026 14:35:41 +1000 Subject: [PATCH] Timing and saftey pass --- README.md | 1 - .../gl/OpenGLComposite.cpp | 7 +++ .../gl/OpenGLDeckLinkBridge.cpp | 7 ++- .../gl/OpenGLRenderPass.cpp | 1 + .../gl/OpenGLRenderer.cpp | 1 + .../runtime/RuntimeHost.cpp | 27 +++++--- .../runtime/RuntimeHost.h | 4 +- shaders/happy-accident/shader.json | 54 ++++++++++++++++ shaders/happy-accident/shader.slang | 61 +++++++++++++++++++ 9 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 shaders/happy-accident/shader.json create mode 100644 shaders/happy-accident/shader.slang diff --git a/README.md b/README.md index 3cd6252..e29d754 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,6 @@ D:\SDKs\slang-2026.8-windows-x86_64 If neither variable is set, the workflow falls back to the repo-local defaults under `3rdParty/`. - ## Still Todo - Audio. diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp index 4c37e9c..b349eb2 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLComposite.cpp @@ -294,13 +294,20 @@ void OpenGLComposite::renderEffect() if (mUseCommittedLayerStates) { layerStates = mShaderPrograms->CommittedLayerStates(); + if (mRuntimeHost) + mRuntimeHost->RefreshDynamicRenderStateFields(layerStates); } else if (mRuntimeHost) { if (mRuntimeHost->TryGetLayerRenderStates(mDeckLink->InputFrameWidth(), mDeckLink->InputFrameHeight(), layerStates)) + { mCachedLayerRenderStates = layerStates; + } else + { layerStates = mCachedLayerRenderStates; + mRuntimeHost->RefreshDynamicRenderStateFields(layerStates); + } } const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0; mRenderPass->Render( diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp index 7b67b50..875c945 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLDeckLinkBridge.cpp @@ -98,6 +98,8 @@ void OpenGLDeckLinkBridge::VideoFrameArrived(IDeckLinkVideoInputFrame* inputFram else { // Use a straightforward texture buffer + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mRenderer.UnpinnedTextureBuffer()); glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, videoPixels, GL_DYNAMIC_DRAW); glBindTexture(GL_TEXTURE_2D, mRenderer.CaptureTexture()); @@ -174,13 +176,14 @@ void OpenGLDeckLinkBridge::PlayoutFrameCompleted(IDeckLinkVideoFrame* completedF if (!mDeckLink.TransferPlayoutFrame(pFrame, mRenderer.OutputTexture())) OutputDebugStringA("Playback: transferFrame() failed\n"); - mPaint(); - // Wait for transfer to system memory to complete mDeckLink.WaitForPlayoutTransferComplete(pFrame); + mPaint(); } else { + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, mRenderer.OutputFramebuffer()); glReadPixels(0, 0, mDeckLink.OutputFrameWidth(), mDeckLink.OutputFrameHeight(), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pFrame); mPaint(); diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderPass.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderPass.cpp index 515bfbd..10997ba 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderPass.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderPass.cpp @@ -23,6 +23,7 @@ void OpenGLRenderPass::Render( VideoFrameTransfer::beginTextureInUse(VideoFrameTransfer::CPUtoGPU); } + glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); if (hasInputSource) diff --git a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp index 10888c1..75ceb7d 100644 --- a/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/gl/OpenGLRenderer.cpp @@ -143,6 +143,7 @@ void OpenGLRenderer::PresentToWindow(HDC hdc, unsigned outputFrameWidth, unsigne glBindFramebuffer(GL_READ_FRAMEBUFFER, mOutputFrameBuf); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDisable(GL_SCISSOR_TEST); glViewport(0, 0, mViewWidth, mViewHeight); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp index ada5948..d09a03e 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.cpp @@ -1274,16 +1274,11 @@ void RuntimeHost::SetFramePacingStatsLocked(double completionIntervalMillisecond void RuntimeHost::AdvanceFrame() { - std::lock_guard lock(mMutex); ++mFrameCounter; } bool RuntimeHost::TryAdvanceFrame() { - std::unique_lock lock(mMutex, std::try_to_lock); - if (!lock.owns_lock()) - return false; - ++mFrameCounter; return true; } @@ -1345,9 +1340,23 @@ bool RuntimeHost::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputH return true; } -void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const +void RuntimeHost::RefreshDynamicRenderStateFields(std::vector& states) const { const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot(); + const double timeSeconds = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mStartTime).count(); + const double frameCount = static_cast(mFrameCounter.load(std::memory_order_relaxed)); + + for (RuntimeRenderState& state : states) + { + state.timeSeconds = timeSeconds; + state.utcTimeSeconds = clock.utcTimeSeconds; + state.utcOffsetSeconds = clock.utcOffsetSeconds; + state.frameCount = frameCount; + } +} + +void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned outputHeight, std::vector& states) const +{ for (const LayerPersistentState& layer : mPersistentState.layers) { auto shaderIt = mPackagesById.find(layer.shaderId); @@ -1357,10 +1366,6 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou RuntimeRenderState state; state.layerId = layer.id; state.shaderId = layer.shaderId; - state.timeSeconds = std::chrono::duration_cast>(std::chrono::steady_clock::now() - mStartTime).count(); - state.utcTimeSeconds = clock.utcTimeSeconds; - state.utcOffsetSeconds = clock.utcOffsetSeconds; - state.frameCount = static_cast(mFrameCounter); state.mixAmount = 1.0; state.bypass = layer.bypass ? 1.0 : 0.0; state.inputWidth = mSignalWidth; @@ -1386,6 +1391,8 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou states.push_back(state); } + + RefreshDynamicRenderStateFields(states); } std::string RuntimeHost::BuildStateJson() const diff --git a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h index 223a80b..cbde57c 100644 --- a/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/runtime/RuntimeHost.h @@ -3,6 +3,7 @@ #include "RuntimeJson.h" #include "ShaderTypes.h" +#include #include #include #include @@ -50,6 +51,7 @@ public: bool BuildLayerFragmentShaderSource(const std::string& layerId, std::string& fragmentShaderSource, std::string& error); std::vector GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const; bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector& states) const; + void RefreshDynamicRenderStateFields(std::vector& states) const; std::string BuildStateJson() const; const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; } @@ -173,6 +175,6 @@ private: bool mAutoReloadEnabled; std::chrono::steady_clock::time_point mStartTime; std::chrono::steady_clock::time_point mLastScanTime; - uint64_t mFrameCounter; + std::atomic mFrameCounter; uint64_t mNextLayerId; }; diff --git a/shaders/happy-accident/shader.json b/shaders/happy-accident/shader.json new file mode 100644 index 0000000..6e392e5 --- /dev/null +++ b/shaders/happy-accident/shader.json @@ -0,0 +1,54 @@ +{ + "id": "happy-accident", + "name": "Happy Accident", + "description": "Raymarched generative line field. CC0 original 'Clearly a bug' adapted from https://www.shadertoy.com/view/33cGDj.", + "category": "Generative", + "entryPoint": "shadeVideo", + "parameters": [ + { + "id": "speed", + "label": "Speed", + "type": "float", + "default": 1.0, + "min": 0.0, + "max": 4.0, + "step": 0.01 + }, + { + "id": "scale", + "label": "Scale", + "type": "float", + "default": 1.0, + "min": 0.25, + "max": 3.0, + "step": 0.01 + }, + { + "id": "raySteps", + "label": "Ray Steps", + "type": "float", + "default": 77.0, + "min": 8.0, + "max": 77.0, + "step": 1.0 + }, + { + "id": "intensity", + "label": "Intensity", + "type": "float", + "default": 1.0, + "min": 0.1, + "max": 4.0, + "step": 0.01 + }, + { + "id": "sourceMix", + "label": "Source Mix", + "type": "float", + "default": 0.0, + "min": 0.0, + "max": 1.0, + "step": 0.01 + } + ] +} diff --git a/shaders/happy-accident/shader.slang b/shaders/happy-accident/shader.slang new file mode 100644 index 0000000..02ee7d8 --- /dev/null +++ b/shaders/happy-accident/shader.slang @@ -0,0 +1,61 @@ +float happyNoise(float2 p) +{ + return frac(dot(p, sin(p))) - 0.5; +} + +float2x2 rotateAroundZ(float angle) +{ + float c = cos(angle); + float s = sin(angle); + return float2x2(c, s, -s, c); +} + +float2x2 happyAccidentMatrix(float3 originalPosition, float timeCos) +{ + return float2x2( + cos(originalPosition.x), + sin(originalPosition.y), + -sin(originalPosition.z), + timeCos); +} + +float4 shadeVideo(ShaderContext context) +{ + float2 resolution = max(context.outputResolution, float2(1.0, 1.0)); + float2 fragCoord = context.uv * resolution; + float2 normalizedCoord = (fragCoord - 0.5 * resolution) / resolution.y / max(scale, 0.001); + float time = context.time * speed; + float timeCos = cos(0.1 * time); + float3 direction = normalize(float3(normalizedCoord, 1.0)); + float3 origin = float3(0.0, 0.0, time); + + float z = happyNoise(fragCoord); + float distanceToSurface = 0.0; + float4 accumulated = float4(0.0, 0.0, 0.0, 0.0); + float clampedSteps = clamp(raySteps, 1.0, 77.0); + + for (int i = 0; i < 77; ++i) + { + if (float(i) >= clampedSteps) + break; + + z += 0.6 * distanceToSurface; + + float3 position = origin + z * direction; + float3 originalPosition = position; + + position.xy = mul(rotateAroundZ(2.0 + originalPosition.z), position.xy); + position.xy = mul(happyAccidentMatrix(originalPosition, timeCos), position.xy); + + float colorSeed = 0.5 * originalPosition.z + length(position - originalPosition); + float4 palette = 1.0 + sin(colorSeed + float4(0.0, 4.0, 3.0, 6.0)); + palette /= 0.5 + 2.0 * dot(originalPosition.xy, originalPosition.xy); + + position = abs(frac(position) - 0.5); + distanceToSurface = abs(min(length(position.xy) - 0.125, min(position.x, position.y) + 0.001)) + 0.001; + accumulated += palette.w * palette / distanceToSurface; + } + + float4 color = float4(tanh((accumulated.rgb * intensity) / 20000.0), 1.0); + return saturate(lerp(color, context.sourceColor, sourceMix)); +}