using System; using Engine.Core; using Engine.Systems.Graphics; using SDL3; using Silk.NET.OpenGL; namespace Engine.Integration.SDL3; public class Sdl3TriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdate { private GL gl = null!; private readonly uint MaxVertices = 1024; private uint vao; private uint vbo; private uint shader; private uint vertexIndex = 0; private VertexPositionColor[] vertices = new VertexPositionColor[1024]; private Matrix4x4 view = Matrix4x4.Identity; private Matrix4x4 projection = Matrix4x4.Identity; private Sdl3Window sdl3window = null!; private ICamera? camera = null!; private nint glContext; public void FirstActiveFrame() { sdl3window = BehaviourController.GetRequiredBehaviourInParent(); camera = BehaviourController.GetRequiredBehaviourInParent(); nint window = sdl3window.Window; SDL.GLSetAttribute(SDL.GLAttr.ContextMajorVersion, 3); SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 3); SDL.GLSetAttribute(SDL.GLAttr.ContextProfileMask, (int)SDL.GLProfile.Core); SDL.GLSetAttribute(SDL.GLAttr.DoubleBuffer, 1); glContext = SDL.GLCreateContext(window); if (glContext == nint.Zero) throw new Exception(SDL.GetError()); SDL.GLMakeCurrent(window, glContext); gl = GL.GetApi(procName => SDL.GLGetProcAddress(procName)); gl.Viewport(0, 0, (uint)sdl3window.Size.X, (uint)sdl3window.Size.Y); vertices = new VertexPositionColor[MaxVertices]; // --- GL objects --- vao = gl.GenVertexArray(); vbo = gl.GenBuffer(); gl.BindVertexArray(vao); gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo); unsafe { gl.BufferData( BufferTargetARB.ArrayBuffer, (nuint)(MaxVertices * sizeof(VertexPositionColor)), null, BufferUsageARB.DynamicDraw ); // position gl.EnableVertexAttribArray(0); gl.VertexAttribPointer( 0, 3, VertexAttribPointerType.Float, false, (uint)sizeof(VertexPositionColor), (void*)0 ); // color gl.EnableVertexAttribArray(1); gl.VertexAttribPointer( 1, 4, VertexAttribPointerType.Float, false, (uint)sizeof(VertexPositionColor), (void*)(3 * sizeof(float)) ); gl.BindVertexArray(0); } shader = CreateShader(); } public void Draw(Triangle triangle, ColorRGBA colorRGBA) { if (vertexIndex + 3 >= vertices.Length) Flush(); Vector2D A = triangle.A; Vector2D B = triangle.B; Vector2D C = triangle.C; Vector4D c = new Vector4D(colorRGBA.R, colorRGBA.G, colorRGBA.B, colorRGBA.A) / 256f; vertices[vertexIndex++] = new() { Position = new Vector3D(A.X, A.Y, 0f), Color = c }; vertices[vertexIndex++] = new() { Position = new Vector3D(B.X, B.Y, 0f), Color = c }; vertices[vertexIndex++] = new() { Position = new Vector3D(C.X, C.Y, 0f), Color = c }; } public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null) { this.view = view ?? camera?.ViewMatrix ?? Matrix4x4.Identity; if (projection != null) this.projection = projection.Value; else this.projection = camera?.ProjectionMatrix ?? Matrix4x4.CreateOrthographicViewCentered(sdl3window.Size.X, sdl3window.Size.Y); vertexIndex = 0; SDL.GLMakeCurrent(sdl3window.Window, glContext); gl.ClearColor(sdl3window.BackgroundColor.R / 256f, sdl3window.BackgroundColor.G / 256f, sdl3window.BackgroundColor.B / 256f, 1f); gl.Clear(ClearBufferMask.ColorBufferBit); } public void End() { Flush(); SDL.GLSwapWindow(sdl3window.Window); } private void Flush() { if (vertexIndex == 0) return; gl.UseProgram(shader); int viewLoc = gl.GetUniformLocation(shader, "uView"); int projLoc = gl.GetUniformLocation(shader, "uProjection"); unsafe { Span temp = stackalloc float[16]; CopyTo(view.Transposed, temp); fixed (float* v = temp) gl.UniformMatrix4(viewLoc, 1, false, v); CopyTo(projection.Transposed, temp); fixed (float* p = temp) gl.UniformMatrix4(projLoc, 1, false, p); gl.BindVertexArray(vao); gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo); fixed (VertexPositionColor* ptr = vertices) { gl.BufferSubData( BufferTargetARB.ArrayBuffer, 0, (nuint)(vertexIndex * sizeof(VertexPositionColor)), ptr ); } } gl.DrawArrays(PrimitiveType.Triangles, 0, vertexIndex); vertexIndex = 0; } public static void CopyTo(Matrix4x4 m, Span destination) { if (destination.Length < 16) throw new ArgumentException("Destination span must have at least 16 elements."); destination[0] = m.M11; destination[1] = m.M12; destination[2] = m.M13; destination[3] = m.M14; destination[4] = m.M21; destination[5] = m.M22; destination[6] = m.M23; destination[7] = m.M24; destination[8] = m.M31; destination[9] = m.M32; destination[10] = m.M33; destination[11] = m.M34; destination[12] = m.M41; destination[13] = m.M42; destination[14] = m.M43; destination[15] = m.M44; } private uint CreateShader() { const string vertexSrc = """ #version 330 core layout (location = 0) in vec3 aPosition; layout (location = 1) in vec4 aColor; uniform mat4 uView; uniform mat4 uProjection; out vec4 vColor; void main() { gl_Position = uProjection * uView * vec4(aPosition, 1.0); vColor = aColor; } """; const string fragmentSrc = """ #version 330 core in vec4 vColor; out vec4 FragColor; void main() { FragColor = vColor; } """; uint vs = CompileShader(ShaderType.VertexShader, vertexSrc); uint fs = CompileShader(ShaderType.FragmentShader, fragmentSrc); uint program = gl.CreateProgram(); gl.AttachShader(program, vs); gl.AttachShader(program, fs); gl.LinkProgram(program); gl.DeleteShader(vs); gl.DeleteShader(fs); return program; } private uint CompileShader(ShaderType type, string src) { uint shader = gl.CreateShader(type); gl.ShaderSource(shader, src); gl.CompileShader(shader); return shader; } struct VertexPositionColor { public Vector3D Position; public Vector4D Color; public const int SizeInBytes = (3 + 4) * sizeof(float); } }