Dashboard/Quik/OpenGL/GL21Driver.cs

287 lines
9.0 KiB
C#

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<DrawQueue, DrawData> data = new Dictionary<DrawQueue, DrawData>();
public bool IsInit { get; private set; } = false;
public event Action<GL21Driver> 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);
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.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);
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)
{
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);
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_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);
}
}
}
}