diff --git a/src/AcDream.App/Rendering/RenderBootstrap.cs b/src/AcDream.App/Rendering/RenderBootstrap.cs new file mode 100644 index 00000000..8f8a1001 --- /dev/null +++ b/src/AcDream.App/Rendering/RenderBootstrap.cs @@ -0,0 +1,162 @@ +using DatReaderWriter; +using Microsoft.Extensions.Logging.Abstractions; +using Silk.NET.OpenGL; + +namespace AcDream.App.Rendering; + +/// +/// The subset of the production render stack that the UI Studio needs: +/// GL + dats + UiHost + the WB mesh pipeline. Constructed from the same +/// classes and the same order as , minus +/// terrain / sky / physics / streaming. +/// +public sealed record RenderStack( + GL Gl, + DatReaderWriter.DatCollection Dats, + string ShaderDir, + Wb.BindlessSupport Bindless, + TextureCache TextureCache, + Shader MeshShader, + Wb.WbMeshAdapter MeshAdapter, + Wb.EntitySpawnAdapter EntitySpawnAdapter, + Wb.WbDrawDispatcher DrawDispatcher, + SceneLightingUboBinding LightingUbo, + AcDream.App.UI.UiHost UiHost, + AcDream.App.UI.UiDatFont? VitalsDatFont) +{ + /// + /// Resolves a sprite id (0x06xxxxxx) to a (GL handle, width, height) triple. + /// Copied verbatim from GameWindow's ResolveChrome closure — it calls + /// TextureCache.GetOrUploadRenderSurface(id, out w, out h). + /// + public (uint handle, int width, int height) ResolveChrome(uint spriteId) + { + uint t = TextureCache.GetOrUploadRenderSurface(spriteId, out int w, out int h); + return (t, w, h); + } +} + +/// Options for . +public sealed record RenderBootstrapOptions( + AcDream.UI.Abstractions.Settings.QualitySettings Quality); + +/// +/// Constructs the UI Studio's render stack from the production classes, +/// in the same order as . +/// +public static class RenderBootstrap +{ + /// + /// Build the studio's render stack. Throws + /// (same message as GameWindow) if GL_ARB_bindless_texture or + /// GL_ARB_shader_draw_parameters are absent — the modern path is mandatory. + /// + public static RenderStack Create( + GL gl, + DatReaderWriter.DatCollection dats, + RenderBootstrapOptions opts) + { + // --- Bindless detection (GameWindow ~1701-1723) --- + if (!Wb.BindlessSupport.TryCreate(gl, out var bindless) + || bindless is null + || !bindless.HasShaderDrawParameters(gl)) + { + throw new NotSupportedException( + "acdream requires GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters " + + "(GL 4.3+ with bindless support). Your GPU/driver does not expose these extensions. " + + "If this is unexpected, please file a bug report with your GPU vendor + driver version."); + } + + // --- Shared infra (GameWindow ~1198, ~1211) --- + string shaderDir = Path.Combine(AppContext.BaseDirectory, "Rendering", "Shaders"); + var lightingUbo = new SceneLightingUboBinding(gl); + + // --- Mesh shader (GameWindow ~1769-1771) --- + var meshShader = new Shader(gl, + Path.Combine(shaderDir, "mesh_modern.vert"), + Path.Combine(shaderDir, "mesh_modern.frag")); + + // --- TextureCache (GameWindow ~1774) --- + var textureCache = new TextureCache(gl, dats, bindless); + + // --- AnimLoader (GameWindow ~1240) --- + var animLoader = new AcDream.Core.Physics.DatCollectionLoader(dats); + + // --- WbMeshAdapter (GameWindow ~2286-2287) --- + var wbLogger = NullLogger.Instance; + var meshAdapter = new Wb.WbMeshAdapter(gl, dats, wbLogger); + + // --- SequencerFactory (GameWindow ~2306-2334) --- + var capturedDats = dats; + var capturedAnimLoader = animLoader; + AcDream.Core.Physics.AnimationSequencer SequencerFactory(AcDream.Core.World.WorldEntity e) + { + if (capturedDats is not null && capturedAnimLoader is not null) + { + var setup = capturedDats.Get(e.SourceGfxObjOrSetupId); + if (setup is not null) + { + uint mtableId = (uint)setup.DefaultMotionTable; + if (mtableId != 0) + { + var mtable = capturedDats.Get(mtableId); + if (mtable is not null) + return new AcDream.Core.Physics.AnimationSequencer( + setup, mtable, capturedAnimLoader); + } + // Setup exists but no motion table — no-op sequencer. + return new AcDream.Core.Physics.AnimationSequencer( + setup, + new DatReaderWriter.DBObjs.MotionTable(), + capturedAnimLoader); + } + } + // Complete fallback: empty setup + empty motion table + null loader. + return new AcDream.Core.Physics.AnimationSequencer( + new DatReaderWriter.DBObjs.Setup(), + new DatReaderWriter.DBObjs.MotionTable(), + new NullAnimLoader()); + } + + // --- EntitySpawnAdapter (GameWindow ~2335-2336) --- + var entitySpawnAdapter = new Wb.EntitySpawnAdapter( + textureCache, SequencerFactory, meshAdapter); + + // --- EntityClassificationCache (GameWindow ~217 — field initializer, new()) --- + var classificationCache = new Wb.EntityClassificationCache(); + + // --- WbDrawDispatcher (GameWindow ~2377-2381) --- + var drawDispatcher = new Wb.WbDrawDispatcher( + gl, meshShader, textureCache, meshAdapter, entitySpawnAdapter, + bindless, classificationCache); + drawDispatcher.AlphaToCoverage = opts.Quality.AlphaToCoverage; + + // --- Vitals dat font (GameWindow ~1820-1822) --- + var vitalsDatFont = AcDream.App.UI.UiDatFont.Load(dats, textureCache); + + // --- UiHost (GameWindow ~1790); pass null for debugFont (only used as + // a fallback BitmapFont for the world-space HUD — not needed for the + // UI Studio, and BitmapFont requires a system font byte array) --- + var uiHost = new AcDream.App.UI.UiHost(gl, shaderDir, defaultFont: null); + + return new RenderStack( + Gl: gl, + Dats: dats, + ShaderDir: shaderDir, + Bindless: bindless, + TextureCache: textureCache, + MeshShader: meshShader, + MeshAdapter: meshAdapter, + EntitySpawnAdapter: entitySpawnAdapter, + DrawDispatcher: drawDispatcher, + LightingUbo: lightingUbo, + UiHost: uiHost, + VitalsDatFont: vitalsDatFont); + } + + // NullAnimLoader mirrors GameWindow's private NullAnimLoader (GameWindow ~13327-13330). + private sealed class NullAnimLoader : AcDream.Core.Physics.IAnimationLoader + { + public DatReaderWriter.DBObjs.Animation? LoadAnimation(uint id) => null; + } +}