using Vortice.Direct2D1; using Vortice.DirectWrite; using Vortice.DXGI; using Vortice.Mathematics; using DWriteFactory = Vortice.DirectWrite.IDWriteFactory; using D2dFactoryType = Vortice.Direct2D1.FactoryType; using DwFactoryType = Vortice.DirectWrite.FactoryType; namespace Automata.Ui.Overlay; public sealed class D2dRenderContext : IDisposable { private readonly nint _hwnd; private readonly int _width; private readonly int _height; // Factories public ID2D1Factory1 D2dFactory { get; } public DWriteFactory DWriteFactory { get; } // Render target (recreated on device loss) public ID2D1HwndRenderTarget RenderTarget { get; private set; } = null!; // Pre-created brushes public ID2D1SolidColorBrush Red { get; private set; } = null!; public ID2D1SolidColorBrush Yellow { get; private set; } = null!; public ID2D1SolidColorBrush Cyan { get; private set; } = null!; public ID2D1SolidColorBrush Green { get; private set; } = null!; public ID2D1SolidColorBrush White { get; private set; } = null!; public ID2D1SolidColorBrush Gray { get; private set; } = null!; public ID2D1SolidColorBrush LifeBrush { get; private set; } = null!; public ID2D1SolidColorBrush ManaBrush { get; private set; } = null!; public ID2D1SolidColorBrush ShieldBrush { get; private set; } = null!; public ID2D1SolidColorBrush BarBgBrush { get; private set; } = null!; public ID2D1SolidColorBrush LabelBgBrush { get; private set; } = null!; public ID2D1SolidColorBrush DebugTextBrush { get; private set; } = null!; public ID2D1SolidColorBrush TimingBrush { get; private set; } = null!; public ID2D1SolidColorBrush DebugBgBrush { get; private set; } = null!; public ID2D1SolidColorBrush ProfilerBrush { get; private set; } = null!; // Rarity brushes for entity labels public ID2D1SolidColorBrush MagicBrush { get; private set; } = null!; public ID2D1SolidColorBrush RareBrush { get; private set; } = null!; public ID2D1SolidColorBrush UniqueBrush { get; private set; } = null!; // Text formats public IDWriteTextFormat LabelFormat { get; } // 12pt — enemy labels public IDWriteTextFormat BarValueFormat { get; } // 11pt — bar values public IDWriteTextFormat DebugFormat { get; } // 13pt — debug overlay public D2dRenderContext(nint hwnd, int width, int height) { _hwnd = hwnd; _width = width; _height = height; D2dFactory = D2D1.D2D1CreateFactory(D2dFactoryType.SingleThreaded); DWriteFactory = Vortice.DirectWrite.DWrite.DWriteCreateFactory(DwFactoryType.Shared); CreateRenderTarget(); CreateBrushes(); // Text formats (these survive device loss — they're DWrite, not D2D) LabelFormat = DWriteFactory.CreateTextFormat("Consolas", 12f); BarValueFormat = DWriteFactory.CreateTextFormat("Consolas", 11f); DebugFormat = DWriteFactory.CreateTextFormat("Consolas", 13f); } private void CreateRenderTarget() { var rtProps = new RenderTargetProperties { Type = RenderTargetType.Software, PixelFormat = new Vortice.DCommon.PixelFormat(Format.B8G8R8A8_UNorm, Vortice.DCommon.AlphaMode.Premultiplied), }; var hwndProps = new HwndRenderTargetProperties { Hwnd = _hwnd, PixelSize = new SizeI(_width, _height), PresentOptions = PresentOptions.Immediately, }; RenderTarget = D2dFactory.CreateHwndRenderTarget(rtProps, hwndProps); RenderTarget.TextAntialiasMode = Vortice.Direct2D1.TextAntialiasMode.Grayscale; } private void CreateBrushes() { Red = RenderTarget.CreateSolidColorBrush(new Color4(1f, 0f, 0f, 1f)); Yellow = RenderTarget.CreateSolidColorBrush(new Color4(1f, 1f, 0f, 1f)); Cyan = RenderTarget.CreateSolidColorBrush(new Color4(0f, 1f, 1f, 1f)); Green = RenderTarget.CreateSolidColorBrush(new Color4(0.31f, 1f, 0.31f, 1f)); // 80,255,80 White = RenderTarget.CreateSolidColorBrush(new Color4(1f, 1f, 1f, 1f)); Gray = RenderTarget.CreateSolidColorBrush(new Color4(0.5f, 0.5f, 0.5f, 1f)); LifeBrush = RenderTarget.CreateSolidColorBrush(new Color4(200 / 255f, 40 / 255f, 40 / 255f, 1f)); ManaBrush = RenderTarget.CreateSolidColorBrush(new Color4(40 / 255f, 80 / 255f, 200 / 255f, 1f)); ShieldBrush = RenderTarget.CreateSolidColorBrush(new Color4(100 / 255f, 180 / 255f, 220 / 255f, 1f)); BarBgBrush = RenderTarget.CreateSolidColorBrush(new Color4(20 / 255f, 20 / 255f, 20 / 255f, 140 / 255f)); LabelBgBrush = RenderTarget.CreateSolidColorBrush(new Color4(0f, 0f, 0f, 160 / 255f)); DebugTextBrush = RenderTarget.CreateSolidColorBrush(new Color4(80 / 255f, 1f, 80 / 255f, 1f)); TimingBrush = RenderTarget.CreateSolidColorBrush(new Color4(1f, 200 / 255f, 80 / 255f, 1f)); DebugBgBrush = RenderTarget.CreateSolidColorBrush(new Color4(0f, 0f, 0f, 160 / 255f)); ProfilerBrush = RenderTarget.CreateSolidColorBrush(new Color4(180 / 255f, 140 / 255f, 1f, 1f)); // light purple MagicBrush = RenderTarget.CreateSolidColorBrush(new Color4(0.4f, 0.53f, 1f, 1f)); // #6688FF RareBrush = RenderTarget.CreateSolidColorBrush(new Color4(1f, 0.93f, 0.34f, 1f)); // #FFEE57 UniqueBrush = RenderTarget.CreateSolidColorBrush(new Color4(1f, 0.55f, 0f, 1f)); // #FF8C00 } private void DisposeBrushes() { Red?.Dispose(); Yellow?.Dispose(); Cyan?.Dispose(); Green?.Dispose(); White?.Dispose(); Gray?.Dispose(); LifeBrush?.Dispose(); ManaBrush?.Dispose(); ShieldBrush?.Dispose(); BarBgBrush?.Dispose(); LabelBgBrush?.Dispose(); DebugTextBrush?.Dispose(); TimingBrush?.Dispose(); DebugBgBrush?.Dispose(); ProfilerBrush?.Dispose(); MagicBrush?.Dispose(); RareBrush?.Dispose(); UniqueBrush?.Dispose(); } /// /// Call after EndDraw returns D2DERR_RECREATE_TARGET. /// Recreates the render target and all device-dependent resources. /// public void RecreateTarget() { DisposeBrushes(); RenderTarget?.Dispose(); CreateRenderTarget(); CreateBrushes(); } /// Create a text layout for measurement + drawing. public IDWriteTextLayout CreateTextLayout(string text, IDWriteTextFormat format, float maxWidth = 4096f, float maxHeight = 4096f) { return DWriteFactory.CreateTextLayout(text, format, maxWidth, maxHeight); } public void Dispose() { DisposeBrushes(); RenderTarget?.Dispose(); LabelFormat?.Dispose(); BarValueFormat?.Dispose(); DebugFormat?.Dispose(); DWriteFactory?.Dispose(); D2dFactory?.Dispose(); } }