diff --git a/Diamond/Buffer.cs b/Diamond/Buffer.cs new file mode 100644 index 0000000..7d484c5 --- /dev/null +++ b/Diamond/Buffer.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using NLog; +using OpenTK.Graphics.OpenGL4; + +namespace Diamond +{ + /// + /// Wrap an OpenGL buffer object + /// + public class Buffer : GLObject + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + #region Static + + private static readonly Dictionary BoundBuffers; + + /// + static Buffer() + { + BoundBuffers = new Dictionary(); + foreach (var value in Enum.GetValues(typeof(BufferTarget)).Cast()) + BoundBuffers[value] = null; + } + + #region ArrayBuffer + + /// + /// The buffer will be used as a source for vertex data, but the connection is only made when + /// glVertexAttribPointer is called. The pointer field of this function is taken as a byte + /// offset from the beginning of whatever buffer is currently bound to this target. + /// + public static Buffer ArrayBuffer + { + get => BoundBuffers[BufferTarget.ArrayBuffer]; + set => Bind(BufferTarget.ArrayBuffer, value); + } + + #endregion + + #region ElementArrayBuffer + + /// + /// All rendering functions of the form gl*Draw*Elements* will use the pointer field as a byte + /// offset from the beginning of the buffer object bound to this target. The indices used for + /// indexed rendering will be taken from the buffer object. Note that this binding target is + /// part of a Vertex Array Objects state, so a VAO must be bound before binding a buffer here. + /// + public static Buffer ElementArrayBuffer + { + get => BoundBuffers[BufferTarget.ArrayBuffer]; + set => Bind(BufferTarget.ElementArrayBuffer, value); + } + + #endregion + + #region DrawIndirectBuffer + + /// + /// The buffer bound to this target will be used as the source for the indirect data when performing + /// indirect rendering. This is only available in core OpenGL 4.0 or with ARB_draw_indirect. + /// + public static Buffer DrawIndirectBuffer + { + get => BoundBuffers[BufferTarget.DrawIndirectBuffer]; + set => Bind(BufferTarget.DrawIndirectBuffer, value); + } + + #endregion + + /// + /// Bind a buffer to a target. If buffer is null, unbinds the target. + /// + /// The binding target + /// The buffer to bind, or 0 if null + public static void Bind(BufferTarget target, Buffer buffer) + { + GL.BindBuffer(target, buffer?.Id ?? 0); + Logger.Debug("Bound {0} to {1}", (object) buffer ?? "default buffer", target); + + BoundBuffers[target] = buffer; + } + + #endregion + + #region ctor, Delete() + + /// + /// Create a buffer object wrapper + /// + internal Buffer() + : base(GL.GenBuffer()) + { + Logger.Debug("Created {0}", this); + } + + /// + protected override void Delete() + { + Logger.Debug("Disposing {0}", this); + GL.DeleteBuffer(Id); + } + + #endregion + + #region Properties + + #region Queries + + /// + /// The usage hint for the current data store + /// + public BufferUsageHint Usage => (BufferUsageHint) Get(BufferParameterName.BufferUsage); + + /// + /// The size of the current data store in bytes + /// + public int Size => Get(BufferParameterName.BufferSize); + + #endregion + + #region Stored + + #endregion + + #endregion + + #region Methods + + /// + /// Get a property of this buffer + /// + /// The property to get + /// The int value of the property + public int Get(BufferParameterName param) + { + ArrayBuffer = this; + GL.GetBufferParameter(BufferTarget.ArrayBuffer, param, out int res); + return res; + } + + /// + /// Bind this buffer to a target + /// + /// The binding target + public void Bind(BufferTarget target) => Bind(target, this); + + /// + public override string ToString() => + $"'Buffer {Id}'"; + + /// + /// Upload data to this buffer. Deletes the existing data store and creates a new one + /// from the marshalled size of T. + /// + /// Data element type, used with Marshal.SizeOf to calculate size of data store + /// Values to upload + /// Usage hint for for this data. + // todo: add usage hint documentation + public void Upload(T[] data, BufferUsageHint usage = BufferUsageHint.StaticDraw) where T : struct + { + Logger.Debug("Updating {0} data (replacing store)", this); + ArrayBuffer = this; + GL.BufferData(BufferTarget.ArrayBuffer, data.SizeInBytes(), data, usage); + } + + /// + /// Upload the data to this buffer within a range. Does not delete the existing data store. + /// + /// The data to upload. The range is also applied to this array. + /// The offset index of data to begin uploading + /// The number of T elements to upload + /// Data element type. Offsets are applied according to the size of this in bytes. + public void Upload(T[] data, int offset, int count) where T : struct + { + Logger.Debug("Updating {0} data range ({1} for {2}] from type {3}", + this, offset, count, typeof(T).Name); + ArrayBuffer = this; + GL.BufferSubData(BufferTarget.ArrayBuffer, (IntPtr) offset, count, data); + } + + #endregion + + #region Factory Methods + + /// + /// Creates a buffer, initialized with an array + /// + /// Data element type. + /// Data used to initialize the buffer + /// The usage hint for this buffer + /// The initialized buffer, or null if initialization failed + // todo: add usage hint documentation + public static Buffer FromData(T[] data, BufferUsageHint usage = BufferUsageHint.StaticDraw) + where T : struct + { + var buffer = new Buffer(); + + buffer.Upload(data, usage); + + return buffer; + } + + #endregion + } + + /// + /// Generic overload for Buffer. Caches properties like data, usage, and size. + /// + /// Data type used for all operations + // todo this needs to be made as complete as the non-generic version + public sealed class Buffer : Buffer where T : struct + { + /// + /// Cached usage hint for the buffer's storage + /// + public new BufferUsageHint Usage { get; private set; } + + /// + /// Size of the buffer data storage in T units. + /// + public new int Size { get; private set; } + + /// + /// The data in this buffer. Not copied to the buffer until Upload() is called. + /// + public T[] Data { get; private set; } + + /// + /// Upload data to this buffer. Deletes the existing data store and creates a new one. + /// + /// Values to upload + /// Usage hint for for this data. + // todo: add usage hint documentation + public void Upload(T[] data, BufferUsageHint usage = BufferUsageHint.StaticDraw) + { + base.Upload(data, usage); + Data = data; + Usage = usage; + Size = data.Length; + } + + /// + /// Upload the data to this buffer within a range. Does not delete the existing data store. + /// + /// The offset index of data to begin uploading + /// The number of T elements to upload + public void Upload(int offset, int count) + { + base.Upload(Data, offset, count); + } + } +} \ No newline at end of file diff --git a/Diamond/Diamond.csproj b/Diamond/Diamond.csproj index 0d25f7c..0e47752 100644 --- a/Diamond/Diamond.csproj +++ b/Diamond/Diamond.csproj @@ -21,6 +21,7 @@ DEBUG;TRACE prompt 4 + bin\Debug\Diamond.xml pdbonly @@ -51,6 +52,8 @@ + + diff --git a/Diamond/Extensions.cs b/Diamond/Extensions.cs new file mode 100644 index 0000000..85346fc --- /dev/null +++ b/Diamond/Extensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Diamond +{ + internal static class Extensions + { + private static readonly Dictionary ByteSizes = new Dictionary(); + + public static int SizeInBytes(this Type type) + { + if (!ByteSizes.ContainsKey(type)) + ByteSizes[type] = Marshal.SizeOf(type); + return ByteSizes[type]; + } + + public static int SizeInBytes(this T[] arr) where T : struct + { + return typeof(T).SizeInBytes() * arr.Length; + } + } +} \ No newline at end of file diff --git a/Diamond/GLObject.cs b/Diamond/GLObject.cs index b7b434f..63373bb 100644 --- a/Diamond/GLObject.cs +++ b/Diamond/GLObject.cs @@ -4,6 +4,9 @@ using OpenTK.Graphics; namespace Diamond { + /// + /// A wrapper class for any OpenGL object + /// public abstract class GLObject : IDisposable { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/Diamond/Shaders/Program.cs b/Diamond/Shaders/Program.cs index a1be8e7..a6c3be6 100644 --- a/Diamond/Shaders/Program.cs +++ b/Diamond/Shaders/Program.cs @@ -10,7 +10,7 @@ namespace Diamond.Shaders /// /// Wrap and OpenGL program object /// - public class Program : GLObject + public sealed class Program : GLObject { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -19,31 +19,36 @@ namespace Diamond.Shaders private static Program _current; /// - /// The currently active program + /// The currently active program, or null if the default program is active. /// public static Program Current { get => _current; - set - { - if (value != null && !value.Linked) - { - Logger.Error("Cannot use {0} because it is not linked", value); - value = null; - } + set => Use(value); + } - Logger.Debug("Using {0}", (object) value ?? "default program"); - GL.UseProgram((_current = value)?.Id ?? 0); + /// + /// Use this program. If program is null, use the default program. + /// + public static void Use(Program program) + { + if (program != null && !program.Linked) + { + Logger.Error("Cannot use {0} because it is not linked", program); + program = null; } + + GL.UseProgram(program?.Id ?? 0); + Logger.Debug("Using {0}", (object) program ?? "default program"); + + _current = program; } #endregion - #region Constructor, Delete() + #region ctor, Delete() - /// - /// Create a program object wrapper - /// + /// private Program() : base(GL.CreateProgram()) { @@ -64,12 +69,12 @@ namespace Diamond.Shaders #region Queries /// - /// The number of active uniforms + /// Gets the number of active uniforms /// public int ActiveUniforms => Get(GetProgramParameterName.ActiveUniforms); /// - /// The number of active attributes + /// Gets the number of active attributes /// public int ActiveAttributes => Get(GetProgramParameterName.ActiveAttributes); @@ -78,7 +83,7 @@ namespace Diamond.Shaders #region Stored /// - /// The InfoLog for this program + /// The InfoLog for this program since it was last linked /// public string InfoLog { get; private set; } @@ -94,7 +99,7 @@ namespace Diamond.Shaders #region Methods /// - /// Get a property of this program + /// Get a property of this program. invokes glGetProgram and returns an int result. /// /// The program property to get /// The int value of the program property @@ -105,7 +110,8 @@ namespace Diamond.Shaders } /// - /// Try to link this program + /// Try to link this program. If linking fails, the InfoLog is updated, and attribute and uniform caches are reset. + /// If linking is successful, attribute and uniform caches are generated /// public void Link() { @@ -148,11 +154,9 @@ namespace Diamond.Shaders } /// - /// Use this program - /// - /// Equivalent to Program.Current = value + /// Use this program. Equivalent to Program.Use(this); /// - public void Use() => Current = this; + public void Use() => Use(this); #region Attribute Locations @@ -163,18 +167,19 @@ namespace Diamond.Shaders /// Check if this program has an active attribute /// /// The attribute name - /// Whether the progrma has an active attribute + /// Whether the program has an active attribute public bool HasAttribute(string name) => _attributes.ContainsKey(name); /// /// Check if this program has an active uniform /// /// The uniform name - /// Whether the progrma has an active uniform + /// Whether the program has an active uniform public bool HasUniform(string name) => _uniforms.ContainsKey(name); /// - /// Get the location of an attribute by name + /// Get the location of an attribute by name. Throws InvalidOperationException if the attribute + /// is not found. This can be checked with HasAttribute /// /// The attribute name /// The location of the attribute @@ -186,7 +191,8 @@ namespace Diamond.Shaders } /// - /// Get the location of an uniform by name + /// Get the location of an uniform by name. Throws InvalidOperationException if the uniform + /// is not found. This can be checked with HasUniform /// /// The uniform name /// The location of the uniform @@ -199,11 +205,21 @@ namespace Diamond.Shaders #endregion /// - /// Attach a shader to this program + /// Attach a shader to this program. If shader is null, does nothing. If shader is not compiled + /// it is still attached but program link will likely fail. /// /// The shader to attach public void Attach(Shader shader) { + if (shader == null) + { + Logger.Error("Cannot attach null shader to {0}", this); + return; + } + + if (!shader.Compiled) + Logger.Warn("{0} is not compiled. Program link will likely fail.", shader); + Logger.Debug("Attaching {0} to {1}", shader, this); GL.AttachShader(Id, shader.Id); } @@ -217,14 +233,16 @@ namespace Diamond.Shaders #region Factory Methods /// - /// Create and link a program from precompiled shaders + /// Create and link a program from precompiled shaders. If any shader is null or uncompiled it is still + /// attached, although program link will likely fail. /// /// The shaders used in this program /// A linked program, or null if initialization failed public static Program FromShaders(params Shader[] shaders) => FromShaders((IEnumerable) shaders); /// - /// Create and link a program from precompiled shaders + /// Create and link a program from precompiled shaders. If any shader is null or uncompiled it is still + /// attached, although program link will likely fail. /// /// The shaders used in this program /// A linked program, or null if initialization failed @@ -239,15 +257,7 @@ namespace Diamond.Shaders var program = new Program(); foreach (var shader in shaders) - { - if (shader == null) - { - Logger.Error("One or more shaders is null - cannot create program"); - program.Dispose(); - return null; - } program.Attach(shader); - } program.Link(); diff --git a/Diamond/Shaders/Shader.cs b/Diamond/Shaders/Shader.cs index b5eb4e4..21735a1 100644 --- a/Diamond/Shaders/Shader.cs +++ b/Diamond/Shaders/Shader.cs @@ -12,7 +12,7 @@ namespace Diamond.Shaders { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - #region Constructor, Delete() + #region ctor, Delete() /// /// Create a shader object wrapper diff --git a/Diamond/Util/SubArray.cs b/Diamond/Util/SubArray.cs index 6f3cb79..39eef25 100644 --- a/Diamond/Util/SubArray.cs +++ b/Diamond/Util/SubArray.cs @@ -89,6 +89,7 @@ namespace Diamond.Util return arr; } + /// public override string ToString() { if (Length == 0) diff --git a/hexworld/HexRender.cs b/hexworld/HexRender.cs index 0de084c..fdd4354 100644 --- a/hexworld/HexRender.cs +++ b/hexworld/HexRender.cs @@ -1,14 +1,11 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using Diamond.Shaders; -using Diamond.Util; -using Newtonsoft.Json.Linq; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL4; +using Diamond; +using Buffer = Diamond.Buffer; namespace hexworld { @@ -24,6 +21,7 @@ namespace hexworld } private Program _pgm; + private Buffer _buf; /// protected override void OnUnload(EventArgs e) @@ -41,7 +39,25 @@ namespace hexworld _pgm = Program.FromFiles("res/obj.fs.glsl", "res/obj.vs.glsl"); Program.Current = _pgm; - Program.Current = null; + + _buf = Buffer.FromData(new float[] + { + -.8f, -.8f, + +.8f, -.8f, + +.0f, +.8f + }); + } + + /// + protected override void OnRenderFrame(FrameEventArgs e) + { + base.OnRenderFrame(e); + + GL.Viewport(ClientRectangle); + + GL.Clear(ClearBufferMask.ColorBufferBit); + + SwapBuffers(); } } } \ No newline at end of file diff --git a/hexworld/hexworld.csproj b/hexworld/hexworld.csproj index 1ed6a79..805bca4 100644 --- a/hexworld/hexworld.csproj +++ b/hexworld/hexworld.csproj @@ -19,10 +19,12 @@ full false bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG prompt 4 false + + AnyCPU