Compare commits

24 Commits

Author SHA1 Message Date
509d88f990 Update README.md 2017-08-01 22:06:35 -04:00
7a3ac51609 Removed documentation command 2017-03-22 11:26:19 -04:00
0164992bb0 Removed docs 2017-03-14 11:31:38 -04:00
273e519e70 removed header link 2017-03-10 01:16:17 -05:00
50fc40ecd3 updated headers 2017-03-10 00:57:31 -05:00
37da237a5c changed index.html to index.md 2017-03-10 00:53:52 -05:00
b0d6c467ff Set theme jekyll-theme-slate 2017-03-10 00:51:42 -05:00
fd7ea9fc2c Merge branch 'master' of https://github.com/allemangD/Diamond 2017-03-10 00:28:59 -05:00
599fa8a8d8 Update index.html 2017-03-10 00:28:13 -05:00
95bf5afe36 Tweaked automatic documentation generation settings 2017-03-10 00:22:46 -05:00
dbe9b15944 readded docs 2017-03-10 00:17:53 -05:00
fb2aabb847 added new index.html 2017-03-10 00:15:09 -05:00
1521c06821 removed docs 2017-03-10 00:12:04 -05:00
be8f3e64ff Set theme jekyll-theme-cayman 2017-03-10 00:08:54 -05:00
bc7eb114f4 Create index.html 2017-03-10 00:07:36 -05:00
62ecbec498 Added pages site 2017-03-10 00:00:18 -05:00
083e08bb76 Set theme jekyll-theme-cayman 2017-03-09 23:59:50 -05:00
eb5aadee39 remove generated docs 2017-03-09 23:58:34 -05:00
002accf026 Merge branch 'master' of https://github.com/allemangD/Diamond 2017-03-09 23:58:06 -05:00
d1e02711be Removed generated files 2017-03-09 23:57:20 -05:00
d9c8402d15 Set theme jekyll-theme-slate 2017-03-09 23:56:52 -05:00
190f7d38e3 Set theme jekyll-theme-slate 2017-03-09 23:56:33 -05:00
9e0b9fa788 Set theme jekyll-theme-slate 2017-03-09 23:55:37 -05:00
4134c5b49b auto-generate documentation page with doxygen 2017-03-09 23:50:56 -05:00
35 changed files with 5234 additions and 2197 deletions

View File

@@ -1,43 +0,0 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Attributes
{
/// <summary>
/// Use this field as a source for vertex attribute data
/// </summary>
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = true)]
public sealed class VertexAttribAttribute : Attribute
{
/// <summary>
/// The attribute ID to send this value
/// </summary>
public int Attribute { get; }
/// <summary>
/// The number of elements for this attribute
/// </summary>
public int Size { get; }
/// <summary>
/// Whether this attribute should be normalized
/// </summary>
public bool Normalized { get; set; } = false;
/// <summary>
/// The eleemnt type for this attribute
/// </summary>
public VertexAttribPointerType Type { get; set; } = VertexAttribPointerType.Float;
/// <summary>
/// Mark a field as a source for a vertex attribute
/// </summary>
/// <param name="attribute">The attribute ID for this value</param>
/// <param name="size">The number of elements for this attribute</param>
public VertexAttribAttribute(int attribute, int size)
{
Attribute = attribute;
Size = size;
}
}
}

View File

@@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Diamond.Attributes
{
/// <summary>
/// Use this struct's contents as vertex attribute information
/// </summary>
[AttributeUsage(AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
public sealed class VertexDataAttribute : Attribute
{
/// <summary>
/// The VertexAttribDivisor for all attribs associated with this struct
/// </summary>
public int Divisor { get; }
/// <summary>
/// Mark a struct as a source for a VAO's attrib pointer information
/// </summary>
/// <param name="divisor"></param>
public VertexDataAttribute(int divisor = 0)
{
Divisor = divisor;
}
}
}

View File

@@ -1,286 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using NLog;
using OpenTK.Graphics.OpenGL4;
namespace Diamond
{
/// <summary>
/// Wrap an OpenGL buffer object
/// </summary>
public class Buffer : GLObject
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
#region Static
private static readonly Dictionary<BufferTarget, Buffer> BoundBuffers;
/// <inheritdoc/>
static Buffer()
{
BoundBuffers = new Dictionary<BufferTarget, Buffer>();
foreach (var value in Enum.GetValues(typeof(BufferTarget)).Cast<BufferTarget>())
BoundBuffers[value] = null;
}
#region ArrayBuffer
/// <summary>
/// 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.
/// </summary>
public static Buffer ArrayBuffer
{
get => BoundBuffers[BufferTarget.ArrayBuffer];
set => Bind(BufferTarget.ArrayBuffer, value);
}
#endregion
#region ElementArrayBuffer
/// <summary>
/// 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.
/// </summary>
public static Buffer ElementArrayBuffer
{
get => BoundBuffers[BufferTarget.ArrayBuffer];
set => Bind(BufferTarget.ElementArrayBuffer, value);
}
#endregion
#region DrawIndirectBuffer
/// <summary>
/// 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.
/// </summary>
public static Buffer DrawIndirectBuffer
{
get => BoundBuffers[BufferTarget.DrawIndirectBuffer];
set => Bind(BufferTarget.DrawIndirectBuffer, value);
}
#endregion
/// <summary>
/// Bind a buffer to a target. If buffer is null, unbinds the target.
/// </summary>
/// <param name="target">The binding target</param>
/// <param name="buffer">The buffer to bind, or 0 if null</param>
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()
/// <summary>
/// Create a buffer object wrapper
/// </summary>
internal Buffer()
: base(GL.GenBuffer())
{
Logger.Debug("Created {0}", this);
}
/// <inheritdoc />
protected override void Delete()
{
Logger.Debug("Disposing {0}", this);
GL.DeleteBuffer(Id);
}
#endregion
#region Properties
#region Queries
/// <summary>
/// The usage hint for the current data store
/// </summary>
public BufferUsageHint Usage => (BufferUsageHint) Get(BufferParameterName.BufferUsage);
/// <summary>
/// The size of the current data store in bytes
/// </summary>
public int Size => Get(BufferParameterName.BufferSize);
#endregion
#region Stored
#endregion
#endregion
#region Methods
/// <summary>
/// Get a property of this buffer
/// </summary>
/// <param name="param">The property to get</param>
/// <returns>The int value of the property</returns>
public int Get(BufferParameterName param)
{
ArrayBuffer = this;
GL.GetBufferParameter(BufferTarget.ArrayBuffer, param, out int res);
return res;
}
/// <summary>
/// Bind this buffer to a target
/// </summary>
/// <param name="target">The binding target</param>
public void Bind(BufferTarget target) => Bind(target, this);
/// <inheritdoc />
public override string ToString() =>
$"'Buffer {Id}'";
/// <summary>
/// Upload data to this buffer. Deletes the existing data store and creates a new one
/// from the marshalled size of T.
/// </summary>
/// <remarks>
/// BufferUsageHint should be based off of what the user will be doing with the buffer.
///
/// DRAW: The user will be writing data to the buffer, but the user will not read it.
/// READ: The user will not be writing data, but the user will be reading it back.
/// COPY: The user will be neither writing nor reading the data.
///
/// STATIC: The user will set the data once.
/// DYNAMIC: The user will set the data occasionally.
/// STREAM: The user will be changing the data after every use.Or almost every use.
/// </remarks>
/// <typeparam name="T">Data element type, used with Marshal.SizeOf to calculate size of data store</typeparam>
/// <param name="data">Values to upload</param>
/// <param name="usage">Usage hint for for this data.</param>
public void Upload<T>(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);
}
/// <summary>
/// Upload the data to this buffer within a range. Does not delete the existing data store.
/// </summary>
/// <param name="data">The data to upload. The range is also applied to this array.</param>
/// <param name="offset">The offset index of data to begin uploading</param>
/// <param name="count">The number of T elements to upload</param>
/// <typeparam name="T">Data element type. Offsets are applied according to the size of this in bytes.</typeparam>
public void Upload<T>(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
/// <summary>
/// Creates a buffer, initialized with an array
/// </summary>
/// <remarks>
/// BufferUsageHint should be based off of what the user will be doing with the buffer.
///
/// DRAW: The user will be writing data to the buffer, but the user will not read it.
/// READ: The user will not be writing data, but the user will be reading it back.
/// COPY: The user will be neither writing nor reading the data.
///
/// STATIC: The user will set the data once.
/// DYNAMIC: The user will set the data occasionally.
/// STREAM: The user will be changing the data after every use.Or almost every use.
/// </remarks>
/// <typeparam name="T">Data element type.</typeparam>
/// <param name="data">Data used to initialize the buffer</param>
/// <param name="usage">The usage hint for this buffer</param>
/// <returns>The initialized buffer, or null if initialization failed</returns>
public static Buffer<T> FromData<T>(T[] data, BufferUsageHint usage = BufferUsageHint.StaticDraw)
where T : struct
{
var buffer = new Buffer<T>();
buffer.Upload(data, usage);
return buffer;
}
#endregion
}
/// <summary>
/// Generic overload for Buffer. Caches properties like data, usage, and size.
/// </summary>
/// <typeparam name="T">Data type used for all operations</typeparam>
public sealed class Buffer<T> : Buffer where T : struct
{
/// <summary>
/// Cached usage hint for the buffer's storage
/// </summary>
public new BufferUsageHint Usage { get; private set; }
/// <summary>
/// Size of the buffer data storage in T units.
/// </summary>
public new int Size { get; private set; }
/// <summary>
/// The data in this buffer. Not copied to the buffer until Upload() is called.
/// </summary>
public T[] Data { get; private set; }
/// <summary>
/// Upload data to this buffer. Deletes the existing data store and creates a new one.
/// </summary>
/// <remarks>
/// BufferUsageHint should be based off of what the user will be doing with the buffer.
///
/// DRAW: The user will be writing data to the buffer, but the user will not read it.
/// READ: The user will not be writing data, but the user will be reading it back.
/// COPY: The user will be neither writing nor reading the data.
///
/// STATIC: The user will set the data once.
/// DYNAMIC: The user will set the data occasionally.
/// STREAM: The user will be changing the data after every use.Or almost every use.
/// </remarks>
/// <param name="data">Values to upload</param>
/// <param name="usage">Usage hint for for this data.</param>
public void Upload(T[] data, BufferUsageHint usage = BufferUsageHint.StaticDraw)
{
base.Upload<T>(data, usage);
Data = data;
Usage = usage;
Size = data.Length;
}
/// <summary>
/// Upload the data to this buffer within a range. Does not delete the existing data store.
/// </summary>
/// <param name="offset">The offset index of data to begin uploading</param>
/// <param name="count">The number of T elements to upload</param>
public void Upload(int offset, int count)
{
base.Upload<T>(Data, offset, count);
}
}
}

161
Diamond/Buffers/Buffer.cs Normal file
View File

@@ -0,0 +1,161 @@
using System;
using System.Runtime.InteropServices;
using Diamond.Shaders;
using Diamond.Util;
using Diamond.Wrappers;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Buffers
{
/// <summary>
/// Manages an OpenGL Buffer object
/// </summary>
/// <typeparam name="T">The type of data used for this buffer</typeparam>
public class Buffer<T> : GLObject where T : struct
{
internal readonly BufferWrap Wrapper;
private readonly VertexDataInfo _vdi;
/// <summary>
/// The target for this buffer; its type
/// </summary>
public BufferTarget Target => Wrapper.Target;
/// <summary>
/// The usage hint for this buffer. Use StaticDraw for one-time uploads to
/// vertex buffers, and DynamicDraw for repeated uploads to vertex buffers.
/// </summary>
public BufferUsageHint Usage
{
get => Wrapper.Usage;
set => Wrapper.Usage = value;
}
internal Buffer(BufferWrap wrapper, string name)
{
Wrapper = wrapper;
Name = name;
_vdi = VertexDataInfo.GetInfo<T>();
}
/// <summary>
/// Upload data to this buffer
/// </summary>
/// <param name="data">The data to upload</param>
public void Data(T[] data) => Wrapper.Data(_vdi.Stride, data);
/// <summary>
/// Upload a range of data to this buffer
/// </summary>
/// <param name="offset">The range offset</param>
/// <param name="count">The range length</param>
/// <param name="data">The data to upload, offset and length apply to both this and the target</param>
public void Data(int offset, int count, T[] data) => Wrapper.SubData(_vdi.Stride, offset, count, data);
/// <summary>
/// Upload a range of data to this buffer
/// </summary>
/// <param name="data">The data to upload</param>
public void Data(SubArray<T> data) => Data(data.Offset, data.Length, data.Array);
/// <summary>
/// Point this buffer to a program's vertex attributes. T must have [VertexDataAttribute], and all fields
/// of T must have [VertexPointerAttribute] to infer vertex pointer locations.
/// </summary>
/// <param name="program">The program to point this buffer to</param>
public void PointTo(Program program)
{
if (_vdi == null)
{
var exception = new InvalidOperationException($"Cannot use type {typeof(T)} to create a Vertex Buffer");
Logger.Error(exception);
throw exception;
}
Wrapper.Bind();
foreach (var attr in _vdi.Pointers)
{
if (program.HasAttribute(attr.Name))
GL.VertexAttribPointer(program.AttributeLocation(attr.Name), attr.Size, attr.Type, attr.Normalized,
_vdi.Stride, attr.Offset);
}
}
public override string ToString() => Name == null
? $"Buffer<{typeof(T).Name}> {Wrapper}"
: $"Buffer<{typeof(T).Name}> {Wrapper} \'{Name}\'";
public override void Dispose()
{
Logger.Debug("Disposing {0}", this);
Wrapper.Dispose();
}
#region Factory Methods
/// <summary>
/// Create an empty buffer of this type
/// </summary>
/// <param name="target">The buffer target</param>
/// <param name="usage">The initial usage hint</param>
/// <param name="name">The name of this GLObject</param>
/// <returns>The buffer, or null if initialization failed</returns>
internal static Buffer<T> Empty(BufferTarget target, BufferUsageHint usage, string name)
{
var wrapper = new BufferWrap(target, usage);
var service = new Buffer<T>(wrapper, name);
Logger.Debug("Created {0}", service);
return service;
}
/// <summary>
/// Create a buffer of this type and upload data
/// </summary>
/// <param name="data">The data to upload</param>
/// <param name="target">The buffer target</param>
/// <param name="usage">The initial usage hint</param>
/// <param name="name">The name of this GLObject</param>
/// <returns>The buffer, or null if initialization failed</returns>
internal static Buffer<T> FromData(T[] data, BufferTarget target, BufferUsageHint usage, string name = null)
{
var service = Empty(target, usage, name);
service?.Data(data);
return service;
}
#endregion
}
/// <summary>
/// Class for static Buffer operations and public factory methods
/// </summary>
public static class Buffer
{
/// <summary>
/// Create an empty buffer of this type
/// </summary>
/// <param name="target">The buffer target</param>
/// <param name="usage">The initial usage hint</param>
/// <param name="name">The name of this GLObject</param>
/// <returns>The buffer, or null if initialization failed</returns>
public static Buffer<T> Empty<T>(BufferTarget target, BufferUsageHint usage = BufferUsageHint.StaticDraw,
string name = null) where T : struct => Buffer<T>.Empty(target, usage, name);
/// <summary>
/// Create a buffer of this type and upload data
/// </summary>
/// <param name="data">The data to upload</param>
/// <param name="target">The buffer target</param>
/// <param name="usage">The initial usage hint</param>
/// <param name="name">The name of this GLObject</param>
/// <returns>The buffer, or null if initialization failed</returns>
public static Buffer<T> FromData<T>(T[] data, BufferTarget target,
BufferUsageHint usage = BufferUsageHint.StaticDraw,
string name = null) where T : struct => Buffer<T>.FromData(data, target, usage, name);
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Diamond.Buffers
{
/// <summary>
/// Marks a struct as vertex data that can be sent to a shader attribute
/// </summary>
[AttributeUsage(AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
public sealed class VertexDataAttribute : Attribute
{
/// <summary>
/// The pointer divisor for this type. A value of 0 indicates per-vertex, a value of 1+
/// indicates every n instances
/// </summary>
public int Divisor { get; set; } = 0;
/// <summary>
/// Mark a struct with information about how to iterate over vertex data of this type.
/// All fields of this struct must have [VertexPointer]
/// </summary>
public VertexDataAttribute()
{
}
}
}

View File

@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using Diamond.Shaders;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Buffers
{
/// <summary>
/// Get vertex pointer information about a struct to infer how to point shader attributes to it
/// </summary>
public class VertexDataInfo
{
/// <summary>
/// All shader attributes supported by this type
/// </summary>
public IReadOnlyCollection<VertexPointerAttribute> Pointers { get; }
/// <summary>
/// The size of this type in bytes
/// </summary>
public int Stride { get; }
/// <summary>
/// The pointer divisor for this type. A value of 0 indicates per-vertex, a value of 1+
/// indicates every n instances
/// </summary>
public readonly int Divisor;
/// <summary>
/// Create an info class with pre-computed values
/// </summary>
/// <param name="pointers">The type's supported attributes</param>
/// <param name="stride">The type's stride</param>
/// <param name="divisor">The type's pointer divisor</param>
private VertexDataInfo(IList<VertexPointerAttribute> pointers, int stride, int divisor)
{
Pointers = new ReadOnlyCollection<VertexPointerAttribute>(pointers);
Stride = stride;
Divisor = divisor;
}
/// <summary>
/// Enable the attributes associated with this type on Program.Current
/// </summary>
public void EnableVertexPointers()
{
if (Program.Current == null)
throw new InvalidOperationException("Cant render a mesh with no active shader.");
foreach (var attr in Pointers)
{
if (!Program.Current.HasAttribute(attr.Name))
continue;
var loc = Program.Current.AttributeLocation(attr.Name);
GL.EnableVertexAttribArray(loc);
GL.VertexAttribDivisor(loc, Divisor);
}
}
/// <summary>
/// Disable the attributes associated with this type on Program.Current
/// </summary>
public void DisableVertexPointers()
{
if (Program.Current == null)
throw new InvalidOperationException("Cant render a mesh with no active shader.");
foreach (var attr in Pointers)
{
if (!Program.Current.HasAttribute(attr.Name))
continue;
var loc = Program.Current.AttributeLocation(attr.Name);
GL.DisableVertexAttribArray(loc);
}
}
/// <summary>
/// A cache of already computed information to prevent redundant reflection calls
/// </summary>
private static readonly Dictionary<Type, VertexDataInfo> attribCache =
new Dictionary<Type, VertexDataInfo>();
/// <summary>
/// Get the VertexDataInfo for a particular type
/// </summary>
/// <typeparam name="T">The type to analyse</typeparam>
/// <returns>The VertexDataInfo for the type, or null if the type is not supported</returns>
public static VertexDataInfo GetInfo<T>() where T : struct
{
if (attribCache.ContainsKey(typeof(T))) return attribCache[typeof(T)];
var vertexDataAttributes = typeof(T).GetCustomAttributes(typeof(VertexDataAttribute), false);
// the type must have [VertexData]
if (vertexDataAttributes.Length != 1)
return null;
var vertdataattrib = (VertexDataAttribute) vertexDataAttributes[0];
var divisor = vertdataattrib.Divisor;
var attribList = new List<VertexPointerAttribute>();
var stride = Marshal.SizeOf<T>();
foreach (var fieldInfo in typeof(T).GetFields())
{
var attrs = fieldInfo.GetCustomAttributes(typeof(VertexPointerAttribute), false);
// all fields must have [VertexPointer]
if (attrs.Length == 0)
return null;
var offset = (int) Marshal.OffsetOf<T>(fieldInfo.Name);
foreach (var attr in attrs)
{
var vpa = (VertexPointerAttribute) attr;
vpa.Offset = offset;
attribList.Add(vpa);
}
}
return new VertexDataInfo(attribList, stride, divisor);
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Buffers
{
/// <summary>
/// Marks a field as an attribute to be sent to a shader. Must be used on public fields of a struct.
/// </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public sealed class VertexPointerAttribute : Attribute
{
/// <summary>
/// The attribute name that the values of this field should point to
/// </summary>
public string Name { get; }
/// <summary>
/// The number of elements in this attribute
/// Corresponds to the <code>size</code> parameter to <code>glVertexAttribPointer</code>.
/// </summary>
public int Size { get; }
/// <summary>
/// The element type of the attribute
/// Corresponds to the <code>type</code> parameter to <code>glVertexAttribPointer</code>
/// </summary>
public VertexAttribPointerType Type { get; set; } = VertexAttribPointerType.Float;
/// <summary>
/// Whether to normalize the values of this attribute
/// Corresponds to the <code>normalized</code> parameter to <code>glVertexAttribPointer</code>
/// </summary>
public bool Normalized { get; set; } = false;
/// <summary>
/// The offset of this attribute within each element
/// Corresponds to the <code>offset</code> parameter to <code>glVertexAttribPointer</code>
/// </summary>
// todo this, and other values, should be moved into a different type for use with VertexDataInfo.
// this class should just mark fields with their use - other types should manage binding those fields
public int Offset { get; internal set; } = 0;
/// <summary>
/// Mark a field with information about how to point a shader attribute to it
/// </summary>
/// <param name="name">The name of the attribute to point to this</param>
/// <param name="size">The number of elements to read from this field</param>
public VertexPointerAttribute(string name, int size)
{
Name = name;
Size = size;
}
}
}

View File

@@ -21,7 +21,8 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Diamond.xml</DocumentationFile> <DocumentationFile>
</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@@ -36,7 +37,7 @@
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.4\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net45\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll</HintPath> <HintPath>..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll</HintPath>
@@ -52,16 +53,26 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Attributes\VertexAttribAttribute.cs" /> <Compile Include="Buffers\Buffer.cs" />
<Compile Include="Attributes\VertexDataAttribute.cs" /> <Compile Include="Buffers\VertexDataInfo.cs" />
<Compile Include="Buffer.cs" /> <Compile Include="Render\Camera.cs" />
<Compile Include="Extensions.cs" /> <Compile Include="Render\RenderGroup.cs" />
<Compile Include="Wrappers\BufferWrap.cs" />
<Compile Include="GLObject.cs" /> <Compile Include="GLObject.cs" />
<Compile Include="Wrappers\ProgramWrap.cs" />
<Compile Include="Wrappers\ShaderWrap.cs" />
<Compile Include="Wrappers\TextureWrap.cs" />
<Compile Include="Util\SubArray.cs" /> <Compile Include="Util\SubArray.cs" />
<Compile Include="Buffers\VertexDataAttribute.cs" />
<Compile Include="Wrappers\Wrapper.cs" />
<Compile Include="Util\TileData.cs" />
<Compile Include="Render\VertexBuffer.cs" />
<Compile Include="Util\ObjVertex.cs" />
<Compile Include="Shaders\Program.cs" /> <Compile Include="Shaders\Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Shaders\Shader.cs" /> <Compile Include="Shaders\Shader.cs" />
<Compile Include="VertexArray.cs" /> <Compile Include="Textures\Texture.cs" />
<Compile Include="Buffers\VertexPointerAttribute.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="NLog.config"> <Content Include="NLog.config">
@@ -75,4 +86,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project> </Project>

View File

@@ -1,26 +0,0 @@
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<Type, int> ByteSizes = new Dictionary<Type, int>();
public static int SizeInBytes(this Type type)
{
if (!ByteSizes.ContainsKey(type))
ByteSizes[type] = Marshal.SizeOf(type);
return ByteSizes[type];
}
public static int SizeInBytes<T>(this T[] arr) where T : struct
{
return typeof(T).SizeInBytes() * arr.Length;
}
}
}

View File

@@ -1,64 +1,26 @@
using System; using System;
using NLog; using NLog;
using OpenTK.Graphics;
namespace Diamond namespace Diamond
{ {
/// <summary> /// <summary>
/// A wrapper class for any OpenGL object /// Provide managed access to OpenGL objects
/// </summary> /// </summary>
public abstract class GLObject : IDisposable public abstract class GLObject : IDisposable
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); /// <summary>
/// Logger for all GLObjects
/// </summary>
protected static readonly Logger Logger = LogManager.GetLogger(nameof(GLObject));
/// <summary> /// <summary>
/// The OpenGL object name /// Name of this GLObject used for identification
/// </summary> /// </summary>
public int Id { get; private set; } public string Name { get; protected set; } = nameof(GLObject);
/// <summary> /// <summary>
/// Force object name assignment /// Delegate Dispose to underlying wrapper class
/// </summary> /// </summary>
/// <param name="id">The OpenGL object name</param> public abstract void Dispose();
protected GLObject(int id)
{
Id = id;
}
/// <summary>
/// Free this object name on the GPU
/// </summary>
protected abstract void Delete();
#region IDisposable
/// <inheritdoc />
protected virtual void Dispose(bool disposing)
{
if (GraphicsContext.CurrentContext != null)
Delete();
else
Logger.Error("Cannot delete {0} because there is no graphics context.", this);
if (disposing)
{
// no managed resources to dispose
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <inheritdoc />
~GLObject()
{
Dispose(false);
}
#endregion
} }
} }

View File

@@ -1,41 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" <nlog xmlns="http://www.nlog-project.org/schemas/NLogger.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd" xsi:schemaLocation="http://www.nlog-project.org/schemas/NLogger.xsd NLog.xsd"
autoReload="true" autoReload="true"
throwExceptions="false" throwExceptions="false"
internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log"> internalLoggerLevel="Off" internalLoggerFile="c:\temp\nlog-internal.log">
<!-- optional, add some variables <variable name="Layout" value="[${level}] ${logger}: ${message}" />
https://github.com/nlog/NLog/wiki/Configuration-file#variables
-->
<variable name="myvar" value="myvalue"/>
<!--
See https://github.com/nlog/nlog/wiki/Configuration-file
for information on customizing logging rules and outputs.
-->
<targets> <targets>
<target xsi:type="File" name="file" fileName="$Log/${shortdate}.log"
<!-- layout="${Layout}"/>
add your targets here <target xsi:type="Debugger" name="debug" layout="${Layout}"/>
See https://github.com/nlog/NLog/wiki/Targets for possible targets.
See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers.
-->
<!--
Write events to a file with the date in the filename.
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
-->
</targets> </targets>
<rules> <rules>
<!-- add your logging rules here --> <logger name="*" minlevel="Info" writeTo="file"/>
<logger name="*" minlevel="Trace" writeTo="debug"/>
<!--
Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace) to "f"
<logger name="*" minlevel="Debug" writeTo="f" />
-->
</rules> </rules>
</nlog> </nlog>

File diff suppressed because it is too large Load Diff

71
Diamond/Render/Camera.cs Normal file
View File

@@ -0,0 +1,71 @@
using OpenTK;
namespace Diamond.Render
{
/// <summary>
/// Manages a projection and view matrix
/// </summary>
public class Camera
{
private Vector3 _position = Vector3.Zero;
private Vector3 _target = -Vector3.One;
private Vector3 _up = Vector3.UnitZ;
/// <summary>
/// The view matrix
/// </summary>
public Matrix4 View;
/// <summary>
/// The projection matrix
/// </summary>
public Matrix4 Projection;
/// <summary>
/// Sets and updates the position of the view matrix
/// </summary>
public Vector3 Position
{
set
{
_position = value;
UpdateView();
}
get => _position;
}
/// <summary>
/// Sets and updates the target of the view matrix
/// </summary>
public Vector3 Target
{
set
{
_target = value;
UpdateView();
}
get => _target;
}
/// <summary>
/// Sets and updates the up vector of the view matrix
/// </summary>
public Vector3 Up
{
set
{
_up = value;
UpdateView();
}
get => _up;
}
/// <summary>
/// Recalculate the view matrix
/// </summary>
private void UpdateView()
{
View = Matrix4.LookAt(_position, _target, _up);
}
}
}

View File

@@ -0,0 +1,61 @@
using Diamond.Shaders;
using Diamond.Textures;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Render
{
/// <summary>
/// Manage a group of buffers, ranges, and uniforms to render
/// </summary>
/// <typeparam name="TInstance">The type of data to use as Instance information</typeparam>
/// <typeparam name="TVertex">The type of data to use as Vertex information</typeparam>
public class RenderGroup<TInstance, TVertex> where TInstance : struct where TVertex : struct
{
/// <summary>
/// The range of vertex values to render
/// </summary>
public VertexBuffer<TVertex> Vertices;
/// <summary>
/// The range of instance values to render
/// </summary>
public VertexBuffer<TInstance> Instance;
/// <summary>
/// The program to use to render this Rendergroup
/// </summary>
public Program Program;
/// <summary>
/// The Texture to use for this Rendergroup
/// </summary>
public Texture Texture;
/// <summary>
/// View and Projection information for this Rendergroup
/// </summary>
public Camera Camera;
/// <summary>
/// Draw this rendergroup using the predefined settings.
/// </summary>
public void Draw()
{
Program.Use();
Texture.Bind(0);
if (Program.HasUniform("tex"))
GL.Uniform1(Program.UniformLocation("tex"), 0);
if (Program.HasUniform("view"))
GL.UniformMatrix4(Program.UniformLocation("view"), false, ref Camera.View);
if (Program.HasUniform("proj"))
GL.UniformMatrix4(Program.UniformLocation("proj"), false, ref Camera.Projection);
if (Instance != null)
Vertices.DrawInstanced(Instance);
else
Vertices.Draw();
}
}
}

View File

@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Diamond.Buffers;
using Diamond.Shaders;
using Diamond.Util;
using NLog;
using OpenTK;
using OpenTK.Graphics.OpenGL4;
using Buffer = Diamond.Buffers.Buffer;
namespace Diamond.Render
{
/// <summary>
/// Manage a vertex buffer object
/// </summary>
/// <typeparam name="T">Buffer data type</typeparam>
public class VertexBuffer<T> : IDisposable where T : struct
{
private Logger Logger = LogManager.GetLogger("VertexBuffer");
/// <summary>
/// The name of this buffer object for identification
/// </summary>
public string Name { get; set; }
/// <summary>
/// The underlying buffer for this object
/// </summary>
public Buffer<T> Buffer;
/// <summary>
/// A subset of the Buffer's array for this buffer
/// </summary>
public SubArray<T> Vertices;
/// <summary>
/// Primitive type to render this object
/// </summary>
public PrimitiveType Primitive;
/// <summary>
/// Vertex data info for this type of data
/// </summary>
private static readonly VertexDataInfo tVdi = VertexDataInfo.GetInfo<T>();
internal VertexBuffer(Buffer<T> buffer, SubArray<T> vertices, PrimitiveType primitive, string name)
{
Buffer = buffer;
Vertices = vertices;
Primitive = primitive;
Name = name;
}
/// <summary>
/// Render this buffer
/// </summary>
public void Draw()
{
Buffer.PointTo(Program.Current);
tVdi.EnableVertexPointers();
GL.DrawArrays(Primitive, Vertices.Offset, Vertices.Length);
tVdi.DisableVertexPointers();
}
/// <summary>
/// Render this buffer using a second buffer as instance data
/// </summary>
/// <typeparam name="TI"></typeparam>
/// <param name="instance"></param>
public void DrawInstanced<TI>(VertexBuffer<TI> instance) where TI : struct
{
var tiVdi = VertexBuffer<TI>.tVdi;
if (tiVdi.Divisor == 0)
{
Logger.Error("Cannot render mesh with instanced of type {0} - Divisor is 0", typeof(TI).Name);
return;
}
Buffer.PointTo(Program.Current);
instance.Buffer.PointTo(Program.Current);
tVdi.EnableVertexPointers();
tiVdi.EnableVertexPointers();
GL.DrawArraysInstancedBaseInstance(Primitive, Vertices.Offset, Vertices.Length, instance.Vertices.Length,
instance.Vertices.Offset);
tVdi.DisableVertexPointers();
tiVdi.DisableVertexPointers();
}
public void Dispose()
{
Buffer.Dispose();
}
}
/// <summary>
/// Static operations for vertex buffers
/// </summary>
public static class VertexBuffer
{
public static VertexBuffer<T>[] FromArrays<T>(T[][] arrays, PrimitiveType primitive = PrimitiveType.Triangles,
string name = null) where T : struct
{
var buffer = Buffer.Empty<T>(BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw, name);
var vertices = arrays.SelectMany(x => x).ToArray();
buffer.Data(vertices);
var vertBuffers = new List<VertexBuffer<T>>();
var offset = 0;
foreach (var array in arrays)
{
vertBuffers.Add(new VertexBuffer<T>(
buffer,
new SubArray<T>(vertices, offset, array.Length),
primitive, name));
offset += array.Length;
}
return vertBuffers.ToArray();
}
public static VertexBuffer<T>[] FromArrays<T>(IEnumerable<IEnumerable<T>> arrays,
PrimitiveType primitive = PrimitiveType.Triangles, string name = null) where T : struct =>
FromArrays(arrays.Select(x => x.ToArray()).ToArray(), primitive, name);
public static VertexBuffer<ObjVertex>[] FromWavefront(string file)
{
var lines = File.ReadAllLines(file).Where(l => !l.StartsWith("#")).Select(l => l.Split(' ')).ToArray();
var buffer = Buffer<ObjVertex>.Empty(BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw,
$"{file} buffer");
var name = file;
var vertBuffers = new List<VertexBuffer<ObjVertex>>();
// get all positional data
var vs = lines
.Where(items => items[0] == "v") // only "v" lines
.Select(items => new Vector3( // get a vector3 from the values
float.Parse(items[1]),
float.Parse(items[2]),
float.Parse(items[3])))
.ToArray();
// get all uv data
var vts = lines
.Where(items => items[0] == "vt") // only "vt" lines
.Select(items => new Vector2( // get a vector2 from the values
float.Parse(items[1]),
1 - float.Parse(items[2]))) // flip along y
.ToArray();
// get all normal data
var vns = lines
.Where(items => items[0] == "vn") // only "vn" lines
.Select(items => new Vector3( // get a vector3 from the values
float.Parse(items[1]),
float.Parse(items[2]),
float.Parse(items[3])))
.ToArray();
// get all vertex data
var vertices = lines
.Where(items => items[0] == "f") // only "f" liens
.Select(items => items.Skip(1)) // skip the "f" item
.Select(items => items // get each vertex from the triangle
.Select(inds => inds.Split('/'))) // split items into indices
.SelectMany(inds => inds) // collapse nested index array into array of indexes
.Select(inds => new ObjVertex( // get vertexdata from the value data at each index
inds[0] == "" ? Vector3.Zero : vs[int.Parse(inds[0]) - 1],
inds[1] == "" ? Vector2.Zero : vts[int.Parse(inds[1]) - 1],
inds[2] == "" ? Vector3.Zero : vns[int.Parse(inds[2]) - 1]))
.ToArray();
buffer.Data(vertices); // upload vertex data to the buffer
var offset = 0; // offset of each vertexbuffer
var count = 0; // length of each vertexbuffer
foreach (var items in lines.Where(items => items[0] == "o" || items[0] == "f"))
{
if (items[0] != "f")
{
if (count > 0)
{
vertBuffers.Add(new VertexBuffer<ObjVertex>(
buffer,
new SubArray<ObjVertex>(vertices, offset, count),
PrimitiveType.Triangles,
name));
}
// reset for next vbo and move offset to the end of this one
name = items[1];
offset += count;
count = 0;
}
else
{
count += items.Length - 1; // size of current vbo increases by that many vertices
}
}
// at the last vbo, or only vbo.
if (count > 0)
{
vertBuffers.Add(new VertexBuffer<ObjVertex>(
buffer,
new SubArray<ObjVertex>(vertices, offset, count),
PrimitiveType.Triangles,
name));
}
return vertBuffers.ToArray();
}
}
}

View File

@@ -1,307 +1,233 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using Diamond.Wrappers;
using NLog;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
namespace Diamond.Shaders namespace Diamond.Shaders
{ {
/// <summary> /// <summary>
/// Wrap and OpenGL program object /// Manages an OpenGL Program object
/// </summary> /// </summary>
public sealed class Program : GLObject public class Program : GLObject
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); internal readonly ProgramWrap Wrapper;
#region Static
private static Program _current;
/// <summary> /// <summary>
/// The currently active program, or null if the default program is active. /// The currently active program. Manually invoking glUseProgram will break this.
/// </summary> /// </summary>
public static Program Current public static Program Current { get; private set; }
{
get => _current;
set => Use(value);
}
/// <summary> // keep a cache of uniform and attributes to prevent repeated queries
/// Use a program.
/// </summary>
/// <param name="program">The program to use, or null to use the default program</param>
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.Trace("Using {0}", (object) program ?? "default program");
_current = program;
}
#endregion
#region ctor, Delete()
/// <inheritdoc />
private Program()
: base(GL.CreateProgram())
{
Logger.Debug("Created {0}", this);
}
/// <inheritdoc />
protected override void Delete()
{
Logger.Debug("Disposing {0}", this);
GL.DeleteProgram(Id);
}
#endregion
#region Properties
#region Queries
/// <summary>
/// Gets the number of active uniforms
/// </summary>
public int ActiveUniforms => Get(GetProgramParameterName.ActiveUniforms);
/// <summary>
/// Gets the number of active attributes
/// </summary>
public int ActiveAttributes => Get(GetProgramParameterName.ActiveAttributes);
#endregion
#region Stored
/// <summary>
/// The InfoLog for this program since it was last linked
/// </summary>
public string InfoLog { get; private set; }
/// <summary>
/// The link status of this program
/// </summary>
public bool Linked { get; private set; }
#endregion
#endregion
#region Methods
/// <summary>
/// Get a property of this program. invokes glGetProgram and returns an int result.
/// </summary>
/// <param name="param">The program property to get</param>
/// <returns>The int value of the program property</returns>
private int Get(GetProgramParameterName param)
{
GL.GetProgram(Id, param, out int res);
return res;
}
/// <summary>
/// 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
/// </summary>
public void Link()
{
_attributes.Clear();
_uniforms.Clear();
Logger.Debug("Linking {0}", this);
GL.LinkProgram(Id);
// link status can only change after link attempt
Linked = Get(GetProgramParameterName.LinkStatus) != 0;
if (Linked)
{
Logger.Trace("Successfully linked {0}", this);
for (var i = 0; i < ActiveAttributes; i++)
{
var sb = new StringBuilder(256);
GL.GetActiveAttrib(Id, i, sb.Capacity, out int length, out int size, out ActiveAttribType type, sb);
var name = sb.ToString();
_attributes[name] = i;
}
for (var i = 0; i < ActiveUniforms; i++)
{
var sb = new StringBuilder(256);
GL.GetActiveUniform(Id, i, sb.Capacity, out int length, out int size, out ActiveUniformType type,
sb);
var name = sb.ToString();
_uniforms[name] = i;
}
}
else
{
InfoLog = GL.GetProgramInfoLog(Id).Trim();
Logger.Error("Failed to link {0}", this);
Logger.Trace("InfoLog for {0}:\n{1}", this, InfoLog);
}
}
/// <summary>
/// Use this program. Equivalent to Program.Use(this);
/// </summary>
public void Use() => Use(this);
#region Locations
private readonly Dictionary<string, int> _attributes = new Dictionary<string, int>();
private readonly Dictionary<string, int> _uniforms = new Dictionary<string, int>(); private readonly Dictionary<string, int> _uniforms = new Dictionary<string, int>();
/// <summary> private readonly Dictionary<string, int> _attributes = new Dictionary<string, int>();
/// Check if this program has an active attribute
/// </summary>
/// <param name="name">The attribute name</param>
/// <returns>Whether the program has an active attribute</returns>
public bool HasAttribute(string name) => _attributes.ContainsKey(name);
/// <summary> internal Program(ProgramWrap wrapper, string name)
/// Check if this program has an active uniform
/// </summary>
/// <param name="name">The uniform name</param>
/// <returns>Whether the program has an active uniform</returns>
public bool HasUniform(string name) => _uniforms.ContainsKey(name);
/// <summary>
/// Get the location of an attribute by name. Throws InvalidOperationException if the attribute
/// is not found. This can be checked with HasAttribute
/// </summary>
/// <param name="name">The attribute name</param>
/// <returns>The location of the attribute</returns>
public int AttributeLocation(string name)
{ {
if (!HasAttribute(name)) Wrapper = wrapper;
throw new InvalidOperationException($"{this} does not have active attribute '{name}'"); Name = name;
return _attributes[name];
} }
/// <summary> /// <summary>
/// Get the location of an uniform by name. Throws InvalidOperationException if the uniform /// Check if the program has a uniform
/// is not found. This can be checked with HasUniform
/// </summary> /// </summary>
/// <param name="name">The uniform name</param> /// <param name="name">The name of the uniform</param>
/// <returns>Whether the program has this uniform</returns>
public bool HasUniform(string name)
{
return _uniforms.ContainsKey(name);
}
/// <summary>
/// Get the location of a uniform
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <returns>The location of the uniform</returns> /// <returns>The location of the uniform</returns>
public int UniformLocation(string name) public int UniformLocation(string name)
{ {
if (!HasUniform(name)) throw new InvalidOperationException($"{this} does not have active uniform '{name}'"); if (HasUniform(name)) return _uniforms[name];
return _uniforms[name]; throw new KeyNotFoundException($"Shader {this} does not contain uniform {name}");
} }
#endregion
/// <summary> /// <summary>
/// Attach a shader to this program. If shader is null, does nothing. If shader is not compiled /// Check if the program has an attribute
/// it is still attached but program link will likely fail.
/// </summary> /// </summary>
/// <param name="shader">The shader to attach</param> /// <param name="name">The name of the attribute</param>
public void Attach(Shader shader) /// <returns>Whether the program has this attribute</returns>
public bool HasAttribute(string name)
{ {
if (shader == null) return _attributes.ContainsKey(name);
{
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);
} }
/// <inheritdoc /> /// <summary>
public override string ToString() => /// Get the location of an attribute
$"'Program {Id}'"; /// </summary>
/// <param name="name">The name of the attribute</param>
/// <returns>The location of the attribute</returns>
public int AttributeLocation(string name)
{
if (HasAttribute(name)) return _attributes[name];
throw new KeyNotFoundException($"Shader {this} does not contain attribute {name}");
}
#endregion /// <summary>
/// Use this Program to render. Also updates Program.Current
/// </summary>
public void Use()
{
Wrapper.Use();
Current = this;
}
/// <summary>
/// Use the default shader to render
/// </summary>
//? Could create static Program instance which wraps the default shader
// ie Shader.Default.Use()
// would also allow sending arrays to the default attribs like gl_Vertex etc.
public static void UseDefault()
{
GL.UseProgram(0);
Current = null;
}
/// <summary>
/// Helper method to try to link this program
/// </summary>
/// <returns></returns>
private bool Link()
{
_uniforms.Clear();
_attributes.Clear();
Wrapper.Link();
if (!Wrapper.Linked)
return false;
for (var i = 0; i < Wrapper.ActiveUniforms; i++)
_uniforms[Wrapper.UniformName(i)] = i;
for (var i = 0; i < Wrapper.ActiveAttributes; i++)
_attributes[Wrapper.AttributeName(i)] = i;
return true;
}
/// <summary>
/// Helper method to attach a shader to this program
/// </summary>
/// <param name="shader">The shader to attach</param>
private void Attach(Shader shader)
{
Wrapper.Attach(shader.Wrapper);
}
public override string ToString() => $"Program {Wrapper} \'{Name}\'";
public override void Dispose()
{
Logger.Debug("Disposing {0}", this);
Wrapper.Dispose();
}
#region Factory Methods #region Factory Methods
/// <summary> /// <summary>
/// Create and link a program from precompiled shaders. If any shader is null or uncompiled it is still /// Create a program from compiled shaders
/// attached, although program link will likely fail.
/// </summary> /// </summary>
/// <param name="shaders">The shaders used in this program</param> /// <param name="name">The name of this GLObject</param>
/// <returns>A linked program, or null if initialization failed</returns> /// <param name="shaders">The shaders to use in this program</param>
public static Program FromShaders(params Shader[] shaders) => FromShaders((IEnumerable<Shader>) shaders); /// <returns>The linked program, or null if initialization failed</returns>
public static Program FromShaders(string name, params Shader[] shaders) => FromShaders(name,
(IEnumerable<Shader>) shaders);
/// <summary> /// <summary>
/// Create and link a program from precompiled shaders. If any shader is null or uncompiled it is still /// Create a program from compiled shaders
/// attached, although program link will likely fail.
/// </summary> /// </summary>
/// <param name="shaders">The shaders used in this program</param> /// <param name="name">The name of this GLObject</param>
/// <returns>A linked program, or null if initialization failed</returns> /// <param name="shaders">The shaders to use in this program</param>
public static Program FromShaders(IEnumerable<Shader> shaders) /// <returns>The linked program, or null if initialization failed</returns>
public static Program FromShaders(string name, IEnumerable<Shader>shaders)
{ {
if (shaders == null) if (shaders == null)
{ {
Logger.Error("Cannot create program from no shaders."); Logger.Error("Cannot create program {0} with no shaders.", name);
return null; return null;
} }
var program = new Program(); var wrapper = new ProgramWrap();
var service = new Program(wrapper, name);
Logger.Debug("Created {0}", service);
foreach (var shader in shaders) foreach (var shader in shaders)
{ {
program.Attach(shader); if (shader == null)
{
Logger.Error("One or more shaders failed to compile - cannot create program {0}", name);
service.Dispose();
return null;
}
service.Attach(shader);
} }
program.Link(); var linked = service.Link();
if (program.Linked) return program; if (!linked)
program.Dispose();
return null;
}
/// <summary>
/// Create and link a program from glsl source files. Shader types must be inferrable from file extensions.
/// <para></para>
/// See <see cref="Shader.FromFile(string,ShaderType)"/> for file extension details
/// </summary>
/// <param name="files"></param>
/// <returns>The linked program, or null if initialization failed</returns>
public static Program FromFiles(params string[] files) => FromFiles((IEnumerable<string>) files);
/// <summary>
/// Create and link a program from glsl source files. Shader types must be inferrable from file extensions.
/// <para></para>
/// See <see cref="Shader.FromFile(string,ShaderType)"/> for file extension details
/// </summary>
/// <param name="files"></param>
/// <returns>The linked program, or null if initialization failed</returns>
public static Program FromFiles(IEnumerable<string> files)
{
if (files == null)
{ {
Logger.Warn("Cannot create a program from no shaders"); Logger.Warn("Failed to link {0}", service);
Logger.Debug("InfoLog for {0}", service);
service.Dispose();
return null; return null;
} }
var shaders = files.Select(Shader.FromFile).ToList(); Logger.Debug("Successfully linked {0}", service);
return service;
}
/// <summary>
/// Create a program from compiled shaders
/// </summary>
/// <param name="shaders">The shaders to use in this program</param>
/// <returns>The linked program, or null if initialization failed</returns>
public static Program FromShaders(params Shader[] shaders) => FromShaders((IEnumerable<Shader>) shaders);
/// <summary>
/// Create a program from compiled shaders
/// </summary>
/// <param name="shaders">The shaders to use in this program</param>
/// <returns>The linked program, or null if initialization failed</returns>
public static Program FromShaders(IEnumerable<Shader> shaders)
{
var shaderList = shaders.ToList(); // prevent multiple enumeration
var shaderNames = shaderList.Select(s => s.Name);
string name = $"[{string.Join(", ", shaderNames)}]";
return FromShaders(name, shaderList);
}
/// <summary>
/// Create shaders from glsl source files, and create a program using them.
/// Shader types must be inferrable from file extensions.
/// </summary>
/// <param name="paths">The glsl source files</param>
/// <returns>The linked program, or null if initialization faileds</returns>
public static Program FromFiles(params string[] paths)
{
if (paths == null)
{
Logger.Warn("Cannot create a program from no shaders.");
return null;
}
var shaders = paths.Select(path => Shader.FromFile(path)).ToList();
var program = FromShaders(shaders); var program = FromShaders(shaders);
foreach (var shader in shaders) foreach (var shader in shaders)
{
shader?.Dispose(); shader?.Dispose();
}
return program; return program;
} }

View File

@@ -1,168 +1,96 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using NLog; using Diamond.Wrappers;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
namespace Diamond.Shaders namespace Diamond.Shaders
{ {
/// <summary> /// <summary>
/// Wrap an OpenGL shader object /// Manges a OpenGL Shader object
/// </summary> /// </summary>
public sealed class Shader : GLObject public class Shader : GLObject
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); internal readonly ShaderWrap Wrapper;
#region ctor, Delete()
/// <summary> /// <summary>
/// Create a shader object wrapper /// The source used to create this shader
/// </summary> /// </summary>
/// <param name="type">The type of this shader</param> public string Source { get; }
private Shader(ShaderType type)
: base(GL.CreateShader(type))
{
ShaderType = type;
Logger.Debug("Created {0}", this);
}
/// <inheritdoc />
protected override void Delete()
{
Logger.Debug("Disposing {0}", this);
GL.DeleteShader(Id);
}
#endregion
#region Properties
#region Queries
#endregion
#region Stored
/// <summary>
/// Store the source code to prevent repeated queries to glGetShaderSource
/// </summary>
private string _source;
/// <summary>
/// GLSL source code for this shader
/// </summary>
public string Source
{
get => _source;
set
{
_source = value;
GL.ShaderSource(Id, _source);
Logger.Debug("Set shader source for {0}", this);
}
}
/// <summary>
/// The compilation status of this shader
/// </summary>
public bool Compiled { get; private set; }
/// <summary> /// <summary>
/// The type of this shader /// The type of this shader
/// </summary> /// </summary>
public ShaderType ShaderType { get; private set; } public ShaderType Type { get; }
/// <summary> internal Shader(ShaderWrap wrapper, string source, ShaderType type, string name)
/// The InfoLog for this program
/// </summary>
public string InfoLog { get; private set; }
#endregion
#endregion
#region Methods
/// <summary>
/// Get a property of this shader
/// </summary>
/// <param name="param">The shader property to get</param>
/// <returns>The int value of the shader property</returns>
private int Get(ShaderParameter param)
{ {
GL.GetShader(Id, param, out int res); Wrapper = wrapper;
return res; Source = source;
Type = type;
Name = name;
} }
/// <summary> public override string ToString() => Name == null
/// Try to compile this shader ? $"{Wrapper}"
/// </summary> : $"{Wrapper} \'{Name}\'";
public void Compile()
public override void Dispose()
{ {
Logger.Debug("Compiling {0}", this); Logger.Debug("Disposing {0}", this);
GL.CompileShader(Id); Wrapper.Dispose();
// compilation status can only change after glCompileShader
Compiled = Get(ShaderParameter.CompileStatus) != 0;
if (Compiled)
Logger.Trace("Successfully compiled {0}", this);
else
{
InfoLog = GL.GetShaderInfoLog(Id).Trim();
Logger.Error("Failed to compile {0}", this);
Logger.Trace("InfoLog for {0}: \n{1}", this, InfoLog);
}
} }
/// <inheritdoc />
public override string ToString() =>
$"'Shader {Id}: {ShaderType}'";
#endregion
#region Factory Methods #region Factory Methods
/// <summary> // Used to infer shader type based on file extension
/// Map file extensions to appropriate shader type
/// </summary>
private static readonly Dictionary<string, ShaderType> Extensions = new Dictionary<string, ShaderType> private static readonly Dictionary<string, ShaderType> Extensions = new Dictionary<string, ShaderType>
{ {
[".vs"] = ShaderType.VertexShader, [".vs"] = ShaderType.VertexShader,
[".vert"] = ShaderType.VertexShader, [".vert"] = ShaderType.VertexShader,
[".gs"] = ShaderType.GeometryShader,
[".geom"] = ShaderType.GeometryShader,
[".fs"] = ShaderType.FragmentShader, [".fs"] = ShaderType.FragmentShader,
[".frag"] = ShaderType.FragmentShader, [".frag"] = ShaderType.FragmentShader,
[".gs"] = ShaderType.GeometryShader,
[".geom"] = ShaderType.GeometryShader,
}; };
/// <summary> /// <summary>
/// Create and compile a shader from source /// Create and compile a shader from glsl source code
/// </summary> /// </summary>
/// <param name="source">The GLSL source for the shader</param> /// <param name="source">The glsl source</param>
/// <param name="type">The type of the shader</param> /// <param name="type">The type of shader to create</param>
/// <returns>The compiled shader, or null if initialization failed</returns> /// <param name="name">The name of this GLObject</param>
public static Shader FromSource(string source, ShaderType type) /// <returns>The compiled Shader, or null if initialization failed</returns>
public static Shader FromSource(string source, ShaderType type, string name = "Shader")
{ {
var shader = new Shader(type) {Source = source}; var wrapper = new ShaderWrap(type);
shader.Compile(); var service = new Shader(wrapper, source, type, name);
if (!shader.Compiled) Logger.Debug("Created {0}", service);
wrapper.Source = source;
wrapper.Compile();
if (!wrapper.Compiled)
{ {
shader.Dispose(); Logger.Warn("Failed to compile {0}", service);
Logger.Debug("InfoLog for {0}", service);
service.Dispose();
return null; return null;
} }
return shader; Logger.Debug("Successfully compiled {0}", service);
return service;
} }
/// <summary> /// <summary>
/// Create and compile a shader from a source file /// Create and compile a shader from a glsl source file
/// </summary> /// </summary>
/// <param name="path">The path to the source file</param> /// <param name="path">The path to the glsl source file</param>
/// <param name="type">The type of the shader</param> /// <param name="type">The type of the shader to create</param>
/// <returns>The compiled shader, or null if initialization failed</returns> /// <param name="name">The name of this GLObject</param>
public static Shader FromFile(string path, ShaderType type) /// <returns></returns>
public static Shader FromFile(string path, ShaderType type, string name = null)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{ {
@@ -170,20 +98,21 @@ namespace Diamond.Shaders
return null; return null;
} }
var source = File.ReadAllText(path); if (name == null)
return FromSource(source, type); name = Path.GetFileNameWithoutExtension(path);
return FromSource(File.ReadAllText(path), type, name);
} }
/// <summary> /// <summary>
/// Create and compile a shader from a source file, and infer the type of the shader from the file extension. /// Create and compile a shader from a glsl source file. Shader type is inferred from file extension.
/// <para></para> /// Extension must be .vs, .vert, .fs, .frag, .gs, or .geom. This can optionally be followed by .glsl or .txt,
/// File must have extension or sub-extension <code>.vs</code>, <code>.fs</code>, <code>.gs</code>, <code>.vert</code>, /// but the shader type extension must be present.
/// <code>.frag</code>, or <code>.geom</code>. For example: <code>shader.fs</code>, <code>shader.vert.glsl</code>,
/// <code>shader.gs.txt</code>
/// </summary> /// </summary>
/// <param name="path"></param> /// <param name="path">The path to the glsl source file</param>
/// <returns></returns> /// <param name="name">The name of this GLObject</param>
public static Shader FromFile(string path) /// <returns>The compiled shader, or null if initialization failed or shader type cannot be inferred</returns>
public static Shader FromFile(string path, string name = null)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{ {
@@ -192,12 +121,12 @@ namespace Diamond.Shaders
} }
var ext = Path.GetExtension(path); var ext = Path.GetExtension(path);
var file = Path.GetFileNameWithoutExtension(path); var fileName = Path.GetFileNameWithoutExtension(path);
// get sub-extension if real extension is not valid // get sub-extension if real extension is not valid
if (ext != null) if (ext != null)
if (!Extensions.ContainsKey(ext)) if (!Extensions.ContainsKey(ext))
ext = Path.GetExtension(file); ext = Path.GetExtension(fileName);
// if no extension, no sub-extension, or invalid sub-extension // if no extension, no sub-extension, or invalid sub-extension
if (ext == null || !Extensions.ContainsKey(ext)) if (ext == null || !Extensions.ContainsKey(ext))
@@ -207,7 +136,10 @@ namespace Diamond.Shaders
} }
var type = Extensions[ext]; var type = Extensions[ext];
return FromFile(path, type); if (name == null)
name = fileName;
return FromFile(path, type, name);
} }
#endregion #endregion

View File

@@ -0,0 +1,89 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Diamond.Wrappers;
using OpenTK.Graphics.OpenGL4;
using PixelFormat = OpenTK.Graphics.OpenGL4.PixelFormat;
namespace Diamond.Textures
{
/// <summary>
/// Manages a OpenGL Texture object
/// </summary>
public class Texture : GLObject
{
internal readonly TextureWrap Wrapper;
internal Texture(TextureWrap wrapper, string name)
{
Wrapper = wrapper;
Name = name;
}
/// <summary>
/// This textures target; how it is used
/// </summary>
public TextureTarget Target => Wrapper.Target;
/// <summary>
/// Bind this texture to a particular unit
/// </summary>
public void Bind(int unit) => Wrapper.Bind(unit);
public override string ToString() => Name == null
? $"{Wrapper}"
: $"{Wrapper} \'{Name}\'";
public override void Dispose()
{
Logger.Debug("Disposing {0}", this);
Wrapper.Dispose();
}
#region Factory Methods
/// <summary>
/// Create a texture object and upload bitmap data to it
/// </summary>
/// <param name="bmp">The image to upload</param>
/// <param name="name">The name of this GLObject</param>
/// <returns>The initialized Texture, or null if initialsation failed</returns>
public static Texture FromBitmap(Bitmap bmp, string name = null)
{
var wrapper = new TextureWrap(TextureTarget.Texture2D);
var service = new Texture(wrapper, null);
Logger.Debug("Created Texture {0}", service);
wrapper.Bind();
// todo: expose texture parameters to enable setting different filters
wrapper.TexParameter(TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest);
wrapper.TexParameter(TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Nearest);
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
wrapper.Image2D(PixelInternalFormat.Rgba, bmp.Width, bmp.Height, PixelFormat.Bgra, PixelType.UnsignedByte,
data.Scan0);
bmp.UnlockBits(data);
return service;
}
/// <summary>
/// Create a texture and upload the contents of an image file to it
/// </summary>
/// <param name="path">The path to the file</param>
/// <param name="name">The name of this GLObject</param>
/// <returns>The initialized Texture, or null if instantiation failed</returns>
public static Texture FromFile(string path, string name = null)
{
if (name == null)
name = Path.GetFileNameWithoutExtension(path);
return FromBitmap(new Bitmap(path), name);
}
#endregion
}
}

46
Diamond/Util/ObjVertex.cs Normal file
View File

@@ -0,0 +1,46 @@
using Diamond.Buffers;
using OpenTK;
namespace Diamond.Util
{
/// <summary>
/// Vertex buffer data for Wavefront meshes
/// </summary>
[VertexData]
public struct ObjVertex
{
/// <summary>
/// Vertex position (v)
/// </summary>
[VertexPointer("position", 3)]
[VertexPointer("v", 3)]
public Vector3 Position;
/// <summary>
/// UV coordinate (vt)
/// </summary>
[VertexPointer("uv", 2)]
[VertexPointer("vt", 2)]
public Vector2 UV;
/// <summary>
/// Vertex normal (vn)
/// </summary>
[VertexPointer("normal", 3)]
[VertexPointer("vn", 3)]
public Vector3 Normal;
/// <summary>
/// Create a new ObjVertex
/// </summary>
/// <param name="position">The vertex position (v)</param>
/// <param name="uv">The uv coordinate (vt)</param>
/// <param name="normal">The vertex normal (n)</param>
public ObjVertex(Vector3 position, Vector2 uv, Vector3 normal)
{
Position = position;
UV = uv;
Normal = normal;
}
}
}

View File

@@ -89,7 +89,6 @@ namespace Diamond.Util
return arr; return arr;
} }
/// <inheritdoc />
public override string ToString() public override string ToString()
{ {
if (Length == 0) if (Length == 0)

28
Diamond/Util/TileData.cs Normal file
View File

@@ -0,0 +1,28 @@
using Diamond.Buffers;
using OpenTK;
namespace Diamond.Util
{
/// <summary>
/// Vertex buffer data for instanced rendering
/// </summary>
[VertexData(Divisor = 1)]
public struct TileData
{
/// <summary>
/// The global position of the instance
/// </summary>
[VertexPointer("glbpos", 3)]
[VertexPointer("global_pos", 3)]
public Vector3 Position;
/// <summary>
/// Create a new TileData
/// </summary>
/// <param name="position">The global position of the instance</param>
public TileData(Vector3 position)
{
Position = position;
}
}
}

View File

@@ -1,162 +0,0 @@
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
{
/// <summary>
/// Wrap an OpenGL Vertex Array Object
/// </summary>
public sealed class VertexArray : GLObject
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
#region Static
private static VertexArray _current;
/// <summary>
/// The currently bound VAO, or null if no vao is bound.
/// </summary>
public static VertexArray Current
{
get => _current;
set => Bind(value);
}
/// <summary>
/// Bind a VAO to the context
/// </summary>
/// <param name="vao">The vao to bind, or null to unbind the current vao</param>
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()
/// <inheritdoc />
internal VertexArray()
: base(GL.GenVertexArray())
{
Logger.Debug("Created {0}", this);
}
/// <inheritdoc />
protected override void Delete()
{
Logger.Debug("Disposing {0}", this);
GL.DeleteVertexArray(Id);
}
#endregion
#region Properties
#region Queries
#endregion
#region Stored
private Buffer _elementArrayBuffer;
/// <summary>
/// The ElementArrayBuffer associated with this VAO
/// </summary>
public Buffer ElementArrayBuffer
{
get => _elementArrayBuffer;
set
{
Logger.Debug("Using {0} as ElementArrayBuffer for {1}", (object)value ?? "default buffer", this);
Current = this;
Buffer.ElementArrayBuffer = _elementArrayBuffer = value;
}
}
#endregion
#endregion
#region Methods
/// <summary>
/// Attach a VBO to this VAO. Use attributes to infer the
/// vertex attrib pointer information
/// </summary>
/// <typeparam name="T">The struct to use for vertex attrib information. Must be marked with VertexDataAttribute</typeparam>
/// <param name="buffer">The buffer to add</param>
public void Attach<T>(Buffer<T> 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;
}
Logger.Debug("Attaching {0} to {1}", buffer, this);
Current = this;
Buffer.ArrayBuffer = buffer;
var stride = Marshal.SizeOf<T>();
foreach (var field in typeof(T).GetFields())
{
var offset = Marshal.OffsetOf<T>(field.Name);
foreach (var vai in field.GetCustomAttributes<VertexAttribAttribute>(false))
{
Logger.Debug($"Enabling attribute (id:{vai.Attribute}, size:{vai.Size}, " +
$"type:{vai.Type}, norm:{vai.Normalized}, stride:{stride}, offset:{offset})");
GL.EnableVertexAttribArray(vai.Attribute);
GL.VertexAttribPointer(vai.Attribute, vai.Size, vai.Type, vai.Normalized, stride, offset);
GL.VertexAttribDivisor(vai.Attribute, vda.Divisor);
}
}
}
/// <summary>
/// Bind this vao
/// </summary>
public void Bind() => Bind(this);
/// <inheritdoc />
public override string ToString() =>
$"'Vertex Array {Id}'";
#endregion
#region Factory Methods
/// <summary>
/// Create an empty vertex array object. This contains no binding information
/// by default, and must have any vertex buffer objects attached manually.
/// </summary>
/// <returns>A new Vertex Array Object, or null if creation failed</returns>
public static VertexArray Create()
{
return new VertexArray();
}
#endregion
}
}

View File

@@ -0,0 +1,79 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Wrappers
{
/// <summary>
/// Wrapper class for OpenGL Buffer objects
/// </summary>
internal sealed class BufferWrap : Wrapper
{
#region Constructor, GLDelete()
internal BufferWrap(BufferTarget target, BufferUsageHint usage)
: base(GL.GenBuffer())
{
Target = target;
Usage = usage;
}
protected override void GLDelete() => GL.DeleteBuffer(Id);
#endregion
#region Properties
#region Stored
/// <summary>
/// BufferTarget parameter used in gl* calls
/// </summary>
public BufferTarget Target { get; }
/// <summary>
/// BufferUsageHint parameter using in glBufferData calls
/// </summary>
public BufferUsageHint Usage { get; set; }
#endregion
#endregion
#region Methods
/// <summary>
/// Binds this buffer (glBindBuffer)
/// </summary>
public void Bind() => GL.BindBuffer(Target, Id);
/// <summary>
/// Upload data to this buffer (glBufferData)
/// </summary>
/// <typeparam name="T">Type of value to upload</typeparam>
/// <param name="size">Size of T in bytes</param>
/// <param name="data">Values to upload</param>
public void Data<T>(int size, T[] data) where T : struct
{
Bind();
GL.BufferData(Target, (IntPtr)(size * data.Length), data, Usage);
}
/// <summary>
/// Upload a range data to this buffer (glBufferSubData)
/// </summary>
/// <typeparam name="T">Type of value to upload</typeparam>
/// <param name="size">Size of T in bytes</param>
/// <param name="offset">Offset of upload range in bytes</param>
/// <param name="count">Number of bytes to upload</param>
/// <param name="data">All values to upload (offset will be applied to both this and the target)</param>
public void SubData<T>(int size, int offset, int count, T[] data) where T : struct
{
Bind();
GL.BufferSubData(Target, (IntPtr)(offset * size), (IntPtr)(count * size), data);
}
#endregion
public override string ToString() => $"{Target} ({Id})";
}
}

View File

@@ -0,0 +1,103 @@
using System.Text;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Wrappers
{
/// <summary>
/// Wrapper class for OpenGL Program objects
/// </summary>
internal sealed class ProgramWrap : Wrapper
{
#region Constructor, GLDelete()
internal ProgramWrap()
: base(GL.CreateProgram())
{
}
protected override void GLDelete() => GL.DeleteProgram(Id);
#endregion
#region Properties
/// <summary>
/// Get the number of active uniforms for this program
/// </summary>
public int ActiveUniforms => Get(GetProgramParameterName.ActiveUniforms);
/// <summary>
/// Get the number of active attributes for this program
/// </summary>
public int ActiveAttributes => Get(GetProgramParameterName.ActiveAttributes);
/// <summary>
/// Check whether this program has been Linked
/// </summary>
public bool Linked => Get(GetProgramParameterName.LinkStatus) != 0;
/// <summary>
/// Get the InfoLog related to this program. Unless Link() failed, should be null.
/// </summary>
public string InfoLog => GL.GetProgramInfoLog(Id).Trim(); // trim to remove trailing newlines
#endregion
#region Methods
/// <summary>
/// Get a parameter from this program (glGetProgram)
/// </summary>
/// <param name="parameter">The parameter to get</param>
/// <returns>The int value of the parameter</returns>
public int Get(GetProgramParameterName parameter)
{
GL.GetProgram(Id, parameter, out int res);
return res;
}
/// <summary>
/// Attach a compiled shader to this program (glAttachShader)
/// </summary>
/// <param name="shader"></param>
public void Attach(ShaderWrap shader) => GL.AttachShader(Id, shader.Id);
/// <summary>
/// Link this program (glLinkProgram)
/// </summary>
public void Link() => GL.LinkProgram(Id);
/// <summary>
/// Use this program (glUseProgram)
/// </summary>
public void Use() => GL.UseProgram(Id);
/// <summary>
/// Get the name of the uniform at a location
/// </summary>
/// <param name="location">The uniform id</param>
/// <returns>The uniform name</returns>
public string UniformName(int location)
{
var sb = new StringBuilder(64);
GL.GetActiveUniformName(Id, location, sb.Capacity, out int length, sb);
return sb.ToString();
}
/// <summary>
/// Get the name of the attribute at a location
/// </summary>
/// <param name="location">The attribute id</param>
/// <returns>The attribute name</returns>
public string AttributeName(int location)
{
var sb = new StringBuilder(64);
GL.GetActiveAttrib(Id, location, sb.Capacity, out int length, out int size, out ActiveAttribType type, sb);
return sb.ToString();
}
#endregion
public override string ToString() => $"Program ({Id})";
}
}

View File

@@ -0,0 +1,86 @@
using System.Text;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Wrappers
{
/// <summary>
/// Wrapper class for OpenGL Shader objects
/// </summary>
internal sealed class ShaderWrap : Wrapper
{
#region Constructor, GLDelete()
internal ShaderWrap(ShaderType shaderType)
: base(GL.CreateShader(shaderType))
{
ShaderType = shaderType;
}
protected override void GLDelete() => GL.DeleteShader(Id);
#endregion
#region Properties
#region Stored
/// <summary>
/// The type of this shader - stored at creation time to prevent repeated queries
/// </summary>
public ShaderType ShaderType { get; }
#endregion
/// <summary>
/// Get or set the source of this shader (glShaderSource)
/// </summary>
public string Source
{
get
{
var sb = new StringBuilder(1024);
GL.GetShaderSource(Id, sb.Capacity, out int length, sb);
return sb.ToString();
}
set { GL.ShaderSource(Id, value); }
}
/// <summary>
/// Check the compilation status of this shader
/// </summary>
public bool Compiled
{
get
{
GL.GetShader(Id, ShaderParameter.CompileStatus, out int res);
return res != 0;
}
}
public string InfoLog => GL.GetShaderInfoLog(Id);
#endregion
#region Methods
/// <summary>
/// Get a parameter of this shader (glGetShader)
/// </summary>
/// <param name="parameter">The parameter to get</param>
/// <returns>The parameter value</returns>
public int Get(ShaderParameter parameter)
{
GL.GetShader(Id, parameter, out int res);
return res;
}
/// <summary>
/// Compile this shader (glCompileShader)
/// </summary>
public void Compile() => GL.CompileShader(Id);
#endregion
public override string ToString() => $"{ShaderType} ({Id})";
}
}

View File

@@ -0,0 +1,78 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Wrappers
{
/// <summary>
/// Wrapper class for OpenGL Texture objects
/// </summary>
internal sealed class TextureWrap : Wrapper
{
#region Constructor, GLDelete()
internal TextureWrap(TextureTarget target)
: base(GL.GenTexture())
{
Target = target;
}
protected override void GLDelete() => GL.DeleteTexture(Id);
#endregion
#region Properties
#region Stored
/// <summary>
/// The target for this texture; Texture type.
/// </summary>
public TextureTarget Target { get; }
#endregion
#endregion
#region Methods
/// <summary>
/// Bind this texture to the currently active TextureUnit (glBindTexture)
/// </summary>
public void Bind() => GL.BindTexture(Target, Id);
/// <summary>
/// Bind this texture to a particular TextureUnit (glActiveTexture, glBindTexture)
/// </summary>
/// <param name="unit">Unit to bind to</param>
public void Bind(int unit)
{
GL.ActiveTexture(TextureUnit.Texture0 + unit);
Bind();
}
/// <summary>
/// Set a texture parameter (glTexParameter)
/// </summary>
/// <param name="parameter">The parameter to set</param>
/// <param name="value">The value to set</param>
public void TexParameter(TextureParameterName parameter, int value) => GL.TexParameter(Target, parameter,
value);
/// <summary>
/// Upload data to this texture
/// </summary>
/// <param name="internalFormat">The number of color components in the texture</param>
/// <param name="width">The width of the texture</param>
/// <param name="height">The height of the texture</param>
/// <param name="format">The pixel format of the texture</param>
/// <param name="type">The type of the pixel data</param>
/// <param name="pixels">Location of the pixel data</param>
public void Image2D(PixelInternalFormat internalFormat, int width, int height, PixelFormat format,
PixelType type, IntPtr pixels) =>
GL.TexImage2D(Target, 0, internalFormat, width, height, 0, format, type, pixels);
#endregion
public override string ToString() => $"{Target} ({Id})";
}
}

View File

@@ -0,0 +1,70 @@
using System;
using NLog;
using OpenTK.Graphics;
namespace Diamond.Wrappers
{
internal abstract class Wrapper : IDisposable
{
/// <summary>
/// Logger for all Wrapper types.
/// </summary>
protected static readonly Logger Logger = LogManager.GetLogger("Wrapper");
/// <summary>
/// The OpenGL name of this object
/// </summary>
public int Id { get; private set; }
// Force wrapper types to generate an Id at creation time
protected Wrapper(int id)
{
Id = id;
}
public override string ToString() => $"{GetType().Name} {Id}";
#region IDisposable
/// <summary>
/// Delete this OpenGL object (glDelete*)
/// </summary>
protected abstract void GLDelete();
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
// no managed resources to dispose
if (GraphicsContext.CurrentContext == null)
Logger.Error("No graphics context, cannot delete {0}", this);
else
GLDelete();
Id = 0;
_disposed = true;
}
#region Implemented
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Wrapper()
{
Dispose(false);
}
#endregion
#endregion
}
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="NLog" version="4.4.4" targetFramework="net461" /> <package id="NLog" version="4.4.3" targetFramework="net461" />
<package id="NLog.Config" version="4.4.4" targetFramework="net461" /> <package id="NLog.Config" version="4.4.3" targetFramework="net461" />
<package id="NLog.Schema" version="4.4.4" targetFramework="net461" /> <package id="NLog.Schema" version="4.4.3" targetFramework="net461" />
<package id="OpenTK" version="2.0.0" targetFramework="net452" /> <package id="OpenTK" version="2.0.0" targetFramework="net452" />
</packages> </packages>

View File

@@ -1,3 +1,7 @@
# Diamond # Diamond
simple tile/voxel engine and testing This project wraps OpenGL objects with managed .NET types. It uses OpenTK for most of the GL function bindings, and relies on OpenTK's math libraries. This is intended mainly as a convenience for writing simple OpenGL4 programs in .NET.
The wrapper classes use reflection to extract some information about .NET types, allowing them to be seamlessly packed into OpenGL buffers, making it very easy to manipulate buffers with typical .NET collections.
The attributes `[VertexData]` and `[VertexPointer]` indicate which members of a struct should be sent to which index of a shader. The built-in .NET Layout attributes should also be respected.

2492
doxyconfig.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,125 +1,154 @@
using System; using System;
using System.Diagnostics; using System.Collections.Generic;
using System.IO;
using System.Linq;
using Diamond.Render;
using Diamond.Shaders; using Diamond.Shaders;
using Diamond.Textures;
using Diamond.Util;
using Newtonsoft.Json.Linq;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using Diamond;
using Diamond.Attributes;
using Buffer = Diamond.Buffer;
namespace hexworld namespace hexworld
{ {
[VertexData()]
public struct Vert
{
[VertexAttrib(0, 3)] public Vector3 Position;
public Vert(float x, float y, float z = 0)
{
Position = new Vector3(x, y, z);
}
}
[VertexData(1)]
public struct Entity
{
[VertexAttrib(1, 3)] public Vector3 Position;
public Entity(float x, float y, float z = 0)
{
Position = new Vector3(x, y, z);
}
}
public class HexRender : GameWindow public class HexRender : GameWindow
{ {
#region Fields
#region Disposables
private Program _texPgm;
private Texture _doorTex;
private Texture _grassTex;
private Texture _stoneTex;
private Dictionary<string, VertexBuffer<ObjVertex>> _meshVbos;
private VertexBuffer<TileData>[] _tileVbos;
protected override void OnClosed(EventArgs e)
{
_texPgm?.Dispose();
_doorTex?.Dispose();
_grassTex?.Dispose();
_stoneTex?.Dispose();
foreach (var vbo in _meshVbos.Values)
vbo?.Dispose();
foreach (var vbo in _tileVbos)
vbo?.Dispose();
}
#endregion
private List<RenderGroup<TileData, ObjVertex>> _renderGroups;
private Camera _camera;
private double _time;
#endregion
public HexRender(int width, int height) public HexRender(int width, int height)
: base(width, height, new GraphicsMode(32, 24, 0, 0)) : base(width, height, new GraphicsMode(32, 24, 0, 0))
{ {
Width = width; Width = width;
Height = height; Height = Height;
X = (DisplayDevice.Default.Width - Width) / 2; X = (DisplayDevice.Default.Width - Width) / 2;
Y = (DisplayDevice.Default.Height - Height) / 2; Y = (DisplayDevice.Default.Height - Height) / 2;
} }
private Buffer<Vert> _sqrBuff;
private Buffer<uint> _sqrElem;
private VertexArray _sqrAo;
private Buffer<Entity> _entBuff;
private Program _pgm;
/// <inheritdoc />
protected override void OnUnload(EventArgs e)
{
base.OnUnload(e);
_sqrAo?.Dispose();
_sqrBuff?.Dispose();
_sqrElem?.Dispose();
_pgm?.Dispose();
}
/// <inheritdoc />
protected override void OnLoad(EventArgs e) protected override void OnLoad(EventArgs e)
{ {
base.OnLoad(e); base.OnLoad(e);
using (var vs = Shader.FromFile("res/direct.vs.glsl")) _texPgm = Program.FromFiles("res/obj.vs.glsl", "res/obj.fs.glsl");
using (var white = Shader.FromFile("res/white.fs.glsl"))
{
_pgm = Program.FromShaders(vs, white);
}
_sqrBuff = Buffer.FromData(new[] _doorTex = Texture.FromFile("res/door.png");
{ _grassTex = Texture.FromFile("res/grass.png");
new Vert(-.5f, -.5f), _stoneTex = Texture.FromFile("res/stone.png");
new Vert(+.5f, -.5f),
new Vert(+.5f, +.5f),
new Vert(-.5f, +.5f),
});
_sqrElem = Buffer.FromData(new uint[] var dir = "res";
{
0, 1, 2, 2, 3, 0
});
_entBuff = Buffer.FromData(new[] var json = JObject.Parse(File.ReadAllText("res/level.json"));
{
new Entity(-1, -1),
new Entity(-1, 1),
new Entity(1, -1),
new Entity(1, 1),
});
Program.Current = _pgm; var allTiles = json["tiles"]
_sqrAo = VertexArray.Create(); .GroupBy(ti => ti["tex"])
_sqrAo.Attach(_sqrBuff); .Select(g => g
_sqrAo.Attach(_entBuff); .Select(ti => ti["pos"])
_sqrAo.ElementArrayBuffer = _sqrElem; .Select(pos => pos.ToObject<Vector3>())
.Select(pos => new TileData(pos))
.ToArray())
.Select(arr => new SubArray<TileData>(arr))
.ToArray();
_tileVbos = VertexBuffer.FromArrays(allTiles, 0, "tiles");
var vertexBuffers = json["models"]
.Select(path => (string) path)
.Select(path => Path.Combine(dir, path))
.Select(VertexBuffer.FromWavefront)
.SelectMany(meshes => meshes)
.ToArray();
_meshVbos = vertexBuffers.ToDictionary(vbo => vbo.Name, vbo => vbo);
_camera = new Camera();
_renderGroups = new List<RenderGroup<TileData, ObjVertex>>
{
new RenderGroup<TileData, ObjVertex>()
{
Vertices = _meshVbos["Cube"],
Instance = _tileVbos[0],
Program = _texPgm,
Texture = _stoneTex,
Camera = _camera,
},
new RenderGroup<TileData, ObjVertex>()
{
Vertices = _meshVbos["Cube"],
Instance = _tileVbos[1],
Program = _texPgm,
Texture = _grassTex,
Camera = _camera,
}
};
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
_time += e.Time;
_camera.View = Matrix4.CreateRotationZ((float) _time / 3) *
Matrix4.LookAt(10 * Vector3.One, Vector3.Zero, Vector3.UnitZ);
_camera.Projection = Matrix4.CreateOrthographic(Width / 100f, Height / 100f, -100, 100);
} }
/// <inheritdoc />
protected override void OnRenderFrame(FrameEventArgs e) protected override void OnRenderFrame(FrameEventArgs e)
{ {
base.OnRenderFrame(e); base.OnRenderFrame(e);
GL.Viewport(ClientRectangle); GL.Viewport(ClientRectangle);
GL.Clear(ClearBufferMask.ColorBufferBit); GL.ClearColor(0.2392157F, 0.5607843F, 0.9960784F, 1f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
var proj = Matrix4.CreateOrthographic(6f, 6f * Height / Width, -10f, 10f); GL.Enable(EnableCap.DepthTest);
var view = Matrix4.LookAt(Vector3.Zero, -Vector3.One, Vector3.UnitZ); GL.DepthFunc(DepthFunction.Lequal);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
Program.Current = _pgm; foreach (var renderGroup in _renderGroups)
VertexArray.Current = _sqrAo; {
GL.UniformMatrix4(0, false, ref proj); renderGroup.Draw();
GL.UniformMatrix4(1, false, ref view); }
GL.DrawElementsInstanced(PrimitiveType.Triangles, _sqrElem.Size, DrawElementsType.UnsignedInt, IntPtr.Zero,
_entBuff.Size);
SwapBuffers(); SwapBuffers();
} }

View File

@@ -5,7 +5,7 @@
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AD9ED057-FB47-44CB-8839-22924B409706}</ProjectGuid> <ProjectGuid>{AD9ED057-FB47-44CB-8839-22924B409706}</ProjectGuid>
<OutputType>WinExe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>hexworld</RootNamespace> <RootNamespace>hexworld</RootNamespace>
<AssemblyName>hexworld</AssemblyName> <AssemblyName>hexworld</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
@@ -19,12 +19,10 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks> <AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@@ -35,9 +33,6 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -76,15 +71,6 @@
<None Include="res\obj.fs.glsl"> <None Include="res\obj.fs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Include="res\red.fs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\white.fs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\direct.vs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="res\obj.vs.glsl"> <None Include="res\obj.vs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>

View File

@@ -1,13 +0,0 @@
#version 440
in vec3 v;
in vec3 pos;
uniform mat4 proj;
uniform mat4 view;
void main ()
{
gl_Position = proj * view * vec4(pos + v, 1);
}

View File

@@ -1,6 +0,0 @@
#version 440
void main ()
{
gl_FragColor = vec4(1,0,0,1);
}

View File

@@ -1,6 +0,0 @@
#version 440
void main ()
{
gl_FragColor = vec4(1);
}