feat(studio): RenderBootstrap — shared render stack for the UI previewer
Extracts the subset of GameWindow.OnLoad the UI Studio needs into a standalone RenderBootstrap.Create factory: bindless detection, shaderDir, SceneLightingUboBinding, mesh shader, TextureCache, animLoader, WbMeshAdapter, SequencerFactory, EntitySpawnAdapter, EntityClassificationCache, WbDrawDispatcher (+ A2C gate from QualitySettings), UiDatFont load, and UiHost. No terrain / sky / physics / streaming — only the pieces listed in the RenderStack record. GameWindow is untouched; this is additive new code only. Note: task spec listed VitalsDatFont as AcDream.App.UI.Layout.UiDatFont but the type lives in AcDream.App.UI — corrected here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f55650fdcd
commit
79ee3ffbe2
1 changed files with 162 additions and 0 deletions
162
src/AcDream.App/Rendering/RenderBootstrap.cs
Normal file
162
src/AcDream.App/Rendering/RenderBootstrap.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using DatReaderWriter;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="GameWindow.OnLoad"/>, minus
|
||||
/// terrain / sky / physics / streaming.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Options for <see cref="RenderBootstrap.Create"/>.</summary>
|
||||
public sealed record RenderBootstrapOptions(
|
||||
AcDream.UI.Abstractions.Settings.QualitySettings Quality);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the UI Studio's render stack from the production classes,
|
||||
/// in the same order as <see cref="GameWindow.OnLoad"/>.
|
||||
/// </summary>
|
||||
public static class RenderBootstrap
|
||||
{
|
||||
/// <summary>
|
||||
/// Build the studio's render stack. Throws <see cref="NotSupportedException"/>
|
||||
/// (same message as GameWindow) if GL_ARB_bindless_texture or
|
||||
/// GL_ARB_shader_draw_parameters are absent — the modern path is mandatory.
|
||||
/// </summary>
|
||||
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<Wb.WbMeshAdapter>.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<DatReaderWriter.DBObjs.Setup>(e.SourceGfxObjOrSetupId);
|
||||
if (setup is not null)
|
||||
{
|
||||
uint mtableId = (uint)setup.DefaultMotionTable;
|
||||
if (mtableId != 0)
|
||||
{
|
||||
var mtable = capturedDats.Get<DatReaderWriter.DBObjs.MotionTable>(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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue