refactor(app): compose world rendering startup
Move Region/environment, mandatory modern rendering, terrain, WB, texture, and sampler construction behind the typed Phase-4 composition boundary. Give every fallible GL constructor prefix retryable ownership so partial startup failure cannot leak or replay resource deletion while preserving the accepted render path and DAT inputs. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
cd7b519f78
commit
6a2fe98cc4
22 changed files with 1983 additions and 634 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using Chorizite.Core.Render;
|
||||
using AcDream.App.Rendering;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
|
|
@ -161,7 +162,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
|
||||
if (string.IsNullOrWhiteSpace(vertShaderSource) || string.IsNullOrWhiteSpace(fragShaderSource)) {
|
||||
_log.LogError($"Shader {Name} has no source code!");
|
||||
return;
|
||||
throw new InvalidOperationException($"Shader {Name} has no source code.");
|
||||
}
|
||||
|
||||
if (_device.HasOpenGL43 && _device.HasBindless) {
|
||||
|
|
@ -170,41 +171,47 @@ namespace AcDream.App.Rendering.Wb {
|
|||
fragShaderSource = fragShaderSource.Replace("#version 330 core", replacement);
|
||||
}
|
||||
|
||||
uint vertexShader = CompileShader(ShaderType.VertexShader, Name, vertShaderSource);
|
||||
uint fragmentShader = CompileShader(ShaderType.FragmentShader, Name, fragShaderSource);
|
||||
var resources = new ResourceCleanupGroup();
|
||||
uint prog = 0;
|
||||
bool accountingPublished = false;
|
||||
try {
|
||||
prog = ShaderProgramConstruction.Build(
|
||||
new GlShaderProgramBuildApi(GL),
|
||||
vertShaderSource,
|
||||
fragShaderSource);
|
||||
uint ownedProgram = prog;
|
||||
var unpublishedProgramRelease = new RetryableGpuResourceRelease(
|
||||
() => GlResourceCommand.DeleteProgram(
|
||||
GL,
|
||||
ownedProgram,
|
||||
$"delete unpublished WB shader program {ownedProgram}"),
|
||||
() => {
|
||||
if (accountingPublished)
|
||||
{
|
||||
GpuMemoryTracker.TrackResourceDeallocation(
|
||||
GpuResourceType.Shader);
|
||||
}
|
||||
});
|
||||
resources.Add("WB shader program", unpublishedProgramRelease.Run);
|
||||
|
||||
var prog = GL.CreateProgram();
|
||||
GLHelpers.CheckErrors(GL, true);
|
||||
GL.AttachShader(prog, vertexShader);
|
||||
GLHelpers.CheckErrors(GL, true);
|
||||
GL.AttachShader(prog, fragmentShader);
|
||||
GLHelpers.CheckErrors(GL, true);
|
||||
GL.LinkProgram(prog);
|
||||
GLHelpers.CheckErrors(GL, true);
|
||||
// Bind SceneData uniform block to point 0 if it exists.
|
||||
GlResourceCommand.Execute(GL, $"configure shader {Name} SceneData binding", () => {
|
||||
uint sceneDataIndex = GL.GetUniformBlockIndex(prog, "SceneData");
|
||||
if (sceneDataIndex != uint.MaxValue)
|
||||
GL.UniformBlockBinding(prog, sceneDataIndex, 0);
|
||||
});
|
||||
|
||||
GL.GetProgram(prog, GLEnum.LinkStatus, out int success);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
if (success != 1) {
|
||||
var infoLog = GL.GetProgramInfoLog(prog);
|
||||
_log.LogError($"Error: shader {Name} link failed: {infoLog}");
|
||||
GL.DeleteProgram(prog);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
_log.LogTrace($"{(Program != 0 ? "Reloaded" : "Loaded")} shader: {Name}");
|
||||
GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Shader);
|
||||
accountingPublished = true;
|
||||
resources.TransferAll();
|
||||
} catch (Exception constructionFailure) {
|
||||
_log.LogError(constructionFailure, "Failed to construct shader {ShaderName}", Name);
|
||||
resources.RollbackConstructionAndThrow(
|
||||
$"Shader {Name} construction failed and its GL program did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
}
|
||||
|
||||
// Bind SceneData uniform block to point 0 if it exists
|
||||
var sceneDataIndex = GL.GetUniformBlockIndex(prog, "SceneData");
|
||||
if (sceneDataIndex != uint.MaxValue) {
|
||||
GL.UniformBlockBinding(prog, sceneDataIndex, 0);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
}
|
||||
|
||||
GL.DeleteShader(vertexShader);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
GL.DeleteShader(fragmentShader);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
_log.LogTrace($"{(Program != 0 ? "Reloaded" : "Loaded")} shader: {Name}");
|
||||
|
||||
if (Program != 0) {
|
||||
Unload();
|
||||
|
|
@ -212,32 +219,12 @@ namespace AcDream.App.Rendering.Wb {
|
|||
_uniformLocations.Clear();
|
||||
_uniformValues.Clear();
|
||||
|
||||
GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Shader);
|
||||
Program = prog;
|
||||
ProgramId = prog;
|
||||
NeedsLoad = false;
|
||||
GLHelpers.CheckErrors(GL);
|
||||
}
|
||||
|
||||
private uint CompileShader(ShaderType shaderType, string name, string shaderSource) {
|
||||
uint shader = GL.CreateShader(shaderType);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
GL.ShaderSource(shader, shaderSource);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
GL.CompileShader(shader);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
GL.GetShader(shader, ShaderParameterName.CompileStatus, out int success);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
if (success != 1) {
|
||||
var infoLog = GL.GetShaderInfoLog(shader);
|
||||
_log.LogError($"Error: {name}:{shaderType} compilation failed: {infoLog}");
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
public override void Bind() {
|
||||
lock (_lock) {
|
||||
SetActive();
|
||||
|
|
|
|||
|
|
@ -28,30 +28,44 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <param name="usage">Buffer usage</param>
|
||||
/// <param name="size">The size of the buffer, in bytes</param>
|
||||
public unsafe ManagedGLUniformBuffer(OpenGLGraphicsDevice device, BufferUsage usage, int size) {
|
||||
_device = device;
|
||||
_device = device ?? throw new ArgumentNullException(nameof(device));
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(size, 1);
|
||||
Size = size;
|
||||
Usage = usage;
|
||||
|
||||
// Generate the buffer
|
||||
bufferId = GL.GenBuffer();
|
||||
if (bufferId == 0) {
|
||||
throw new Exception("Failed to generate uniform buffer.");
|
||||
var resources = new AcDream.App.Rendering.ResourceCleanupGroup();
|
||||
uint buffer = 0;
|
||||
bool allocated = false;
|
||||
try {
|
||||
buffer = TrackedGlResource.CreateBuffer(
|
||||
GL,
|
||||
"creating managed uniform buffer");
|
||||
uint ownedBuffer = buffer;
|
||||
RetryableGpuResourceRelease unpublishedBufferRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
GL,
|
||||
ownedBuffer,
|
||||
() => allocated ? Size : 0,
|
||||
"rolling back managed uniform buffer");
|
||||
resources.Add(
|
||||
"managed uniform buffer",
|
||||
unpublishedBufferRelease.Run);
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
GL,
|
||||
GLEnum.UniformBuffer,
|
||||
buffer,
|
||||
0,
|
||||
Size,
|
||||
GLEnum.DynamicDraw,
|
||||
"allocating managed uniform buffer");
|
||||
allocated = true;
|
||||
resources.TransferAll();
|
||||
} catch (Exception constructionFailure) {
|
||||
resources.RollbackConstructionAndThrow(
|
||||
"ManagedGLUniformBuffer construction failed and its GL buffer did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
}
|
||||
GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Buffer);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
// Allocate the buffer with the specified size
|
||||
GL.BindBuffer(GLEnum.UniformBuffer, bufferId);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
GL.BufferData(
|
||||
GLEnum.UniformBuffer,
|
||||
(uint)Size,
|
||||
(void*)0, // No initial data
|
||||
GLEnum.DynamicDraw);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
GpuMemoryTracker.TrackAllocation(Size, GpuResourceType.Buffer);
|
||||
bufferId = buffer;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -139,5 +153,25 @@ namespace AcDream.App.Rendering.Wb {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases an unpublished constructor-owned buffer synchronously on
|
||||
/// the GL thread. This is deliberately separate from ordinary queued
|
||||
/// disposal so an enclosing constructor can prove rollback before it
|
||||
/// propagates its failure.
|
||||
/// </summary>
|
||||
internal void DisposeImmediately() {
|
||||
if (bufferId == 0)
|
||||
return;
|
||||
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
GL,
|
||||
bufferId,
|
||||
Size,
|
||||
"rolling back unpublished managed uniform buffer");
|
||||
release.Run();
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Vertex;
|
||||
using AcDream.App.Rendering;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Silk.NET.OpenGL;
|
||||
// IUniformBuffer is in Chorizite.Core.dll but under the Chorizite.OpenGLSDLBackend namespace
|
||||
|
|
@ -149,38 +150,49 @@ namespace AcDream.App.Rendering.Wb {
|
|||
HasBindless = false;
|
||||
}
|
||||
|
||||
GL.GenBuffers(1, out uint instanceVbo);
|
||||
InstanceVBO = instanceVbo;
|
||||
var resources = new ResourceCleanupGroup();
|
||||
try {
|
||||
InstanceVBO = CreateConstructionBuffer(resources, "WB instance buffer");
|
||||
|
||||
// Query this immutable device limit once. Atlas construction can
|
||||
// happen hundreds of times during portal streaming; repeating a
|
||||
// driver GetFloat for every texture serialized the upload burst.
|
||||
if (renderSettings.EnableAnisotropicFiltering) {
|
||||
GL.GetFloat(GLEnum.MaxTextureMaxAnisotropy, out float maxAniso);
|
||||
MaxSupportedAnisotropy = Math.Max(0f, maxAniso);
|
||||
// Query this immutable device limit once. Atlas construction can
|
||||
// happen hundreds of times during portal streaming; repeating a
|
||||
// driver GetFloat for every texture serialized the upload burst.
|
||||
if (renderSettings.EnableAnisotropicFiltering) {
|
||||
MaxSupportedAnisotropy = GlResourceCommand.Execute(
|
||||
GL,
|
||||
"query maximum texture anisotropy",
|
||||
() => {
|
||||
GL.GetFloat(GLEnum.MaxTextureMaxAnisotropy, out float maxAniso);
|
||||
return Math.Max(0f, maxAniso);
|
||||
});
|
||||
}
|
||||
|
||||
WrapSampler = CreateConstructionSampler(
|
||||
resources,
|
||||
TextureWrapMode.Repeat,
|
||||
"WB repeat sampler");
|
||||
ClampSampler = CreateConstructionSampler(
|
||||
resources,
|
||||
TextureWrapMode.ClampToEdge,
|
||||
"WB clamp sampler");
|
||||
|
||||
_sceneDataBuffer = new ManagedGLUniformBuffer(
|
||||
this,
|
||||
BufferUsage.Dynamic,
|
||||
Marshal.SizeOf<SceneData>());
|
||||
ManagedGLUniformBuffer ownedSceneDataBuffer = _sceneDataBuffer;
|
||||
resources.Add(
|
||||
"WB scene-data uniform buffer",
|
||||
ownedSceneDataBuffer.DisposeImmediately);
|
||||
|
||||
InitializeSharedDebugResources(resources);
|
||||
resources.TransferAll();
|
||||
} catch (Exception constructionFailure) {
|
||||
resources.RollbackConstructionAndThrow(
|
||||
"OpenGLGraphicsDevice construction failed and its GL prefix did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
}
|
||||
|
||||
// Create sampler objects for wrap vs clamp
|
||||
WrapSampler = GL.GenSampler();
|
||||
GL.SamplerParameter(WrapSampler, SamplerParameterI.WrapS, (int)TextureWrapMode.Repeat);
|
||||
GL.SamplerParameter(WrapSampler, SamplerParameterI.WrapT, (int)TextureWrapMode.Repeat);
|
||||
GL.SamplerParameter(WrapSampler, SamplerParameterI.MinFilter, (int)TextureMinFilter.LinearMipmapLinear);
|
||||
GL.SamplerParameter(WrapSampler, SamplerParameterI.MagFilter, (int)TextureMagFilter.Linear);
|
||||
if (MaxSupportedAnisotropy > 0)
|
||||
GL.SamplerParameter(WrapSampler, GLEnum.TextureMaxAnisotropy, MaxSupportedAnisotropy);
|
||||
|
||||
ClampSampler = GL.GenSampler();
|
||||
GL.SamplerParameter(ClampSampler, SamplerParameterI.WrapS, (int)TextureWrapMode.ClampToEdge);
|
||||
GL.SamplerParameter(ClampSampler, SamplerParameterI.WrapT, (int)TextureWrapMode.ClampToEdge);
|
||||
GL.SamplerParameter(ClampSampler, SamplerParameterI.MinFilter, (int)TextureMinFilter.LinearMipmapLinear);
|
||||
GL.SamplerParameter(ClampSampler, SamplerParameterI.MagFilter, (int)TextureMagFilter.Linear);
|
||||
if (MaxSupportedAnisotropy > 0)
|
||||
GL.SamplerParameter(ClampSampler, GLEnum.TextureMaxAnisotropy, MaxSupportedAnisotropy);
|
||||
|
||||
_sceneDataBuffer = new ManagedGLUniformBuffer(this, BufferUsage.Dynamic, Marshal.SizeOf<SceneData>());
|
||||
|
||||
InitializeSharedDebugResources();
|
||||
|
||||
// ParticleBatcher is constructed post-ctor by WbMeshAdapter (WbMeshAdapter.cs:78)
|
||||
// after the adapter has wired up all dependencies. The null! here is overridden
|
||||
// immediately after construction; it is not observable as null at runtime.
|
||||
|
|
@ -196,7 +208,49 @@ namespace AcDream.App.Rendering.Wb {
|
|||
internal AcDream.App.Rendering.IGpuResourceRetirementQueue ResourceRetirement =>
|
||||
_resourceRetirement;
|
||||
|
||||
private void InitializeSharedDebugResources() {
|
||||
private uint CreateConstructionBuffer(ResourceCleanupGroup resources, string name) {
|
||||
uint buffer = GlResourceCommand.CreateName(GL, name, GL.GenBuffer, GL.DeleteBuffer);
|
||||
resources.Add(
|
||||
name,
|
||||
() => GlResourceCommand.DeleteBuffer(
|
||||
GL,
|
||||
buffer,
|
||||
$"delete {name} {buffer}"));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private uint CreateConstructionSampler(
|
||||
ResourceCleanupGroup resources,
|
||||
TextureWrapMode wrapMode,
|
||||
string name) {
|
||||
uint sampler = GlResourceCommand.CreateName(GL, name, GL.GenSampler, GL.DeleteSampler);
|
||||
resources.Add(
|
||||
name,
|
||||
() => GlResourceCommand.Execute(
|
||||
GL,
|
||||
$"delete {name} {sampler}",
|
||||
() => GL.DeleteSampler(sampler)));
|
||||
GlResourceCommand.Execute(GL, $"configure {name}", () => {
|
||||
GL.SamplerParameter(sampler, SamplerParameterI.WrapS, (int)wrapMode);
|
||||
GL.SamplerParameter(sampler, SamplerParameterI.WrapT, (int)wrapMode);
|
||||
GL.SamplerParameter(
|
||||
sampler,
|
||||
SamplerParameterI.MinFilter,
|
||||
(int)TextureMinFilter.LinearMipmapLinear);
|
||||
GL.SamplerParameter(
|
||||
sampler,
|
||||
SamplerParameterI.MagFilter,
|
||||
(int)TextureMagFilter.Linear);
|
||||
if (MaxSupportedAnisotropy > 0)
|
||||
GL.SamplerParameter(
|
||||
sampler,
|
||||
GLEnum.TextureMaxAnisotropy,
|
||||
MaxSupportedAnisotropy);
|
||||
});
|
||||
return sampler;
|
||||
}
|
||||
|
||||
private void InitializeSharedDebugResources(ResourceCleanupGroup resources) {
|
||||
// Unit quad vertices for two triangles (0 to 1 for length, -0.5 to 0.5 for thickness)
|
||||
float[] quadVertices = {
|
||||
0.0f, -0.5f,
|
||||
|
|
@ -207,53 +261,74 @@ namespace AcDream.App.Rendering.Wb {
|
|||
0.0f, 0.5f
|
||||
};
|
||||
|
||||
GL.GenBuffers(1, out uint quadVbo);
|
||||
SharedQuadVBO = quadVbo;
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
|
||||
fixed (float* pQuad = quadVertices) {
|
||||
GL.BufferData(GLEnum.ArrayBuffer, (nuint)(quadVertices.Length * sizeof(float)), pQuad, GLEnum.StaticDraw);
|
||||
}
|
||||
SharedQuadVBO = CreateConstructionBuffer(resources, "WB shared debug quad buffer");
|
||||
SharedDebugInstanceVBO = CreateConstructionBuffer(
|
||||
resources,
|
||||
"WB shared debug instance buffer");
|
||||
SharedDebugVAO = GlResourceCommand.CreateName(
|
||||
GL,
|
||||
"WB shared debug vertex array",
|
||||
GL.GenVertexArray,
|
||||
GL.DeleteVertexArray);
|
||||
uint ownedDebugVao = SharedDebugVAO;
|
||||
resources.Add(
|
||||
"WB shared debug vertex array",
|
||||
() => GlResourceCommand.DeleteVertexArray(
|
||||
GL,
|
||||
ownedDebugVao,
|
||||
$"delete WB shared debug vertex array {ownedDebugVao}"));
|
||||
|
||||
GL.GenBuffers(1, out uint debugInstanceVbo);
|
||||
SharedDebugInstanceVBO = debugInstanceVbo;
|
||||
// Initial capacity for debug instances
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
|
||||
GL.BufferData(GLEnum.ArrayBuffer, (nuint)(1024 * 44), (void*)0, GLEnum.StreamDraw); // 44 bytes is sizeof(LineInstance)
|
||||
GlResourceCommand.Execute(GL, "configure WB shared debug resources", () => {
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
|
||||
fixed (float* pQuad = quadVertices) {
|
||||
GL.BufferData(
|
||||
GLEnum.ArrayBuffer,
|
||||
(nuint)(quadVertices.Length * sizeof(float)),
|
||||
pQuad,
|
||||
GLEnum.StaticDraw);
|
||||
}
|
||||
|
||||
GL.GenVertexArrays(1, out uint debugVao);
|
||||
SharedDebugVAO = debugVao;
|
||||
GL.BindVertexArray(SharedDebugVAO);
|
||||
// Initial capacity for debug instances.
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
|
||||
GL.BufferData(
|
||||
GLEnum.ArrayBuffer,
|
||||
(nuint)(1024 * 44),
|
||||
(void*)0,
|
||||
GLEnum.StreamDraw); // 44 bytes is sizeof(LineInstance)
|
||||
|
||||
// Quad Pos attribute (location 0)
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 2, GLEnum.Float, false, 2 * sizeof(float), (void*)0);
|
||||
GL.BindVertexArray(SharedDebugVAO);
|
||||
|
||||
// Instance attributes
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
|
||||
uint lineInstanceSize = 44; // Marshal.SizeOf<LineInstance>() - we'll hardcode or use a constant later
|
||||
// Quad Pos attribute (location 0)
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 2, GLEnum.Float, false, 2 * sizeof(float), (void*)0);
|
||||
|
||||
// aStart (location 1)
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, GLEnum.Float, false, lineInstanceSize, (void*)0);
|
||||
GL.VertexAttribDivisor(1, 1);
|
||||
// Instance attributes
|
||||
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
|
||||
uint lineInstanceSize = 44;
|
||||
|
||||
// aEnd (location 2)
|
||||
GL.EnableVertexAttribArray(2);
|
||||
GL.VertexAttribPointer(2, 3, GLEnum.Float, false, lineInstanceSize, (void*)12); // OffsetOf End
|
||||
GL.VertexAttribDivisor(2, 1);
|
||||
// aStart (location 1)
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, GLEnum.Float, false, lineInstanceSize, (void*)0);
|
||||
GL.VertexAttribDivisor(1, 1);
|
||||
|
||||
// aColor (location 3)
|
||||
GL.EnableVertexAttribArray(3);
|
||||
GL.VertexAttribPointer(3, 4, GLEnum.Float, false, lineInstanceSize, (void*)24); // OffsetOf Color
|
||||
GL.VertexAttribDivisor(3, 1);
|
||||
// aEnd (location 2)
|
||||
GL.EnableVertexAttribArray(2);
|
||||
GL.VertexAttribPointer(2, 3, GLEnum.Float, false, lineInstanceSize, (void*)12);
|
||||
GL.VertexAttribDivisor(2, 1);
|
||||
|
||||
// aThickness (location 4)
|
||||
GL.EnableVertexAttribArray(4);
|
||||
GL.VertexAttribPointer(4, 1, GLEnum.Float, false, lineInstanceSize, (void*)40); // OffsetOf Thickness
|
||||
GL.VertexAttribDivisor(4, 1);
|
||||
// aColor (location 3)
|
||||
GL.EnableVertexAttribArray(3);
|
||||
GL.VertexAttribPointer(3, 4, GLEnum.Float, false, lineInstanceSize, (void*)24);
|
||||
GL.VertexAttribDivisor(3, 1);
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
// aThickness (location 4)
|
||||
GL.EnableVertexAttribArray(4);
|
||||
GL.VertexAttribPointer(4, 1, GLEnum.Float, false, lineInstanceSize, (void*)40);
|
||||
GL.VertexAttribDivisor(4, 1);
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
});
|
||||
}
|
||||
|
||||
public void EnsureInstanceBufferCapacity(int count, int stride, bool forceOrphan = false) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
private readonly uint _ibo;
|
||||
private readonly uint _instanceVbo;
|
||||
private readonly IShader _shader;
|
||||
private readonly ResourceCleanupGroup _resources;
|
||||
private readonly ParticleInstance[] _instanceData = new ParticleInstance[MAX_PARTICLES_TOTAL];
|
||||
private readonly List<ParticleRenderData> _allParticles = new();
|
||||
private int _currentInstanceCount = 0;
|
||||
|
|
@ -43,91 +44,108 @@ namespace AcDream.App.Rendering.Wb {
|
|||
private Vector3 _cameraRight;
|
||||
|
||||
public ParticleBatcher(OpenGLGraphicsDevice graphicsDevice) {
|
||||
_graphicsDevice = graphicsDevice;
|
||||
_graphicsDevice = graphicsDevice ?? throw new ArgumentNullException(nameof(graphicsDevice));
|
||||
var gl = _graphicsDevice.GL;
|
||||
var resources = new ResourceCleanupGroup();
|
||||
IShader? shader = null;
|
||||
uint vao = 0;
|
||||
uint vbo = 0;
|
||||
uint ibo = 0;
|
||||
uint instanceVbo = 0;
|
||||
try {
|
||||
var vertSource = EmbeddedResourceReader.GetEmbeddedResource("Shaders.Particle.vert");
|
||||
var fragSource = EmbeddedResourceReader.GetEmbeddedResource("Shaders.Particle.frag");
|
||||
shader = _graphicsDevice.CreateShader("Particle", vertSource, fragSource);
|
||||
if (shader is IDisposable disposableShader)
|
||||
resources.Add("WB particle shader", disposableShader.Dispose);
|
||||
|
||||
var vertSource = EmbeddedResourceReader.GetEmbeddedResource("Shaders.Particle.vert");
|
||||
var fragSource = EmbeddedResourceReader.GetEmbeddedResource("Shaders.Particle.frag");
|
||||
_shader = _graphicsDevice.CreateShader("Particle", vertSource, fragSource);
|
||||
// Create quad vertices - centered to match ACViewer expansion logic
|
||||
float[] vertices = {
|
||||
-0.5f, 0.0f, -0.5f, 0.0f, 1.0f,
|
||||
0.5f, 0.0f, -0.5f, 1.0f, 1.0f,
|
||||
0.5f, 0.0f, 0.5f, 1.0f, 0.0f,
|
||||
-0.5f, 0.0f, 0.5f, 0.0f, 0.0f
|
||||
};
|
||||
ushort[] indices = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
// Create quad vertices - centered to match ACViewer expansion logic
|
||||
float[] vertices = {
|
||||
// x, y, z, u, v
|
||||
-0.5f, 0.0f, -0.5f, 0.0f, 1.0f,
|
||||
0.5f, 0.0f, -0.5f, 1.0f, 1.0f,
|
||||
0.5f, 0.0f, 0.5f, 1.0f, 0.0f,
|
||||
-0.5f, 0.0f, 0.5f, 0.0f, 0.0f
|
||||
};
|
||||
vao = CreateVertexArray(resources, gl, "WB particle VAO");
|
||||
vbo = CreateBuffer(resources, gl, "WB particle vertex buffer");
|
||||
ibo = CreateBuffer(resources, gl, "WB particle index buffer");
|
||||
instanceVbo = CreateBuffer(resources, gl, "WB particle instance buffer");
|
||||
|
||||
ushort[] indices = { 0, 1, 2, 2, 3, 0 };
|
||||
GlResourceCommand.Execute(gl, "configure WB particle batcher", () => {
|
||||
gl.BindVertexArray(vao);
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||
fixed (float* p = vertices) {
|
||||
gl.BufferData(BufferTargetARB.ArrayBuffer, (uint)(vertices.Length * sizeof(float)), p, BufferUsageARB.StaticDraw);
|
||||
}
|
||||
gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ibo);
|
||||
fixed (ushort* p = indices) {
|
||||
gl.BufferData(BufferTargetARB.ElementArrayBuffer, (uint)(indices.Length * sizeof(ushort)), p, BufferUsageARB.StaticDraw);
|
||||
}
|
||||
|
||||
_vao = gl.GenVertexArray();
|
||||
gl.BindVertexArray(_vao);
|
||||
gl.EnableVertexAttribArray(0);
|
||||
gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)0);
|
||||
gl.EnableVertexAttribArray(1);
|
||||
gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
|
||||
_vbo = gl.GenBuffer();
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);
|
||||
unsafe {
|
||||
fixed (float* p = vertices) {
|
||||
gl.BufferData(BufferTargetARB.ArrayBuffer, (uint)(vertices.Length * sizeof(float)), p, BufferUsageARB.StaticDraw);
|
||||
}
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, instanceVbo);
|
||||
gl.BufferData(BufferTargetARB.ArrayBuffer, (uint)(MAX_PARTICLES_TOTAL * Marshal.SizeOf<ParticleInstance>()), (void*)0, BufferUsageARB.DynamicDraw);
|
||||
uint stride = (uint)Marshal.SizeOf<ParticleInstance>();
|
||||
gl.EnableVertexAttribArray(2);
|
||||
gl.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||
gl.VertexAttribDivisor(2, 1);
|
||||
gl.EnableVertexAttribArray(3);
|
||||
gl.VertexAttribPointer(3, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(3, 1);
|
||||
gl.EnableVertexAttribArray(4);
|
||||
gl.VertexAttribPointer(4, 1, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(4, 1);
|
||||
gl.EnableVertexAttribArray(5);
|
||||
gl.VertexAttribPointer(5, 4, VertexAttribPointerType.Float, false, stride, (void*)(7 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(5, 1);
|
||||
gl.EnableVertexAttribArray(6);
|
||||
gl.VertexAttribPointer(6, 2, VertexAttribPointerType.Float, false, stride, (void*)(11 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(6, 1);
|
||||
gl.EnableVertexAttribArray(7);
|
||||
gl.VertexAttribPointer(7, 1, VertexAttribPointerType.Float, false, stride, (void*)(13 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(7, 1);
|
||||
gl.BindVertexArray(0);
|
||||
});
|
||||
|
||||
shader.Bind();
|
||||
shader.SetUniform("uTextureArray", 0);
|
||||
shader.Unbind();
|
||||
} catch (Exception constructionFailure) {
|
||||
resources.RollbackConstructionAndThrow(
|
||||
"ParticleBatcher construction failed and its GL prefix did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
}
|
||||
|
||||
_ibo = gl.GenBuffer();
|
||||
gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ibo);
|
||||
unsafe {
|
||||
fixed (ushort* p = indices) {
|
||||
gl.BufferData(BufferTargetARB.ElementArrayBuffer, (uint)(indices.Length * sizeof(ushort)), p, BufferUsageARB.StaticDraw);
|
||||
}
|
||||
}
|
||||
_resources = resources;
|
||||
_shader = shader!;
|
||||
_vao = vao;
|
||||
_vbo = vbo;
|
||||
_ibo = ibo;
|
||||
_instanceVbo = instanceVbo;
|
||||
}
|
||||
|
||||
// Quad attributes
|
||||
gl.EnableVertexAttribArray(0);
|
||||
gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)0);
|
||||
gl.EnableVertexAttribArray(1);
|
||||
gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
private static uint CreateBuffer(
|
||||
ResourceCleanupGroup resources,
|
||||
GL gl,
|
||||
string name) {
|
||||
uint buffer = GlResourceCommand.CreateName(gl, name, gl.GenBuffer, gl.DeleteBuffer);
|
||||
resources.Add(name, () => GlResourceCommand.DeleteBuffer(gl, buffer, $"delete {name} {buffer}"));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Instance attributes
|
||||
_instanceVbo = gl.GenBuffer();
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, _instanceVbo);
|
||||
gl.BufferData(BufferTargetARB.ArrayBuffer, (uint)(MAX_PARTICLES_TOTAL * Marshal.SizeOf<ParticleInstance>()), (void*)0, BufferUsageARB.DynamicDraw);
|
||||
|
||||
uint stride = (uint)Marshal.SizeOf<ParticleInstance>();
|
||||
|
||||
// iPosition
|
||||
gl.EnableVertexAttribArray(2);
|
||||
gl.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||
gl.VertexAttribDivisor(2, 1);
|
||||
|
||||
// iScaleOpacityActive
|
||||
gl.EnableVertexAttribArray(3);
|
||||
gl.VertexAttribPointer(3, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(3, 1);
|
||||
|
||||
// iTextureIndex
|
||||
gl.EnableVertexAttribArray(4);
|
||||
gl.VertexAttribPointer(4, 1, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(4, 1);
|
||||
|
||||
// iRotation (Quaternion)
|
||||
gl.EnableVertexAttribArray(5);
|
||||
gl.VertexAttribPointer(5, 4, VertexAttribPointerType.Float, false, stride, (void*)(7 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(5, 1);
|
||||
|
||||
// iSize
|
||||
gl.EnableVertexAttribArray(6);
|
||||
gl.VertexAttribPointer(6, 2, VertexAttribPointerType.Float, false, stride, (void*)(11 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(6, 1);
|
||||
|
||||
// iIsBillboard
|
||||
gl.EnableVertexAttribArray(7);
|
||||
gl.VertexAttribPointer(7, 1, VertexAttribPointerType.Float, false, stride, (void*)(13 * sizeof(float)));
|
||||
gl.VertexAttribDivisor(7, 1);
|
||||
|
||||
gl.BindVertexArray(0);
|
||||
|
||||
_shader.Bind();
|
||||
_shader.SetUniform("uTextureArray", 0);
|
||||
_shader.Unbind();
|
||||
private static uint CreateVertexArray(
|
||||
ResourceCleanupGroup resources,
|
||||
GL gl,
|
||||
string name) {
|
||||
uint vao = GlResourceCommand.CreateName(gl, name, gl.GenVertexArray, gl.DeleteVertexArray);
|
||||
resources.Add(name, () => GlResourceCommand.DeleteVertexArray(gl, vao, $"delete {name} {vao}"));
|
||||
return vao;
|
||||
}
|
||||
|
||||
public void Begin(Matrix4x4 viewProjection, Vector3 cameraUp, Vector3 cameraRight) {
|
||||
|
|
@ -216,12 +234,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
}
|
||||
|
||||
public void Dispose() {
|
||||
var gl = _graphicsDevice.GL;
|
||||
gl.DeleteVertexArray(_vao);
|
||||
gl.DeleteBuffer(_vbo);
|
||||
gl.DeleteBuffer(_instanceVbo);
|
||||
gl.DeleteBuffer(_ibo);
|
||||
(_shader as IDisposable)?.Dispose();
|
||||
_resources.RetryCleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,22 @@ internal static unsafe class TrackedGlResource
|
|||
GL gl,
|
||||
uint buffer,
|
||||
long capacityBytes,
|
||||
string context) =>
|
||||
CreateRetryableBufferDeletion(
|
||||
gl,
|
||||
buffer,
|
||||
() => capacityBytes,
|
||||
context);
|
||||
|
||||
public static RetryableGpuResourceRelease CreateRetryableBufferDeletion(
|
||||
GL gl,
|
||||
uint buffer,
|
||||
Func<long> capacityBytes,
|
||||
string context)
|
||||
{
|
||||
if (buffer == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(buffer));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(capacityBytes);
|
||||
ArgumentNullException.ThrowIfNull(capacityBytes);
|
||||
return new RetryableGpuResourceRelease(
|
||||
() => GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"),
|
||||
() =>
|
||||
|
|
@ -34,8 +45,10 @@ internal static unsafe class TrackedGlResource
|
|||
},
|
||||
() =>
|
||||
{
|
||||
if (capacityBytes != 0)
|
||||
GpuMemoryTracker.TrackDeallocation(capacityBytes, GpuResourceType.Buffer);
|
||||
long bytes = capacityBytes();
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(bytes);
|
||||
if (bytes != 0)
|
||||
GpuMemoryTracker.TrackDeallocation(bytes, GpuResourceType.Buffer);
|
||||
},
|
||||
() => GpuMemoryTracker.TrackResourceDeallocation(GpuResourceType.Buffer));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,18 +122,48 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
|
||||
_dats = dats;
|
||||
_resourceRetirement = resourceRetirement;
|
||||
_graphicsDevice = new OpenGLGraphicsDevice(
|
||||
gl,
|
||||
logger,
|
||||
new DebugRenderSettings(),
|
||||
resourceRetirement);
|
||||
_graphicsDevice.ParticleBatcher = new ParticleBatcher(_graphicsDevice);
|
||||
// ConsoleErrorLogger surfaces WB's silently-caught exceptions
|
||||
// (ObjectMeshManager.PrepareMeshData try/catch at line ~589).
|
||||
_meshManager = new ObjectMeshManager(
|
||||
_graphicsDevice,
|
||||
dats,
|
||||
new ConsoleErrorLogger<ObjectMeshManager>());
|
||||
var resources = new AcDream.App.Rendering.ResourceCleanupGroup();
|
||||
OpenGLGraphicsDevice? graphicsDevice = null;
|
||||
ObjectMeshManager? meshManager = null;
|
||||
try
|
||||
{
|
||||
graphicsDevice = new OpenGLGraphicsDevice(
|
||||
gl,
|
||||
logger,
|
||||
new DebugRenderSettings(),
|
||||
resourceRetirement);
|
||||
OpenGLGraphicsDevice ownedGraphicsDevice = graphicsDevice;
|
||||
var graphicsDeviceRelease = new RetryableGpuResourceRelease(
|
||||
ownedGraphicsDevice.Dispose,
|
||||
() =>
|
||||
{
|
||||
ownedGraphicsDevice.ProcessGLQueue();
|
||||
if (ownedGraphicsDevice.HasPendingGLWork)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"WB graphics-device construction cleanup still has queued GL work.");
|
||||
}
|
||||
});
|
||||
resources.Add("WB graphics device", graphicsDeviceRelease.Run);
|
||||
graphicsDevice.ParticleBatcher = new ParticleBatcher(graphicsDevice);
|
||||
// ConsoleErrorLogger surfaces WB's silently-caught exceptions
|
||||
// (ObjectMeshManager.PrepareMeshData try/catch at line ~589).
|
||||
meshManager = new ObjectMeshManager(
|
||||
graphicsDevice,
|
||||
dats,
|
||||
new ConsoleErrorLogger<ObjectMeshManager>());
|
||||
resources.Add("WB object mesh manager", meshManager.Dispose);
|
||||
resources.TransferAll();
|
||||
}
|
||||
catch (Exception constructionFailure)
|
||||
{
|
||||
resources.RollbackConstructionAndThrow(
|
||||
"WbMeshAdapter construction failed and its WB/GL prefix did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
}
|
||||
|
||||
_graphicsDevice = graphicsDevice;
|
||||
_meshManager = meshManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue