diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index fa271fb..cab147b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -320,10 +320,14 @@ bool OpenGLComposite::InitDeckLink() BSTR modelNameBstr = NULL; if (deckLinkAttributes->GetString(BMDDeckLinkModelName, &modelNameBstr) == S_OK && modelNameBstr != NULL) { - _bstr_t modelNameWrapper(modelNameBstr, false); - const char* modelNameChars = modelNameWrapper; - if (modelNameChars != NULL) - modelName = modelNameChars; + const int requiredBytes = WideCharToMultiByte(CP_UTF8, 0, modelNameBstr, -1, NULL, 0, NULL, NULL); + if (requiredBytes > 1) + { + std::vector utf8Name(static_cast(requiredBytes), '\0'); + if (WideCharToMultiByte(CP_UTF8, 0, modelNameBstr, -1, utf8Name.data(), requiredBytes, NULL, NULL) > 0) + modelName.assign(utf8Name.data()); + } + SysFreeString(modelNameBstr); } deckLinkAttributes->Release(); deckLinkAttributes = NULL; @@ -525,6 +529,13 @@ bool OpenGLComposite::InitDeckLink() error: if (!bSuccess) { + if (mDLKeyer != NULL) + { + mDLKeyer->Disable(); + mDLKeyer->Release(); + mDLKeyer = NULL; + mDeckLinkExternalKeyingActive = false; + } if (mDLInput != NULL) { mDLInput->Release(); @@ -990,6 +1001,23 @@ bool OpenGLComposite::Stop() if (mControlServer) mControlServer->Stop(); + if (mDLKeyer != NULL) + { + mDLKeyer->Disable(); + mDeckLinkExternalKeyingActive = false; + if (mRuntimeHost) + { + mRuntimeHost->SetDeckLinkOutputStatus( + mDeckLinkOutputModelName, + mDeckLinkSupportsInternalKeying, + mDeckLinkSupportsExternalKeying, + mDeckLinkKeyerInterfaceAvailable, + mRuntimeHost->ExternalKeyingEnabled(), + mDeckLinkExternalKeyingActive, + "External keying has been disabled."); + } + } + mDLInput->StopStreams(); mDLInput->DisableVideoInput(); diff --git a/config/runtime-host.json b/config/runtime-host.json index e9fd108..6f1634b 100644 --- a/config/runtime-host.json +++ b/config/runtime-host.json @@ -2,5 +2,6 @@ "shaderLibrary": "shaders", "serverPort": 8080, "autoReload": true, - "maxTemporalHistoryFrames": 12 + "maxTemporalHistoryFrames": 12, + "enableExternalKeying": true } diff --git a/shaders/dvd-bounce/DVD_Logo.png b/shaders/dvd-bounce/DVD_Logo.png new file mode 100644 index 0000000..82f08ed Binary files /dev/null and b/shaders/dvd-bounce/DVD_Logo.png differ diff --git a/shaders/dvd-bounce/shader.json b/shaders/dvd-bounce/shader.json new file mode 100644 index 0000000..a4f3ba8 --- /dev/null +++ b/shaders/dvd-bounce/shader.json @@ -0,0 +1,54 @@ +{ + "id": "dvd-bounce", + "name": "DVD Bounce", + "description": "A transparent DVD-style logo that bounces endlessly and changes color on each screen hit.", + "category": "Built-in", + "entryPoint": "shadeVideo", + "parameters": [ + { + "id": "logoScale", + "label": "Logo Scale", + "type": "float", + "default": 0.28, + "min": 0.12, + "max": 0.5, + "step": 0.01 + }, + { + "id": "bounceSpeed", + "label": "Bounce Speed", + "type": "float", + "default": 0.22, + "min": 0.02, + "max": 0.8, + "step": 0.01 + }, + { + "id": "edgePadding", + "label": "Edge Padding", + "type": "float", + "default": 0.018, + "min": 0.0, + "max": 0.08, + "step": 0.001 + }, + { + "id": "glowAmount", + "label": "Glow", + "type": "float", + "default": 0.18, + "min": 0.0, + "max": 0.75, + "step": 0.01 + }, + { + "id": "baseAlpha", + "label": "Alpha", + "type": "float", + "default": 1.0, + "min": 0.05, + "max": 1.0, + "step": 0.01 + } + ] +} diff --git a/shaders/dvd-bounce/shader.slang b/shaders/dvd-bounce/shader.slang new file mode 100644 index 0000000..3cb10f8 --- /dev/null +++ b/shaders/dvd-bounce/shader.slang @@ -0,0 +1,116 @@ +float sdBox(float2 p, float2 b) +{ + float2 d = abs(p) - b; + return length(max(d, float2(0.0, 0.0))) + min(max(d.x, d.y), 0.0); +} + +float sdRoundedBox(float2 p, float2 b, float r) +{ + return sdBox(p, b - r) - r; +} + +float sdSegment(float2 p, float2 a, float2 b) +{ + float2 pa = p - a; + float2 ba = b - a; + float h = saturate(dot(pa, ba) / max(dot(ba, ba), 0.0001)); + return length(pa - ba * h); +} + +float pingPong(float x, float lengthValue) +{ + float safeLength = max(lengthValue, 0.0001); + float period = safeLength * 2.0; + float wrapped = x - floor(x / period) * period; + return wrapped <= safeLength ? wrapped : (period - wrapped); +} + +float3 hsvToRgb(float3 hsv) +{ + float3 p = abs(frac(hsv.x + float3(0.0, 0.6666667, 0.3333333)) * 6.0 - 3.0); + float3 rgb = saturate(p - 1.0); + return hsv.z * lerp(float3(1.0, 1.0, 1.0), rgb, hsv.y); +} + +float letterD(float2 p, float scale) +{ + float2 offset = p; + float stem = sdBox(offset + float2(scale * 0.18, 0.0), float2(scale * 0.065, scale * 0.47)); + float outer = sdRoundedBox(offset + float2(scale * 0.03, 0.0), float2(scale * 0.30, scale * 0.47), scale * 0.23); + float inner = sdRoundedBox(offset + float2(scale * 0.11, 0.0), float2(scale * 0.15, scale * 0.24), scale * 0.12); + float bowl = max(outer, -inner); + return min(stem, bowl); +} + +float letterV(float2 p, float scale) +{ + float leftStroke = sdSegment(p, float2(-scale * 0.30, -scale * 0.48), float2(0.0, scale * 0.48)) - scale * 0.085; + float rightStroke = sdSegment(p, float2(scale * 0.30, -scale * 0.48), float2(0.0, scale * 0.48)) - scale * 0.085; + return min(leftStroke, rightStroke); +} + +float logoLetters(float2 p, float scale) +{ + float left = letterD(p + float2(scale * 0.92, 0.0), scale); + float middle = letterV(p, scale); + float right = letterD(p - float2(scale * 0.92, 0.0), scale); + return min(left, min(middle, right)); +} + +float4 shadeVideo(ShaderContext context) +{ + float2 resolution = max(context.outputResolution, float2(1.0, 1.0)); + float minDimension = min(resolution.x, resolution.y); + + float safeScale = max(logoScale, 0.05); + float2 logoHalfSize = float2(minDimension * safeScale * 0.82, minDimension * safeScale * 0.28); + float2 paddingPx = float2(minDimension * max(edgePadding, 0.0), minDimension * max(edgePadding, 0.0)); + float2 minCenterPx = logoHalfSize + paddingPx; + float2 maxCenterPx = resolution - logoHalfSize - paddingPx; + float2 travelPx = max(maxCenterPx - minCenterPx, float2(1.0, 1.0)); + + float2 velocityPx = float2( + max(20.0, bounceSpeed * minDimension * 1.00), + max(24.0, bounceSpeed * minDimension * 0.77)); + float2 motionPx = context.time * velocityPx; + float2 centerPx = minCenterPx + float2( + pingPong(motionPx.x, travelPx.x), + pingPong(motionPx.y, travelPx.y)); + + int xHits = int(floor(motionPx.x / max(travelPx.x, 1.0))); + int yHits = int(floor(motionPx.y / max(travelPx.y, 1.0))); + int totalHits = max(0, xHits + yHits); + + float hue = frac(0.09 + float(totalHits) * 0.173); + float3 badgeColor = hsvToRgb(float3(hue, 0.86, 1.0)); + float3 glowColor = hsvToRgb(float3(frac(hue + 0.06), 0.72, 1.0)); + + float2 fragPx = context.uv * resolution; + float2 p = fragPx - centerPx; + + float badgeDist = sdRoundedBox(p, logoHalfSize, logoHalfSize.y * 0.42); + float innerBadgeDist = sdRoundedBox(p, logoHalfSize - float2(minDimension * 0.012, minDimension * 0.012), logoHalfSize.y * 0.34); + + float letterScale = logoHalfSize.y * 0.88; + float letterDist = logoLetters(p, letterScale); + + float aa = 1.5; + float badgeMask = 1.0 - smoothstep(0.0, aa, badgeDist); + float rimMask = 1.0 - smoothstep(0.0, aa, abs(badgeDist + minDimension * 0.01)); + float innerShade = 1.0 - smoothstep(0.0, aa, innerBadgeDist); + float lettersMask = 1.0 - smoothstep(0.0, aa, letterDist); + float glowMask = (1.0 - smoothstep(0.0, minDimension * 0.04, badgeDist)) * glowAmount; + + float3 color = float3(0.0, 0.0, 0.0); + color += glowColor * glowMask * 0.55; + color = lerp(color, badgeColor * 0.95, badgeMask); + color = lerp(color, badgeColor * 0.55 + float3(0.08, 0.08, 0.1), innerShade * 0.35); + color = lerp(color, float3(1.0, 1.0, 1.0), rimMask * 0.8); + color = lerp(color, float3(0.98, 0.99, 1.0), lettersMask); + + float alpha = max(badgeMask, glowMask * 0.45); + alpha = max(alpha, lettersMask); + alpha *= saturate(baseAlpha); + + return float4(saturate(color), saturate(alpha)); +}