Complete Slice D4 by adding aggregate lifecycle occupancy and traffic facts, validating physical source reports, and including decoded audio under the typed startup budget. Exercise every domain under forced pressure and retain the real cache/fence convergence gates.
629 lines
23 KiB
C#
629 lines
23 KiB
C#
using System.Collections.Concurrent;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.App.Rendering.Residency;
|
|
using AcDream.App.World;
|
|
using AcDream.Content;
|
|
using AcDream.Content.Vfx;
|
|
using AcDream.Core.Audio;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Terrain;
|
|
using AcDream.UI.Abstractions.Settings;
|
|
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Silk.NET.Input;
|
|
using Silk.NET.OpenGL;
|
|
using Shader = AcDream.App.Rendering.Shader;
|
|
|
|
namespace AcDream.App.Composition;
|
|
|
|
internal sealed record WorldRegionData(Region Region, float[] HeightTable);
|
|
|
|
internal sealed record WorldTerrainBuildContext(
|
|
uint InitialCenterLandblockId,
|
|
int InitialCenterX,
|
|
int InitialCenterY,
|
|
float[] HeightTable,
|
|
TerrainBlendingContext Blending,
|
|
ConcurrentDictionary<uint, SurfaceInfo> SurfaceCache);
|
|
|
|
internal sealed record WorldRenderFoundation(
|
|
string ShadersDirectory,
|
|
BindlessSupport Bindless,
|
|
TerrainAtlas TerrainAtlas,
|
|
Shader TerrainShader,
|
|
SceneLightingUboBinding SceneLighting,
|
|
DebugLineRenderer DebugLines,
|
|
BitmapFont? DebugFont,
|
|
TextRenderer? TextRenderer,
|
|
TerrainModernRenderer Terrain,
|
|
Shader MeshShader,
|
|
WbMeshAdapter MeshAdapter,
|
|
TextureCache TextureCache,
|
|
SamplerCache Samplers,
|
|
ResidencyManager Residency);
|
|
|
|
internal sealed record WorldRenderResult(
|
|
WorldTerrainBuildContext TerrainBuild,
|
|
WorldRenderFoundation Foundation);
|
|
|
|
internal sealed record WorldRenderDependencies(
|
|
WorldEnvironmentController Environment,
|
|
IGameRenderResourceLifetime RenderResources,
|
|
IGpuResourceRetirementQueue ResourceRetirement,
|
|
ResidencyBudgetOptions ResidencyBudgets,
|
|
uint InitialCenterLandblockId,
|
|
Action<string> Log);
|
|
|
|
internal interface IGameWindowWorldRenderPublication
|
|
{
|
|
void PublishBindlessSupport(BindlessSupport value);
|
|
void PublishTerrainShader(Shader value);
|
|
void PublishSceneLighting(SceneLightingUboBinding value);
|
|
void PublishDebugLines(DebugLineRenderer value);
|
|
void PublishHudResources(BitmapFont font, TextRenderer text);
|
|
void PublishTerrain(TerrainModernRenderer value);
|
|
void PublishTerrainBuildState(
|
|
float[] heightTable,
|
|
TerrainBlendingContext blending,
|
|
ConcurrentDictionary<uint, SurfaceInfo> surfaceCache);
|
|
void PublishMeshShader(Shader value);
|
|
void PublishWbMeshAdapter(WbMeshAdapter value);
|
|
void PublishTextureCache(TextureCache value);
|
|
void PublishSamplerCache(SamplerCache value);
|
|
}
|
|
|
|
internal interface IWorldRenderCompositionFactory
|
|
{
|
|
void InitializeGlState(GL gl);
|
|
WorldRegionData LoadRegion(IDatReaderWriter dats);
|
|
void InitializeEnvironment(WorldEnvironmentController environment, Region region);
|
|
BindlessSupport RequireBindless(GL gl, Action<string> log);
|
|
TerrainAtlas AcquireTerrainAtlas(
|
|
IGameRenderResourceLifetime lifetime,
|
|
GL gl,
|
|
IDatReaderWriter dats,
|
|
BindlessSupport bindless);
|
|
void SetTerrainAnisotropic(TerrainAtlas atlas, int level);
|
|
Shader CreateTerrainShader(GL gl, string shadersDirectory);
|
|
SceneLightingUboBinding CreateSceneLighting(GL gl);
|
|
DebugLineRenderer CreateDebugLines(GL gl, string shadersDirectory);
|
|
byte[]? TryLoadDebugFont();
|
|
BitmapFont CreateDebugFont(GL gl, byte[] bytes);
|
|
TextRenderer CreateTextRenderer(GL gl, string shadersDirectory);
|
|
TerrainModernRenderer CreateTerrain(
|
|
GL gl,
|
|
BindlessSupport bindless,
|
|
Shader shader,
|
|
TerrainAtlas atlas,
|
|
IGpuResourceRetirementQueue retirement);
|
|
WorldTerrainBuildContext CreateTerrainBuildContext(
|
|
uint initialCenterLandblockId,
|
|
float[] heightTable,
|
|
TerrainAtlas atlas);
|
|
Shader CreateMeshShader(GL gl, string shadersDirectory);
|
|
WbMeshAdapter CreateMeshAdapter(
|
|
GL gl,
|
|
IDatReaderWriter dats,
|
|
IPreparedAssetSource preparedAssets,
|
|
IGpuResourceRetirementQueue retirement,
|
|
ResidencyBudgetOptions budgets);
|
|
TextureCache CreateTextureCache(
|
|
GL gl,
|
|
IDatReaderWriter dats,
|
|
BindlessSupport bindless,
|
|
IGpuResourceRetirementQueue retirement,
|
|
ResidencyBudgetOptions budgets);
|
|
void RegisterResidencySources(
|
|
ResidencyManager manager,
|
|
WbMeshAdapter meshes,
|
|
TextureCache textures,
|
|
IPreparedAssetSource preparedAssets,
|
|
IAnimationLoader animations,
|
|
DatSoundCache? audio);
|
|
SamplerCache CreateSamplerCache(GL gl);
|
|
void Release(IDisposable resource);
|
|
}
|
|
|
|
internal sealed class RetailWorldRenderCompositionFactory
|
|
: IWorldRenderCompositionFactory
|
|
{
|
|
public void InitializeGlState(GL gl)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(gl);
|
|
gl.ClearColor(0.05f, 0.10f, 0.18f, 1.0f);
|
|
gl.Enable(EnableCap.DepthTest);
|
|
}
|
|
|
|
public WorldRegionData LoadRegion(IDatReaderWriter dats)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(dats);
|
|
Region region = dats.Get<Region>(0x13000000u)
|
|
?? throw new InvalidOperationException(
|
|
"Region dat id 0x13000000 missing");
|
|
float[]? heightTable = region.LandDefs.LandHeightTable;
|
|
if (heightTable is null || heightTable.Length < 256)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Region.LandDefs.LandHeightTable missing or truncated");
|
|
}
|
|
return new WorldRegionData(region, heightTable);
|
|
}
|
|
|
|
public void InitializeEnvironment(
|
|
WorldEnvironmentController environment,
|
|
Region region)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(environment);
|
|
ArgumentNullException.ThrowIfNull(region);
|
|
environment.Initialize(region);
|
|
}
|
|
|
|
public BindlessSupport RequireBindless(GL gl, Action<string> log)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(gl);
|
|
ArgumentNullException.ThrowIfNull(log);
|
|
if (BindlessSupport.TryCreate(gl, out BindlessSupport? bindless))
|
|
{
|
|
if (bindless!.HasShaderDrawParameters(gl))
|
|
{
|
|
log("[N.5] modern path capabilities present " +
|
|
"(bindless + ARB_shader_draw_parameters)");
|
|
return bindless;
|
|
}
|
|
log("[N.5] GL_ARB_shader_draw_parameters not present — " +
|
|
"modern path not available");
|
|
}
|
|
else
|
|
{
|
|
log("[N.5] GL_ARB_bindless_texture not present — " +
|
|
"modern path not available");
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
public TerrainAtlas AcquireTerrainAtlas(
|
|
IGameRenderResourceLifetime lifetime,
|
|
GL gl,
|
|
IDatReaderWriter dats,
|
|
BindlessSupport bindless)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(lifetime);
|
|
return lifetime.AcquireTerrainAtlas(
|
|
() => TerrainAtlas.Build(gl, dats, bindless));
|
|
}
|
|
|
|
public void SetTerrainAnisotropic(TerrainAtlas atlas, int level) =>
|
|
atlas.SetAnisotropic(level);
|
|
|
|
public Shader CreateTerrainShader(GL gl, string shadersDirectory) =>
|
|
new(
|
|
gl,
|
|
Path.Combine(shadersDirectory, "terrain_modern.vert"),
|
|
Path.Combine(shadersDirectory, "terrain_modern.frag"));
|
|
|
|
public SceneLightingUboBinding CreateSceneLighting(GL gl) => new(gl);
|
|
|
|
public DebugLineRenderer CreateDebugLines(
|
|
GL gl,
|
|
string shadersDirectory) =>
|
|
new(gl, shadersDirectory);
|
|
|
|
public byte[]? TryLoadDebugFont() =>
|
|
BitmapFont.TryLoadSystemMonospaceFont();
|
|
|
|
public BitmapFont CreateDebugFont(GL gl, byte[] bytes) =>
|
|
new(gl, bytes, pixelHeight: 15f, atlasSize: 512);
|
|
|
|
public TextRenderer CreateTextRenderer(
|
|
GL gl,
|
|
string shadersDirectory) =>
|
|
new(gl, shadersDirectory);
|
|
|
|
public TerrainModernRenderer CreateTerrain(
|
|
GL gl,
|
|
BindlessSupport bindless,
|
|
Shader shader,
|
|
TerrainAtlas atlas,
|
|
IGpuResourceRetirementQueue retirement) =>
|
|
new(gl, bindless, shader, atlas, retirement);
|
|
|
|
public WorldTerrainBuildContext CreateTerrainBuildContext(
|
|
uint initialCenterLandblockId,
|
|
float[] heightTable,
|
|
TerrainAtlas atlas)
|
|
{
|
|
int centerX = (int)((initialCenterLandblockId >> 24) & 0xFFu);
|
|
int centerY = (int)((initialCenterLandblockId >> 16) & 0xFFu);
|
|
var layers = new Dictionary<uint, byte>(atlas.TerrainTypeToLayer.Count);
|
|
foreach ((uint terrainType, uint layer) in atlas.TerrainTypeToLayer)
|
|
layers[terrainType] = (byte)layer;
|
|
|
|
const uint RoadTypeEnumValue = 0x20;
|
|
byte roadLayer = layers.TryGetValue(RoadTypeEnumValue, out byte road)
|
|
? road
|
|
: SurfaceInfo.None;
|
|
var blending = new TerrainBlendingContext(
|
|
TerrainTypeToLayer: layers,
|
|
RoadLayer: roadLayer,
|
|
CornerAlphaLayers: atlas.CornerAlphaLayers,
|
|
SideAlphaLayers: atlas.SideAlphaLayers,
|
|
RoadAlphaLayers: atlas.RoadAlphaLayers,
|
|
CornerAlphaTCodes: atlas.CornerAlphaTCodes,
|
|
SideAlphaTCodes: atlas.SideAlphaTCodes,
|
|
RoadAlphaRCodes: atlas.RoadAlphaRCodes);
|
|
return new WorldTerrainBuildContext(
|
|
initialCenterLandblockId,
|
|
centerX,
|
|
centerY,
|
|
heightTable,
|
|
blending,
|
|
new ConcurrentDictionary<uint, SurfaceInfo>());
|
|
}
|
|
|
|
public Shader CreateMeshShader(GL gl, string shadersDirectory) =>
|
|
new(
|
|
gl,
|
|
Path.Combine(shadersDirectory, "mesh_modern.vert"),
|
|
Path.Combine(shadersDirectory, "mesh_modern.frag"));
|
|
|
|
public WbMeshAdapter CreateMeshAdapter(
|
|
GL gl,
|
|
IDatReaderWriter dats,
|
|
IPreparedAssetSource preparedAssets,
|
|
IGpuResourceRetirementQueue retirement,
|
|
ResidencyBudgetOptions budgets) =>
|
|
new(
|
|
gl,
|
|
dats,
|
|
preparedAssets,
|
|
NullLogger<WbMeshAdapter>.Instance,
|
|
retirement,
|
|
budgets);
|
|
|
|
public TextureCache CreateTextureCache(
|
|
GL gl,
|
|
IDatReaderWriter dats,
|
|
BindlessSupport bindless,
|
|
IGpuResourceRetirementQueue retirement,
|
|
ResidencyBudgetOptions budgets) =>
|
|
new(gl, dats, bindless, retirement, budgets);
|
|
|
|
public void RegisterResidencySources(
|
|
ResidencyManager manager,
|
|
WbMeshAdapter meshes,
|
|
TextureCache textures,
|
|
IPreparedAssetSource preparedAssets,
|
|
IAnimationLoader animations,
|
|
DatSoundCache? audio)
|
|
{
|
|
meshes.RegisterResidencySources(manager);
|
|
textures.RegisterResidencySources(manager);
|
|
manager.RegisterDomainSource(new DelegateResidencyDomainSource(
|
|
ResidencyDomain.PreparedPackage,
|
|
() => new ResidencyDomainSnapshot(
|
|
ResidencyDomain.PreparedPackage,
|
|
EntryCount: 1,
|
|
OwnerCount: 1,
|
|
Charges: new ResidencyCharges(
|
|
MappedVirtualBytes:
|
|
preparedAssets.MappedVirtualBytes))));
|
|
if (animations is RetailAnimationLoader retailAnimations)
|
|
{
|
|
manager.RegisterDomainSource(new DelegateResidencyDomainSource(
|
|
ResidencyDomain.Animations,
|
|
() =>
|
|
{
|
|
AnimationCacheDiagnostics diagnostics =
|
|
retailAnimations.Diagnostics;
|
|
return new ResidencyDomainSnapshot(
|
|
ResidencyDomain.Animations,
|
|
EntryCount: diagnostics.Count,
|
|
OwnerCount: 0,
|
|
Charges: new ResidencyCharges(
|
|
DecodedBytes:
|
|
diagnostics.EstimatedBytes),
|
|
BudgetBytes: diagnostics.BudgetBytes,
|
|
Hits: diagnostics.Stats.Hits,
|
|
Misses: diagnostics.Stats.Misses,
|
|
Evictions: diagnostics.Stats.Evictions);
|
|
}));
|
|
}
|
|
if (audio is not null)
|
|
{
|
|
manager.RegisterDomainSource(new DelegateResidencyDomainSource(
|
|
ResidencyDomain.Audio,
|
|
() =>
|
|
{
|
|
DatSoundCacheDiagnostics diagnostics = audio.Diagnostics;
|
|
return new ResidencyDomainSnapshot(
|
|
ResidencyDomain.Audio,
|
|
EntryCount: diagnostics.CachedWaveCount,
|
|
OwnerCount: 0,
|
|
Charges: new ResidencyCharges(
|
|
DecodedBytes: diagnostics.ResidentWaveBytes),
|
|
BudgetBytes: diagnostics.BudgetBytes,
|
|
Hits: diagnostics.Hits,
|
|
Misses: diagnostics.Misses,
|
|
Evictions: diagnostics.Evictions);
|
|
}));
|
|
}
|
|
}
|
|
|
|
public SamplerCache CreateSamplerCache(GL gl) => new(gl);
|
|
|
|
public void Release(IDisposable resource) => resource.Dispose();
|
|
}
|
|
|
|
internal enum WorldRenderCompositionPoint
|
|
{
|
|
GlStateInitialized,
|
|
RegionLoaded,
|
|
EnvironmentInitialized,
|
|
BindlessPublished,
|
|
TerrainAtlasAcquired,
|
|
TerrainShaderPublished,
|
|
SceneLightingPublished,
|
|
DebugLinesPublished,
|
|
DebugFontCreated,
|
|
TextRendererCreated,
|
|
HudResourcesPublished,
|
|
HudResourcesCompleted,
|
|
TerrainPublished,
|
|
TerrainBuildStatePublished,
|
|
MeshShaderPublished,
|
|
MeshAdapterPublished,
|
|
TextureCachePublished,
|
|
SamplerCachePublished,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Production Phase 4. It preserves the mandatory modern renderer and sole
|
|
/// DAT path while moving construction into an ordered, failure-injectable
|
|
/// ownership boundary.
|
|
/// </summary>
|
|
internal sealed class WorldRenderCompositionPhase
|
|
: IWorldRenderCompositionPhase<
|
|
GameWindowPlatformResult<GL, IInputContext>,
|
|
ContentEffectsAudioResult,
|
|
SettingsDevToolsResult,
|
|
WorldRenderResult>
|
|
{
|
|
private readonly WorldRenderDependencies _dependencies;
|
|
private readonly IGameWindowWorldRenderPublication _publication;
|
|
private readonly IWorldRenderCompositionFactory _factory;
|
|
private readonly Action<WorldRenderCompositionPoint>? _faultInjection;
|
|
|
|
public WorldRenderCompositionPhase(
|
|
WorldRenderDependencies dependencies,
|
|
IGameWindowWorldRenderPublication publication,
|
|
IWorldRenderCompositionFactory? factory = null,
|
|
Action<WorldRenderCompositionPoint>? faultInjection = null)
|
|
{
|
|
_dependencies = dependencies
|
|
?? throw new ArgumentNullException(nameof(dependencies));
|
|
_publication = publication
|
|
?? throw new ArgumentNullException(nameof(publication));
|
|
_factory = factory ?? new RetailWorldRenderCompositionFactory();
|
|
_faultInjection = faultInjection;
|
|
}
|
|
|
|
public WorldRenderResult Compose(
|
|
GameWindowPlatformResult<GL, IInputContext> platform,
|
|
ContentEffectsAudioResult content,
|
|
SettingsDevToolsResult settings)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(platform);
|
|
ArgumentNullException.ThrowIfNull(content);
|
|
ArgumentNullException.ThrowIfNull(settings);
|
|
|
|
var scope = new CompositionAcquisitionScope();
|
|
try
|
|
{
|
|
GL gl = platform.Graphics;
|
|
var residency = new ResidencyManager(
|
|
_dependencies.ResidencyBudgets);
|
|
_factory.InitializeGlState(gl);
|
|
Fault(WorldRenderCompositionPoint.GlStateInitialized);
|
|
|
|
WorldRegionData region = _factory.LoadRegion(content.Dats);
|
|
Fault(WorldRenderCompositionPoint.RegionLoaded);
|
|
_factory.InitializeEnvironment(_dependencies.Environment, region.Region);
|
|
Fault(WorldRenderCompositionPoint.EnvironmentInitialized);
|
|
|
|
BindlessSupport bindless = _factory.RequireBindless(gl, _dependencies.Log);
|
|
_publication.PublishBindlessSupport(bindless);
|
|
Fault(WorldRenderCompositionPoint.BindlessPublished);
|
|
|
|
TerrainAtlas terrainAtlas = _factory.AcquireTerrainAtlas(
|
|
_dependencies.RenderResources,
|
|
gl,
|
|
content.Dats,
|
|
bindless);
|
|
_factory.SetTerrainAnisotropic(
|
|
terrainAtlas,
|
|
settings.ResolvedQuality.AnisotropicLevel);
|
|
Fault(WorldRenderCompositionPoint.TerrainAtlasAcquired);
|
|
|
|
string shadersDirectory = Path.Combine(
|
|
AppContext.BaseDirectory,
|
|
"Rendering",
|
|
"Shaders");
|
|
Shader terrainShader = AcquireAndPublish(
|
|
scope,
|
|
"terrain shader",
|
|
() => _factory.CreateTerrainShader(gl, shadersDirectory),
|
|
_publication.PublishTerrainShader,
|
|
WorldRenderCompositionPoint.TerrainShaderPublished);
|
|
SceneLightingUboBinding sceneLighting = AcquireAndPublish(
|
|
scope,
|
|
"scene lighting",
|
|
() => _factory.CreateSceneLighting(gl),
|
|
_publication.PublishSceneLighting,
|
|
WorldRenderCompositionPoint.SceneLightingPublished);
|
|
DebugLineRenderer debugLines = AcquireAndPublish(
|
|
scope,
|
|
"debug lines",
|
|
() => _factory.CreateDebugLines(gl, shadersDirectory),
|
|
_publication.PublishDebugLines,
|
|
WorldRenderCompositionPoint.DebugLinesPublished);
|
|
|
|
(BitmapFont? debugFont, TextRenderer? textRenderer) =
|
|
ComposeOptionalHudResources(scope, gl, shadersDirectory);
|
|
|
|
TerrainModernRenderer terrain = AcquireAndPublish(
|
|
scope,
|
|
"terrain renderer",
|
|
() => _factory.CreateTerrain(
|
|
gl,
|
|
bindless,
|
|
terrainShader,
|
|
terrainAtlas,
|
|
_dependencies.ResourceRetirement),
|
|
_publication.PublishTerrain,
|
|
WorldRenderCompositionPoint.TerrainPublished);
|
|
|
|
WorldTerrainBuildContext terrainBuild =
|
|
_factory.CreateTerrainBuildContext(
|
|
_dependencies.InitialCenterLandblockId,
|
|
region.HeightTable,
|
|
terrainAtlas);
|
|
_publication.PublishTerrainBuildState(
|
|
terrainBuild.HeightTable,
|
|
terrainBuild.Blending,
|
|
terrainBuild.SurfaceCache);
|
|
Fault(WorldRenderCompositionPoint.TerrainBuildStatePublished);
|
|
|
|
Shader meshShader = AcquireAndPublish(
|
|
scope,
|
|
"mesh shader",
|
|
() => _factory.CreateMeshShader(gl, shadersDirectory),
|
|
_publication.PublishMeshShader,
|
|
WorldRenderCompositionPoint.MeshShaderPublished);
|
|
_dependencies.Log("[N.5] mesh_modern shader loaded");
|
|
WbMeshAdapter meshAdapter = AcquireAndPublish(
|
|
scope,
|
|
"WB mesh adapter",
|
|
() => _factory.CreateMeshAdapter(
|
|
gl,
|
|
content.Dats,
|
|
content.PreparedAssets,
|
|
_dependencies.ResourceRetirement,
|
|
residency.Budgets),
|
|
_publication.PublishWbMeshAdapter,
|
|
WorldRenderCompositionPoint.MeshAdapterPublished);
|
|
TextureCache textureCache = AcquireAndPublish(
|
|
scope,
|
|
"texture cache",
|
|
() => _factory.CreateTextureCache(
|
|
gl,
|
|
content.Dats,
|
|
bindless,
|
|
_dependencies.ResourceRetirement,
|
|
residency.Budgets),
|
|
_publication.PublishTextureCache,
|
|
WorldRenderCompositionPoint.TextureCachePublished);
|
|
SamplerCache samplers = AcquireAndPublish(
|
|
scope,
|
|
"sampler cache",
|
|
() => _factory.CreateSamplerCache(gl),
|
|
_publication.PublishSamplerCache,
|
|
WorldRenderCompositionPoint.SamplerCachePublished);
|
|
_factory.RegisterResidencySources(
|
|
residency,
|
|
meshAdapter,
|
|
textureCache,
|
|
content.PreparedAssets,
|
|
content.AnimationLoader,
|
|
content.Audio?.SoundCache);
|
|
|
|
scope.Complete();
|
|
_dependencies.Log(
|
|
"[N.4+N.5] WB foundation + modern path active — " +
|
|
"routing all content through ObjectMeshManager.");
|
|
return new WorldRenderResult(
|
|
terrainBuild,
|
|
new WorldRenderFoundation(
|
|
shadersDirectory,
|
|
bindless,
|
|
terrainAtlas,
|
|
terrainShader,
|
|
sceneLighting,
|
|
debugLines,
|
|
debugFont,
|
|
textRenderer,
|
|
terrain,
|
|
meshShader,
|
|
meshAdapter,
|
|
textureCache,
|
|
samplers,
|
|
residency));
|
|
}
|
|
catch (Exception failure)
|
|
{
|
|
scope.RollbackAndThrow(failure);
|
|
throw new System.Diagnostics.UnreachableException();
|
|
}
|
|
}
|
|
|
|
private (BitmapFont? Font, TextRenderer? Text) ComposeOptionalHudResources(
|
|
CompositionAcquisitionScope scope,
|
|
GL gl,
|
|
string shadersDirectory)
|
|
{
|
|
byte[]? fontBytes = _factory.TryLoadDebugFont();
|
|
if (fontBytes is null)
|
|
{
|
|
_dependencies.Log("world-hud font: no system monospace font found");
|
|
Fault(WorldRenderCompositionPoint.HudResourcesCompleted);
|
|
return (null, null);
|
|
}
|
|
|
|
var fontLease = scope.Acquire(
|
|
"world HUD font",
|
|
() => _factory.CreateDebugFont(gl, fontBytes),
|
|
_factory.Release);
|
|
BitmapFont font = fontLease.Resource;
|
|
Fault(WorldRenderCompositionPoint.DebugFontCreated);
|
|
var textLease = scope.Acquire(
|
|
"world HUD text renderer",
|
|
() => _factory.CreateTextRenderer(gl, shadersDirectory),
|
|
_factory.Release);
|
|
TextRenderer text = textLease.Resource;
|
|
Fault(WorldRenderCompositionPoint.TextRendererCreated);
|
|
|
|
_publication.PublishHudResources(font, text);
|
|
fontLease.Transfer();
|
|
textLease.Transfer();
|
|
Fault(WorldRenderCompositionPoint.HudResourcesPublished);
|
|
_dependencies.Log(
|
|
$"world-hud font: loaded {fontBytes.Length / 1024}KB, " +
|
|
$"atlas {font.AtlasWidth}x{font.AtlasHeight}, " +
|
|
$"lineHeight={font.LineHeight:F1}px (reserved for D.6 HUD)");
|
|
Fault(WorldRenderCompositionPoint.HudResourcesCompleted);
|
|
return (font, text);
|
|
}
|
|
|
|
private T AcquireAndPublish<T>(
|
|
CompositionAcquisitionScope scope,
|
|
string name,
|
|
Func<T> factory,
|
|
Action<T> publish,
|
|
WorldRenderCompositionPoint point)
|
|
where T : class, IDisposable
|
|
{
|
|
T value = scope.Acquire(name, factory, _factory.Release).Publish(publish);
|
|
Fault(point);
|
|
return value;
|
|
}
|
|
|
|
private void Fault(WorldRenderCompositionPoint point) =>
|
|
_faultInjection?.Invoke(point);
|
|
}
|