acdream/src/AcDream.App/Rendering/Wb/OpenGLGraphicsDevice.cs
Erik c0326523ac fix(O-T4): address spec-review findings — InstanceData + using cleanups
Four fixes from T4 spec review:

1. Extracted InstanceData.cs (14-line struct) verbatim to
   src/AcDream.App/Rendering/Wb/InstanceData.cs (per O-D1).

2. ObjectMeshManager.cs: replaced `using Chorizite.OpenGLSDLBackend.Lib;`
   with `using AcDream.Core.Rendering.Wb;` (TextureHelpers comes from
   our T2 Core extraction; InstanceData comes from new T4 cleanup).

3. EmbeddedResourceReader.GetEmbeddedResource promoted from `internal`
   to `public` per O-D9 intent (the type promotion only changed the
   class signature in T3; this finishes the spec).

4. OpenGLGraphicsDevice.cs: removed stale T3 interim comment at
   lines 142-145 — T4 resolved the ParticleBatcher construction
   via post-ctor assignment in WbMeshAdapter.cs:78.

Build green; tests green (1147 passing, 8 pre-existing failures
baseline maintained).

Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 16:50:05 +02:00

632 lines
27 KiB
C#

using Chorizite.Core.Render;
using Chorizite.Core.Render.Enums;
using Chorizite.Core.Render.Vertex;
using Microsoft.Extensions.Logging;
using Silk.NET.OpenGL;
// IUniformBuffer is in Chorizite.Core.dll but under the Chorizite.OpenGLSDLBackend namespace
using IUniformBuffer = Chorizite.OpenGLSDLBackend.IUniformBuffer;
using Silk.NET.OpenGL.Extensions.ARB;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading;
using PolygonMode = Silk.NET.OpenGL.PolygonMode;
using PrimitiveType = Silk.NET.OpenGL.PrimitiveType;
namespace AcDream.App.Rendering.Wb {
/// <summary>
/// OpenGL graphics device
/// </summary>
public unsafe class OpenGLGraphicsDevice : BaseGraphicsDevice {
private readonly ILogger _log;
private readonly DebugRenderSettings _renderSettings;
public GL GL { get; }
public DebugRenderSettings RenderSettings => _renderSettings;
private readonly ConcurrentQueue<Action<GL>> _glThreadQueue = new();
public void QueueGLAction(Action<GL> action) {
_glThreadQueue.Enqueue(action);
}
public void ProcessGLQueue() {
while (_glThreadQueue.TryDequeue(out var action)) {
try {
action(GL);
} catch (Exception ex) {
_log.LogError(ex, "Error processing GL queue action");
}
}
}
public bool HasBindless { get; private set; }
public bool HasOpenGL43 { get; private set; }
public bool HasBufferStorage { get; private set; }
public bool HasTextureStorage { get; private set; }
public ArbBindlessTexture? BindlessExtension { get; private set; }
public uint InstanceVBO { get; private set; }
public void* InstanceVBOPtr { get; private set; }
public uint SharedQuadVBO { get; private set; }
public uint SharedDebugVAO { get; private set; }
public uint SharedDebugInstanceVBO { get; private set; }
public ParticleBatcher ParticleBatcher { get; internal set; } = null!;
/// <summary>OpenGL sampler object with TextureWrapMode.Repeat (for meshes with wrapping UVs).</summary>
public uint WrapSampler { get; private set; }
/// <summary>OpenGL sampler object with TextureWrapMode.ClampToEdge (for meshes without wrapping UVs).</summary>
public uint ClampSampler { get; private set; }
private ManagedGLUniformBuffer? _sceneDataBuffer;
/// <summary>Shared SceneData UBO.</summary>
public ManagedGLUniformBuffer SceneDataBuffer => _sceneDataBuffer!;
private SceneData _currentSceneData;
public SceneData CurrentSceneData => _currentSceneData;
public void SetSceneData(ref SceneData data) {
_currentSceneData = data;
SceneDataBuffer.SetData(ref data);
}
private int _instanceBufferCapacity = 0;
private int _instanceBufferStride = 0;
/// <inheritdoc />
public override IntPtr NativeDevice { get; }
protected OpenGLGraphicsDevice() : base() {
_log = null!;
_renderSettings = null!;
GL = null!;
}
public OpenGLGraphicsDevice(GL gl, ILogger log, DebugRenderSettings renderSettings, bool allowBindless = true) : base() {
_log = log;
_renderSettings = renderSettings;
GL = gl;
GLHelpers.Init(this, log);
try {
GL.GetInteger(GLEnum.MajorVersion, out int major);
GL.GetInteger(GLEnum.MinorVersion, out int minor);
HasOpenGL43 = major > 4 || (major == 4 && minor >= 3);
HasTextureStorage = major > 4 || (major == 4 && minor >= 2) || GL.IsExtensionPresent("GL_ARB_texture_storage");
HasBufferStorage = major > 4 || (major == 4 && minor >= 4) || GL.IsExtensionPresent("GL_ARB_buffer_storage");
if (allowBindless && GL.TryGetExtension(out ArbBindlessTexture ext)) {
BindlessExtension = ext;
HasBindless = true;
} else {
HasBindless = false;
}
} catch {
HasOpenGL43 = false;
HasBindless = false;
}
GL.GenBuffers(1, out uint instanceVbo);
InstanceVBO = instanceVbo;
// 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 (renderSettings.EnableAnisotropicFiltering) {
GL.GetFloat(GLEnum.MaxTextureMaxAnisotropy, out float maxAniso);
if (maxAniso > 0) GL.SamplerParameter(WrapSampler, GLEnum.TextureMaxAnisotropy, maxAniso);
}
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 (renderSettings.EnableAnisotropicFiltering) {
GL.GetFloat(GLEnum.MaxTextureMaxAnisotropy, out float maxAniso);
if (maxAniso > 0) GL.SamplerParameter(ClampSampler, GLEnum.TextureMaxAnisotropy, maxAniso);
}
_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.
ParticleBatcher = null!;
}
private void InitializeSharedDebugResources() {
// Unit quad vertices for two triangles (0 to 1 for length, -0.5 to 0.5 for thickness)
float[] quadVertices = {
0.0f, -0.5f,
1.0f, -0.5f,
1.0f, 0.5f,
0.0f, -0.5f,
1.0f, 0.5f,
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);
}
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)
GL.GenVertexArrays(1, out uint debugVao);
SharedDebugVAO = debugVao;
GL.BindVertexArray(SharedDebugVAO);
// 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);
// Instance attributes
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
uint lineInstanceSize = 44; // Marshal.SizeOf<LineInstance>() - we'll hardcode or use a constant later
// aStart (location 1)
GL.EnableVertexAttribArray(1);
GL.VertexAttribPointer(1, 3, GLEnum.Float, false, lineInstanceSize, (void*)0);
GL.VertexAttribDivisor(1, 1);
// aEnd (location 2)
GL.EnableVertexAttribArray(2);
GL.VertexAttribPointer(2, 3, GLEnum.Float, false, lineInstanceSize, (void*)12); // OffsetOf End
GL.VertexAttribDivisor(2, 1);
// aColor (location 3)
GL.EnableVertexAttribArray(3);
GL.VertexAttribPointer(3, 4, GLEnum.Float, false, lineInstanceSize, (void*)24); // OffsetOf Color
GL.VertexAttribDivisor(3, 1);
// aThickness (location 4)
GL.EnableVertexAttribArray(4);
GL.VertexAttribPointer(4, 1, GLEnum.Float, false, lineInstanceSize, (void*)40); // OffsetOf Thickness
GL.VertexAttribDivisor(4, 1);
GL.BindVertexArray(0);
}
public void EnsureInstanceBufferCapacity(int count, int stride, bool forceOrphan = false) {
if (count <= _instanceBufferCapacity && !forceOrphan) return;
if (_instanceBufferCapacity > 0) {
GpuMemoryTracker.TrackDeallocation(_instanceBufferCapacity * _instanceBufferStride);
}
_instanceBufferCapacity = Math.Max(count, 256);
_instanceBufferStride = stride;
if (HasBufferStorage) {
if (InstanceVBO != 0) {
GL.DeleteBuffer(InstanceVBO);
}
GL.GenBuffers(1, out uint instanceVbo);
InstanceVBO = instanceVbo;
GL.BindBuffer(GLEnum.ArrayBuffer, InstanceVBO);
var flags = BufferStorageMask.MapWriteBit | BufferStorageMask.MapPersistentBit | BufferStorageMask.MapCoherentBit | BufferStorageMask.DynamicStorageBit;
GL.BufferStorage(GLEnum.ArrayBuffer, (nuint)(_instanceBufferCapacity * _instanceBufferStride), (void*)0, flags);
InstanceVBOPtr = GL.MapBufferRange(GLEnum.ArrayBuffer, 0, (nuint)(_instanceBufferCapacity * _instanceBufferStride), MapBufferAccessMask.WriteBit | MapBufferAccessMask.PersistentBit | MapBufferAccessMask.CoherentBit);
} else {
GL.BindBuffer(GLEnum.ArrayBuffer, InstanceVBO);
GL.BufferData(GLEnum.ArrayBuffer, (nuint)(_instanceBufferCapacity * _instanceBufferStride),
(void*)null, GLEnum.DynamicDraw);
InstanceVBOPtr = null;
}
GpuMemoryTracker.TrackAllocation(_instanceBufferCapacity * _instanceBufferStride);
}
public void UpdateInstanceBuffer<T>(List<T> data) where T : unmanaged {
EnsureInstanceBufferCapacity(data.Count, Marshal.SizeOf<T>(), true);
var span = CollectionsMarshal.AsSpan(data);
if (InstanceVBOPtr != null) {
var destSpan = new Span<T>(InstanceVBOPtr, data.Count);
span.CopyTo(destSpan);
} else {
GL.BindBuffer(GLEnum.ArrayBuffer, InstanceVBO);
fixed (T* ptr = span) {
GL.BufferSubData(GLEnum.ArrayBuffer, 0, (nuint)(data.Count * Marshal.SizeOf<T>()), ptr);
}
}
}
public void UpdateInstanceBuffer<T>(Span<T> data) where T : unmanaged {
EnsureInstanceBufferCapacity(data.Length, Marshal.SizeOf<T>(), true);
if (InstanceVBOPtr != null) {
var destSpan = new Span<T>(InstanceVBOPtr, data.Length);
data.CopyTo(destSpan);
} else {
GL.BindBuffer(GLEnum.ArrayBuffer, InstanceVBO);
fixed (T* ptr = data) {
GL.BufferSubData(GLEnum.ArrayBuffer, 0, (nuint)(data.Length * Marshal.SizeOf<T>()), ptr);
}
}
}
/// <inheritdoc />
public override void Clear(ColorVec color, ClearFlags flags, float depth, int stencil) {
GL.ClearColor(color.R, color.G, color.B, color.A);
GLHelpers.CheckErrors(GL);
GL.Clear((uint)Convert(flags));
GLHelpers.CheckErrors(GL);
}
/// <inheritdoc />
public override IIndexBuffer CreateIndexBuffer(int size,
Chorizite.Core.Render.Enums.BufferUsage usage = Chorizite.Core.Render.Enums.BufferUsage.Static) {
return new ManagedGLIndexBuffer(this, usage, size);
}
/// <inheritdoc />
public override IVertexBuffer CreateVertexBuffer(int size,
Chorizite.Core.Render.Enums.BufferUsage usage = Chorizite.Core.Render.Enums.BufferUsage.Static) {
return new ManagedGLVertexBuffer(this, usage, size);
}
/// <inheritdoc />
public override IVertexArray CreateArrayBuffer(IVertexBuffer vertexBuffer, VertexFormat format) {
return new ManagedGLVertexArray(this, vertexBuffer, format);
}
/// <inheritdoc />
public override void DrawElements(Chorizite.Core.Render.Enums.PrimitiveType type, int numElements, int indiceOffset = 0) {
GL.DrawElements(Convert(type), (uint)numElements, GLEnum.UnsignedInt, (void*)(indiceOffset * sizeof(uint)));
GLHelpers.CheckErrors(GL);
}
public override IShader CreateShader(string name, string vertexCode, string fragmentCode) {
var key = $"{GL.GetHashCode()}_{name}_{vertexCode.GetHashCode()}_{fragmentCode.GetHashCode()}";
while (true) {
if (_shaderCache.TryGetValue(key, out var existing)) {
if (existing is SharedShader shared && shared.TryIncrement()) {
return existing;
}
}
var inner = new GLSLShader(this, name, vertexCode, fragmentCode, _log);
var newShader = new SharedShader(inner, () => _shaderCache.TryRemove(key, out _));
if (_shaderCache.TryAdd(key, newShader)) {
return newShader;
}
// Someone else added it first, dispose ours and try again
newShader.DisposeInternal();
}
}
/// <inheritdoc />
public override IShader CreateShader(string name, string shaderDirectory) {
var key = $"{GL.GetHashCode()}_{name}";
while (true) {
if (_shaderCache.TryGetValue(key, out var existing)) {
if (existing is SharedShader shared && shared.TryIncrement()) {
return existing;
}
}
var inner = new GLSLShader(this, name, shaderDirectory, _log);
var newShader = new SharedShader(inner, () => _shaderCache.TryRemove(key, out _));
if (_shaderCache.TryAdd(key, newShader)) {
return newShader;
}
// Someone else added it first, dispose ours and try again
newShader.DisposeInternal();
}
}
private static readonly ConcurrentDictionary<string, IShader> _shaderCache = new();
private class SharedShader : IShader, IDisposable {
private readonly IShader _shader;
private readonly Action _onDispose;
private int _refCount = 1;
public string Name => _shader.Name;
public uint ProgramId => _shader.ProgramId;
public SharedShader(IShader shader, Action onDispose) {
_shader = shader;
_onDispose = onDispose;
}
public bool TryIncrement() {
while (true) {
int current = _refCount;
if (current <= 0) return false;
if (Interlocked.CompareExchange(ref _refCount, current + 1, current) == current) {
return true;
}
}
}
public void Bind() => _shader.Bind();
public void Unbind() => _shader.Unbind();
public void Load(string vertexSource, string fragmentSource) => _shader.Load(vertexSource, fragmentSource);
public void SetUniform(string name, int value) => _shader.SetUniform(name, value);
public void SetUniform(string name, float value) => _shader.SetUniform(name, value);
public void SetUniform(string name, Vector2 value) => _shader.SetUniform(name, value);
public void SetUniform(string name, Vector3 value) => _shader.SetUniform(name, value);
public void SetUniform(string name, Vector4 value) => _shader.SetUniform(name, value);
public void SetUniform(string name, Matrix4x4 value) => _shader.SetUniform(name, value);
public void SetUniform(string name, float[] values) => _shader.SetUniform(name, values);
public void DisposeInternal() {
_refCount = 0;
(_shader as IDisposable)?.Dispose();
}
public void Dispose() {
if (Interlocked.Decrement(ref _refCount) == 0) {
(_shader as IDisposable)?.Dispose();
_onDispose();
}
}
}
/// <inheritdoc />
public override ITexture
CreateTextureInternal(TextureFormat format, int width, int height, byte[]? data = null) {
if (format != TextureFormat.RGBA8) {
throw new NotImplementedException($"Texture format {format} is not supported.");
}
return new ManagedGLTexture(this, data, width, height);
}
/// <summary>
/// Creates a texture with custom texture parameters.
/// </summary>
public ITexture CreateTextureInternal(TextureFormat format, int width, int height, byte[]? data, TextureParameters texParams) {
if (format != TextureFormat.RGBA8) {
throw new NotImplementedException($"Texture format {format} is not supported.");
}
return new ManagedGLTexture(this, data, width, height, texParams);
}
/// <inheritdoc />
public override ITexture? CreateTextureInternal(TextureFormat format, string filename) {
if (format != TextureFormat.RGBA8) {
throw new NotImplementedException($"Texture format {format} is not supported.");
}
return new ManagedGLTexture(this, filename);
}
/// <inheritdoc />
public override ITextureArray
CreateTextureArrayInternal(TextureFormat format, int width, int height, int size) {
return new ManagedGLTextureArray(this, format, width, height, size, _log);
}
/// <summary>
/// Creates a texture array with custom texture parameters.
/// </summary>
public ITextureArray CreateTextureArrayInternal(TextureFormat format, int width, int height, int size, TextureParameters texParams) {
return new ManagedGLTextureArray(this, format, width, height, size, _log, texParams);
}
/// <inheritdoc />
public override void BeginFrame() {
GL.Viewport(Viewport.X, Viewport.Y, (uint)Viewport.Width, (uint)Viewport.Height);
GLHelpers.CheckErrors(GL);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
GLHelpers.CheckErrors(GL);
}
/// <inheritdoc />
public override void EndFrame() {
}
/// <inheritdoc />
protected override void SetRenderStateInternal(RenderState state, bool enabled) {
switch (state) {
case RenderState.AlphaBlend:
if (enabled) GL.Enable(EnableCap.Blend);
else GL.Disable(EnableCap.Blend);
GLHelpers.CheckErrors(GL);
break;
case RenderState.DepthTest:
if (enabled) GL.Enable(EnableCap.DepthTest);
else GL.Disable(EnableCap.DepthTest);
GLHelpers.CheckErrors(GL);
break;
case RenderState.ScissorTest:
if (enabled) GL.Enable(EnableCap.ScissorTest);
else GL.Disable(EnableCap.ScissorTest);
GLHelpers.CheckErrors(GL);
break;
case RenderState.DepthWrite:
if (enabled) GL.DepthMask(true);
else GL.DepthMask(false);
GLHelpers.CheckErrors(GL);
break;
case RenderState.Fog:
break;
case RenderState.Lighting:
break;
}
}
/// <inheritdoc />
protected override void SetBlendFactorInternal(BlendFactor srcBlendFactor, BlendFactor dstBlendFactor) {
GL.BlendFunc(Convert(srcBlendFactor), Convert(dstBlendFactor));
GLHelpers.CheckErrors(GL);
}
protected override void SetScissorRectInternal(Rectangle scissor) {
var gtop = (int)Viewport.Height - scissor.Y - scissor.Height;
GL.Scissor(scissor.X, gtop, (uint)scissor.Width, (uint)scissor.Height);
GLHelpers.CheckErrors(GL);
}
protected override void SetViewportInternal(Rectangle viewport) {
GL.Viewport(viewport.X, viewport.Y, (uint)viewport.Width, (uint)viewport.Height);
GLHelpers.CheckErrors(GL);
}
protected override void SetPolygonModeInternal(Chorizite.Core.Render.Enums.PolygonMode polygonMode) {
GL.PolygonMode(GLEnum.FrontAndBack, Convert(polygonMode));
GLHelpers.CheckErrors(GL);
}
protected override void SetCullModeInternal(CullMode cullMode) {
switch (cullMode) {
case CullMode.None:
GL.Disable(EnableCap.CullFace);
break;
case CullMode.Front:
GL.Enable(EnableCap.CullFace);
GL.CullFace(GLEnum.Front);
break;
case CullMode.Back:
GL.Enable(EnableCap.CullFace);
GL.CullFace(GLEnum.Back);
break;
}
}
private GLEnum Convert(Chorizite.Core.Render.Enums.PolygonMode mode) {
switch (mode) {
case Chorizite.Core.Render.Enums.PolygonMode.Fill:
return GLEnum.Fill;
case Chorizite.Core.Render.Enums.PolygonMode.Line:
return GLEnum.Line;
case Chorizite.Core.Render.Enums.PolygonMode.Point:
return GLEnum.Point;
default:
return GLEnum.Fill;
}
}
private GLEnum Convert(ClearFlags flags) {
GLEnum mask = 0;
if ((flags & ClearFlags.Color) == ClearFlags.Color) mask |= GLEnum.ColorBufferBit;
if ((flags & ClearFlags.Depth) == ClearFlags.Depth) mask |= GLEnum.DepthBufferBit;
if ((flags & ClearFlags.Stencil) == ClearFlags.Stencil) mask |= GLEnum.StencilBufferBit;
return mask;
}
private GLEnum Convert(BlendFactor factor) {
switch (factor) {
case BlendFactor.One:
return GLEnum.One;
case BlendFactor.SrcAlpha:
return GLEnum.SrcAlpha;
case BlendFactor.OneMinusSrcAlpha:
return GLEnum.OneMinusSrcAlpha;
case BlendFactor.DstAlpha:
return GLEnum.DstAlpha;
case BlendFactor.OneMinusDstAlpha:
return GLEnum.OneMinusDstAlpha;
default:
return GLEnum.One;
}
}
private PrimitiveType Convert(Chorizite.Core.Render.Enums.PrimitiveType type) {
switch (type) {
case Chorizite.Core.Render.Enums.PrimitiveType.PointList:
return PrimitiveType.Points;
case Chorizite.Core.Render.Enums.PrimitiveType.LineList:
return PrimitiveType.Lines;
case Chorizite.Core.Render.Enums.PrimitiveType.LineStrip:
return PrimitiveType.LineStrip;
case Chorizite.Core.Render.Enums.PrimitiveType.TriangleList:
return PrimitiveType.Triangles;
case Chorizite.Core.Render.Enums.PrimitiveType.TriangleStrip:
return PrimitiveType.TriangleStrip;
default:
throw new NotImplementedException($"Primitive type {type} is not supported.");
}
}
/// <inheritdoc />
public override IFramebuffer CreateFramebuffer(ITexture texture, int width, int height,
bool hasDepthStencil = true) {
if (texture == null) {
throw new ArgumentNullException(nameof(texture));
}
if (width <= 0 || height <= 0) {
throw new ArgumentException("Width and height must be positive.");
}
return new ManagedGLFramebuffer(this, texture, width, height, hasDepthStencil);
}
/// <inheritdoc />
public override void BindFramebuffer(IFramebuffer? framebuffer) {
uint fboId = framebuffer != null ? (uint)framebuffer.NativeHandle.ToInt32() : 0;
GL.BindFramebuffer(FramebufferTarget.Framebuffer, fboId);
}
/// <inheritdoc />
public override void Dispose() {
var instanceVBO = InstanceVBO;
var instanceBufferCapacity = _instanceBufferCapacity;
var instanceBufferStride = _instanceBufferStride;
var wrapSampler = WrapSampler;
var clampSampler = ClampSampler;
var sharedQuadVbo = SharedQuadVBO;
var sharedDebugInstanceVbo = SharedDebugInstanceVBO;
var sharedDebugVao = SharedDebugVAO;
QueueGLAction(gl => {
if (sharedQuadVbo != 0) gl.DeleteBuffer(sharedQuadVbo);
if (sharedDebugInstanceVbo != 0) gl.DeleteBuffer(sharedDebugInstanceVbo);
if (sharedDebugVao != 0) gl.DeleteVertexArray(sharedDebugVao);
if (instanceVBO != 0) {
gl.DeleteBuffer(instanceVBO);
if (instanceBufferCapacity > 0) {
GpuMemoryTracker.TrackDeallocation(instanceBufferCapacity * instanceBufferStride);
}
}
if (wrapSampler != 0) {
gl.DeleteSampler(wrapSampler);
}
if (clampSampler != 0) {
gl.DeleteSampler(clampSampler);
}
});
InstanceVBO = 0;
InstanceVBOPtr = null;
WrapSampler = 0;
ClampSampler = 0;
_sceneDataBuffer?.Dispose();
_sceneDataBuffer = null;
ParticleBatcher?.Dispose();
}
public override IUniformBuffer CreateUniformBuffer(BufferUsage usage, int size) {
return (IUniformBuffer)new ManagedGLUniformBuffer(this, usage, size);
}
}
}