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