diff --git a/Diamond/Attributes/VertexAttribAttribute.cs b/Diamond/Attributes/VertexAttribAttribute.cs new file mode 100644 index 0000000..697608c --- /dev/null +++ b/Diamond/Attributes/VertexAttribAttribute.cs @@ -0,0 +1,43 @@ +using System; +using OpenTK.Graphics.OpenGL4; + +namespace Diamond.Attributes +{ + /// + /// Use this field as a source for vertex attribute data + /// + [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)] + public sealed class VertexAttribAttribute : Attribute + { + /// + /// The attribute ID to send this value + /// + public int Attribute { get; } + + /// + /// The number of elements for this attribute + /// + public int Size { get; } + + /// + /// Whether this attribute should be normalized + /// + public bool Normalized { get; set; } = false; + + /// + /// The eleemnt type for this attribute + /// + public VertexAttribPointerType Type { get; set; } = VertexAttribPointerType.Float; + + /// + /// Mark a field as a source for a vertex attribute + /// + /// The attribute ID for this value + /// The number of elements for this attribute + public VertexAttribAttribute(int attribute, int size) + { + Attribute = attribute; + Size = size; + } + } +} \ No newline at end of file diff --git a/Diamond/Attributes/VertexDataAttribute.cs b/Diamond/Attributes/VertexDataAttribute.cs new file mode 100644 index 0000000..589acd2 --- /dev/null +++ b/Diamond/Attributes/VertexDataAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Diamond.Attributes +{ + /// + /// Use this struct's contents as vertex attribute information + /// + [AttributeUsage(AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] + public sealed class VertexDataAttribute : Attribute + { + /// + /// The VertexAttribDivisor for all attribs associated with this struct + /// + public int Divisor { get; } + + /// + /// Mark a struct as a source for a VAO's attrib pointer information + /// + /// + public VertexDataAttribute(int divisor = 0) + { + Divisor = divisor; + } + } +} \ No newline at end of file diff --git a/Diamond/Diamond.csproj b/Diamond/Diamond.csproj index 0e47752..f616b86 100644 --- a/Diamond/Diamond.csproj +++ b/Diamond/Diamond.csproj @@ -52,6 +52,8 @@ + + @@ -59,6 +61,7 @@ + diff --git a/Diamond/NLog.config b/Diamond/NLog.config index 65bd082..b68bf14 100644 --- a/Diamond/NLog.config +++ b/Diamond/NLog.config @@ -15,6 +15,11 @@ fileName="${basedir}/logs/${date:format=yyyMMdd_HHmmss:cached=true}.log" layout="${fileHeader}: ${message}" /> + + diff --git a/Diamond/Shaders/Program.cs b/Diamond/Shaders/Program.cs index fc8b027..7df7a9c 100644 --- a/Diamond/Shaders/Program.cs +++ b/Diamond/Shaders/Program.cs @@ -28,8 +28,9 @@ namespace Diamond.Shaders } /// - /// Use this program. If program is null, use the default program. + /// Use a program. /// + /// The program to use, or null to use the default program public static void Use(Program program) { if (program != null && !program.Linked) @@ -158,7 +159,7 @@ namespace Diamond.Shaders /// public void Use() => Use(this); - #region Attribute Locations + #region Locations private readonly Dictionary _attributes = new Dictionary(); private readonly Dictionary _uniforms = new Dictionary(); @@ -257,7 +258,9 @@ namespace Diamond.Shaders var program = new Program(); foreach (var shader in shaders) + { program.Attach(shader); + } program.Link(); diff --git a/Diamond/VertexArray.cs b/Diamond/VertexArray.cs new file mode 100644 index 0000000..9caddd3 --- /dev/null +++ b/Diamond/VertexArray.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Diamond.Attributes; +using Diamond.Shaders; +using NLog; +using OpenTK.Graphics.OpenGL4; + +namespace Diamond +{ + /// + /// Wrap an OpenGL Vertex Array Object + /// + public sealed class VertexArray : GLObject + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + #region Static + + private static VertexArray _current; + + /// + /// The currently bound VAO, or null if no vao is bound. + /// + public static VertexArray Current + { + get => _current; + set => Bind(value); + } + + /// + /// Bind a VAO to the context + /// + /// The vao to bind, or null to unbind the current vao + public static void Bind(VertexArray vao) + { + GL.BindVertexArray(vao?.Id ?? 0); + Logger.Trace("Bound {0}", (object) vao ?? "default vao"); + + _current = vao; + } + + #endregion + + #region ctor, Delete() + + /// + internal VertexArray() + : base(GL.GenVertexArray()) + { + Logger.Debug("Created {0}", this); + } + + /// + protected override void Delete() + { + Logger.Debug("Disposing {0}", this); + GL.DeleteVertexArray(Id); + } + + #endregion + + #region Properties + + #region Queries + + #endregion + + #region Stored + + private Buffer _elementArrayBuffer; + + /// + /// The ElementArrayBuffer associated with this VAO + /// + public Buffer ElementArrayBuffer + { + get => _elementArrayBuffer; + set + { + Current = this; + Buffer.ElementArrayBuffer = _elementArrayBuffer = value; + } + } + + #endregion + + #endregion + + #region Methods + + /// + /// Attach a VBO to this VAO. Use attributes to infer the + /// vertex attrib pointer information + /// + /// The struct to use for vertex attrib information. Must be marked with VertexDataAttribute + /// The buffer to add + public void Attach(Buffer buffer) where T : struct + { + var vda = (VertexDataAttribute) typeof(T) + .GetCustomAttributes(typeof(VertexDataAttribute), false) + .FirstOrDefault(); + + if (vda == null) + { + Logger.Error("Cannot attach buffer {0} to {1}, {2} is missing VertexDataAttribute", buffer, this, + typeof(T).Name); + return; + } + + Current = this; + Buffer.ArrayBuffer = buffer; + + var stride = Marshal.SizeOf(); + foreach (var field in typeof(T).GetFields()) + { + Logger.Debug("Analyzing {0}", field.Name); + var offset = Marshal.OffsetOf(field.Name); + foreach (var vai in field.GetCustomAttributes(false)) + { + Logger.Debug( + $"Enabling {vai.Attribute}, {vai.Size}, {vai.Type}, {vai.Normalized}, {stride}, {offset}"); + + GL.EnableVertexAttribArray(vai.Attribute); + GL.VertexAttribPointer(vai.Attribute, vai.Size, vai.Type, vai.Normalized, stride, offset); + } + } + } + + /// + /// Bind this vao + /// + public void Bind() => Bind(this); + + #endregion + + #region Factory Methods + + /// + /// Create an empty vertex array object. This contains no binding information + /// by default, and must have any vertex buffer objects attached manually. + /// + /// A new Vertex Array Object, or null if creation failed + public static VertexArray Create() + { + return new VertexArray(); + } + + #endregion + } +} \ No newline at end of file diff --git a/hexworld/HexRender.cs b/hexworld/HexRender.cs index 303a29e..cfa669b 100644 --- a/hexworld/HexRender.cs +++ b/hexworld/HexRender.cs @@ -5,10 +5,23 @@ using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL4; using Diamond; +using Diamond.Attributes; using Buffer = Diamond.Buffer; namespace hexworld { + [VertexData()] + public struct Vert + { + [VertexAttrib(0, 2)] + public Vector2 Position; + + public Vert(float x, float y) + { + Position = new Vector2(x, y); + } + } + public class HexRender : GameWindow { public HexRender(int width, int height) @@ -20,12 +33,12 @@ namespace hexworld Y = (DisplayDevice.Default.Height - Height) / 2; } - private int _triVao; - private Buffer _triVbo; + private VertexArray _triVao; + private Buffer _triVbo; private Program _whitePgm; - private int _recVao; - private Buffer _recVbo; + private VertexArray _recVao; + private Buffer _recVbo; private Program _redPgm; /// @@ -39,36 +52,30 @@ namespace hexworld { _whitePgm = Program.FromShaders(vs, red); _redPgm = Program.FromShaders(vs, white); - - _triVbo = Buffer.FromData(new float[] - { - -.8f, -.8f, - +.8f, -.8f, - +.0f, +.8f - }); - _triVao = GL.GenVertexArray(); - GL.BindVertexArray(_triVao); - Program.Current = _redPgm; - Buffer.ArrayBuffer = _triVbo; - GL.EnableVertexAttribArray(0); - GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0); - - _recVbo = Buffer.FromData(new float[] - { - -.9f, -.5f, - +.9f, -.5f, - +.9f, +.5f, - +.9f, +.5f, - -.9f, +.5f, - -.9f, -.5f, - }); - _recVao = GL.GenVertexArray(); - GL.BindVertexArray(_recVao); - Program.Current = _whitePgm; - Buffer.ArrayBuffer = _recVbo; - GL.EnableVertexAttribArray(0); - GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0); } + + _triVbo = Buffer.FromData(new[] + { + new Vert(-.8f, -.8f), + new Vert(+.8f, -.8f), + new Vert(+.0f, +.8f) + }); + Program.Current = _redPgm; + _triVao = VertexArray.Create(); + _triVao.Attach(_triVbo); + + _recVbo = Buffer.FromData(new[] + { + new Vert(-.9f, -.5f), + new Vert(+.9f, -.5f), + new Vert(+.9f, +.5f), + new Vert(+.9f, +.5f), + new Vert(-.9f, +.5f), + new Vert(-.9f, -.5f) + }); + Program.Current = _whitePgm; + _recVao = VertexArray.Create(); + _recVao.Attach(_recVbo); } /// @@ -93,11 +100,11 @@ namespace hexworld GL.Clear(ClearBufferMask.ColorBufferBit); Program.Current = _redPgm; - GL.BindVertexArray(_triVao); + VertexArray.Current = _triVao; GL.DrawArrays(PrimitiveType.Triangles, 0, 3); Program.Current = _whitePgm; - GL.BindVertexArray(_recVao); + VertexArray.Current = _recVao; GL.DrawArrays(PrimitiveType.Triangles, 0, 6); SwapBuffers();