Compare commits

11 Commits

33 changed files with 2198 additions and 2729 deletions

View File

@@ -0,0 +1,43 @@
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

@@ -0,0 +1,29 @@
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;
}
}
}

286
Diamond/Buffer.cs Normal file
View File

@@ -0,0 +1,286 @@
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);
}
}
}

View File

@@ -1,161 +0,0 @@
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

@@ -1,25 +0,0 @@
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

@@ -1,127 +0,0 @@
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

@@ -1,54 +0,0 @@
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,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Diamond.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -35,7 +36,7 @@
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.3\lib\net45\NLog.dll</HintPath>
<HintPath>..\packages\NLog.4.4.4\lib\net45\NLog.dll</HintPath>
</Reference>
<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>
@@ -51,26 +52,16 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Buffers\Buffer.cs" />
<Compile Include="Buffers\VertexDataInfo.cs" />
<Compile Include="Render\Camera.cs" />
<Compile Include="Render\RenderGroup.cs" />
<Compile Include="Wrappers\BufferWrap.cs" />
<Compile Include="Attributes\VertexAttribAttribute.cs" />
<Compile Include="Attributes\VertexDataAttribute.cs" />
<Compile Include="Buffer.cs" />
<Compile Include="Extensions.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="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="Properties\AssemblyInfo.cs" />
<Compile Include="Shaders\Shader.cs" />
<Compile Include="Textures\Texture.cs" />
<Compile Include="Buffers\VertexPointerAttribute.cs" />
<Compile Include="VertexArray.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="NLog.config">

26
Diamond/Extensions.cs Normal file
View File

@@ -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<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,26 +1,64 @@
using System;
using NLog;
using OpenTK.Graphics;
namespace Diamond
{
/// <summary>
/// Provide managed access to OpenGL objects
/// A wrapper class for any OpenGL object
/// </summary>
public abstract class GLObject : IDisposable
{
/// <summary>
/// Logger for all GLObjects
/// </summary>
protected static readonly Logger Logger = LogManager.GetLogger(nameof(GLObject));
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// Name of this GLObject used for identification
/// The OpenGL object name
/// </summary>
public string Name { get; protected set; } = nameof(GLObject);
public int Id { get; private set; }
/// <summary>
/// Delegate Dispose to underlying wrapper class
/// Force object name assignment
/// </summary>
public abstract void Dispose();
/// <param name="id">The OpenGL object name</param>
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,21 +1,41 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLogger.xsd"
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLogger.xsd NLog.xsd"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLoggerLevel="Off" internalLoggerFile="c:\temp\nlog-internal.log">
internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
<variable name="Layout" value="[${level}] ${logger}: ${message}" />
<!-- optional, add some variables
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>
<target xsi:type="File" name="file" fileName="$Log/${shortdate}.log"
layout="${Layout}"/>
<target xsi:type="Debugger" name="debug" layout="${Layout}"/>
<!--
add your targets here
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>
<rules>
<logger name="*" minlevel="Info" writeTo="file"/>
<logger name="*" minlevel="Trace" writeTo="debug"/>
<!-- add your logging rules here -->
<!--
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>
</nlog>

File diff suppressed because it is too large Load Diff

View File

@@ -1,71 +0,0 @@
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

@@ -1,61 +0,0 @@
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

@@ -1,228 +0,0 @@
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,233 +1,307 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Diamond.Wrappers;
using System.Text;
using NLog;
using OpenTK.Graphics.OpenGL4;
namespace Diamond.Shaders
{
/// <summary>
/// Manages an OpenGL Program object
/// Wrap and OpenGL program object
/// </summary>
public class Program : GLObject
public sealed class Program : GLObject
{
internal readonly ProgramWrap Wrapper;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
#region Static
private static Program _current;
/// <summary>
/// The currently active program. Manually invoking glUseProgram will break this.
/// The currently active program, or null if the default program is active.
/// </summary>
public static Program Current { get; private set; }
public static Program Current
{
get => _current;
set => Use(value);
}
// keep a cache of uniform and attributes to prevent repeated queries
private readonly Dictionary<string, int> _uniforms = new Dictionary<string, int>();
/// <summary>
/// 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>();
internal Program(ProgramWrap wrapper, string name)
{
Wrapper = wrapper;
Name = name;
}
private readonly Dictionary<string, int> _uniforms = new Dictionary<string, int>();
/// <summary>
/// Check if the program has a uniform
/// Check if this program has an active attribute
/// </summary>
/// <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);
}
/// <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>
/// Get the location of a uniform
/// Check if this program has an active uniform
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <returns>The location of the uniform</returns>
public int UniformLocation(string name)
{
if (HasUniform(name)) return _uniforms[name];
throw new KeyNotFoundException($"Shader {this} does not contain uniform {name}");
}
/// <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>
/// Check if the program has an attribute
/// 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 name of the attribute</param>
/// <returns>Whether the program has this attribute</returns>
public bool HasAttribute(string name)
{
return _attributes.ContainsKey(name);
}
/// <summary>
/// Get the location of an attribute
/// </summary>
/// <param name="name">The name of the attribute</param>
/// <param name="name">The attribute name</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}");
if (!HasAttribute(name))
throw new InvalidOperationException($"{this} does not have active attribute '{name}'");
return _attributes[name];
}
/// <summary>
/// Use this Program to render. Also updates Program.Current
/// Get the location of an uniform by name. Throws InvalidOperationException if the uniform
/// is not found. This can be checked with HasUniform
/// </summary>
public void Use()
/// <param name="name">The uniform name</param>
/// <returns>The location of the uniform</returns>
public int UniformLocation(string name)
{
Wrapper.Use();
Current = this;
if (!HasUniform(name)) throw new InvalidOperationException($"{this} does not have active uniform '{name}'");
return _uniforms[name];
}
/// <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;
}
#endregion
/// <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
/// 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.
/// </summary>
/// <param name="shader">The shader to attach</param>
private void Attach(Shader shader)
public void Attach(Shader shader)
{
Wrapper.Attach(shader.Wrapper);
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);
}
public override string ToString() => $"Program {Wrapper} \'{Name}\'";
/// <inheritdoc />
public override string ToString() =>
$"'Program {Id}'";
public override void Dispose()
{
Logger.Debug("Disposing {0}", this);
Wrapper.Dispose();
}
#endregion
#region Factory Methods
/// <summary>
/// Create a program from compiled 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.
/// </summary>
/// <param name="name">The name of this GLObject</param>
/// <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(string name, params Shader[] shaders) => FromShaders(name,
(IEnumerable<Shader>) shaders);
/// <summary>
/// Create a program from compiled shaders
/// </summary>
/// <param name="name">The name of this GLObject</param>
/// <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(string name, IEnumerable<Shader>shaders)
{
if (shaders == null)
{
Logger.Error("Cannot create program {0} with no shaders.", name);
return null;
}
var wrapper = new ProgramWrap();
var service = new Program(wrapper, name);
Logger.Debug("Created {0}", service);
foreach (var shader in shaders)
{
if (shader == null)
{
Logger.Error("One or more shaders failed to compile - cannot create program {0}", name);
service.Dispose();
return null;
}
service.Attach(shader);
}
var linked = service.Link();
if (!linked)
{
Logger.Warn("Failed to link {0}", service);
Logger.Debug("InfoLog for {0}", service);
service.Dispose();
return null;
}
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>
/// <param name="shaders">The shaders used in this program</param>
/// <returns>A 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
/// 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.
/// </summary>
/// <param name="shaders">The shaders to use in this program</param>
/// <returns>The linked program, or null if initialization failed</returns>
/// <param name="shaders">The shaders used in this program</param>
/// <returns>A 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)
if (shaders == null)
{
Logger.Warn("Cannot create a program from no shaders.");
Logger.Error("Cannot create program from no shaders.");
return null;
}
var shaders = paths.Select(path => Shader.FromFile(path)).ToList();
var program = new Program();
foreach (var shader in shaders)
{
program.Attach(shader);
}
program.Link();
if (program.Linked) return program;
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");
return null;
}
var shaders = files.Select(Shader.FromFile).ToList();
var program = FromShaders(shaders);
foreach (var shader in shaders)
{
shader?.Dispose();
}
return program;
}

View File

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

View File

@@ -1,89 +0,0 @@
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
}
}

View File

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

View File

@@ -1,28 +0,0 @@
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;
}
}
}

162
Diamond/VertexArray.cs Normal file
View File

@@ -0,0 +1,162 @@
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

@@ -1,79 +0,0 @@
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

@@ -1,103 +0,0 @@
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

@@ -1,86 +0,0 @@
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

@@ -1,78 +0,0 @@
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

@@ -1,70 +0,0 @@
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"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
<package id="NLog" version="4.4.3" targetFramework="net461" />
<package id="NLog.Config" version="4.4.3" targetFramework="net461" />
<package id="NLog.Schema" version="4.4.3" targetFramework="net461" />
<package id="NLog" version="4.4.4" targetFramework="net461" />
<package id="NLog.Config" version="4.4.4" targetFramework="net461" />
<package id="NLog.Schema" version="4.4.4" targetFramework="net461" />
<package id="OpenTK" version="2.0.0" targetFramework="net452" />
</packages>

View File

@@ -1,154 +1,125 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Diamond.Render;
using System.Diagnostics;
using Diamond.Shaders;
using Diamond.Textures;
using Diamond.Util;
using Newtonsoft.Json.Linq;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL4;
using Diamond;
using Diamond.Attributes;
using Buffer = Diamond.Buffer;
namespace hexworld
{
[VertexData()]
public struct Vert
{
[VertexAttrib(0, 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
{
#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)
: base(width, height, new GraphicsMode(32, 24, 0, 0))
{
Width = width;
Height = Height;
Height = height;
X = (DisplayDevice.Default.Width - Width) / 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)
{
base.OnLoad(e);
_texPgm = Program.FromFiles("res/obj.vs.glsl", "res/obj.fs.glsl");
_doorTex = Texture.FromFile("res/door.png");
_grassTex = Texture.FromFile("res/grass.png");
_stoneTex = Texture.FromFile("res/stone.png");
var dir = "res";
var json = JObject.Parse(File.ReadAllText("res/level.json"));
var allTiles = json["tiles"]
.GroupBy(ti => ti["tex"])
.Select(g => g
.Select(ti => ti["pos"])
.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>>
using (var vs = Shader.FromFile("res/direct.vs.glsl"))
using (var white = Shader.FromFile("res/white.fs.glsl"))
{
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);
_pgm = Program.FromShaders(vs, white);
}
_sqrBuff = Buffer.FromData(new[]
{
new Vert(-.5f, -.5f),
new Vert(+.5f, -.5f),
new Vert(+.5f, +.5f),
new Vert(-.5f, +.5f),
});
_sqrElem = Buffer.FromData(new uint[]
{
0, 1, 2, 2, 3, 0
});
_entBuff = Buffer.FromData(new[]
{
new Entity(-1, -1),
new Entity(-1, 1),
new Entity(1, -1),
new Entity(1, 1),
});
Program.Current = _pgm;
_sqrAo = VertexArray.Create();
_sqrAo.Attach(_sqrBuff);
_sqrAo.Attach(_entBuff);
_sqrAo.ElementArrayBuffer = _sqrElem;
}
/// <inheritdoc />
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Viewport(ClientRectangle);
GL.ClearColor(0.2392157F, 0.5607843F, 0.9960784F, 1f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.Enable(EnableCap.DepthTest);
GL.DepthFunc(DepthFunction.Lequal);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
var proj = Matrix4.CreateOrthographic(6f, 6f * Height / Width, -10f, 10f);
var view = Matrix4.LookAt(Vector3.Zero, -Vector3.One, Vector3.UnitZ);
foreach (var renderGroup in _renderGroups)
{
renderGroup.Draw();
}
Program.Current = _pgm;
VertexArray.Current = _sqrAo;
GL.UniformMatrix4(0, false, ref proj);
GL.UniformMatrix4(1, false, ref view);
GL.DrawElementsInstanced(PrimitiveType.Triangles, _sqrElem.Size, DrawElementsType.UnsignedInt, IntPtr.Zero,
_entBuff.Size);
SwapBuffers();
}

View File

@@ -5,7 +5,7 @@
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AD9ED057-FB47-44CB-8839-22924B409706}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutputType>WinExe</OutputType>
<RootNamespace>hexworld</RootNamespace>
<AssemblyName>hexworld</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
@@ -19,10 +19,12 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -33,6 +35,9 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<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>
@@ -71,6 +76,15 @@
<None Include="res\obj.fs.glsl">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</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">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View File

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

6
hexworld/res/red.fs.glsl Normal file
View File

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

View File

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