feat(text): composite shared text rasters in output

Raster static text on UI-thread changes and reuse the same transparent BGRA frame in preview and all compositor paths. Cache text frame identity in the frame-pump signature and invalidate static bakes on edits. Follow OBS text-source conventions: https://obsproject.com/kb/text-sources
This commit is contained in:
2026-11-04 15:01:47 -08:00
parent 58eb640c4e
commit 5c4acf141b
19 changed files with 622 additions and 81 deletions
+61
View File
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using ytLive.Models;
using ytLive.Services;
namespace ytLive.ViewModels;
public partial class MainViewModel
{
private readonly TextOverlayRenderer _textOverlayRenderer = new();
private readonly HashSet<Source> _wiredTextOverlays = new();
private void UpdateConfiguredTextOverlays()
{
var sources = Scenes.SelectMany(scene => scene.Elements).OfType<Source>()
.Where(source => source.Type == SourceType.TextOverlay).ToArray();
RetainTextOverlaySubscriptions();
_textOverlayRenderer.Retain(sources);
foreach (var source in sources)
{
if (source.Width <= 0) source.Width = 900;
if (source.Height <= 0) source.Height = 140;
WireTextOverlaySource(source);
_textOverlayRenderer.UpdatePreview(source);
}
}
private void WireTextOverlaySource(Source source)
{
if (_wiredTextOverlays.Add(source))
source.PropertyChanged += OnElementPropertyChanged;
}
private void UpdateTextOverlayPreview(Source source) => _textOverlayRenderer.UpdatePreview(source);
private void RemoveTextOverlayPreview(Source source)
{
if (_wiredTextOverlays.Remove(source))
source.PropertyChanged -= OnElementPropertyChanged;
_textOverlayRenderer.Remove(source);
}
private void RetainTextOverlaySubscriptions()
{
var active = Scenes.SelectMany(scene => scene.Elements).OfType<Source>()
.Where(source => source.Type == SourceType.TextOverlay).ToHashSet();
foreach (var source in _wiredTextOverlays.ToArray())
if (!active.Contains(source))
RemoveTextOverlayPreview(source);
}
private void OnTextOverlayPropertyChanged(Source source, string? propertyName)
{
if (propertyName is nameof(Source.TextContent) or nameof(Source.TextFontFamily)
or nameof(Source.TextFontSize) or nameof(Source.TextColor) or nameof(Source.TextBold)
or nameof(Source.TextItalic) or nameof(Source.TextUnderline) or nameof(Source.TextAlignment)
or nameof(Source.Width) or nameof(Source.Height))
UpdateTextOverlayPreview(source);
}
}