Why I Built a Custom C++ HTML Graphics Playout Tool: Solving the "Smooth vs. Clean Alpha" Dilemma in Broadcast Delivery
OBS Studio and CasparCG both feature a "Browser Source" function, allowing users to easily load transparent HTML pages to deliver web-based lower-thirds and animations (on-air graphics).
Furthermore, these tools can interface with Blackmagic Design DeckLink devices (such as the UltraStudio HD Mini) to output 1920×1080 / 59.94i interlaced signals—the standard for Japanese television broadcasting—separating the feed into Fill (video) and Key (alpha mask) via SDI. While these tools are highly popular as affordable and accessible playout solutions, pushing the limits of graphics quality in broadcast and live production environments reveals a critical, unavoidable bottleneck.
It is the dilemma of achieving both "Smooth Motion (60fps)" and "Clean Alpha Blending (No Dark Fringes)" simultaneously.
🧐 The Dilemma: Choppy & Clean vs. Smooth & Dark
What happens when you display a text banner sliding in smoothly from the edge of a professional video monitor?
1. OBS Studio ── "Choppy but Clean"
- Choppy Motion: While OBS can output a 59.94i interlaced signal via SDI, its internal rendering engine (including the browser source) draws frames at only 30fps. As a result, identical images are sent for both the first and second fields, causing scrolling text to stutter heavily (frame drops) when viewed on a professional monitor.
- Clean Alpha: On the bright side, OBS has an excellent "Unmultiplied Filter" plugin. This allows soft shadows and semi-transparent edges to be sent to the switcher with pristine transparency, avoiding the double-premultiplication issues described below.
- Result: Motion is choppy, but semi-transparency looks beautiful.
2. CasparCG ── "Smooth but Dark Fringes"
- Smooth Motion: CasparCG excels here, rendering internally at a full 60fps. Because different timings are applied to the first and second fields, scrolling text moves with buttery-smooth fluidity.
- Dark Fringes: However, CasparCG lacks a native, out-of-the-box solution for "Unpremultiplication." Web browsers (Chromium) internally render RGBA graphics as Premultiplied Alpha ($R \times A, G \times A, B \times A, A$), meaning the RGB channels are already multiplied by the alpha value. If this data is sent directly through DeckLink as Fill ($R \times A, G \times A, B \times A$) and Key ($A$) to an external hardware switcher, the alpha value gets multiplied twice during keying. This results in ugly, dark fringes around soft edges and gradients, destroying the clean transparency.
- Result: Motion is buttery-smooth, but semi-transparent edges look dirty and dark.
| Tool | Motion Smoothness (59.94i) | Edge Alpha Quality | Characteristics |
|---|---|---|---|
| OBS Studio | 30fps Rendering (Choppy) | Unmultiplied Support (Clean) | 🔴 Choppy & Clean |
| CasparCG | 60fps Rendering (Smooth) | Direct Output (Double-Premultiplied) | 🔴 Smooth & Dark |
AI assistants often suggest simply changing the external hardware switcher/keyer setting to "Premultiplied Alpha" to fix the blending. However, affordable production switchers rarely come with such advanced configuration options.
What I wanted was a "buttery-smooth scrolling banner that retains flawless, crystal-clear semi-transparency."
After searching extensively without finding a single software solution that satisfied both requirements, I realized: "If it doesn't exist, I have to build it." That is why I developed CEFDecklink.
🛠️ How "Smooth & Clean" Playout is Achieved
This custom application directly links CEF (Chromium Embedded Framework) with the DeckLink SDK using native C++, utilizing the GPU for real-time pixel manipulation to bridge this gap.
1. Interlace Generation with Full 60fps Synchronization
Using CEF’s Offscreen Rendering (OSR), the web page animations are rendered at a native 60 frames per second.
The playout engine captures these frames and splits them across time, assigning the appropriate pixels from the 60fps timeline to the odd lines (Field 1) and even lines (Field 2) to reconstruct a true interlaced signal. This replicates CasparCG’s fluid, responsive motion graphics.
2. Real-Time "Unpremultiplication" via D3D11 Compute Shader
As mentioned, the pixel data received from CEF is inherently premultiplied.
To prevent dark fringes during hardware keying, the engine divides the RGB values by their corresponding Alpha value ($A$) in the output buffer right before broadcasting, reverting the data back to straight RGBA (Unpremultiplied).
$$R_{\text{unmult}} = \frac{R_{\text{premult}}}{A}, \quad G_{\text{unmult}} = \frac{G_{\text{premult}}}{A}, \quad B_{\text{unmult}} = \frac{B_{\text{premult}}}{A}$$
Executing this division across a Full HD (1920x1080) resolution at 60fps is computationally heavy for a CPU. To solve this, the system offloads the task to a Direct3D 11 Compute Shader (HLSL), leveraging massive GPU parallelism to complete the operation instantly.
// Excerpt from YUVConvert.hlsl
[numthreads(16, 16, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
uint x = DTid.x;
uint y = DTid.y;
// Sample the pixel passed from CEF
float4 pixel = InputTexture.Load(int3(x, y, 0));
// --- Unpremultiply: Divide by Alpha to restore original RGB ---
if (pixel.a > alphaThreshold) {
pixel.rgb /= pixel.a;
}
// --- Pack for Output (ARGB Format) ---
uint A = (uint)(saturate(pixel.a) * 255.0f);
uint R = (uint)(saturate(pixel.r) * 255.0f);
...
OutputBuffer[uint2(x, y)] = packed;
}
This guarantees that even standard, entry-level switchers can key the graphics with absolutely flawless, crystal-clear transparency and zero dark artifacts.
💡 Conclusion
By leveraging the performance of native C++ to bridge the gap between web rendering and broadcast hardware, I successfully created a playout tool that delivers both buttery-smooth 60fps motion and pristine alpha transparency.
If you are looking to combine the endless creative possibilities of HTML/Web technologies with the strict quality demands of broadcast engineering (interlacing + clean alpha keying), I hope this implementation serves as a helpful reference.
Note: The source code and demo application are openly available on the GitHub Repository (tanaka-ryuya/CEFDecklinkDemo).