Added documentation to all GLObject types

This commit is contained in:
2017-03-01 16:30:54 -05:00
parent 4f9843b632
commit 88ab90f186
9 changed files with 240 additions and 59 deletions

View File

@@ -8,6 +8,10 @@ using OpenTK.Graphics.OpenGL4;
namespace Diamond.Buffers 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 public class Buffer<T> : GLObject where T : struct
{ {
private readonly BufferWrap _buffer; private readonly BufferWrap _buffer;
@@ -16,8 +20,15 @@ namespace Diamond.Buffers
private readonly int _size; private readonly int _size;
/// <summary>
/// The target for this buffer; its type
/// </summary>
public BufferTarget Target => _buffer.Target; public BufferTarget Target => _buffer.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 public BufferUsageHint Usage
{ {
get => _buffer.Usage; get => _buffer.Usage;
@@ -32,12 +43,31 @@ namespace Diamond.Buffers
_vdi = VertexDataInfo.GetInfo<T>(); _vdi = VertexDataInfo.GetInfo<T>();
} }
/// <summary>
/// Upload data to this buffer
/// </summary>
/// <param name="data">The data to upload</param>
public void Data(T[] data) => _buffer.Data(_size, data); public void Data(T[] data) => _buffer.Data(_size, 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) => _buffer.SubData(_size, offset, count, data); public void Data(int offset, int count, T[] data) => _buffer.SubData(_size, 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); 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) public void PointTo(Program program)
{ {
if (_vdi == null) if (_vdi == null)
@@ -60,6 +90,13 @@ namespace Diamond.Buffers
? $"Buffer<{typeof(T).Name}> {Target} ({Id})" ? $"Buffer<{typeof(T).Name}> {Target} ({Id})"
: $"Buffer<{typeof(T).Name}> {Target} {Name} ({Id})"; : $"Buffer<{typeof(T).Name}> {Target} {Name} ({Id})";
/// <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) internal static Buffer<T> Empty(BufferTarget target, BufferUsageHint usage, string name)
{ {
var wrapper = new BufferWrap(target, usage); var wrapper = new BufferWrap(target, usage);
@@ -70,6 +107,14 @@ namespace Diamond.Buffers
return 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) internal static Buffer<T> FromData(T[] data, BufferTarget target, BufferUsageHint usage, string name = null)
{ {
var service = Empty(target, usage, name); var service = Empty(target, usage, name);
@@ -80,11 +125,29 @@ namespace Diamond.Buffers
} }
} }
/// <summary>
/// Class for static Buffer operations and public factory methods
/// </summary>
public static class Buffer 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, public static Buffer<T> Empty<T>(BufferTarget target, BufferUsageHint usage = BufferUsageHint.StaticDraw,
string name = null) where T : struct => Buffer<T>.Empty(target, usage, name); 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, public static Buffer<T> FromData<T>(T[] data, BufferTarget target,
BufferUsageHint usage = BufferUsageHint.StaticDraw, BufferUsageHint usage = BufferUsageHint.StaticDraw,
string name = null) where T : struct => Buffer<T>.FromData(data, target, usage, name); string name = null) where T : struct => Buffer<T>.FromData(data, target, usage, name);

View File

@@ -60,13 +60,12 @@
<Compile Include="Util\SubArray.cs" /> <Compile Include="Util\SubArray.cs" />
<Compile Include="Buffers\VertexDataAttribute.cs" /> <Compile Include="Buffers\VertexDataAttribute.cs" />
<Compile Include="Wrappers\Wrapper.cs" /> <Compile Include="Wrappers\Wrapper.cs" />
<Compile Include="Level\TileData.cs" /> <Compile Include="Util\TileData.cs" />
<Compile Include="Util\Mesh.cs" /> <Compile Include="Util\Mesh.cs" />
<Compile Include="Util\ObjVertex.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="Shaders\ShaderException.cs" />
<Compile Include="Textures\Texture.cs" /> <Compile Include="Textures\Texture.cs" />
<Compile Include="Buffers\VertexPointerAttribute.cs" /> <Compile Include="Buffers\VertexPointerAttribute.cs" />
</ItemGroup> </ItemGroup>

View File

@@ -4,17 +4,33 @@ using NLog;
namespace Diamond namespace Diamond
{ {
/// <summary>
/// Provide managed access to OpenGL objects
/// </summary>
public abstract class GLObject : IDisposable public abstract class GLObject : IDisposable
{ {
/// <summary>
/// Logger for all GLObjects
/// </summary>
protected static readonly Logger Logger = LogManager.GetLogger("GLObject"); protected static readonly Logger Logger = LogManager.GetLogger("GLObject");
private bool _disposed; /// <summary>
/// Name of this GLObject used for identification
/// </summary>
public string Name { get; protected set; } = "GLObject"; public string Name { get; protected set; } = "GLObject";
/// <summary>
/// Underlying managed wrapper to this OpenGL object
/// </summary>
internal abstract Wrapper Wrapper { get; } internal abstract Wrapper Wrapper { get; }
/// <summary>
/// The OpenGL name of this object
/// </summary>
public int Id => Wrapper.Id; public int Id => Wrapper.Id;
#region IDisposable
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (_disposed) if (_disposed)
@@ -29,6 +45,10 @@ namespace Diamond
_disposed = true; _disposed = true;
} }
#region Implemented
private bool _disposed;
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@@ -39,5 +59,9 @@ namespace Diamond
{ {
Dispose(false); Dispose(false);
} }
#endregion
#endregion
} }
} }

View File

@@ -6,14 +6,22 @@ using OpenTK.Graphics.OpenGL4;
namespace Diamond.Shaders namespace Diamond.Shaders
{ {
/// <summary>
/// Manages an OpenGL Program object
/// </summary>
public class Program : GLObject public class Program : GLObject
{ {
private readonly ProgramWrap _program; private readonly ProgramWrap _program;
internal override Wrapper Wrapper => _program; internal override Wrapper Wrapper => _program;
/// <summary>
/// The currently active program. Manually invoking glUseProgram will break this.
/// </summary>
public static Program Current { get; private set; } public static Program Current { get; private set; }
// keep a cache of uniform and attributes to prevent repeated queries
private readonly Dictionary<string, int> _uniforms = new Dictionary<string, int>(); private readonly Dictionary<string, int> _uniforms = new Dictionary<string, int>();
private readonly Dictionary<string, int> _attributes = new Dictionary<string, int>(); private readonly Dictionary<string, int> _attributes = new Dictionary<string, int>();
internal Program(ProgramWrap program, string name) internal Program(ProgramWrap program, string name)
@@ -22,24 +30,42 @@ namespace Diamond.Shaders
Name = name; Name = name;
} }
// todo change these to not use int? - possibly use TryGet, or return negative value if not present
/// <summary>
/// Get the location of a uniform
/// </summary>
/// <param name="name">The name of the uniform</param>
/// <returns>The location, or no value if uniform not present</returns>
public int? UniformLocation(string name) public int? UniformLocation(string name)
{ {
if (_uniforms.ContainsKey(name)) return _uniforms[name]; if (_uniforms.ContainsKey(name)) return _uniforms[name];
return null; return null;
} }
/// <summary>
/// Get the location of an attribute
/// </summary>
/// <param name="name">The name of the attribute</param>
/// <returns>The location, or no value if attribute not present</returns>
public int? AttributeLocation(string name) public int? AttributeLocation(string name)
{ {
if (_attributes.ContainsKey(name)) return _attributes[name]; if (_attributes.ContainsKey(name)) return _attributes[name];
return null; return null;
} }
/// <summary>
/// Use this Program to render. Also updates Program.Current
/// </summary>
public void Use() public void Use()
{ {
GL.UseProgram(Id); GL.UseProgram(Id);
Current = this; Current = this;
} }
/// <summary>
/// Use the default shader to render
/// </summary>
//? Could create static Program instance which wraps the default shader //? Could create static Program instance which wraps the default shader
// ie Shader.Default.Use() // ie Shader.Default.Use()
// would also allow sending arrays to the default attribs like gl_Vertex etc. // would also allow sending arrays to the default attribs like gl_Vertex etc.
@@ -49,6 +75,10 @@ namespace Diamond.Shaders
Current = null; Current = null;
} }
/// <summary>
/// Helper method to try to link this program
/// </summary>
/// <returns></returns>
private bool Link() private bool Link()
{ {
_uniforms.Clear(); _uniforms.Clear();
@@ -68,6 +98,10 @@ namespace Diamond.Shaders
return true; 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) private void Attach(Shader shader)
{ {
_program.Attach((ShaderWrap) shader.Wrapper); _program.Attach((ShaderWrap) shader.Wrapper);
@@ -75,16 +109,23 @@ namespace Diamond.Shaders
public override string ToString() => $"Program \'{Name}\' ({Id})"; public override string ToString() => $"Program \'{Name}\' ({Id})";
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
#region Factory Methods #region Factory Methods
/// <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, params Shader[] shaders) => FromShaders(name, public static Program FromShaders(string name, params Shader[] shaders) => FromShaders(name,
(IEnumerable<Shader>) shaders); (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) public static Program FromShaders(string name, IEnumerable<Shader>shaders)
{ {
if (shaders == null) if (shaders == null)
@@ -110,9 +151,9 @@ namespace Diamond.Shaders
service.Attach(shader); service.Attach(shader);
} }
service.Link(); var linked = service.Link();
if (!wrapper.Linked) if (!linked)
{ {
Logger.Warn("Failed to link {0}", service); Logger.Warn("Failed to link {0}", service);
Logger.Debug("InfoLog for {0}", service); Logger.Debug("InfoLog for {0}", service);
@@ -125,9 +166,18 @@ namespace Diamond.Shaders
return 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); 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) public static Program FromShaders(IEnumerable<Shader> shaders)
{ {
var shaderList = shaders.ToList(); // prevent multiple enumeration var shaderList = shaders.ToList(); // prevent multiple enumeration
@@ -136,14 +186,26 @@ namespace Diamond.Shaders
return FromShaders(name, shaderList); 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) public static Program FromFiles(params string[] paths)
{ {
var shaders = paths.Select(Shader.FromFile).ToList(); 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

@@ -6,12 +6,22 @@ using OpenTK.Graphics.OpenGL4;
namespace Diamond.Shaders namespace Diamond.Shaders
{ {
/// <summary>
/// Manges a OpenGL Shader object
/// </summary>
public class Shader : GLObject public class Shader : GLObject
{ {
private readonly ShaderWrap _shader; private readonly ShaderWrap _shader;
internal override Wrapper Wrapper => _shader; internal override Wrapper Wrapper => _shader;
/// <summary>
/// The source used to create this shader
/// </summary>
public string Source { get; } public string Source { get; }
/// <summary>
/// The type of this shader
/// </summary>
public ShaderType Type { get; } public ShaderType Type { get; }
internal Shader(ShaderWrap shader, string source, ShaderType type, string name) internal Shader(ShaderWrap shader, string source, ShaderType type, string name)
@@ -26,14 +36,24 @@ namespace Diamond.Shaders
#region Factory Methods #region Factory Methods
// Used to infer shader type based on file extension
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,
[".fs"] = ShaderType.FragmentShader, [".fs"] = ShaderType.FragmentShader,
[".frag"] = ShaderType.FragmentShader, [".frag"] = ShaderType.FragmentShader,
[".gs"] = ShaderType.GeometryShader,
[".geom"] = ShaderType.GeometryShader,
}; };
/// <summary>
/// Create and compile a shader from glsl source code
/// </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") public static Shader FromSource(string source, ShaderType type, string name = "Shader")
{ {
var wrapper = new ShaderWrap(type); var wrapper = new ShaderWrap(type);
@@ -57,7 +77,14 @@ namespace Diamond.Shaders
return service; return service;
} }
public static Shader FromFile(string path, ShaderType type) /// <summary>
/// Create and compile a shader from a glsl 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)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{ {
@@ -65,11 +92,21 @@ namespace Diamond.Shaders
return null; return null;
} }
var name = Path.GetFileNameWithoutExtension(path); if (name == null)
name = Path.GetFileNameWithoutExtension(path);
return FromSource(File.ReadAllText(path), type, name); return FromSource(File.ReadAllText(path), type, name);
} }
public static Shader FromFile(string path) /// <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.
/// </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)
{ {
if (!File.Exists(path)) if (!File.Exists(path))
{ {
@@ -78,12 +115,14 @@ namespace Diamond.Shaders
} }
var ext = Path.GetExtension(path); var ext = Path.GetExtension(path);
var name = Path.GetFileNameWithoutExtension(path); var fileName = Path.GetFileNameWithoutExtension(path);
// 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(name); ext = Path.GetExtension(fileName);
// if no extension, no sub-extension, or invalid sub-extension
if (ext == null || !Extensions.ContainsKey(ext)) if (ext == null || !Extensions.ContainsKey(ext))
{ {
Logger.Warn("Could not infer shader type from glsl file name {0}", path); Logger.Warn("Could not infer shader type from glsl file name {0}", path);
@@ -91,8 +130,10 @@ namespace Diamond.Shaders
} }
var type = Extensions[ext]; var type = Extensions[ext];
if (name == null)
name = fileName;
return FromFile(path, type); return FromFile(path, type, name);
} }
#endregion #endregion

View File

@@ -1,33 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace Diamond.Shaders
{
/// <summary>
/// Exception relating to <code>Shader</code> and <code>Program</code> operations.
/// </summary>
[Serializable]
public class ShaderException : Exception
{
public ShaderException()
{
}
public ShaderException(string message)
: base(message)
{
}
public ShaderException(string message, Exception inner)
: base(message, inner)
{
}
protected ShaderException(
SerializationInfo info,
StreamingContext context)
: base(info, context)
{
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO;
using Diamond.Wrappers; using Diamond.Wrappers;
using NLog; using NLog;
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
@@ -7,6 +8,9 @@ using PixelFormat = OpenTK.Graphics.OpenGL4.PixelFormat;
namespace Diamond.Textures namespace Diamond.Textures
{ {
/// <summary>
/// Manages a OpenGL Texture object
/// </summary>
public class Texture : GLObject public class Texture : GLObject
{ {
private readonly TextureWrap _texture; private readonly TextureWrap _texture;
@@ -18,15 +22,26 @@ namespace Diamond.Textures
Name = name; Name = name;
} }
/// <summary>
/// This textures target; how it is used
/// </summary>
public TextureTarget Target => _texture.Target; public TextureTarget Target => _texture.Target;
public void Bind() => _texture.Bind(); /// <summary>
/// Bind this texture to a particular unit
/// </summary>
public void Bind(int unit) => _texture.Bind(unit); public void Bind(int unit) => _texture.Bind(unit);
public override string ToString() => Name == null ? $"{Target} ({Id})" : $"{Target} \'{Name}\' ({Id})"; public override string ToString() => Name == null ? $"{Target} ({Id})" : $"{Target} \'{Name}\' ({Id})";
#region Factory Methods #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) public static Texture FromBitmap(Bitmap bmp, string name = null)
{ {
var wrapper = new TextureWrap(TextureTarget.Texture2D); var wrapper = new TextureWrap(TextureTarget.Texture2D);
@@ -35,6 +50,8 @@ namespace Diamond.Textures
Logger.Debug("Created Texture {0}", service); Logger.Debug("Created Texture {0}", service);
wrapper.Bind(); wrapper.Bind();
// todo: expose texture parameters to enable setting different filters
wrapper.TexParameter(TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest); wrapper.TexParameter(TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest);
wrapper.TexParameter(TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Nearest); wrapper.TexParameter(TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Nearest);
@@ -47,9 +64,18 @@ namespace Diamond.Textures
return service; return service;
} }
public static Texture FromFile(string path) /// <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)
{ {
return FromBitmap(new Bitmap(path)); if (name == null)
name = Path.GetFileNameWithoutExtension(path);
return FromBitmap(new Bitmap(path), name);
} }
#endregion #endregion

View File

@@ -1,7 +1,7 @@
using Diamond.Buffers; using Diamond.Buffers;
using OpenTK; using OpenTK;
namespace Diamond.Level namespace Diamond.Util
{ {
[VertexData(Divisor = 1)] [VertexData(Divisor = 1)]
public struct TileData public struct TileData

View File

@@ -2,7 +2,6 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Diamond.Buffers; using Diamond.Buffers;
using Diamond.Level;
using Diamond.Shaders; using Diamond.Shaders;
using Diamond.Textures; using Diamond.Textures;
using Diamond.Util; using Diamond.Util;