From d72a07354a3e22fa731176273a17458808a05c3b Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Fri, 30 Jun 2023 19:35:24 +0300 Subject: [PATCH] We are going real old school GL. --- Quik/OpenGL/GL21Driver.cs | 283 +++++++++++++++++++++++++++++++ Quik/OpenGL/GraphicsException.cs | 46 +++++ Quik/res/gl21.frag | 40 +++++ Quik/res/gl21.vert | 25 +++ 4 files changed, 394 insertions(+) create mode 100644 Quik/OpenGL/GL21Driver.cs create mode 100644 Quik/OpenGL/GraphicsException.cs create mode 100644 Quik/res/gl21.frag create mode 100644 Quik/res/gl21.vert diff --git a/Quik/OpenGL/GL21Driver.cs b/Quik/OpenGL/GL21Driver.cs new file mode 100644 index 0000000..dcc70be --- /dev/null +++ b/Quik/OpenGL/GL21Driver.cs @@ -0,0 +1,283 @@ +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); + } + + // This already binds the vertex array for me. + draw.PrepareFrame(); + + QVec2 size = view.Size; + // TODO: can i has matrices? + float[] matrix = new float[] { + 1/size.X, 0, 0, 0, + 0, 1/size.Y, 0, 0, + 0, 0, 1, 0, + -0.5f, -0.5f, 0, 1 + }; + + GL.UseProgram(program); + GL.Uniform1(iMaxZ, queue.ZDepth); + GL.UniformMatrix4(m4Transforms, false, ref matrix[0]); + 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.DrawArrays(GL_TRIANGLES, call.Count, 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); + + 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_OK) + { + 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); + + isDiposed = true; + } + + public void Dispose() => Dispose(true); + + private struct 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) + { + int a = 0, b = 0, c = 0, d = 0; + Queue = queue; + this.driver = driver; + VertexArray = GL.GenVertexArray(); + GL.GenBuffers(4, out a); + vbo1 = a; + vbo2 = b; + ebo1 = c; + ebo2 = d; + isDisposed = false; + } + + public void PrepareFrame() + { + int vbo, ebo; + vbo = Swap(ref vbo1, ref vbo2); + ebo = Swap(ref ebo1, ref ebo2); + + 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_FLOAT, false, QuikVertex.Stride, QuikVertex.ColorOffset); + + 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); + } + } + } +} \ No newline at end of file diff --git a/Quik/OpenGL/GraphicsException.cs b/Quik/OpenGL/GraphicsException.cs new file mode 100644 index 0000000..bab982c --- /dev/null +++ b/Quik/OpenGL/GraphicsException.cs @@ -0,0 +1,46 @@ +using System; +using static Quik.OpenGL.GLEnum; + +namespace Quik.OpenGL +{ + [System.Serializable] + public class GraphicsException : System.Exception + { + public GraphicsException() + { + AddExtraData(); + } + + public GraphicsException(string message) : base(message) + { + AddExtraData(); + } + + public GraphicsException(string message, System.Exception inner) : base(message, inner) + { + AddExtraData(); + } + + protected GraphicsException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) + { + AddExtraData(); + } + + private void AddExtraData() + { + GL.Get(GL_MAJOR_VERSION, out int major); + GL.Get(GL_MINOR_VERSION, out int minor); + + string version = GL.GetString(GL_VERSION); + string vendor = GL.GetString(GL_VENDOR); + string renderer = GL.GetString(GL_RENDERER); + + Data.Add("OpenGL Version", new Version(major, minor)); + Data.Add("OpenGL Version String", version); + Data.Add("OpenGL Vendor", vendor); + Data.Add("OpenGL Renderer", renderer); + } + } +} \ No newline at end of file diff --git a/Quik/res/gl21.frag b/Quik/res/gl21.frag new file mode 100644 index 0000000..5b1b251 --- /dev/null +++ b/Quik/res/gl21.frag @@ -0,0 +1,40 @@ +/** + * QUIK: User Interface Kit + * Copyright (C) 2023 Halit Utku Maden, et al. + */ +#version 110 + +varying vec2 fv2TexPos; +varying vec4 fv4Color; + +uniform int iEnableSdf; +uniform int iEnableTexture; +uniform int iAlphaDiscard; +uniform float fSdfThreshold; +uniform sampler2D txTexture; + +void main(void) +{ + vec4 albedo = fv4Color; + + if (iEnableTexture != 0) + { + vec4 value = texture2D(txTexture, fv2TexPos); + + if (iEnableSdf != 0) + { + float a = max(value.r, value.a); + + value = (a >= fSdfThreshold) ? vec4(1,1,1,1) : vec4(0,0,0,0); + } + + if (iAlphaDiscard != 0 && value.a == 0.0) + { + discard; + } + + albedo = albedo * value; + } + + gl_FragColor = albedo; +} \ No newline at end of file diff --git a/Quik/res/gl21.vert b/Quik/res/gl21.vert new file mode 100644 index 0000000..a402bbd --- /dev/null +++ b/Quik/res/gl21.vert @@ -0,0 +1,25 @@ +/** + * QUIK: User Interface Kit + * Copyright (C) 2023 Halit Utku Maden, et al. + */ +#version 110 + +attribute vec2 v2Position; /**< The vertex position.*/ +attribute int iZIndex; /**< The z index. */ +attribute vec2 v2TexPos; /**< The texture coorindates. */ +attribute vec4 v4Color; /**< The vertex color. */ + +varying vec2 fv2TexPos; +varying vec4 fv4Color; + +uniform mat4 m4Transforms; /**< The view matrix. */ +uniform int iMaxZ; /**< Highest Z coordinate. */ + +void main(void) +{ + vec4 v = vec4(v2Position, float(iZIndex)/float(iMaxZ), 1); + gl_Position = v * m4Transforms; + + fv2TexPos = v2TexPos; + fv4Color = fv4Color; +} \ No newline at end of file