using System; using System.IO; using System.Collections.Generic; using Quik.VertexGenerator; using static Quik.OpenGL.GLEnum; namespace Quik.OpenGL { public class GL21Driver : IDisposable { private int program; private int v2Position; private int iZIndex; private int v2TexPos; private int v4Color; private int m4Transforms; private int iMaxZ; private int iEnableSdf; private int iEnableTexture; private int iAlphaDiscard; private int fSdfThreshold; private int txTexture; private bool isDiposed; private readonly Dictionary data = new Dictionary(); public bool IsInit { get; private set; } = false; public event Action OnGCDispose; public GL21Driver() { } ~GL21Driver() { Dispose(false); } public void Init() { if (IsInit) return; int vs = CreateShader(GL_VERTEX_SHADER, "Quik.res.gl21.vert"); int fs = CreateShader(GL_FRAGMENT_SHADER, "Quik.res.gl21.frag"); program = GL.CreateProgram(); GL.AttachShader(program, vs); GL.AttachShader(program, fs); GL.LinkProgram(program); if (CheckProgram(program, out string msg) == false) { GraphicsException ex = new GraphicsException("Could not link shader program."); ex.Data.Add("Program Info Log", msg); } GL.DeleteShader(vs); GL.DeleteShader(fs); v2Position = GL.GetAttribLocation(program, nameof(v2Position)); iZIndex = GL.GetAttribLocation(program, nameof(iZIndex)); v2TexPos = GL.GetAttribLocation(program, nameof(v2TexPos)); v4Color = GL.GetAttribLocation(program, nameof(v4Color)); m4Transforms = GL.GetUniformLocation(program, nameof(m4Transforms)); iMaxZ = GL.GetUniformLocation(program, nameof(iMaxZ)); iEnableSdf = GL.GetUniformLocation(program, nameof(iEnableSdf)); iEnableTexture = GL.GetUniformLocation(program, nameof(iEnableSdf)); iAlphaDiscard = GL.GetUniformLocation(program, nameof(iAlphaDiscard)); txTexture = GL.GetUniformLocation(program, nameof(txTexture)); IsInit = true; } private void AssertInit() { if (!IsInit) throw new InvalidOperationException("Initialize the driver first."); } public void Draw(DrawQueue queue, in QRectangle view) { AssertInit(); if (!data.TryGetValue(queue, out DrawData draw)) { draw = new DrawData(this, queue); data.Add(queue, draw); } // This already binds the vertex array for me. draw.PrepareFrame(); QVec2 size = view.Size; QMat4.Orthographic(out QMat4 matrix, view); GL.UseProgram(program); GL.Uniform1(iMaxZ, queue.ZDepth+1); GL.UniformMatrix4(m4Transforms, false, in matrix); GL.Uniform1(fSdfThreshold, 0.0f); GL.Uniform1(txTexture, 0); GL.Enable(GL_BLEND); GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); foreach (DrawCall call in queue) { GL.ActiveTexture(GL_TEXTURE0); GL.BindTexture(GL_TEXTURE_2D, 0); if (call.Texture != null) { // TODO: actually use textures? GL.Uniform1(iEnableTexture, 1); GL.Uniform1(iEnableSdf, call.Texture.SignedDistanceField ? 1 : 0); GL.Uniform1(iAlphaDiscard, 1); } else { GL.Uniform1(iEnableTexture, 0); } GL.DrawElements(GL_TRIANGLES, call.Count, GL_UNSIGNED_INT, sizeof(int)*call.Start); } } public void ClearDrawQueue(DrawQueue queue) { AssertInit(); if (!data.TryGetValue(queue, out DrawData draw)) return; draw.Dispose(); data.Remove(queue); } private static int CreateShader(GLEnum type, string name) { StreamReader source = new StreamReader(typeof(GL21Driver).Assembly.GetManifestResourceStream(name)); string text = source.ReadToEnd(); source.Dispose(); int shader = GL.CreateShader(type); GL.ShaderSource(shader, text); GL.CompileShader(shader); if (CheckShader(shader, out string msg) == false) { GraphicsException ex = new GraphicsException($"Failed to compile {type} shader stage."); ex.Data.Add("Shader Stage", type); ex.Data.Add("Shader Info Log", msg); ex.Data.Add("Shader Source", text); throw ex; } return shader; } private static bool CheckShader(int shader, out string message) { message = string.Empty; GL.GetShader(shader, GL_COMPILE_STATUS, out int i); if (i != (int)GL_TRUE) { message = GL.GetShaderInfoLog(shader); return false; } return true; } private static bool CheckProgram(int program, out string message) { message = string.Empty; GL.GetProgram(program, GL_LINK_STATUS, out int i); if (i != (int)GL_OK) { message = GL.GetProgramInfoLog(program); return false; } return true; } private void Dispose(bool disposing) { if (isDiposed) return; if (!IsInit) { isDiposed = true; return; } if (!disposing) { if (OnGCDispose == null) { throw new Exception("This object must strictly be disposed from the owning thread, not GC"); } else { OnGCDispose(this); return; } } GL.DeleteProgram(program); foreach (DrawData datum in data.Values) { datum.Dispose(); } isDiposed = true; GC.SuppressFinalize(this); } public void Dispose() => Dispose(true); private class DrawData : IDisposable { public DrawQueue Queue { get; } public int VertexArray { get; } private readonly GL21Driver driver; private int vbo1, vbo2; private int ebo1, ebo2; public DrawData(GL21Driver driver, DrawQueue queue) { Queue = queue; this.driver = driver; VertexArray = GL.GenVertexArray(); GL.GenBuffers(1, out vbo1); GL.GenBuffers(1, out vbo2); GL.GenBuffers(1, out ebo1); GL.GenBuffers(1, out ebo2); isDisposed = false; } public void PrepareFrame() { int vbo, ebo; vbo = Swap(ref vbo1, ref vbo2); ebo = Swap(ref ebo1, ref ebo2); if (Queue.VertexCount == 0 || Queue.ElementCount == 0) return; GL.BindVertexArray(VertexArray); GL.BindBuffer(GL_ARRAY_BUFFER, vbo); GL.BufferData(GL_ARRAY_BUFFER, QuikVertex.Stride * Queue.VertexCount, Queue.VertexArray, GL_STREAM_DRAW); GL.VertexAttribPointer(driver.v2Position, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.PositionOffset); GL.VertexAttribIPointer(driver.iZIndex, 1, GL_UNSIGNED_INT, QuikVertex.Stride, QuikVertex.ZIndexOffset); GL.VertexAttribPointer(driver.v2TexPos, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureCoordinatesOffset); GL.VertexAttribPointer(driver.v4Color, 4, GL_UNSIGNED_BYTE, true, QuikVertex.Stride, QuikVertex.ColorOffset); GL.EnableVertexAttribArray(driver.v2Position); GL.EnableVertexAttribArray(driver.iZIndex); GL.EnableVertexAttribArray(driver.v2TexPos); GL.EnableVertexAttribArray(driver.v4Color); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, Queue.ElementCount * sizeof(int), Queue.ElementArray, GL_STREAM_DRAW); int Swap(ref int a, ref int b) { a ^= b; b ^= a; a ^= b; return a; } } private bool isDisposed; public void Dispose() { if (isDisposed) return; GL.DeleteVertexArray(VertexArray); GL.DeleteBuffer(vbo1); GL.DeleteBuffer(vbo2); GL.DeleteBuffer(ebo1); GL.DeleteBuffer(ebo2); } } } }