From 8ce556330fcf7a7afbd905102240e510e0e7fd45 Mon Sep 17 00:00:00 2001 From: Aiden Date: Sat, 2 May 2026 19:29:18 +1000 Subject: [PATCH] Transparency --- .../OpenGLComposite.cpp | 36 +++++- config/runtime-host.json | 3 +- shaders/dvd-bounce/DVD_Logo.png | Bin 0 -> 9733 bytes shaders/dvd-bounce/shader.json | 54 ++++++++ shaders/dvd-bounce/shader.slang | 116 ++++++++++++++++++ 5 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 shaders/dvd-bounce/DVD_Logo.png create mode 100644 shaders/dvd-bounce/shader.json create mode 100644 shaders/dvd-bounce/shader.slang 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 0000000000000000000000000000000000000000..82f08edae839d60e673249a517886f27176d9c93 GIT binary patch literal 9733 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%0Dk`bOlLoTEPvGC@b%5Dnvacpt)ng(*aT+{ z`WvYUeE4K6DF2;<6FH9)7Lj zQI?gBN)#SM)4@iVqZ*j>P@Cr&qnWOqqs-#HG8HcLR7O{B#eUCo;*I0NF+Fl`x|7IE zvO})Qs`W(nzn9~n+jQQ1$c97EFbP^Qcm5t6;I@+ zBY6I3I2@VqIh_)Mf2I@T7gB(bD`M)=8cJ}9DzM&m3VLx|UG;6u>VNXjq z3qCJ>Vo>d{6IbyB046xDnn>}q)x$OB6hSWgWdsCNyx`5vuqXc@C+H*qu--kM>e?SR zbuONkU~x1z?#7C0%-&p~F=wCvxehi(5Df{3MS4&Oz#k=>eAgDsu0vp2R3O*aqZkt2 zDLBRKNJRy-#)ph3w>6e(?;gs4IY54=aGtM-IS5QyK-Rs1!oJ+&jO*3`nt1K#;N+0Ni-6 z%}50ZhbThd4rU)RL4q9vxna7w<(nm=%pf9~j2lA{l=jKMVJIsAp*^ipxpwf##RyPw zoV>9S1&)U7WTXC2q}pwO906&`zy~I}9FEW0EQ&F7pI2iCS8$25Hl;iTaZ7<1X)1CAn4kk+aw ze0tOgMf!$LcTF@7%vCGn&!1V+V)aWTE-#mbVYN24Xr0)kR^`mLB5L!XB$0=3>n&PgN( zT>`Z@HTf8AjWI6Ft-mXfa?VklIxb46STyLP8Q2$u5)K*n`Uy=;F_FW+Vej$OJ?ZhetOC>}N^O<`su?#4 zu&*1I(h^+K`>KqR#7nXt6rqSXr{b*!#kNX)l*cW7h5$o~Sfw7Q5az>tpU%n*Eh5J)FeJ9_W~;^oBv(l3_Z!X^~^1v7Q#Mmvi8Rv|D}uZ zaH+FY9%tcC6C&xmSQkqkjHL)p(x20O6q@h0nqkItPv{v={17ZZ*b1H8a!IJEqKhLH z%oJOO{4h9GH~_pUG7(voI`+V78@3}E$QXDg%n0AI>eYL(QZ z^i$(q4(TM_&DE*AF9X*}&j_uE-D*)ZLj^(f+SlygA;iR z)v=|)afjdHv;u!kN$R68+{ZGs#T!(3hm!i&K0AsDk=HoC{Xi8n<>9h7QO)TuHQzY$ zI<VxbU5r3Qa8Ofp<7pRnO5Z>H1eh9$-|A{TiX#N z-GnpF4NSj`9@(+fPT%d|8ZR;zeyB>{-Ym$jG!=+WPg$0}N@H>rqE&>)AB&7H*r{o%o{b*P_7SzDY zNeAZFiu56A-$ix!HAJSjo=4r}evo>G%d(bbrq1dv$*O%Se)Mzc*>v!%aU+I}&$V{6nz(K60U#2Yw4rI-Gat0#1E%~9MNA)Ju zeL^u@!?ffu^#LW0cZRbhhA)qSNFU37;@Bvn8`fc|uKqIYrQOhR&9=)AmIkm1k||wc z!kgWd3_0)5moIL_Gf}M4Q496ch^lRTNl0dVS3xH*c3wH`@!hsV|MtP|Q|WJq^xNy# z)moHoY1&rdva_1uCk;04%I_ntjW_O)-&P52G^MudNj!5;yIGL3D5Avg0F`KRU2S{R z?5z{r(f*M2)kuHuUEz&S9u2|oTFeAq8<=OlgAmE{eySl`F8-z%_4s)yQhPebsJKm8>-T{#(=wscl@N4~eO-!#*RH=I*UV|g?ed#1uv-zN1?qkv%S&)?V^t@AP?fI4;tIt^n9gs8At4*`wUIF zw9Htjrj$pk8L@0?loDJq8#06tPLW6S^mZUV=5`a8Np#1{1*M*_;h&RxuB)!wKB{!1 z1z>pHu`fD<9ibVrUuC_p(`dBHXStg*%ilrnm0#MJ7XL}iequPhkamLnd?p<<&yD9dG14iyl6xnsx+Zf|SRh?m!P=cDt=SF)b zn>uklDj=UR*MyjSk_5un=VX`Zw86S=3a1wvtsWb2 zWYDFIsNHmpnM;iVY3O7Z4~X3nwsOQlDoodR_ZfDN(a6nKl{mz{j_7_)Hvp{Ud)AK>%D(u zG|Ji0GeU{9|*Y3 ZZDuXi<@fXBZDL2P4bPbAJ=bxJ`VZiR-G=}G literal 0 HcmV?d00001 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)); +}