diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj
index 52b715e..1ec5ad3 100644
--- a/Blarg.GameFramework/Blarg.GameFramework.csproj
+++ b/Blarg.GameFramework/Blarg.GameFramework.csproj
@@ -87,6 +87,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -94,5 +131,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Blarg.GameFramework/Graphics/AutoGridTextureAtlas.cs b/Blarg.GameFramework/Graphics/AutoGridTextureAtlas.cs
new file mode 100644
index 0000000..c0651ec
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/AutoGridTextureAtlas.cs
@@ -0,0 +1,63 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class AutoGridTextureAtlas : TextureAtlas
+ {
+ public int TileWidth { get; private set; }
+ public int TileHeight { get; private set; }
+
+ public AutoGridTextureAtlas(int textureWidth, int textureHeight, int tileWidth, int tileHeight, int tileBorder = 0, float texCoordEdgeOffset = TexCoordEdgeBleedOffset)
+ : base(textureWidth, textureHeight, texCoordEdgeOffset)
+ {
+ Generate(tileWidth, tileHeight, tileBorder);
+ }
+
+ public AutoGridTextureAtlas(Texture texture, int tileHeight, int tileWidth, int tileBorder = 0, float texCoordEdgeOffset = TexCoordEdgeBleedOffset)
+ : base(texture, texCoordEdgeOffset)
+ {
+ Generate(tileWidth, tileHeight, tileBorder);
+ }
+
+ private void Generate(int tileWidth, int tileHeight, int tileBorder)
+ {
+ TileWidth = tileWidth;
+ TileHeight = tileHeight;
+
+ tileWidth += tileBorder;
+ tileHeight += tileBorder;
+
+ int tilesX = (Width - tileBorder) / (TileWidth + tileBorder);
+ int tilesY = (Height - tileBorder) / (TileHeight + tileBorder);
+
+ for (int y = 0; y < tilesY; ++y)
+ {
+ for (int x = 0; x < tilesX; ++x)
+ {
+ var tile = new TextureRegion();
+
+ // set pixel location/dimensions
+ tile.Dimensions.Left = tileBorder + x * tileWidth;
+ tile.Dimensions.Top = tileBorder + y * tileHeight;
+ tile.Dimensions.Right = tile.Dimensions.Left + tileWidth - tileBorder;
+ tile.Dimensions.Bottom = tile.Dimensions.Top + tileHeight - tileBorder;
+
+ // set texture coordinates
+ // HACK: subtract TexCoordEdgeOffset from the bottom right edges to
+ // get around floating point rounding errors (adjacent tiles will
+ // slightly bleed in otherwise)
+ tile.TexCoords.Left = (tile.Dimensions.Left - tileBorder + TexCoordEdgeOffset) / (float)Width;
+ tile.TexCoords.Top = (tile.Dimensions.Top - tileBorder + TexCoordEdgeOffset) / (float)Height;
+ tile.TexCoords.Right = ((float)tile.Dimensions.Right + tileBorder - TexCoordEdgeOffset) / (float)Width;
+ tile.TexCoords.Bottom = ((float)tile.Dimensions.Bottom + tileBorder - TexCoordEdgeOffset) / (float)Height;
+
+ // with the particular order that our for loops are nested in, this will
+ // be the same as if we were using ((y * tilesX) + x) to manually set an
+ // index each loop iteration (but using a List<> doesn't let us preallocate
+ // in such a way that allows us to avoid using Add() ...)
+ Tiles.Add(tile);
+ }
+ }
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BlendState.cs b/Blarg.GameFramework/Graphics/BlendState.cs
new file mode 100644
index 0000000..92a7089
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BlendState.cs
@@ -0,0 +1,120 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum BlendFactor
+ {
+ Zero,
+ One,
+ SrcColor,
+ InverseSrcColor,
+ DstColor,
+ InverseDstColor,
+ SrcAlpha,
+ InverseSrcAlpha,
+ DstAlpha,
+ InverseDstAlpha,
+ SrcAlphaSaturation,
+ ConstantColor,
+ ConstantAlpha
+ }
+
+ public class BlendState
+ {
+ public static readonly BlendState Default;
+ public static readonly BlendState Opaque;
+ public static readonly BlendState AlphaBlend;
+
+ public bool Blending { get; set; }
+ public BlendFactor SourceBlendFactor { get; set; }
+ public BlendFactor DestinationBlendFactor { get; set; }
+
+ static BlendState()
+ {
+ Default = new BlendState();
+ Opaque = new BlendState();
+ AlphaBlend = new BlendState(BlendFactor.SrcAlpha, BlendFactor.InverseSrcAlpha);
+ }
+
+ public BlendState()
+ {
+ Init();
+ }
+
+ public BlendState(BlendFactor sourceFactor, BlendFactor destinationFactor)
+ {
+ Init();
+ Blending = true;
+ SourceBlendFactor = sourceFactor;
+ DestinationBlendFactor = destinationFactor;
+ }
+
+ public void Apply()
+ {
+ if (Blending)
+ {
+ Platform.GL.glEnable(GL20.GL_BLEND);
+
+ var source = GL20.GL_ONE;
+ var dest = GL20.GL_ZERO;
+
+ // OpenTK is missing enum values for these combos (maybe they're not valid in OpenGL ??)
+ System.Diagnostics.Debug.Assert(SourceBlendFactor != BlendFactor.SrcColor);
+ System.Diagnostics.Debug.Assert(SourceBlendFactor != BlendFactor.InverseSrcColor);
+ System.Diagnostics.Debug.Assert(DestinationBlendFactor != BlendFactor.SrcAlphaSaturation);
+
+ switch (SourceBlendFactor)
+ {
+ case BlendFactor.Zero: source = GL20.GL_ZERO; break;
+ case BlendFactor.One: source = GL20.GL_ONE; break;
+ case BlendFactor.DstColor: source = GL20.GL_DST_COLOR; break;
+ case BlendFactor.InverseDstColor: source = GL20.GL_ONE_MINUS_DST_COLOR; break;
+ case BlendFactor.SrcAlpha: source = GL20.GL_SRC_ALPHA; break;
+ case BlendFactor.InverseSrcAlpha: source = GL20.GL_ONE_MINUS_SRC_ALPHA; break;
+ case BlendFactor.DstAlpha: source = GL20.GL_DST_ALPHA; break;
+ case BlendFactor.InverseDstAlpha: source = GL20.GL_ONE_MINUS_DST_ALPHA; break;
+ case BlendFactor.ConstantAlpha: source = GL20.GL_CONSTANT_ALPHA; break;
+ case BlendFactor.ConstantColor: source = GL20.GL_CONSTANT_COLOR; break;
+ case BlendFactor.SrcAlphaSaturation: source = GL20.GL_SRC_ALPHA_SATURATE; break;
+ }
+
+ switch (DestinationBlendFactor)
+ {
+ case BlendFactor.Zero: dest = GL20.GL_ZERO; break;
+ case BlendFactor.One: dest = GL20.GL_ONE; break;
+ case BlendFactor.SrcColor: dest = GL20.GL_SRC_COLOR; break;
+ case BlendFactor.InverseSrcColor: dest = GL20.GL_ONE_MINUS_SRC_COLOR; break;
+ case BlendFactor.DstColor: dest = GL20.GL_DST_COLOR; break;
+ case BlendFactor.InverseDstColor: dest = GL20.GL_ONE_MINUS_DST_COLOR; break;
+ case BlendFactor.SrcAlpha: dest = GL20.GL_SRC_ALPHA; break;
+ case BlendFactor.InverseSrcAlpha: dest = GL20.GL_ONE_MINUS_SRC_ALPHA; break;
+ case BlendFactor.DstAlpha: dest = GL20.GL_DST_ALPHA; break;
+ case BlendFactor.InverseDstAlpha: dest = GL20.GL_ONE_MINUS_DST_ALPHA; break;
+ case BlendFactor.ConstantAlpha: dest = GL20.GL_CONSTANT_ALPHA; break;
+ case BlendFactor.ConstantColor: dest = GL20.GL_CONSTANT_COLOR; break;
+ }
+
+ Platform.GL.glBlendFunc(source, dest);
+ }
+ else
+ Platform.GL.glDisable(GL20.GL_BLEND);
+ }
+
+ private void Init()
+ {
+ Blending = false;
+ SourceBlendFactor = BlendFactor.One;
+ DestinationBlendFactor = BlendFactor.Zero;
+ }
+
+ public BlendState Clone()
+ {
+ var clone = new BlendState();
+ clone.Blending = Blending;
+ clone.DestinationBlendFactor = DestinationBlendFactor;
+ clone.SourceBlendFactor = SourceBlendFactor;
+ return clone;
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BufferObject.cs b/Blarg.GameFramework/Graphics/BufferObject.cs
new file mode 100644
index 0000000..1635847
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BufferObject.cs
@@ -0,0 +1,233 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum BufferObjectType
+ {
+ Vertex,
+ Index
+ }
+
+ public enum BufferObjectUsage
+ {
+ Static,
+ Stream,
+ Dynamic
+ }
+
+ public abstract class BufferObject : GraphicsContextResource where T : struct
+ {
+ public int ID { get; private set; }
+ public bool IsClientSide { get; private set; }
+ public bool IsDirty { get; protected set; }
+ public BufferObjectType Type { get; private set; }
+ public BufferObjectUsage Usage { get; private set; }
+ public int SizeInBytes { get; private set; }
+
+ public bool IsInvalidated
+ {
+ get { return ID == -1; }
+ }
+
+ public abstract int NumElements { get; }
+ public abstract int ElementWidthInBytes { get; }
+ public abstract T[] Data { get; }
+
+ public BufferObject(BufferObjectType type, BufferObjectUsage usage)
+ : base()
+ {
+ Initialize(type, usage);
+ }
+
+ public BufferObject(GraphicsDevice graphicsDevice, BufferObjectType type, BufferObjectUsage usage)
+ : base(graphicsDevice)
+ {
+ Initialize(type, usage);
+ }
+
+ private void Initialize(BufferObjectType type, BufferObjectUsage usage)
+ {
+ Type = type;
+ Usage = usage;
+ ID = -1;
+ IsClientSide = true;
+ IsDirty = false;
+ }
+
+ protected void CreateOnGpu()
+ {
+ if (GraphicsDevice == null)
+ throw new InvalidOperationException("Buffer object was not created with a GraphicsDevice object.");
+ if (!IsInvalidated)
+ throw new InvalidOperationException();
+ CreateBufferObject();
+ }
+
+ protected void RecreateOnGpu()
+ {
+ if (GraphicsDevice == null)
+ throw new InvalidOperationException("Buffer object was not created with a GraphicsDevice object.");
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+ FreeBufferObject();
+ CreateBufferObject();
+ }
+
+ protected void FreeFromGpu()
+ {
+ if (GraphicsDevice == null)
+ throw new InvalidOperationException("Buffer object was not created with a GraphicsDevice object.");
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+ FreeBufferObject();
+ }
+
+ public void Update()
+ {
+ if (!IsDirty)
+ return;
+ if (IsClientSide)
+ {
+ // pretend we updated! (I guess this is pointless anyway)
+ IsDirty = false;
+ return;
+ }
+
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+ if (NumElements <= 0)
+ throw new InvalidOperationException();
+ if (ElementWidthInBytes <= 0)
+ throw new InvalidOperationException();
+
+ int currentSizeInBytes = NumElements * ElementWidthInBytes;
+
+ var usage = GLUsageHint;
+ var target = GLTarget;
+
+ Platform.GL.glBindBuffer(target, ID);
+
+ if (SizeInBytes != currentSizeInBytes)
+ {
+ // means that the buffer object hasn't been allocated. So let's allocate and update at the same time
+ // figure out the size...
+ SizeInBytes = currentSizeInBytes;
+
+ // and then allocate + update
+ Platform.GL.glBufferData(target, SizeInBytes, Data, usage);
+ }
+ else
+ {
+ // possible performance enhancement? passing a NULL pointer to
+ // glBufferData tells the driver that we don't care about the buffer's
+ // previous contents allowing it to do some extra optimizations which is
+ // fine since our glBufferSubData call is going to completely replace
+ // the contents anyway
+ Platform.GL.glBufferData(target, SizeInBytes, IntPtr.Zero, usage);
+ Platform.GL.glBufferSubData(target, 0, SizeInBytes, Data);
+ }
+
+ Platform.GL.glBindBuffer(target, 0);
+
+ IsDirty = false;
+ }
+
+ protected void CreateBufferObject()
+ {
+ ID = Platform.GL.glGenBuffers();
+
+ SizeBufferObject();
+
+ IsDirty = true;
+ IsClientSide = false;
+ }
+
+ protected void FreeBufferObject()
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+
+ Platform.GL.glDeleteBuffers(ID);
+
+ ID = -1;
+ IsClientSide = true;
+ IsDirty = false;
+ SizeInBytes = 0;
+ }
+
+ protected void SizeBufferObject()
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+ if (NumElements <= 0)
+ throw new InvalidOperationException();
+ if (ElementWidthInBytes <= 0)
+ throw new InvalidOperationException();
+
+ var usage = GLUsageHint;
+ var target = GLTarget;
+
+ SizeInBytes = NumElements * ElementWidthInBytes;
+
+ // resize the buffer object without initializing it's data
+ Platform.GL.glBindBuffer(target, ID);
+ Platform.GL.glBufferData(target, SizeInBytes, IntPtr.Zero, usage);
+ Platform.GL.glBindBuffer(target, 0);
+
+ IsDirty = true;
+ }
+
+ private int GLUsageHint
+ {
+ get
+ {
+ if (Usage == BufferObjectUsage.Static)
+ return GL20.GL_STATIC_DRAW;
+ else if (Usage == BufferObjectUsage.Stream)
+ return GL20.GL_STREAM_DRAW;
+ else if (Usage == BufferObjectUsage.Dynamic)
+ return GL20.GL_DYNAMIC_DRAW;
+ else
+ throw new InvalidOperationException();
+ }
+ }
+
+ private int GLTarget
+ {
+ get
+ {
+ if (Type == BufferObjectType.Index)
+ return GL20.GL_ELEMENT_ARRAY_BUFFER;
+ else if (Type == BufferObjectType.Vertex)
+ return GL20.GL_ARRAY_BUFFER;
+ else
+ throw new InvalidOperationException();
+ }
+ }
+
+ #region GraphicsContextResource
+
+ public override void OnNewContext()
+ {
+ RecreateOnGpu();
+ }
+
+ public override void OnLostContext()
+ {
+ }
+
+ protected override bool ReleaseResource()
+ {
+ if (!IsInvalidated && !IsClientSide)
+ {
+ FreeFromGpu();
+ ID = -1;
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/DebugShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/DebugShader.cs
new file mode 100644
index 0000000..350da63
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/DebugShader.cs
@@ -0,0 +1,20 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class DebugShader : StandardShader
+ {
+ public DebugShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.debug.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.debug.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position3D);
+ MapToVBOStandardAttribute("a_color", VertexStandardAttributes.Color);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleColorShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleColorShader.cs
new file mode 100644
index 0000000..5683b50
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleColorShader.cs
@@ -0,0 +1,20 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class SimpleColorShader : StandardShader
+ {
+ public SimpleColorShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.simple_color.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.simple_color.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position3D);
+ MapToVBOStandardAttribute("a_color", VertexStandardAttributes.Color);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleColorTextureShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleColorTextureShader.cs
new file mode 100644
index 0000000..8831deb
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleColorTextureShader.cs
@@ -0,0 +1,21 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class SimpleColorTextureShader : StandardShader
+ {
+ public SimpleColorTextureShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.simple_color_texture.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.simple_color_texture.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position3D);
+ MapToVBOStandardAttribute("a_color", VertexStandardAttributes.Color);
+ MapToVBOStandardAttribute("a_texcoord0", VertexStandardAttributes.TexCoord);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureShader.cs
new file mode 100644
index 0000000..c8090ff
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureShader.cs
@@ -0,0 +1,20 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class SimpleTextureShader : StandardShader
+ {
+ public SimpleTextureShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.simple_texture.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.simple_texture.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position3D);
+ MapToVBOStandardAttribute("a_texcoord0", VertexStandardAttributes.TexCoord);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureVertexLerpShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureVertexLerpShader.cs
new file mode 100644
index 0000000..aaddf99
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureVertexLerpShader.cs
@@ -0,0 +1,21 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class SimpleTextureVertexLerpShader : VertexLerpShader
+ {
+ public SimpleTextureVertexLerpShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.vertexlerp_texture.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.vertexlerp_texture.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOAttribute("a_position1", 0);
+ MapToVBOAttribute("a_position2", 0);
+ MapToVBOStandardAttribute("a_texcoord0", VertexStandardAttributes.TexCoord);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureVertexSkinningShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureVertexSkinningShader.cs
new file mode 100644
index 0000000..7cc2860
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/SimpleTextureVertexSkinningShader.cs
@@ -0,0 +1,21 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class SimpleTextureVertexSkinningShader : VertexSkinningShader
+ {
+ public SimpleTextureVertexSkinningShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.vertexskinning_texture.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.vertexskinning_texture.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOAttribute("a_jointIndex", 0);
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position3D);
+ MapToVBOStandardAttribute("a_texcoord0", VertexStandardAttributes.TexCoord);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/Sprite2DShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/Sprite2DShader.cs
new file mode 100644
index 0000000..91765bc
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/Sprite2DShader.cs
@@ -0,0 +1,21 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class Sprite2DShader : SpriteShader
+ {
+ public Sprite2DShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.sprite2d.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.sprite2d.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position2D);
+ MapToVBOStandardAttribute("a_color", VertexStandardAttributes.Color);
+ MapToVBOStandardAttribute("a_texcoord0", VertexStandardAttributes.TexCoord);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/BuiltinShaders/Sprite3DShader.cs b/Blarg.GameFramework/Graphics/BuiltinShaders/Sprite3DShader.cs
new file mode 100644
index 0000000..358acbf
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/BuiltinShaders/Sprite3DShader.cs
@@ -0,0 +1,21 @@
+using System;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics.BuiltinShaders
+{
+ public class Sprite3DShader : SpriteShader
+ {
+ public Sprite3DShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ var vertexSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.sprite3d.vert.glsl");
+ var fragmentSources = ResourceUtils.GetTextResource("Blarg.GameFramework.Resources.Shaders.sprite3d.frag.glsl");
+
+ LoadCompileAndLinkInlineSources(vertexSources, fragmentSources);
+
+ MapToVBOStandardAttribute("a_position", VertexStandardAttributes.Position3D);
+ MapToVBOStandardAttribute("a_color", VertexStandardAttributes.Color);
+ MapToVBOStandardAttribute("a_texcoord0", VertexStandardAttributes.TexCoord);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Camera.cs b/Blarg.GameFramework/Graphics/Camera.cs
new file mode 100644
index 0000000..e05d952
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Camera.cs
@@ -0,0 +1,180 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class Camera
+ {
+ private ViewContext _viewContext;
+ private float _nearHeight;
+ private float _nearWidth;
+
+ public Frustum Frustum { get; private set; }
+ public Matrix4x4 LookAt { get; private set; }
+ public Matrix4x4 Projection { get; private set; }
+ public Vector3 Forward { get; private set; }
+ public Vector3 Up { get; private set; }
+
+ public Vector3 Orientation { get; set; }
+ public Vector3 Position { get; set; }
+
+ public int ViewportWidth { get; private set; }
+ public int ViewportHeight { get; private set; }
+ public float AspectRatio { get; private set; }
+ public float Near { get; private set; }
+ public float Far { get; private set; }
+ public float FieldOfViewAngle { get; private set; }
+
+ public Camera(ViewContext viewContext, float near = 1.0f, float far = 50.0f, float fieldOfView = MathConstants.Radians60)
+ {
+ if (viewContext == null)
+ throw new ArgumentNullException("viewContext");
+
+ _viewContext = viewContext;
+ Frustum = new Frustum(_viewContext);
+ Position = Vector3.Zero;
+ Orientation = Vector3.Zero;
+ Forward = Vector3.Zero;
+ Up = Vector3.Up;
+ LookAt = Matrix4x4.Identity;
+
+ FieldOfViewAngle = fieldOfView;
+ Near = near;
+ Far = far;
+
+ CalculateDefaultProjection(
+ _viewContext.ViewportLeft,
+ _viewContext.ViewportTop,
+ _viewContext.ViewportRight,
+ _viewContext.ViewportBottom
+ );
+ }
+
+ public virtual void OnUpdate(float delta)
+ {
+ }
+
+ public virtual void OnRender(float delta)
+ {
+ Vector3 movement = Vector3.Zero;
+ UpdateLookAtMatrix(ref movement);
+ _viewContext.ModelViewMatrix = LookAt;
+ Frustum.Calculate();
+ }
+
+ public virtual void OnResize(ref Rect size)
+ {
+ CalculateDefaultProjection(size.Left, size.Top, size.Right, size.Bottom);
+ _viewContext.ProjectionMatrix = Projection;
+ }
+
+ public virtual void UpdateProjectionMatrix()
+ {
+ CalculateDefaultProjection(
+ _viewContext.ViewportLeft,
+ _viewContext.ViewportTop,
+ _viewContext.ViewportRight,
+ _viewContext.ViewportBottom
+ );
+ }
+
+ public virtual void UpdateLookAtMatrix(ref Vector3 movement)
+ {
+ CalculateDefaultLookAt(ref movement);
+ }
+
+ public Ray Pick(int screenX, int screenY)
+ {
+ float nx = 2.0f * ((float)(screenX - (_viewContext.ViewportWidth / 2))) / ((float)_viewContext.ViewportWidth);
+ float ny = 2.0f * -((float)(screenY - (_viewContext.ViewportHeight / 2))) / ((float)_viewContext.ViewportHeight);
+
+ // pick ray calculation method copied from http://code.google.com/p/libgdx/
+ Vector3 vz = Vector3.Normalize(Forward * -1.0f);
+ Vector3 vx = Vector3.Normalize(Vector3.Cross(Vector3.Up, vz));
+ Vector3 vy = Vector3.Normalize(Vector3.Cross(vz, vx));
+
+ Vector3 near_center = Position - (vz * Near);
+
+ Vector3 a = (vx * _nearWidth) * nx;
+ Vector3 b = (vy * _nearHeight) * ny;
+ Vector3 near_point = a + b + near_center;
+
+ Vector3 dir = Vector3.Normalize(near_point - Position);
+
+ return new Ray(Position, dir);
+ }
+
+ public Point2 Project(ref Vector3 objectPosition)
+ {
+ Matrix4x4 modelview = _viewContext.ModelViewMatrix;
+ Matrix4x4 projection = _viewContext.ProjectionMatrix;
+
+ return Project(ref objectPosition, ref modelview, ref projection);
+ }
+
+ public Point2 Project(ref Vector3 objectPosition, ref Matrix4x4 modelView, ref Matrix4x4 projection)
+ {
+ // transform object position by modelview matrix (vector transform, w = 1)
+ float tempX = objectPosition.X * modelView.M11 + objectPosition.Y * modelView.M12 + objectPosition.Z * modelView.M13 + modelView.M14;
+ float tempY = objectPosition.X * modelView.M21 + objectPosition.Y * modelView.M22 + objectPosition.Z * modelView.M23 + modelView.M24;
+ float tempZ = objectPosition.X * modelView.M31 + objectPosition.Y * modelView.M32 + objectPosition.Z * modelView.M33 + modelView.M34;
+ float tempW = objectPosition.X * modelView.M41 + objectPosition.Y * modelView.M42 + objectPosition.Z * modelView.M43 + modelView.M44;
+
+ // transform the above by the projection matrix (optimized for bottom row of the projection matrix always being [0, 0, -1, 0])
+ float transformedX = tempX * projection.M11 + tempY * projection.M12 + tempZ * projection.M13 + tempW * projection.M14;
+ float transformedY = tempX * projection.M21 + tempY * projection.M22 + tempZ * projection.M23 + tempW * projection.M24;
+ float transformedZ = tempX * projection.M31 + tempY * projection.M32 + tempZ * projection.M33 + tempW * projection.M34;
+ float transformedW = -tempZ;
+
+ // w normalizes between -1 and 1
+ // TODO: shouldn't really handle this using an assert... however, I'd like to know when/if this happens
+ System.Diagnostics.Debug.Assert(transformedW != 0.0f);
+ transformedW = 1.0f / transformedW;
+
+ // perspective division
+ transformedX *= transformedW;
+ transformedY *= transformedW;
+ transformedZ *= transformedW;
+
+ // map to 2D viewport coordinates (ignoring Z)
+ Point2 result;
+ result.X = (int)(((transformedX * 0.5f) + 0.5f) * (float)_viewContext.ViewportWidth + (float)_viewContext.ViewportLeft);
+ result.Y = (int)(((transformedY * 0.5f) + 0.5f) * (float)_viewContext.ViewportHeight + (float)_viewContext.ViewportTop);
+ // float z = (1.0f + transformedZ) * 0.5f; // would be between 0.0f and 1.0f
+
+ // adjust Y coordinate so that 0 is at the top of the screen instead of the bottom
+ result.Y = (int)_viewContext.ViewportHeight - result.Y;
+
+ return result;
+ }
+
+ protected void CalculateDefaultProjection(int left, int top, int right, int bottom)
+ {
+ ViewportWidth = right - left;
+ ViewportHeight = bottom - top;
+
+ AspectRatio = (float)ViewportWidth / (float)ViewportHeight;
+
+ _nearHeight = Near * (float)Math.Tan(FieldOfViewAngle / 2.0f);
+ _nearWidth = _nearHeight * AspectRatio;
+
+ Projection = Matrix4x4.CreatePerspectiveFieldOfView(FieldOfViewAngle, AspectRatio, Near, Far);
+ }
+
+ protected void CalculateDefaultLookAt(ref Vector3 movement)
+ {
+ // final camera orientation. angles must be negative (or rather, inverted) for the camera matrix. also the matrix concatenation order is important!
+ Matrix4x4 rotation = Matrix4x4.CreateRotationY(-Orientation.Y) * Matrix4x4.CreateRotationX(-Orientation.X);
+
+ // apply orientation to forward, movement and up vectors so they're pointing in the right direction
+ Forward = Matrix4x4.Transform(rotation, Vector3.Forward);
+ Up = Matrix4x4.Transform(rotation, Vector3.Up);
+ Vector3 orientedMovement = Matrix4x4.Transform(rotation, movement);
+
+ // move the camera position
+ Position += orientedMovement;
+
+ Vector3 target = Forward + Position;
+ LookAt = Matrix4x4.CreateLookAt(Position, target, Vector3.Up);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/CustomTextureAtlas.cs b/Blarg.GameFramework/Graphics/CustomTextureAtlas.cs
new file mode 100644
index 0000000..d836201
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/CustomTextureAtlas.cs
@@ -0,0 +1,63 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class CustomTextureAtlas : TextureAtlas
+ {
+ public CustomTextureAtlas(int textureWidth, int textureHeight, float texCoordEdgeOffset = TexCoordEdgeBleedOffset)
+ : base(textureWidth, textureHeight, texCoordEdgeOffset)
+ {
+ }
+
+ public CustomTextureAtlas(Texture texture, float texCoordEdgeOffset = TexCoordEdgeBleedOffset)
+ : base(texture, texCoordEdgeOffset)
+ {
+ }
+
+ public int Add(ref Rect region)
+ {
+ if (region.Left >= region.Right)
+ throw new InvalidOperationException();
+ if (region.Top >= region.Bottom)
+ throw new InvalidOperationException();
+ if (region.Right >= Width)
+ throw new InvalidOperationException();
+ if (region.Bottom >= Height)
+ throw new InvalidOperationException();
+
+ TextureRegion tile;
+
+ // pixel location/dimensions
+ tile.Dimensions = region;
+
+ // texture coordinates
+ // HACK: subtract TexCoordEdgeOffset from the bottom right edges to
+ // get around floating point rounding errors (adjacent tiles will
+ // slightly bleed in otherwise)
+ tile.TexCoords.Left = ((float)region.Left + TexCoordEdgeOffset) / (float)Width;
+ tile.TexCoords.Top = ((float)region.Top + TexCoordEdgeOffset) / (float)Height;
+ tile.TexCoords.Right = ((float)region.Right - TexCoordEdgeOffset) / (float)Width;
+ tile.TexCoords.Bottom = ((float)region.Bottom - TexCoordEdgeOffset) / (float)Height;
+
+ Tiles.Add(tile);
+
+ return Tiles.Count - 1;
+ }
+
+ public int Add(Rect region)
+ {
+ return Add(ref region);
+ }
+
+ public int Add(int left, int top, int right, int bottom)
+ {
+ var region = new Rect(left, top, right, bottom);
+ return Add(ref region);
+ }
+
+ public void Reset()
+ {
+ Tiles.Clear();
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Framebuffer.cs b/Blarg.GameFramework/Graphics/Framebuffer.cs
new file mode 100644
index 0000000..91c512f
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Framebuffer.cs
@@ -0,0 +1,488 @@
+using System;
+using System.Collections.Generic;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum FramebufferTextureFormat
+ {
+ RGB = 1,
+ RGBA,
+ Depth
+ }
+
+ public enum FramebufferRenderbufferFormat
+ {
+ RGB = 1,
+ RGBA,
+ Depth,
+ Stencil
+ }
+
+ public class Framebuffer : GraphicsContextResource
+ {
+ private int _fixedWidth;
+ private int _fixedHeight;
+ private IDictionary _attachedTextures;
+ private IDictionary _attachedRenderbuffers;
+ private ViewContext _attachedViewContext;
+
+ public int ID { get; private set; }
+
+ public bool IsInvalidated
+ {
+ get { return ID == -1; }
+ }
+
+ public bool IsUsingFixedDimensions
+ {
+ get { return (_fixedWidth != 0 && _fixedHeight != 0); }
+ }
+
+ public ViewContext AttachedViewContext
+ {
+ get { return _attachedViewContext; }
+ }
+
+ public Renderbuffer GetAttachedRenderbuffer(FramebufferRenderbufferFormat format)
+ {
+ Renderbuffer result;
+ _attachedRenderbuffers.TryGetValue(format, out result);
+ return result;
+ }
+
+ public Texture GetAttachedTexture(FramebufferTextureFormat format)
+ {
+ Texture result;
+ _attachedTextures.TryGetValue(format, out result);
+ return result;
+ }
+
+ public Framebuffer(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ ID = -1;
+ _attachedRenderbuffers = new Dictionary();
+ _attachedTextures = new Dictionary();
+ _attachedViewContext = null;
+ Create(0, 0);
+ }
+
+ public Framebuffer(GraphicsDevice graphicsDevice, int width, int height)
+ : base(graphicsDevice)
+ {
+ if (width < 1)
+ throw new ArgumentOutOfRangeException("width");
+ if (height < 1)
+ throw new ArgumentOutOfRangeException("height");
+
+ ID = -1;
+ _attachedRenderbuffers = new Dictionary();
+ _attachedTextures = new Dictionary();
+ _attachedViewContext = null;
+ Create(width, height);
+ }
+
+ private void Create(int width, int height)
+ {
+ if (!IsInvalidated)
+ throw new InvalidOperationException();
+
+ ID = Platform.GL.glGenFramebuffers();
+
+ _fixedWidth = width;
+ _fixedHeight = height;
+ }
+
+ private void Release()
+ {
+ if (!IsInvalidated)
+ {
+ foreach (var renderbuffer in _attachedRenderbuffers)
+ renderbuffer.Value.Dispose();
+ _attachedRenderbuffers.Clear();
+
+ foreach (var texture in _attachedTextures)
+ texture.Value.Dispose();
+ _attachedTextures.Clear();
+
+ Platform.GL.glDeleteFramebuffers(ID);
+ ID = -1;
+ }
+ if (GraphicsDevice.ViewContext == _attachedViewContext)
+ {
+ GraphicsDevice.ViewContext = null;
+ _attachedViewContext = null;
+ }
+
+ _fixedWidth = 0;
+ _fixedHeight = 0;
+ }
+
+ #region Adding attachments
+
+ public void AttachViewContext()
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+ if (_attachedViewContext != null)
+ throw new InvalidOperationException("Existing ViewContext attachment.");
+
+ if (IsUsingFixedDimensions)
+ _attachedViewContext = new ViewContext(GraphicsDevice, new Rect(0, 0, _fixedWidth, _fixedHeight));
+ else
+ _attachedViewContext = new ViewContext(GraphicsDevice);
+ }
+
+ public void AttachTexture(FramebufferTextureFormat format)
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+
+ var existingTexture = GetAttachedTexture(format);
+ if (existingTexture != null)
+ throw new InvalidOperationException("Texture attachment already exists for this format.");
+
+ // also need to make sure a renderbuffer isn't already attached with the same format
+ var renderbufferFormatToCheck = (FramebufferRenderbufferFormat)format;
+ var existingRenderbuffer = GetAttachedRenderbuffer(renderbufferFormatToCheck);
+ if (existingRenderbuffer != null)
+ throw new InvalidOperationException("Renderbuffer attachment already exists with this same texture format.");
+
+ // determine opengl format stuff equivalent to the format passed in
+ TextureFormat textureFormat;
+ int attachmentType;
+ switch (format)
+ {
+ case FramebufferTextureFormat.RGB:
+ textureFormat = TextureFormat.RGB;
+ attachmentType = GL20.GL_COLOR_ATTACHMENT0;
+ break;
+ case FramebufferTextureFormat.RGBA:
+ textureFormat = TextureFormat.RGBA;
+ attachmentType = GL20.GL_COLOR_ATTACHMENT0;
+ break;
+ case FramebufferTextureFormat.Depth:
+ textureFormat = TextureFormat.Depth;
+ attachmentType = GL20.GL_DEPTH_ATTACHMENT;
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ int width;
+ int height;
+ GetDimensionsForAttachment(out width, out height);
+
+ // pixelated == unfiltered
+ var texture = new Texture(GraphicsDevice, width, height, textureFormat, (TextureParameters)TextureParameters.Pixelated.Clone());
+
+ // don't have the GraphicsDevice automatically restore this texture!
+ // since it's dependant on this Framebuffer object, we should let
+ // the Framebuffer object do the restore itself
+ GraphicsDevice.UnregisterManagedResource(texture);
+
+ GraphicsDevice.BindFramebuffer(this);
+ Platform.GL.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, attachmentType, GL20.GL_TEXTURE_2D, texture.ID, 0);
+ GraphicsDevice.UnbindFramebuffer(this);
+
+ _attachedTextures.Add(format, texture);
+ }
+
+ public void AttachRenderbuffer(FramebufferRenderbufferFormat format)
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+
+ var existingRenderbuffer = GetAttachedRenderbuffer(format);
+ if (existingRenderbuffer != null)
+ throw new InvalidOperationException("Renderbuffer attachment already exists for this format.");
+
+ // also need to make sure a texture isn't already attached with the same format
+ var textureFormatToCheck = (FramebufferTextureFormat)format;
+ var existingTexture = GetAttachedTexture(textureFormatToCheck);
+ if (existingTexture != null)
+ throw new InvalidOperationException("Texture attachment already exists with this same renderbuffer format.");
+
+ // determine opengl format stuff equivalent to the format passed in
+ RenderbufferFormat renderbufferFormat;
+ int attachmentType;
+ switch (format)
+ {
+ case FramebufferRenderbufferFormat.RGB:
+ renderbufferFormat = RenderbufferFormat.RGB;
+ attachmentType = GL20.GL_COLOR_ATTACHMENT0;
+ break;
+ case FramebufferRenderbufferFormat.RGBA:
+ renderbufferFormat = RenderbufferFormat.RGBA;
+ attachmentType = GL20.GL_COLOR_ATTACHMENT0;
+ break;
+ case FramebufferRenderbufferFormat.Depth:
+ renderbufferFormat = RenderbufferFormat.Depth;
+ attachmentType = GL20.GL_DEPTH_ATTACHMENT;
+ break;
+ case FramebufferRenderbufferFormat.Stencil:
+ renderbufferFormat = RenderbufferFormat.Stencil;
+ attachmentType = GL20.GL_STENCIL_ATTACHMENT;
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ int width;
+ int height;
+ GetDimensionsForAttachment(out width, out height);
+
+ var renderbuffer = new Renderbuffer(GraphicsDevice, width, height, renderbufferFormat);
+
+ // don't have the GraphicsDevice automatically restore this renderbuffer!
+ // since it's dependant on this Framebuffer object, we should let
+ // the Framebuffer object do the restore itself
+ GraphicsDevice.UnregisterManagedResource(renderbuffer);
+
+ GraphicsDevice.BindFramebuffer(this);
+ Platform.GL.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, attachmentType,GL20.GL_RENDERBUFFER, renderbuffer.ID);
+ GraphicsDevice.UnbindFramebuffer(this);
+
+ _attachedRenderbuffers.Add(format, renderbuffer);
+ }
+
+ #endregion
+
+ #region Removing attachments
+
+ public void RemoveViewContext()
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+
+ if (_attachedViewContext == null)
+ return;
+
+ if (GraphicsDevice.ViewContext == _attachedViewContext)
+ GraphicsDevice.ViewContext = null;
+
+ _attachedViewContext = null;
+ }
+
+ public void RemoveTexture(FramebufferTextureFormat format)
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+
+ Texture existing;
+ _attachedTextures.TryGetValue(format, out existing);
+ if (existing == null)
+ return;
+
+ int attachmentType;
+ switch (existing.Format)
+ {
+ case TextureFormat.RGB: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case TextureFormat.RGBA: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case TextureFormat.Depth: attachmentType = GL20.GL_DEPTH_ATTACHMENT; break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ GraphicsDevice.BindFramebuffer(this);
+ Platform.GL.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, attachmentType, GL20.GL_TEXTURE_2D, 0, 0);
+ GraphicsDevice.UnbindFramebuffer(this);
+
+ _attachedTextures.Remove(format);
+ existing.Dispose();
+ }
+
+ public void RemoveRenderbuffer(FramebufferRenderbufferFormat format)
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+
+ Renderbuffer existing;
+ _attachedRenderbuffers.TryGetValue(format, out existing);
+ if (existing == null)
+ return;
+
+ int attachmentType;
+ switch (existing.Format)
+ {
+ case RenderbufferFormat.RGB: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case RenderbufferFormat.RGBA: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case RenderbufferFormat.Depth: attachmentType = GL20.GL_DEPTH_ATTACHMENT; break;
+ case RenderbufferFormat.Stencil: attachmentType = GL20.GL_STENCIL_ATTACHMENT; break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ GraphicsDevice.BindFramebuffer(this);
+ Platform.GL.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, attachmentType, GL20.GL_RENDERBUFFER, 0);
+ GraphicsDevice.UnbindFramebuffer(this);
+
+ _attachedRenderbuffers.Remove(format);
+ existing.Dispose();
+ }
+
+ #endregion
+
+ #region Re-attaching existing attachments
+
+ private void RecreateAndAttach(FramebufferTextureFormat key)
+ {
+ var existing = _attachedTextures[key];
+
+ int attachmentType;
+ switch (existing.Format)
+ {
+ case TextureFormat.RGB: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case TextureFormat.RGBA: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case TextureFormat.Depth: attachmentType = GL20.GL_DEPTH_ATTACHMENT; break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ var format = existing.Format;
+ int width;
+ int height;
+ GetDimensionsForAttachment(out width, out height);
+
+ // this will essentially do nothing if we're recreating due to a new context
+ // (existing.IsInvalidated will be true, so Dispose() won't release anything)
+ existing.Dispose();
+
+ // note that we recreate the texture instead of just calling it's OnNewContext()
+ // method because OnNewContext() will recreate it using it's initial size and we
+ // may be recreating+attaching due to a viewport resize where this framebuffer is
+ // to be sized the same as the viewport (non-fixed size)
+ // pixelated == unfiltered
+ var newTexture = new Texture(GraphicsDevice, width, height, format, (TextureParameters)TextureParameters.Pixelated.Clone());
+
+ // don't have the GraphicsDevice automatically restore this texture!
+ // since it's dependant on this Framebuffer object, we should let
+ // the Framebuffer object do the restore itself
+ GraphicsDevice.UnregisterManagedResource(newTexture);
+
+ GraphicsDevice.BindFramebuffer(this);
+ Platform.GL.glFramebufferTexture2D(GL20.GL_FRAMEBUFFER, attachmentType, GL20.GL_TEXTURE_2D, newTexture.ID, 0);
+ GraphicsDevice.UnbindFramebuffer(this);
+
+ _attachedTextures[key] = newTexture;
+ }
+
+ private void RecreateAndAttach(FramebufferRenderbufferFormat key)
+ {
+ var existing = _attachedRenderbuffers[key];
+
+ int attachmentType;
+ switch (existing.Format)
+ {
+ case RenderbufferFormat.RGB: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case RenderbufferFormat.RGBA: attachmentType = GL20.GL_COLOR_ATTACHMENT0; break;
+ case RenderbufferFormat.Depth: attachmentType = GL20.GL_DEPTH_ATTACHMENT; break;
+ case RenderbufferFormat.Stencil: attachmentType = GL20.GL_STENCIL_ATTACHMENT; break;
+ default:
+ throw new InvalidOperationException();
+ }
+
+ var format = existing.Format;
+ int width;
+ int height;
+ GetDimensionsForAttachment(out width, out height);
+
+ // this will essentially do nothing if we're recreating due to a new context
+ // (existing.IsInvalidated will be true, so Dispose() won't release anything)
+ existing.Dispose();
+
+ var newRenderbuffer = new Renderbuffer(GraphicsDevice, width, height, format);
+
+ // don't have the GraphicsDevice automatically restore this renderbuffer!
+ // since it's dependant on this Framebuffer object, we should let
+ // the Framebuffer object do the restore itself
+ GraphicsDevice.UnregisterManagedResource(newRenderbuffer);
+
+ GraphicsDevice.BindFramebuffer(this);
+ Platform.GL.glFramebufferRenderbuffer(GL20.GL_FRAMEBUFFER, attachmentType, GL20.GL_RENDERBUFFER, newRenderbuffer.ID);
+ GraphicsDevice.UnbindFramebuffer(this);
+
+ _attachedRenderbuffers[key] = newRenderbuffer;
+ }
+
+ #endregion
+
+ private void GetDimensionsForAttachment(out int width, out int height)
+ {
+ if (IsUsingFixedDimensions)
+ {
+ width = _fixedWidth;
+ height = _fixedHeight;
+ }
+ else
+ {
+ ViewContext currentViewContext = (_attachedViewContext == null ? GraphicsDevice.ViewContext : _attachedViewContext);
+ width = currentViewContext.ViewportWidth;
+ height = currentViewContext.ViewportHeight;
+ }
+ }
+
+ public void OnResize()
+ {
+ if (IsInvalidated)
+ return;
+
+ // TODO: check that the check for GraphicsDevice.ViewContext != _attachedViewContext is actually needed
+ if (_attachedViewContext != null && GraphicsDevice.ViewContext != _attachedViewContext)
+ {
+ Rect r = Platform.Application.Window.ClientRectangle;
+ _attachedViewContext.OnResize(ref r, GraphicsDevice.ScreenOrientation);
+ }
+
+ // now recreate & reattach all the attachment points that were set
+ foreach (var texture in _attachedTextures)
+ RecreateAndAttach(texture.Key);
+ foreach (var renderbuffer in _attachedRenderbuffers)
+ RecreateAndAttach(renderbuffer.Key);
+ }
+
+ #region GraphicsContextResource
+
+ public override void OnNewContext()
+ {
+ // recreate using the same settings
+ Create(_fixedWidth, _fixedHeight);
+
+ // TODO: check that the check for GraphicsDevice.ViewContext != _attachedViewContext is actually needed
+ if (_attachedViewContext != null && GraphicsDevice.ViewContext != _attachedViewContext)
+ _attachedViewContext.OnNewContext();
+
+ // now recreate & reattach all the attachment points that were set
+ foreach (var texture in _attachedTextures)
+ RecreateAndAttach(texture.Key);
+ foreach (var renderbuffer in _attachedRenderbuffers)
+ RecreateAndAttach(renderbuffer.Key);
+ }
+
+ public override void OnLostContext()
+ {
+ ID = -1;
+ // TODO: check that the check for GraphicsDevice.ViewContext != _attachedViewContext is actually needed
+ if (_attachedViewContext != null && GraphicsDevice.ViewContext != _attachedViewContext)
+ _attachedViewContext.OnLostContext();
+ foreach (var texture in _attachedTextures)
+ texture.Value.OnLostContext();
+ foreach (var renderbuffer in _attachedRenderbuffers)
+ renderbuffer.Value.OnLostContext();
+ }
+
+ protected override bool ReleaseResource()
+ {
+ if (!IsInvalidated)
+ {
+ Release();
+ ID = -1;
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Frustum.cs b/Blarg.GameFramework/Graphics/Frustum.cs
new file mode 100644
index 0000000..f980f63
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Frustum.cs
@@ -0,0 +1,150 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum FrustumSides
+ {
+ Right = 0,
+ Left = 1,
+ Bottom = 2,
+ Top = 3,
+ Back = 4,
+ Front = 5
+ }
+
+ public class Frustum
+ {
+ private ViewContext _viewContext;
+ private Plane[] _planes = new Plane[6];
+
+ public Frustum(ViewContext viewContext)
+ {
+ if (viewContext == null)
+ throw new ArgumentNullException("viewContext");
+
+ _viewContext = viewContext;
+ Calculate();
+ }
+
+ public void Calculate()
+ {
+ Matrix4x4 combined = _viewContext.ProjectionMatrix * _viewContext.ModelViewMatrix;
+
+ // Extract the sides of each of the 6 planes from this to get our viewing frustum
+ _planes[(int)FrustumSides.Right].Normal.X = combined.M41 - combined.M11;
+ _planes[(int)FrustumSides.Right].Normal.Y = combined.M42 - combined.M12;
+ _planes[(int)FrustumSides.Right].Normal.Z = combined.M43 - combined.M13;
+ _planes[(int)FrustumSides.Right].D = combined.M44 - combined.M14;
+
+ _planes[(int)FrustumSides.Left].Normal.X = combined.M41 + combined.M11;
+ _planes[(int)FrustumSides.Left].Normal.Y = combined.M42 + combined.M12;
+ _planes[(int)FrustumSides.Left].Normal.Z = combined.M43 + combined.M13;
+ _planes[(int)FrustumSides.Left].D = combined.M44 + combined.M14;
+
+ _planes[(int)FrustumSides.Bottom].Normal.X = combined.M41 + combined.M21;
+ _planes[(int)FrustumSides.Bottom].Normal.Y = combined.M42 + combined.M22;
+ _planes[(int)FrustumSides.Bottom].Normal.Z = combined.M43 + combined.M23;
+ _planes[(int)FrustumSides.Bottom].D = combined.M44 + combined.M24;
+
+ _planes[(int)FrustumSides.Top].Normal.X = combined.M41 - combined.M21;
+ _planes[(int)FrustumSides.Top].Normal.Y = combined.M42 - combined.M22;
+ _planes[(int)FrustumSides.Top].Normal.Z = combined.M43 - combined.M23;
+ _planes[(int)FrustumSides.Top].D = combined.M44 - combined.M24;
+
+ _planes[(int)FrustumSides.Back].Normal.X = combined.M41 - combined.M31;
+ _planes[(int)FrustumSides.Back].Normal.Y = combined.M42 - combined.M32;
+ _planes[(int)FrustumSides.Back].Normal.Z = combined.M43 - combined.M33;
+ _planes[(int)FrustumSides.Back].D = combined.M44 - combined.M34;
+
+ _planes[(int)FrustumSides.Front].Normal.X = combined.M41 + combined.M31;
+ _planes[(int)FrustumSides.Front].Normal.Y = combined.M42 + combined.M32;
+ _planes[(int)FrustumSides.Front].Normal.Z = combined.M43 + combined.M33;
+ _planes[(int)FrustumSides.Front].D = combined.M44 + combined.M34;
+
+ Plane.Normalize(ref _planes[(int)FrustumSides.Right], out _planes[(int)FrustumSides.Right]);
+ Plane.Normalize(ref _planes[(int)FrustumSides.Left], out _planes[(int)FrustumSides.Left]);
+ Plane.Normalize(ref _planes[(int)FrustumSides.Bottom], out _planes[(int)FrustumSides.Bottom]);
+ Plane.Normalize(ref _planes[(int)FrustumSides.Top], out _planes[(int)FrustumSides.Top]);
+ Plane.Normalize(ref _planes[(int)FrustumSides.Back], out _planes[(int)FrustumSides.Back]);
+ Plane.Normalize(ref _planes[(int)FrustumSides.Front], out _planes[(int)FrustumSides.Front]);
+ }
+
+ public bool Test(ref Vector3 point)
+ {
+ for (int p = 0; p < 6; ++p)
+ {
+ if (Plane.ClassifyPoint(ref _planes[p], ref point) == PlanePointClassify.Behind)
+ return false;
+ }
+
+ return true;
+ }
+
+ public bool Test(ref BoundingBox box)
+ {
+ if (!TestPlaneAgainstBox(ref _planes[(int)FrustumSides.Right], box.Min.X, box.Min.Y, box.Min.Z, box.Width, box.Height, box.Depth))
+ return false;
+ if (!TestPlaneAgainstBox(ref _planes[(int)FrustumSides.Left], box.Min.X, box.Min.Y, box.Min.Z, box.Width, box.Height, box.Depth))
+ return false;
+ if (!TestPlaneAgainstBox(ref _planes[(int)FrustumSides.Bottom], box.Min.X, box.Min.Y, box.Min.Z, box.Width, box.Height, box.Depth))
+ return false;
+ if (!TestPlaneAgainstBox(ref _planes[(int)FrustumSides.Top], box.Min.X, box.Min.Y, box.Min.Z, box.Width, box.Height, box.Depth))
+ return false;
+ if (!TestPlaneAgainstBox(ref _planes[(int)FrustumSides.Back], box.Min.X, box.Min.Y, box.Min.Z, box.Width, box.Height, box.Depth))
+ return false;
+ if (!TestPlaneAgainstBox(ref _planes[(int)FrustumSides.Front], box.Min.X, box.Min.Y, box.Min.Z, box.Width, box.Height, box.Depth))
+ return false;
+
+ return true;
+ }
+
+ public bool Test(ref BoundingSphere sphere)
+ {
+ if (!TestPlaneAgainstSphere(ref _planes[(int)FrustumSides.Right], ref sphere.Center, sphere.Radius))
+ return false;
+ if (!TestPlaneAgainstSphere(ref _planes[(int)FrustumSides.Left], ref sphere.Center, sphere.Radius))
+ return false;
+ if (!TestPlaneAgainstSphere(ref _planes[(int)FrustumSides.Bottom], ref sphere.Center, sphere.Radius))
+ return false;
+ if (!TestPlaneAgainstSphere(ref _planes[(int)FrustumSides.Top], ref sphere.Center, sphere.Radius))
+ return false;
+ if (!TestPlaneAgainstSphere(ref _planes[(int)FrustumSides.Back], ref sphere.Center, sphere.Radius))
+ return false;
+ if (!TestPlaneAgainstSphere(ref _planes[(int)FrustumSides.Front], ref sphere.Center, sphere.Radius))
+ return false;
+
+ return true;
+ }
+
+ private bool TestPlaneAgainstBox(ref Plane plane, float minX, float minY, float minZ, float width, float height, float depth)
+ {
+ if (Plane.ClassifyPoint(ref plane, minX, minY, minZ) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX, minY, minZ + depth) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX + width, minY, minZ + depth) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX + width, minY, minZ) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX, minY + height, minZ) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX, minY + height, minZ + depth) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX + width, minY + height, minZ + depth) != PlanePointClassify.Behind)
+ return true;
+ if (Plane.ClassifyPoint(ref plane, minX + width, minY + height, minZ) != PlanePointClassify.Behind)
+ return true;
+
+ return false;
+ }
+
+ private bool TestPlaneAgainstSphere(ref Plane plane, ref Vector3 center, float radius)
+ {
+ float distance = Plane.DistanceBetween(ref plane, ref center);
+ if (distance <= -radius)
+ return false;
+ else
+ return true;
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/GeometryDebugRenderer.cs b/Blarg.GameFramework/Graphics/GeometryDebugRenderer.cs
new file mode 100644
index 0000000..f863d93
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/GeometryDebugRenderer.cs
@@ -0,0 +1,395 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class GeometryDebugRenderer
+ {
+ private const int DefaultVerticesAmount = 4096;
+
+ private VertexBuffer _vertices;
+ private RenderState _renderState;
+ private Color _color1;
+ private Color _color2;
+ private bool _hasBegunRendering;
+
+ public GraphicsDevice GraphicsDevice { get; private set; }
+
+ public GeometryDebugRenderer(GraphicsDevice graphicsDevice)
+ {
+ if (graphicsDevice == null)
+ throw new ArgumentNullException("graphicsDevice");
+
+ GraphicsDevice = graphicsDevice;
+
+ _vertices = new VertexBuffer(GraphicsDevice, VertexAttributeDeclarations.ColorPosition3D, DefaultVerticesAmount, BufferObjectUsage.Stream);
+
+ _color1 = new Color(1.0f, 1.0f, 0.0f);
+ _color2 = new Color(1.0f, 0.0f, 0.0f);
+
+ _renderState = (RenderState)RenderState.Default.Clone();
+ _renderState.LineWidth = 2.0f;
+
+ _hasBegunRendering = false;
+ }
+
+ #region Begin/End
+
+ public void Begin(bool depthTesting = true)
+ {
+ if (_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ _vertices.MoveToStart();
+ _renderState.DepthTesting = depthTesting;
+
+ _hasBegunRendering = true;
+ }
+
+ public void End()
+ {
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ if (_vertices.CurrentPosition == 0)
+ {
+ // nothing to render!
+ _hasBegunRendering = false;
+ return;
+ }
+
+ int numPointsToRender = _vertices.CurrentPosition;
+ int numLinesToRender = numPointsToRender / 2;
+
+ var shader = GraphicsDevice.DebugShader;
+
+ var modelView = GraphicsDevice.ViewContext.ModelViewMatrix;
+ var projection = GraphicsDevice.ViewContext.ProjectionMatrix;
+
+ GraphicsDevice.BindShader(shader);
+ shader.SetModelViewMatrix(ref modelView);
+ shader.SetProjectionMatrix(ref projection);
+
+ _renderState.Apply();
+
+ GraphicsDevice.BindVertexBuffer(_vertices);
+ GraphicsDevice.RenderLines(0, numLinesToRender);
+ GraphicsDevice.RenderPoints(0, numPointsToRender);
+ GraphicsDevice.UnbindVertexBuffer();
+
+ GraphicsDevice.UnbindShader();
+
+ _hasBegunRendering = false;
+ }
+
+ #endregion
+
+ #region Primitive/Shape Rendering
+
+ public void Render(ref BoundingBox box)
+ {
+ Render(ref box, ref _color1);
+ }
+
+ public void Render(ref BoundingBox box, ref Color color)
+ {
+ const int NumVerticesForBox = 24;
+
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(NumVerticesForBox);
+
+ int i = _vertices.CurrentPosition;
+
+ // removed lines which are duplicated by more then one face
+ // left and right faces don't need to be drawn at all (entirely duplicated lines)
+
+ // top
+ _vertices.SetPosition3D(i++, box.Min.X, box.Max.Y, box.Min.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Max.Y, box.Min.Z);
+
+ _vertices.SetPosition3D(i++, box.Max.X, box.Max.Y, box.Min.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Max.Y, box.Max.Z);
+
+ _vertices.SetPosition3D(i++, box.Max.X, box.Max.Y, box.Max.Z);
+ _vertices.SetPosition3D(i++, box.Min.X, box.Max.Y, box.Max.Z);
+
+ _vertices.SetPosition3D(i++, box.Min.X, box.Max.Y, box.Max.Z);
+ _vertices.SetPosition3D(i++, box.Min.X, box.Max.Y, box.Min.Z);
+
+ // back
+ _vertices.SetPosition3D(i++, box.Min.X, box.Min.Y, box.Min.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Min.Y, box.Min.Z);
+
+ _vertices.SetPosition3D(i++, box.Max.X, box.Min.Y, box.Min.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Max.Y, box.Min.Z);
+
+ _vertices.SetPosition3D(i++, box.Min.X, box.Max.Y, box.Min.Z);
+ _vertices.SetPosition3D(i++, box.Min.X, box.Min.Y, box.Min.Z);
+
+ // front
+ _vertices.SetPosition3D(i++, box.Min.X, box.Min.Y, box.Max.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Min.Y, box.Max.Z);
+
+ _vertices.SetPosition3D(i++, box.Max.X, box.Min.Y, box.Max.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Max.Y, box.Max.Z);
+
+ _vertices.SetPosition3D(i++, box.Min.X, box.Max.Y, box.Max.Z);
+ _vertices.SetPosition3D(i++, box.Min.X, box.Min.Y, box.Max.Z);
+
+ // bottom
+ _vertices.SetPosition3D(i++, box.Max.X, box.Min.Y, box.Min.Z);
+ _vertices.SetPosition3D(i++, box.Max.X, box.Min.Y, box.Max.Z);
+
+ _vertices.SetPosition3D(i++, box.Min.X, box.Min.Y, box.Max.Z);
+ _vertices.SetPosition3D(i++, box.Min.X, box.Min.Y, box.Min.Z);
+
+ // fill in all the colours
+ for (int j = _vertices.CurrentPosition; j < i; ++j)
+ _vertices.SetColor(j, ref color);
+
+ _vertices.Move(NumVerticesForBox);
+ }
+
+ public void Render(ref Point3 boxMin, ref Point3 boxMax)
+ {
+ Render(ref boxMin, ref boxMax, ref _color1);
+ }
+
+ public void Render(ref Point3 boxMin, ref Point3 boxMax, ref Color color)
+ {
+ var box = new BoundingBox();
+ box.Min.Set(ref boxMin);
+ box.Max.Set(ref boxMax);
+ Render(ref box, ref color);
+ }
+
+ public void Render(ref BoundingSphere sphere)
+ {
+ Render(ref sphere, ref _color1);
+ }
+
+ public void Render(ref BoundingSphere sphere, ref Color color)
+ {
+ const int NumVerticesForSphere = 615;
+
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(NumVerticesForSphere);
+
+ int p = _vertices.CurrentPosition;
+
+ float ax, ay, az;
+ float bx, by, bz;
+ float cx = 0.0f, cy = 0.0f, cz = 0.0f;
+ float dx = 0.0f, dy = 0.0f, dz = 0.0f;
+ float theta1, theta2, theta3;
+
+ int n = 12;
+ for (int j = 0; j < n / 2; ++j)
+ {
+ theta1 = j * MathConstants.Pi * 2 / n - MathConstants.Pi / 2;
+ theta2 = (j + 1) * MathConstants.Pi * 2 / n - MathConstants.Pi / 2;
+
+ for (int i = 0; i <= n; ++i)
+ {
+ theta3 = i * MathConstants.Pi * 2 / n;
+ ax = sphere.Center.X + sphere.Radius * (float)Math.Cos(theta2) * (float)Math.Cos(theta3);
+ ay = sphere.Center.Y + sphere.Radius * (float)Math.Sin(theta2);
+ az = sphere.Center.Z + sphere.Radius * (float)Math.Cos(theta2) * (float)Math.Sin(theta3);
+
+ bx = sphere.Center.X + sphere.Radius * (float)Math.Cos(theta1) * (float)Math.Cos(theta3);
+ by = sphere.Center.Y + sphere.Radius * (float)Math.Sin(theta1);
+ bz = sphere.Center.Z + sphere.Radius * (float)Math.Cos(theta1) * (float)Math.Sin(theta3);
+
+ if (j > 0 || i > 0)
+ {
+ _vertices.SetPosition3D(p++, ax, ay, az);
+ _vertices.SetPosition3D(p++, bx, by, bz);
+
+ _vertices.SetPosition3D(p++, bx, by, bz);
+ _vertices.SetPosition3D(p++, dx, dy, dz);
+
+ _vertices.SetPosition3D(p++, dx, dy, dz);
+ _vertices.SetPosition3D(p++, cx, cy, cz);
+
+ _vertices.SetPosition3D(p++, cx, cy, cz);
+ _vertices.SetPosition3D(p++, ax, ay, az);
+ }
+
+ cx = ax;
+ cy = ay;
+ cz = az;
+ dx = bx;
+ dy = by;
+ dz = bz;
+ }
+ }
+
+ // fill in all the colours
+ for (int i = _vertices.CurrentPosition; i < p; ++i)
+ _vertices.SetColor(i, ref color);
+
+ _vertices.Move(NumVerticesForSphere);
+ }
+
+ public void Render(ref Ray ray, float length)
+ {
+ Render(ref ray, length, ref _color1, ref _color2);
+ }
+
+ public void Render(ref Ray ray, float length, ref Color originColor, ref Color endColor)
+ {
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(2);
+
+ Vector3 endPoint = ray.GetPositionAt(length);
+
+ _vertices.SetCurrentPosition3D(ref ray.Position);
+ _vertices.SetCurrentColor(ref originColor);
+ _vertices.MoveNext();
+
+ _vertices.SetCurrentPosition3D(ref endPoint);
+ _vertices.SetCurrentColor(ref endColor);
+ _vertices.MoveNext();
+ }
+
+ public void Render(ref LineSegment line)
+ {
+ Render(ref line, ref _color1);
+ }
+
+ public void Render(ref LineSegment line, ref Color color)
+ {
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(2);
+
+ _vertices.SetCurrentPosition3D(ref line.A);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ _vertices.SetCurrentPosition3D(ref line.B);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ }
+
+ public void Render(ref Vector3 a, ref Vector3 b)
+ {
+ Render(ref a, ref b, ref _color1);
+ }
+
+ public void Render(ref Vector3 a, ref Vector3 b, ref Color color)
+ {
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(2);
+
+ _vertices.SetCurrentPosition3D(ref a);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ _vertices.SetCurrentPosition3D(ref b);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ }
+
+ public void Render(ref Vector3 a, ref Vector3 b, ref Vector3 c)
+ {
+ Render(ref a, ref b, ref c, ref _color1);
+ }
+
+ public void Render(ref Vector3 a, ref Vector3 b, ref Vector3 c, ref Color color)
+ {
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(6);
+
+ // A -> B
+ _vertices.SetCurrentPosition3D(ref a);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref b);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ // A -> C
+ _vertices.SetCurrentPosition3D(ref a);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref c);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ // B -> C
+ _vertices.SetCurrentPosition3D(ref b);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref c);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ }
+
+ public void Render(ref Vector3 a, ref Vector3 b, ref Vector3 c, ref Vector3 d)
+ {
+ Render(ref a, ref b, ref c, ref d, ref _color1);
+ }
+
+ public void Render(ref Vector3 a, ref Vector3 b, ref Vector3 c, ref Vector3 d, ref Color color)
+ {
+ if (!_hasBegunRendering)
+ throw new InvalidOperationException();
+
+ EnsureSpaceFor(8);
+
+ // A -> B
+ _vertices.SetCurrentPosition3D(ref a);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref b);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ // A -> C
+ _vertices.SetCurrentPosition3D(ref a);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref c);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ // C -> D
+ _vertices.SetCurrentPosition3D(ref c);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref d);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+
+ // B -> D
+ _vertices.SetCurrentPosition3D(ref b);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ _vertices.SetCurrentPosition3D(ref d);
+ _vertices.SetCurrentColor(ref color);
+ _vertices.MoveNext();
+ }
+
+ #endregion
+
+ private void EnsureSpaceFor(int numVertices)
+ {
+ if (_vertices.RemainingElements >= numVertices)
+ return;
+
+ int numVerticesNeeded = numVertices - _vertices.RemainingElements;
+ _vertices.Extend(numVerticesNeeded);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/GraphicsContextResource.cs b/Blarg.GameFramework/Graphics/GraphicsContextResource.cs
new file mode 100644
index 0000000..215a2d8
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/GraphicsContextResource.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public abstract class GraphicsContextResource : IDisposable
+ {
+ public GraphicsDevice GraphicsDevice { get; private set; }
+ public bool IsReleased { get; private set; }
+
+ public GraphicsContextResource()
+ {
+ GraphicsDevice = null;
+ }
+
+ public GraphicsContextResource(GraphicsDevice graphicsDevice)
+ {
+ if (graphicsDevice == null)
+ throw new ArgumentNullException("graphicsDevice");
+
+ GraphicsDevice = graphicsDevice;
+ GraphicsDevice.RegisterManagedResource(this);
+ }
+
+ public virtual void OnNewContext()
+ {
+ }
+
+ public virtual void OnLostContext()
+ {
+ }
+
+ ~GraphicsContextResource()
+ {
+ if (!IsReleased)
+ {
+ ReleaseResource();
+ if (GraphicsDevice != null)
+ GraphicsDevice.UnregisterManagedResource(this);
+ }
+ }
+
+ protected virtual bool ReleaseResource()
+ {
+ return true;
+ }
+
+ public void Dispose()
+ {
+ if (!IsReleased)
+ {
+ IsReleased = ReleaseResource();
+ if (GraphicsDevice != null)
+ GraphicsDevice.UnregisterManagedResource(this);
+ }
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/GraphicsDevice.cs b/Blarg.GameFramework/Graphics/GraphicsDevice.cs
new file mode 100644
index 0000000..f046a0d
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/GraphicsDevice.cs
@@ -0,0 +1,927 @@
+using System;
+using System.Collections.Generic;
+using PortableGL;
+using Blarg.GameFramework;
+using Blarg.GameFramework.Graphics.BuiltinShaders;
+using Blarg.GameFramework.Resources;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class GraphicsDevice : IDisposable
+ {
+ private const int MaxTextureUnits = 8;
+ private const int MaxGpuAttributeSlots = 8;
+ private const int SolidColorTextureWidth = 8;
+ private const int SolidColorTextureHeight = 8;
+
+ private IList _managedResources;
+ private IDictionary _solidColorTextures;
+ private VertexBuffer _boundVertexBuffer;
+ private IndexBuffer _boundIndexBuffer;
+ private Texture[] _boundTextures;
+ private Renderbuffer _boundRenderbuffer;
+ private Framebuffer _boundFramebuffer;
+ private Shader _boundShader;
+ private bool _isShaderVertexAttribsSet;
+ private Stack _enabledVertexAttribIndices;
+ private ViewContext _defaultViewContext;
+ private ViewContext _activeViewContext;
+ private TextureParameters _currentTextureParams;
+ private TextureParameters _solidColorTextureParams;
+
+ public ScreenOrientation ScreenOrientation { get; private set; }
+
+ public bool IsNonPowerOfTwoTextureSupported { get; private set; }
+ public bool IsDepthTextureSupported { get; private set; }
+
+ public StandardShader DebugShader { get; private set; }
+ public StandardShader SimpleColorShader { get; private set; }
+ public StandardShader SimpleColorTextureShader { get; private set; }
+ public StandardShader SimpleTextureShader { get; private set; }
+ public VertexLerpShader SimpleTextureVertexLerpShader { get; private set; }
+ public VertexSkinningShader SimpleTextureVertexSkinningShader { get; private set; }
+ public SpriteShader Sprite2DShader { get; private set; }
+ public SpriteShader Sprite3DShader { get; private set; }
+
+ public SpriteFont SansSerifFont { get; private set; }
+ public SpriteFont MonospaceFont { get; private set; }
+
+ public GeometryDebugRenderer DebugRenderer { get; private set; }
+
+ public ViewContext ViewContext
+ {
+ get
+ {
+ return _activeViewContext;
+ }
+ set
+ {
+ if (value == _activeViewContext)
+ return;
+
+ if (value == null)
+ _activeViewContext = _defaultViewContext;
+ else
+ _activeViewContext = value;
+
+ Rect r = Platform.Application.Window.ClientRectangle;
+ _activeViewContext.OnApply(ref r, ScreenOrientation);
+ }
+ }
+
+ public GraphicsDevice()
+ {
+ ScreenOrientation = ScreenOrientation.Rotation0;
+
+ _boundTextures = new Texture[MaxTextureUnits];
+ _enabledVertexAttribIndices = new Stack(MaxGpuAttributeSlots);
+
+ string vendor = Platform.GL.glGetString(GL20.GL_VENDOR);
+ string renderer = Platform.GL.glGetString(GL20.GL_RENDERER);
+ string version = Platform.GL.glGetString(GL20.GL_VERSION);
+ string extensions = Platform.GL.glGetString(GL20.GL_EXTENSIONS);
+ string shadingLangVersion = Platform.GL.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION);
+
+ Platform.Logger.Info("Graphics", "GL_VENDOR = {0}", vendor);
+ Platform.Logger.Info("Graphics", "GL_RENDERER = {0}", renderer);
+ Platform.Logger.Info("Graphics", "GL_VERSION = {0}", version);
+ Platform.Logger.Info("Graphics", "GL_EXTENSIONS = {0}", extensions);
+ Platform.Logger.Info("Graphics", "GL_SHADING_LANGUAGE_VERSION = {0}", shadingLangVersion);
+
+ if (Platform.Type == PlatformType.Mobile)
+ {
+ IsNonPowerOfTwoTextureSupported = extensions.Contains("OES_texture_npot");
+ IsDepthTextureSupported = extensions.Contains("OES_depth_texture");
+ }
+ else
+ {
+ IsNonPowerOfTwoTextureSupported = extensions.Contains("ARB_texture_non_power_of_two");
+ IsDepthTextureSupported = extensions.Contains("ARB_depth_texture");
+ }
+
+ _defaultViewContext = new ViewContext(this);
+ _activeViewContext = _defaultViewContext;
+
+ _currentTextureParams = TextureParameters.Default;
+ _solidColorTextureParams = TextureParameters.Pixelated; // no filtering
+
+ _managedResources = new List();
+ _solidColorTextures = new Dictionary();
+
+ LoadStandardShaders();
+ LoadStandardFonts();
+
+ DebugRenderer = new GeometryDebugRenderer(this);
+ }
+
+ private void LoadStandardShaders()
+ {
+ DebugShader = new DebugShader(this);
+ SimpleColorShader = new SimpleColorShader(this);
+ SimpleColorTextureShader = new SimpleColorTextureShader(this);
+ SimpleTextureShader = new SimpleTextureShader(this);
+ SimpleTextureVertexLerpShader = new SimpleTextureVertexLerpShader(this);
+ SimpleTextureVertexSkinningShader = new SimpleTextureVertexSkinningShader(this);
+ Sprite2DShader = new Sprite2DShader(this);
+ Sprite3DShader = new Sprite3DShader(this);
+ }
+
+ private void LoadStandardFonts()
+ {
+ var sansSerifFontStream = ResourceUtils.GetResource("Blarg.GameFramework.Resources.Fonts.Vera.ttf");
+ SansSerifFont = SpriteFontTrueTypeLoader.Load(this, sansSerifFontStream, 16, SansSerifFont);
+
+ var monospaceFontStream = ResourceUtils.GetResource("Blarg.GameFramework.Resources.Fonts.VeraMono.ttf");
+ MonospaceFont = SpriteFontTrueTypeLoader.Load(this, monospaceFontStream, 16, MonospaceFont);
+ }
+
+ public void OnLostContext()
+ {
+ Platform.Logger.Info("Graphics", "Cleaning up objects/state specific to the lost OpenGL context.");
+
+ _activeViewContext.OnLostContext();
+
+ Platform.Logger.Info("Graphics", "Invoking OnLostContext callback for managed resources.");
+
+ foreach (var resource in _managedResources)
+ resource.OnLostContext();
+
+ Platform.Logger.Info("Graphics", "Finished cleaning up lost managed resources.");
+ }
+
+ public void OnNewContext()
+ {
+ Platform.Logger.Info("Graphics", "Initializing default state for new OpenGL context.");
+
+ _activeViewContext.OnNewContext();
+
+ RenderState.Default.Apply();
+ BlendState.Default.Apply();
+
+ UnbindVertexBuffer();
+ UnbindIndexBuffer();
+ for (int i = 0; i < MaxTextureUnits; ++i)
+ UnbindTexture(i);
+ UnbindShader();
+ UnbindRenderbuffer();
+ UnbindFramebuffer();
+
+ Platform.Logger.Info("Graphics", "Invoking OnNewContext callback for managed resources.");
+
+ foreach (var resource in _managedResources)
+ resource.OnNewContext();
+
+ Platform.Logger.Info("Graphics", "Finished restoring managed resources.");
+
+ Platform.Logger.Info("Graphics", "Restoring image data for solid color texture cache.");
+
+ foreach (var texture in _solidColorTextures)
+ {
+ Color color = Color.FromInt(texture.Key);
+ FillSolidColorTexture(texture.Value, ref color);
+ }
+
+ Platform.Logger.Info("Graphics", "Restoring standard fonts.");
+ LoadStandardFonts();
+ }
+
+ public void OnUnload()
+ {
+ Platform.Logger.Info("Graphics", "Unloading managed resources.");
+ while (_managedResources.Count > 0)
+ {
+ var resource = _managedResources[0];
+ resource.Dispose();
+ }
+ _managedResources.Clear();
+ }
+
+ public void OnRender(float delta)
+ {
+ int error = Platform.GL.glGetError();
+ System.Diagnostics.Debug.Assert(error == GL20.GL_NO_ERROR);
+ if (error != GL20.GL_NO_ERROR)
+ {
+ Platform.Logger.Error("OpenGL", "OpenGL error \"{0}\"", error.ToString());
+
+ // keep checking for and reporting errors until there are no more left
+ while ((error = Platform.GL.glGetError()) != GL20.GL_NO_ERROR)
+ Platform.Logger.Error("OpenGL", "OpenGL error \"{0}\"", error.ToString());
+ }
+
+ _activeViewContext.OnRender(delta);
+ }
+
+ public void OnResize(ref Rect rect, ScreenOrientation orientation)
+ {
+ Platform.Logger.Info("Graphics", "Window resized ({0}, {1}) - ({2}, {3}).", rect.Left, rect.Top, rect.Width, rect.Height);
+ if (orientation != ScreenOrientation.Rotation0)
+ Platform.Logger.Info("Graphics", "Screen is rotated (angle = {0}).", (int)orientation);
+
+ ScreenOrientation = orientation;
+ _activeViewContext.OnResize(ref rect, orientation);
+ }
+
+ public void Clear(float r, float g, float b, float a = Color.AlphaOpaque)
+ {
+ var color = new Color(r, g, b, a);
+ Clear(ref color);
+ Platform.GL.glClearColor(r, g, b, a);
+ }
+
+ public void Clear(Color color)
+ {
+ Clear(ref color);
+ }
+
+ public void Clear(ref Color color)
+ {
+ Platform.GL.glClearColor(color.R, color.G, color.B, color.A);
+ Platform.GL.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
+ }
+
+ public void SetTextureParameters(TextureParameters parameters)
+ {
+ if (parameters == null)
+ throw new ArgumentNullException("parameters");
+
+ _currentTextureParams = (TextureParameters)parameters.Clone();
+ }
+
+ internal TextureParameters GetCopyOfTextureParameters()
+ {
+ return (TextureParameters)_currentTextureParams.Clone();
+ }
+
+ #region Solid Color Textures
+
+ public Texture GetSolidColorTexture(Color color)
+ {
+ return GetSolidColorTexture(ref color);
+ }
+
+ public Texture GetSolidColorTexture(ref Color color)
+ {
+ int rgba = color.RGBA;
+ Texture result;
+ _solidColorTextures.TryGetValue(rgba, out result);
+ if (result == null)
+ {
+ result = CreateSolidColorTexture(ref color);
+ _solidColorTextures.Add(rgba, result);
+ }
+
+ return result;
+ }
+
+ private Texture CreateSolidColorTexture(ref Color color)
+ {
+ Platform.Logger.Info("Graphics", "Creating texture for solid color 0x{0:x}.", color.RGBA);
+
+ var solidColorImage = new Image(SolidColorTextureWidth, SolidColorTextureHeight, ImageFormat.RGBA);
+ solidColorImage.Clear(ref color);
+
+ var texture = new Texture(this, solidColorImage, _solidColorTextureParams);
+ return texture;
+ }
+
+ private void FillSolidColorTexture(Texture texture, ref Color color)
+ {
+ Platform.Logger.Info("Graphics", "Filling image data for solid color texture using color 0x{0:x}.", color.RGBA);
+
+ if (texture == null || texture.IsInvalidated || texture.Width != SolidColorTextureWidth || texture.Height != SolidColorTextureHeight)
+ throw new ArgumentException("Invalid texture.");
+
+ var solidColorImage = new Image(SolidColorTextureWidth, SolidColorTextureHeight, ImageFormat.RGBA);
+ solidColorImage.Clear(ref color);
+
+ texture.Update(solidColorImage);
+ }
+
+ #endregion
+
+ #region Binding
+
+ public void BindTexture(Texture texture, int unit = 0)
+ {
+ if (unit < 0 || unit >= MaxTextureUnits)
+ throw new ArgumentOutOfRangeException("unit");
+ if (texture == null || texture.IsInvalidated)
+ throw new ArgumentException("Invalid texture.");
+
+ if (texture != _boundTextures[unit])
+ {
+ Platform.GL.glActiveTexture(GL20.GL_TEXTURE0 + unit);
+ Platform.GL.glBindTexture(GL20.GL_TEXTURE_2D, texture.ID);
+
+ _boundTextures[unit] = texture;
+ }
+ }
+
+ public void UnbindTexture(int unit = 0)
+ {
+ if (unit < 0 || unit >= MaxTextureUnits)
+ throw new ArgumentOutOfRangeException("unit");
+
+ Platform.GL.glActiveTexture(GL20.GL_TEXTURE0 + unit);
+ Platform.GL.glBindTexture(GL20.GL_TEXTURE_2D, 0);
+
+ _boundTextures[unit] = null;
+ }
+
+ public void UnbindTexture(Texture texture)
+ {
+ if (texture == null || texture.IsInvalidated)
+ throw new ArgumentException("Invalid texture.");
+
+ for (int i = 0; i < MaxTextureUnits; ++i)
+ {
+ if (_boundTextures[i] == texture)
+ UnbindTexture(i);
+ }
+ }
+
+ public void BindRenderbuffer(Renderbuffer renderbuffer)
+ {
+ if (renderbuffer == null || renderbuffer.IsInvalidated)
+ throw new ArgumentException("Invalid renderbuffer.");
+
+ if (_boundRenderbuffer != renderbuffer)
+ {
+ Platform.GL.glBindRenderbuffer(GL20.GL_RENDERBUFFER, renderbuffer.ID);
+ _boundRenderbuffer = renderbuffer;
+ }
+ }
+
+ public void UnbindRenderbuffer()
+ {
+ Platform.GL.glBindRenderbuffer(GL20.GL_RENDERBUFFER, 0);
+ _boundRenderbuffer = null;
+ }
+
+ public void UnbindRenderbuffer(Renderbuffer renderbuffer)
+ {
+ if (renderbuffer == null)
+ throw new ArgumentNullException("renderbuffer");
+
+ if (renderbuffer == _boundRenderbuffer)
+ UnbindRenderbuffer();
+ }
+
+ public void BindFramebuffer(Framebuffer framebuffer)
+ {
+ if (framebuffer == null || framebuffer.IsInvalidated)
+ throw new ArgumentException("Invalid framebuffer.");
+
+ if (_boundFramebuffer != framebuffer)
+ {
+ Platform.GL.glBindFramebuffer(GL20.GL_FRAMEBUFFER, framebuffer.ID);
+ _boundFramebuffer = framebuffer;
+ }
+ }
+
+ public void UnbindFramebuffer()
+ {
+ Platform.GL.glBindFramebuffer(GL20.GL_FRAMEBUFFER, 0);
+ _boundFramebuffer = null;
+ }
+
+ public void UnbindFramebuffer(Framebuffer framebuffer)
+ {
+ if (framebuffer == null)
+ throw new ArgumentNullException("framebuffer");
+
+ if (framebuffer == _boundFramebuffer)
+ UnbindFramebuffer();
+ }
+
+ public void BindVertexBuffer(VertexBuffer buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+ if (buffer.NumElements <= 0)
+ throw new InvalidOperationException();
+
+ if (_boundVertexBuffer == buffer)
+ return;
+
+ if (buffer.IsClientSide)
+ BindVertexClientArrays(buffer);
+ else
+ BindVBO(buffer);
+
+ _boundVertexBuffer = buffer;
+
+ if (_isShaderVertexAttribsSet)
+ ClearSetShaderVertexAttributes();
+ }
+
+ public void UnbindVertexBuffer()
+ {
+ Platform.GL.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
+ _boundVertexBuffer = null;
+
+ if (_isShaderVertexAttribsSet)
+ ClearSetShaderVertexAttributes();
+ }
+
+ private void BindVBO(VertexBuffer buffer)
+ {
+ if (buffer.IsDirty)
+ buffer.Update();
+
+ Platform.GL.glBindBuffer(GL20.GL_ARRAY_BUFFER, buffer.ID);
+ }
+
+ private void BindVertexClientArrays(VertexBuffer buffer)
+ {
+ Platform.GL.glBindBuffer(GL20.GL_ARRAY_BUFFER, 0);
+ }
+
+ public void BindIndexBuffer(IndexBuffer buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+ if (buffer.NumElements <= 0)
+ throw new InvalidOperationException();
+
+ if (_boundIndexBuffer == buffer)
+ return;
+
+ if (buffer.IsClientSide)
+ BindIndexClientArray(buffer);
+ else
+ BindIBO(buffer);
+
+ _boundIndexBuffer = buffer;
+ }
+
+ public void UnbindIndexBuffer()
+ {
+ Platform.GL.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, 0);
+ _boundIndexBuffer = null;
+ }
+
+ private void BindIBO(IndexBuffer buffer)
+ {
+ if (buffer.IsDirty)
+ buffer.Update();
+
+ Platform.GL.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, buffer.ID);
+ }
+
+ private void BindIndexClientArray(IndexBuffer buffer)
+ {
+ Platform.GL.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, 0);
+ }
+
+ public void BindShader(Shader shader)
+ {
+ if (shader == null)
+ throw new ArgumentNullException("shader");
+ if (!shader.IsReadyForUse)
+ throw new InvalidOperationException("Shader hasn't been compiled and linked.");
+
+ Platform.GL.glUseProgram(shader.ProgramID);
+ _boundShader = shader;
+
+ if (_isShaderVertexAttribsSet)
+ ClearSetShaderVertexAttributes();
+
+ _boundShader.OnBind();
+ }
+
+ public void UnbindShader()
+ {
+ Platform.GL.glUseProgram(0);
+
+ if (_boundShader != null)
+ _boundShader.OnUnbind();
+ _boundShader = null;
+
+ if (_isShaderVertexAttribsSet)
+ ClearSetShaderVertexAttributes();
+ }
+
+ private void SetShaderVertexAttributes()
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+ if (_boundShader == null)
+ throw new InvalidOperationException("No bound shader.");
+ if (_boundVertexBuffer.NumAttributes < _boundShader.NumAttributes)
+ throw new InvalidOperationException("The bound vertex buffer does not have enough attributes to use the bound shader.");
+
+ for (int i = 0; i < _boundShader.NumAttributes; ++i)
+ {
+ int bufferAttribIndex;
+ int offset;
+ int size;
+
+ if (_boundShader.IsMappedToVBOStandardAttribute(i))
+ {
+ // "automatic attribute index discovery" by mapping via standard types
+ var standardType = _boundShader.GetMappedVBOStandardAttribute(i);
+ bufferAttribIndex = _boundVertexBuffer.GetIndexOfStandardAttribute(standardType);
+ if (bufferAttribIndex == -1)
+ throw new InvalidOperationException("Standard attribute type not present in bound vertex buffer.");
+ }
+ else
+ bufferAttribIndex = _boundShader.GetMappedVBOAttributeIndexFor(i);
+
+ // offset value will be in terms of floats (not bytes)
+ offset = _boundVertexBuffer.GetAttributeOffset(bufferAttribIndex);
+ size = _boundVertexBuffer.GetAttributeSize(bufferAttribIndex);
+
+ Platform.GL.glEnableVertexAttribArray(i);
+ if (_boundVertexBuffer.IsClientSide)
+ {
+ // pass reference to the first vertex and the first float within that vertex that is for this attribute
+ unsafe
+ {
+ fixed (float *p = _boundVertexBuffer.Data)
+ {
+ float *src = p + offset;
+ Platform.GL.glVertexAttribPointer(i, size, GL20.GL_FLOAT, false, _boundVertexBuffer.ElementWidthInBytes, new IntPtr((long)src));
+ }
+ }
+
+ }
+ else
+ // pass offset in bytes (starting from zero) that corresponds with the start of this attribute
+ Platform.GL.glVertexAttribPointer(i, size, GL20.GL_FLOAT, false, _boundVertexBuffer.ElementWidthInBytes, (IntPtr)(offset * sizeof(float)));
+
+ _enabledVertexAttribIndices.Push(i);
+ }
+
+ _isShaderVertexAttribsSet = true;
+ }
+
+ private void ClearSetShaderVertexAttributes()
+ {
+ while (_enabledVertexAttribIndices.Count > 0)
+ {
+ int index = _enabledVertexAttribIndices.Pop();
+ Platform.GL.glDisableVertexAttribArray(index);
+ }
+
+ _isShaderVertexAttribsSet = false;
+ }
+
+ private bool IsReadyToRender
+ {
+ get
+ {
+ if (_boundShader != null && _boundVertexBuffer != null && _isShaderVertexAttribsSet)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ #endregion
+
+ #region Rendering
+
+ public void RenderTriangles(IndexBuffer buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+ if (!buffer.IsClientSide)
+ throw new InvalidOperationException("GPU index buffers must be bound first.");
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+ if (_boundIndexBuffer != null)
+ throw new InvalidOperationException("GPU index buffer currently bound.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ int numVertices = buffer.NumElements;
+ if (numVertices % 3 != 0)
+ throw new InvalidOperationException("Number of elements in index buffer do not perfectly make up a set of triangles.");
+
+ Platform.GL.glDrawElements(GL20.GL_TRIANGLES, numVertices, GL20.GL_UNSIGNED_SHORT, buffer.Data);
+ }
+
+ public void RenderTriangles()
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ if (_boundIndexBuffer != null)
+ {
+ // using bound index buffer
+ int numVertices = _boundIndexBuffer.NumElements;
+ if (numVertices % 3 != 0)
+ throw new InvalidOperationException("Number of elements in bound index buffer do not perfectly make up a set of triangles.");
+
+ if (_boundIndexBuffer.IsClientSide)
+ Platform.GL.glDrawElements(GL20.GL_TRIANGLES, numVertices, GL20.GL_UNSIGNED_SHORT, _boundIndexBuffer.Data);
+ else
+ Platform.GL.glDrawElements(GL20.GL_TRIANGLES, numVertices, GL20.GL_UNSIGNED_SHORT, (IntPtr)0);
+ }
+ else
+ {
+ // no index buffer, just render the whole vertex buffer
+ int numVertices = _boundVertexBuffer.NumElements;
+ if (numVertices % 3 != 0)
+ throw new InvalidOperationException("Number of vertices in bound vertex buffer do not perfectly make up a set of triangles.");
+
+ Platform.GL.glDrawArrays(GL20.GL_TRIANGLES, 0, numVertices);
+ }
+ }
+
+ public void RenderTriangles(int startVertex, int numTriangles)
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ int numVertices = numTriangles * 3;
+
+ if (_boundIndexBuffer != null)
+ {
+ // using bound index buffer
+ if ((_boundIndexBuffer.NumElements - startVertex) < numVertices)
+ throw new InvalidOperationException("Bound index buffer does not contain enough elements.");
+
+ if (_boundIndexBuffer.IsClientSide)
+ {
+ unsafe
+ {
+ fixed (ushort *p = _boundIndexBuffer.Data)
+ {
+ ushort *src = p + startVertex;
+ Platform.GL.glDrawElements(GL20.GL_TRIANGLES, numVertices, GL20.GL_UNSIGNED_SHORT, new IntPtr((long)src));
+ }
+ }
+ }
+ else
+ {
+ // this offset needs to be in terms of bytes
+ int offset = startVertex * _boundIndexBuffer.ElementWidthInBytes;
+ Platform.GL.glDrawElements(GL20.GL_TRIANGLES, numVertices, GL20.GL_UNSIGNED_SHORT, (IntPtr)offset);
+ }
+ }
+ else
+ {
+ // no index buffer, just render the whole vertex buffer
+ if ((_boundVertexBuffer.NumElements - startVertex) < numVertices)
+ throw new InvalidOperationException("Bound vertex buffer does not contain enough vertices.");
+
+ Platform.GL.glDrawArrays(GL20.GL_TRIANGLES, startVertex, numVertices);
+ }
+ }
+
+ public void RenderLines(IndexBuffer buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+ if (!buffer.IsClientSide)
+ throw new InvalidOperationException("GPU index buffers must be bound first.");
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+ if (_boundIndexBuffer != null)
+ throw new InvalidOperationException("GPU index buffer currently bound.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ int numVertices = buffer.NumElements;
+ if (numVertices % 2 != 0)
+ throw new InvalidOperationException("Number of elements in index buffer do not perfectly make up a set of lines.");
+
+ Platform.GL.glDrawElements(GL20.GL_LINES, numVertices, GL20.GL_UNSIGNED_SHORT, buffer.Data);
+ }
+
+ public void RenderLines()
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ if (_boundIndexBuffer != null)
+ {
+ // using bound index buffer
+ int numVertices = _boundIndexBuffer.NumElements;
+ if (numVertices % 2 != 0)
+ throw new InvalidOperationException("Number of elements in bound index buffer do not perfectly make up a set of lines.");
+
+ if (_boundIndexBuffer.IsClientSide)
+ Platform.GL.glDrawElements(GL20.GL_LINES, numVertices, GL20.GL_UNSIGNED_SHORT, _boundIndexBuffer.Data);
+ else
+ Platform.GL.glDrawElements(GL20.GL_LINES, numVertices, GL20.GL_UNSIGNED_SHORT, (IntPtr)0);
+ }
+ else
+ {
+ // no index buffer, just render the whole vertex buffer
+ int numVertices = _boundVertexBuffer.NumElements;
+ if (numVertices % 2 != 0)
+ throw new InvalidOperationException("Number of vertices in bound vertex buffer do not perfectly make up a set of lines.");
+
+ Platform.GL.glDrawArrays(GL20.GL_LINES, 0, numVertices);
+ }
+ }
+
+ public void RenderLines(int startVertex, int numLines)
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ int numVertices = numLines * 2;
+
+ if (_boundIndexBuffer != null)
+ {
+ // using bound index buffer
+ if ((_boundIndexBuffer.NumElements - startVertex) < numVertices)
+ throw new InvalidOperationException("Bound index buffer does not contain enough elements.");
+
+ if (_boundIndexBuffer.IsClientSide)
+ {
+ unsafe
+ {
+ fixed (ushort *p = _boundIndexBuffer.Data)
+ {
+ ushort *src = p + startVertex;
+ Platform.GL.glDrawElements(GL20.GL_LINES, numVertices, GL20.GL_UNSIGNED_SHORT, new IntPtr((long)src));
+ }
+ }
+ }
+ else
+ {
+ // this offset needs to be in terms of bytes
+ int offset = startVertex * _boundIndexBuffer.ElementWidthInBytes;
+ Platform.GL.glDrawElements(GL20.GL_LINES, numVertices, GL20.GL_UNSIGNED_SHORT, (IntPtr)offset);
+ }
+ }
+ else
+ {
+ // no index buffer, just render the whole vertex buffer
+ if ((_boundVertexBuffer.NumElements - startVertex) < numVertices)
+ throw new InvalidOperationException("Bound vertex buffer does not contain enough vertices.");
+
+ Platform.GL.glDrawArrays(GL20.GL_LINES, startVertex, numVertices);
+ }
+ }
+
+ public void RenderPoints(IndexBuffer buffer)
+ {
+ if (buffer == null)
+ throw new ArgumentNullException("buffer");
+ if (!buffer.IsClientSide)
+ throw new InvalidOperationException("GPU index buffers must be bound first.");
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+ if (_boundIndexBuffer != null)
+ throw new InvalidOperationException("GPU index buffer currently bound.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ int numVertices = buffer.NumElements;
+
+ Platform.GL.glDrawElements(GL20.GL_POINTS, numVertices, GL20.GL_UNSIGNED_SHORT, buffer.Data);
+ }
+
+ public void RenderPoints()
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ if (_boundIndexBuffer != null)
+ {
+ // using bound index buffer
+ int numVertices = _boundIndexBuffer.NumElements;
+
+ if (_boundIndexBuffer.IsClientSide)
+ Platform.GL.glDrawElements(GL20.GL_POINTS, numVertices, GL20.GL_UNSIGNED_SHORT, _boundIndexBuffer.Data);
+ else
+ Platform.GL.glDrawElements(GL20.GL_POINTS, numVertices, GL20.GL_UNSIGNED_SHORT, (IntPtr)0);
+ }
+ else
+ {
+ // no index buffer, just render the whole vertex buffer
+ int numVertices = _boundVertexBuffer.NumElements;
+
+ Platform.GL.glDrawArrays(GL20.GL_POINTS, 0, numVertices);
+ }
+ }
+
+ public void RenderPoints(int startVertex, int numPoints)
+ {
+ if (_boundVertexBuffer == null)
+ throw new InvalidOperationException("No bound vertex buffer.");
+
+ if (!_isShaderVertexAttribsSet)
+ SetShaderVertexAttributes();
+
+ if (_boundIndexBuffer != null)
+ {
+ // using bound index buffer
+ if ((_boundIndexBuffer.NumElements - startVertex) < numPoints)
+ throw new InvalidOperationException("Bound index buffer does not contain enough elements.");
+
+ if (_boundIndexBuffer.IsClientSide)
+ {
+ unsafe
+ {
+ fixed (ushort *p = _boundIndexBuffer.Data)
+ {
+ ushort *src = p + startVertex;
+ Platform.GL.glDrawElements(GL20.GL_POINTS, numPoints, GL20.GL_UNSIGNED_SHORT, new IntPtr((long)src));
+ }
+ }
+ }
+ else
+ {
+ // this offset needs to be in terms of bytes
+ int offset = startVertex * _boundIndexBuffer.ElementWidthInBytes;
+ Platform.GL.glDrawElements(GL20.GL_POINTS, numPoints, GL20.GL_UNSIGNED_SHORT, (IntPtr)offset);
+ }
+ }
+ else
+ {
+ // no index buffer, just render the whole vertex buffer
+ if ((_boundVertexBuffer.NumElements - startVertex) < numPoints)
+ throw new InvalidOperationException("Bound vertex buffer does not contain enough vertices.");
+
+ Platform.GL.glDrawArrays(GL20.GL_POINTS, startVertex, numPoints);
+ }
+ }
+
+ #endregion
+
+ #region OpenGL Resource Management
+
+ public void RegisterManagedResource(GraphicsContextResource resource)
+ {
+ if (resource == null)
+ throw new ArgumentNullException("resource");
+ if (resource.IsReleased)
+ throw new ArgumentException("Resource has been released already.");
+ if (resource.GraphicsDevice == null)
+ throw new ArgumentException("Resource is not tied to the graphics context (GraphicsDevice is null).");
+
+ _managedResources.Add(resource);
+ }
+
+ public void UnregisterManagedResource(GraphicsContextResource resource)
+ {
+ if (resource == null)
+ throw new ArgumentNullException("resource");
+
+ _managedResources.Remove(resource);
+ }
+
+ public void UnregisterAllManagedResources()
+ {
+ _managedResources.Clear();
+ }
+
+ private bool _isGlResourcesReleased = false;
+
+ private void ReleaseGlResources()
+ {
+ if (_isGlResourcesReleased)
+ return;
+
+ while (_managedResources.Count > 0)
+ {
+ var resource = _managedResources[0];
+ resource.Dispose();
+ }
+ _managedResources.Clear();
+
+ _isGlResourcesReleased = true;
+
+ Platform.Logger.Info("Graphics", "Managed graphics context resources cleaned up.");
+ }
+
+ ~GraphicsDevice()
+ {
+ ReleaseGlResources();
+ }
+
+ public void Dispose()
+ {
+ ReleaseGlResources();
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Image.cs b/Blarg.GameFramework/Graphics/Image.cs
index 480aef0..1625d85 100644
--- a/Blarg.GameFramework/Graphics/Image.cs
+++ b/Blarg.GameFramework/Graphics/Image.cs
@@ -346,4 +346,3 @@ namespace Blarg.GameFramework.Graphics
}
}
}
-
diff --git a/Blarg.GameFramework/Graphics/IndexBuffer.cs b/Blarg.GameFramework/Graphics/IndexBuffer.cs
new file mode 100644
index 0000000..4fa8e20
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/IndexBuffer.cs
@@ -0,0 +1,168 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class IndexBuffer : BufferObject
+ {
+ private ushort[] _buffer;
+
+ public int CurrentPosition { get; private set; }
+
+ public int RemainingElements
+ {
+ get { return (NumElements - 1) - CurrentPosition; }
+ }
+
+ public override int NumElements
+ {
+ get { return _buffer.Length; }
+ }
+
+ public override int ElementWidthInBytes
+ {
+ get { return sizeof(ushort); }
+ }
+
+ public override ushort[] Data
+ {
+ get { return _buffer; }
+ }
+
+ public IndexBuffer(int numIndices, BufferObjectUsage usage)
+ : base(BufferObjectType.Index, usage)
+ {
+ Initialize(numIndices);
+ }
+
+ public IndexBuffer(GraphicsDevice graphicsDevice, int numIndices, BufferObjectUsage usage)
+ : base(graphicsDevice, BufferObjectType.Index, usage)
+ {
+ Initialize(numIndices);
+ CreateOnGpu();
+ }
+
+ public IndexBuffer(IndexBuffer source)
+ : base(BufferObjectType.Index, source.Usage)
+ {
+ Initialize(source);
+ }
+
+ public IndexBuffer(GraphicsDevice graphicsDevice, IndexBuffer source)
+ : base(graphicsDevice, BufferObjectType.Index, source.Usage)
+ {
+ Initialize(source);
+ CreateOnGpu();
+ }
+
+ private void Initialize(int numIndices)
+ {
+ Resize(numIndices);
+ }
+
+ private void Initialize(IndexBuffer source)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ if (source.NumElements <= 0)
+ throw new InvalidOperationException();
+
+ Resize(source.NumElements);
+ Array.Copy(source.Data, 0, _buffer, 0, NumElements);
+ }
+
+ public void Set(ushort[] indices)
+ {
+ if (indices == null)
+ throw new ArgumentNullException("indices");
+ if (indices.Length == 0)
+ throw new InvalidOperationException();
+ if (indices.Length > _buffer.Length)
+ throw new InvalidOperationException();
+
+ Array.Copy(indices, 0, _buffer, 0, indices.Length);
+ }
+
+ public void Set(int index, ushort value)
+ {
+ _buffer[index] = value;
+ }
+
+ public bool MoveNext()
+ {
+ ++CurrentPosition;
+ if (CurrentPosition >= NumElements)
+ {
+ --CurrentPosition;
+ return false;
+ }
+ else
+ return true;
+ }
+
+ public bool MovePrevious()
+ {
+ if (CurrentPosition == 0)
+ return false;
+ else
+ {
+ --CurrentPosition;
+ return true;
+ }
+ }
+
+ public void Move(int amount)
+ {
+ CurrentPosition += amount;
+ if (CurrentPosition < 0)
+ CurrentPosition = 0;
+ else if (CurrentPosition >= NumElements)
+ CurrentPosition = NumElements - 1;
+ }
+
+ public void MoveToStart()
+ {
+ CurrentPosition = 0;
+ }
+
+ public void MoveToEnd()
+ {
+ CurrentPosition = NumElements - 1;
+ }
+
+ public void MoveTo(int index)
+ {
+ CurrentPosition = index;
+ }
+
+ public void SetCurrent(ushort value)
+ {
+ Set(CurrentPosition, value);
+ }
+
+ public void Resize(int numIndices)
+ {
+ if (numIndices <= 0)
+ throw new ArgumentOutOfRangeException("numIndices");
+
+ if (_buffer == null)
+ _buffer = new ushort[numIndices];
+ else
+ Array.Resize(ref _buffer, numIndices);
+
+ if (!IsClientSide)
+ SizeBufferObject();
+
+ if (CurrentPosition >= NumElements)
+ CurrentPosition = NumElements - 1;
+ }
+
+ public void Extend(int amount)
+ {
+ int newSize = NumElements + amount;
+ if (newSize <= 0)
+ throw new ArgumentOutOfRangeException("amount");
+
+ Resize(newSize);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/RenderState.cs b/Blarg.GameFramework/Graphics/RenderState.cs
new file mode 100644
index 0000000..815e395
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/RenderState.cs
@@ -0,0 +1,113 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum CullMode
+ {
+ Back,
+ Front,
+ FrontAndBack
+ }
+
+ public enum DepthFunc
+ {
+ Never,
+ Less,
+ Equal,
+ LessOrEqual,
+ Greater,
+ NotEqual,
+ GreaterOrEqual,
+ Always
+ }
+
+ public class RenderState
+ {
+ public static readonly RenderState Default;
+ public static readonly RenderState NoDepthTesting;
+ public static readonly RenderState NoCulling;
+
+ static RenderState()
+ {
+ Default = new RenderState();
+
+ NoDepthTesting = new RenderState();
+ NoDepthTesting.DepthTesting = false;
+
+ NoCulling = new RenderState();
+ NoCulling.FaceCulling = false;
+ }
+
+ public bool DepthTesting { get; set; }
+ public DepthFunc DepthFunc { get; set; }
+ public bool FaceCulling { get; set; }
+ public CullMode FaceCullingMode { get; set; }
+ public float LineWidth { get; set; }
+
+ public RenderState()
+ {
+ Init();
+ }
+
+ public void Apply()
+ {
+ if (DepthTesting)
+ {
+ Platform.GL.glEnable(GL20.GL_DEPTH_TEST);
+
+ int depthFunc = GL20.GL_LESS;
+ switch (DepthFunc)
+ {
+ case DepthFunc.Never: depthFunc = GL20.GL_NEVER; break;
+ case DepthFunc.Less: depthFunc = GL20.GL_LESS; break;
+ case DepthFunc.Equal: depthFunc = GL20.GL_EQUAL; break;
+ case DepthFunc.LessOrEqual: depthFunc = GL20.GL_LEQUAL; break;
+ case DepthFunc.Greater: depthFunc = GL20.GL_GREATER; break;;
+ case DepthFunc.NotEqual: depthFunc = GL20.GL_NOTEQUAL; break;
+ case DepthFunc.GreaterOrEqual: depthFunc = GL20.GL_GEQUAL; break;
+ case DepthFunc.Always: depthFunc = GL20.GL_ALWAYS; break;
+ }
+ Platform.GL.glDepthFunc(depthFunc);
+ }
+ else
+ Platform.GL.glDisable(GL20.GL_DEPTH_TEST);
+
+ if (FaceCulling)
+ {
+ Platform.GL.glEnable(GL20.GL_CULL_FACE);
+ if (FaceCullingMode == CullMode.FrontAndBack)
+ Platform.GL.glCullFace(GL20.GL_FRONT_AND_BACK);
+ else if (FaceCullingMode == CullMode.Front)
+ Platform.GL.glCullFace(GL20.GL_FRONT);
+ else
+ Platform.GL.glCullFace(GL20.GL_BACK);
+ }
+ else
+ Platform.GL.glDisable(GL20.GL_CULL_FACE);
+
+ Platform.GL.glLineWidth(LineWidth);
+ }
+
+ private void Init()
+ {
+ DepthTesting = true;
+ DepthFunc = DepthFunc.Less;
+ FaceCulling = true;
+ FaceCullingMode = CullMode.Back;
+ LineWidth = 1.0f;
+ }
+
+ public RenderState Clone()
+ {
+ var clone = new RenderState();
+ clone.DepthFunc = DepthFunc;
+ clone.DepthTesting = DepthTesting;
+ clone.FaceCulling = FaceCulling;
+ clone.FaceCullingMode = FaceCullingMode;
+ clone.LineWidth = LineWidth;
+ return clone;
+ }
+
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Renderbuffer.cs b/Blarg.GameFramework/Graphics/Renderbuffer.cs
new file mode 100644
index 0000000..c7f0f1b
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Renderbuffer.cs
@@ -0,0 +1,109 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum RenderbufferFormat
+ {
+ RGB,
+ RGBA,
+ Depth,
+ Stencil
+ }
+
+ public class Renderbuffer : GraphicsContextResource
+ {
+ public int ID { get; private set; }
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public RenderbufferFormat Format { get; private set; }
+
+ public bool IsInvalidated
+ {
+ get { return ID == -1; }
+ }
+
+ public Renderbuffer(GraphicsDevice graphicsDevice, int width, int height, RenderbufferFormat format)
+ : base(graphicsDevice)
+ {
+ ID = -1;
+ Create(width, height, format);
+ }
+
+ private void Create(int width, int height, RenderbufferFormat format)
+ {
+ if (width < 1)
+ throw new ArgumentOutOfRangeException("width");
+ if (height < 1)
+ throw new ArgumentOutOfRangeException("height");
+ if (!IsInvalidated)
+ throw new InvalidOperationException();
+
+ int glFormat = 0;
+ if (Platform.Type == PlatformType.Mobile)
+ {
+ switch (format)
+ {
+ case RenderbufferFormat.RGB: glFormat = GL20.GL_RGB565; break;
+ case RenderbufferFormat.RGBA: glFormat = GL20.GL_RGBA4; break;
+ case RenderbufferFormat.Depth: glFormat = GL20.GL_DEPTH_COMPONENT16; break;
+ case RenderbufferFormat.Stencil: glFormat = GL20.GL_STENCIL_INDEX8; break;
+ }
+ }
+ else
+ {
+ switch (format)
+ {
+ case RenderbufferFormat.RGB: glFormat = GL20.GL_RGB; break;
+ case RenderbufferFormat.RGBA: glFormat = GL20.GL_RGBA; break;
+ case RenderbufferFormat.Depth: glFormat = GL20.GL_DEPTH_COMPONENT; break;
+ case RenderbufferFormat.Stencil: glFormat = GL20.GL_STENCIL_INDEX; break;
+ }
+ }
+ if (glFormat == 0)
+ throw new InvalidOperationException();
+
+ ID = Platform.GL.glGenRenderbuffers();
+
+ Width = width;
+ Height = height;
+ Format = format;
+
+ GraphicsDevice.BindRenderbuffer(this);
+ Platform.GL.glRenderbufferStorage(GL20.GL_RENDERBUFFER, glFormat, Width, Height);
+ GraphicsDevice.UnbindRenderbuffer(this);
+
+ Platform.Logger.Info("Graphics", "Created renderbuffer. ID = {0}, format = {1}, size = {2}x{3}.", ID, Format.ToString(), Width, Height);
+ }
+
+ #region GraphicsContextResource
+
+ public override void OnNewContext()
+ {
+ Create(Width, Height, Format);
+ }
+
+ public override void OnLostContext()
+ {
+ ID = -1;
+ }
+
+ protected override bool ReleaseResource()
+ {
+ if (!IsInvalidated)
+ {
+ GraphicsDevice.UnbindRenderbuffer(this);
+
+ Platform.GL.glDeleteRenderbuffers(ID);
+
+ Platform.Logger.Info("Graphics", "Deleted Renderbuffer ID = {0}.", ID);
+
+ ID = -1;
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Shader.cs b/Blarg.GameFramework/Graphics/Shader.cs
new file mode 100644
index 0000000..a323666
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Shader.cs
@@ -0,0 +1,1246 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using PortableGL;
+using Blarg.GameFramework.Support;
+
+namespace Blarg.GameFramework.Graphics
+{
+ #region Support Classes / Structures
+
+ public class ShaderUniform
+ {
+ public string Name;
+ public int Location;
+ public int Type;
+ public int Size;
+ }
+
+ public class ShaderAttribute
+ {
+ public string Name;
+ public int Location;
+ public int Type;
+ public int Size;
+ public bool IsTypeBound;
+ }
+
+ public class ShaderAttributeMapInfo
+ {
+ public bool UsesStandardType;
+ public VertexStandardAttributes StandardType;
+ public int AttributeIndex;
+ }
+
+ public enum ShaderCachedUniformType
+ {
+ Float1,
+ Int1,
+ Float2,
+ Int2,
+ Float3,
+ Int3,
+ Float4,
+ Int4,
+ Float9,
+ Float16
+ }
+
+ public class ShaderCachedUniform
+ {
+ public string Name;
+ public bool HasNewValue;
+ public ShaderCachedUniformType Type;
+
+ public float[] Floats = new float[16];
+ public int[] Ints = new int[4];
+ }
+
+ #endregion
+
+ public class Shader : GraphicsContextResource
+ {
+ // NOTE: using simple arrays instead of Dictionary, or some other key/value storage
+ // to avoid having to use IEnumerables + foreach-loops to walk through
+ // these lists (which would allocate memory).
+ // These lists will contain less then 10 items 99% of the time anyway
+ // so name lookups via a for-loop won't be too bad... I think
+ // Might be worthwhile to populate them via the uniform/attribute
+ // location value (same ordering) assuming the location value isn't
+ // some weird large number sometimes... that if it's zero-based in all cases
+ private ShaderUniform[] _uniforms;
+ private ShaderAttribute[] _attributes;
+ private ShaderAttributeMapInfo[] _attributeMapping;
+ private ShaderCachedUniform[] _cachedUniforms;
+
+ private string _inlineVertexShaderSource;
+ private string _inlineFragmentShaderSource;
+
+ private string _cachedVertexShaderSource;
+ private string _cachedFragmentShaderSource;
+
+ public int ProgramID { get; private set; }
+ public int VertexShaderID { get; private set; }
+ public int FragmentShaderID { get; private set; }
+
+ public bool IsLinked { get; private set; }
+ public bool IsVertexShaderCompiled { get; private set; }
+ public bool IsFragmentShaderCompiled { get; private set; }
+
+ public bool IsBound { get; private set; }
+
+ public int NumUniforms { get; private set; }
+ public int NumAttributes { get; private set; }
+
+ public bool IsReadyForUse
+ {
+ get { return (IsLinked && IsVertexShaderCompiled && IsFragmentShaderCompiled); }
+ }
+
+ public Shader(GraphicsDevice graphicsDevice, string vertexShaderSource, string fragmentShaderSource)
+ : base(graphicsDevice)
+ {
+ if (String.IsNullOrEmpty(vertexShaderSource))
+ throw new ArgumentNullException("vertexShaderSource");
+ if (String.IsNullOrEmpty(fragmentShaderSource))
+ throw new ArgumentNullException("fragmentShaderSource");
+
+ Initialize();
+ LoadCompileAndLink(vertexShaderSource, fragmentShaderSource);
+ CacheShaderSources(vertexShaderSource, fragmentShaderSource);
+ }
+
+ public Shader(GraphicsDevice graphicsDevice, TextReader vertexShaderSourceReader, TextReader fragmentShaderSourceReader)
+ : base(graphicsDevice)
+ {
+ if (vertexShaderSourceReader == null)
+ throw new ArgumentNullException("vertexShaderSourceReader");
+ if (fragmentShaderSourceReader == null)
+ throw new ArgumentNullException("fragmentShaderSourceReader");
+
+ string vertexShaderSource = vertexShaderSourceReader.ReadToEnd();
+ string fragmentShaderSource = fragmentShaderSourceReader.ReadToEnd();
+
+ Initialize();
+ LoadCompileAndLink(vertexShaderSource, fragmentShaderSource);
+ CacheShaderSources(vertexShaderSource, fragmentShaderSource);
+ }
+
+ protected Shader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ IsBound = false;
+ ProgramID = -1;
+ VertexShaderID = -1;
+ FragmentShaderID = -1;
+ IsLinked = false;
+ IsVertexShaderCompiled = false;
+ IsFragmentShaderCompiled = false;
+ _uniforms = null;
+ _attributes = null;
+ _cachedUniforms = null;
+ }
+
+ #region Compile and Linking
+
+ private void Compile(string vertexShaderSource, string fragmentShaderSource)
+ {
+ if (String.IsNullOrEmpty(vertexShaderSource))
+ throw new ArgumentException("vertexShaderSource");
+ if (String.IsNullOrEmpty(fragmentShaderSource))
+ throw new ArgumentException("fragmentShaderSource");
+ if (ProgramID != -1 || VertexShaderID != -1 || FragmentShaderID != -1)
+ throw new InvalidOperationException();
+
+ // first load and compile the vertex shader
+
+ int vertexShaderId = Platform.GL.glCreateShader(GL20.GL_VERTEX_SHADER);
+ if (vertexShaderId == 0)
+ throw new Exception("Failed to create OpenGL Vertex Shader object.");
+
+ // add in a special #define for convenience when we want to put both
+ // vertex and fragment shaders in the same source file
+ string[] vertexSources = { "#define VERTEX\n", vertexShaderSource };
+ int[] vertexSourcesLength = { vertexSources[0].Length, vertexSources[1].Length };
+
+ Platform.GL.glShaderSource(vertexShaderId, 2, vertexSources, vertexSourcesLength);
+ Platform.GL.glCompileShader(vertexShaderId);
+
+ int vertexShaderCompileStatus = Platform.GL.glGetShaderiv(vertexShaderId, GL20.GL_COMPILE_STATUS);
+
+ // log compiler error
+ if (vertexShaderCompileStatus == 0)
+ {
+ string log = GetShaderLog(vertexShaderId);
+ Platform.Logger.Error("OpenGL", "Error compiling vertex shader:\n{0}", log);
+ }
+
+ // and now the fragment shader
+
+ int fragmentShaderId = Platform.GL.glCreateShader(GL20.GL_FRAGMENT_SHADER);
+ if (fragmentShaderId == 0)
+ throw new Exception("Failed to create OpenGL Fragment Shader object.");
+
+ // add in a special #define for convenience when we want to put both
+ // vertex and fragment shaders in the same source file
+ string[] fragmentSources = { "#define FRAGMENT\n", fragmentShaderSource };
+ int[] fragmentSourcesLength = { fragmentSources[0].Length, fragmentSources[1].Length };
+
+ Platform.GL.glShaderSource(fragmentShaderId, 2, fragmentSources, fragmentSourcesLength);
+ Platform.GL.glCompileShader(fragmentShaderId);
+
+ int fragmentShaderCompileStatus = Platform.GL.glGetShaderiv(fragmentShaderId, GL20.GL_COMPILE_STATUS);
+
+ // log compiler error
+ if (fragmentShaderCompileStatus == 0)
+ {
+ string log = GetShaderLog(fragmentShaderId);
+ Platform.Logger.Error("OpenGL", "Error compiling fragment shader:\n{0}", log);
+ }
+
+ // only return success if both compiled successfully
+ if (vertexShaderCompileStatus != 0 && fragmentShaderCompileStatus != 0)
+ {
+ VertexShaderID = vertexShaderId;
+ IsVertexShaderCompiled = true;
+ FragmentShaderID = fragmentShaderId;
+ IsFragmentShaderCompiled = true;
+ }
+ else
+ throw new Exception("Vertex and/or fragment shader failed to compile.");
+ }
+
+ private string GetShaderLog(int shaderId)
+ {
+ return Platform.GL.glGetShaderInfoLog(shaderId);
+ }
+
+ private void Link()
+ {
+ if (ProgramID != -1)
+ throw new InvalidOperationException();
+ if (VertexShaderID == -1 || FragmentShaderID == -1)
+ throw new InvalidOperationException();
+
+ int programId = Platform.GL.glCreateProgram();
+ if (programId == 0)
+ throw new Exception("Failed to create OpenGL Shader Program object.");
+
+ Platform.GL.glAttachShader(programId, VertexShaderID);
+ Platform.GL.glAttachShader(programId, FragmentShaderID);
+ Platform.GL.glLinkProgram(programId);
+
+ int programLinkStatus = Platform.GL.glGetProgramiv(programId, GL20.GL_LINK_STATUS);
+
+ // log linker error
+ if (programLinkStatus == 0)
+ {
+ string log = Platform.GL.glGetProgramInfoLog(programId);
+ Platform.Logger.Error("OpenGL", "Error linking program:\n{0}", log);
+ }
+
+ if (programLinkStatus != 0)
+ {
+ ProgramID = programId;
+ IsLinked = true;
+ }
+ else
+ throw new Exception("Shader program failed to link.");
+ }
+
+ protected void LoadCompileAndLink(string vertexShaderSource, string fragmentShaderSource)
+ {
+ string vertexShaderToUse = vertexShaderSource;
+ string fragmentShaderToUse = fragmentShaderSource;
+
+ // if no source was provided, see if there is some cached source to load instead
+ if (vertexShaderToUse == null)
+ vertexShaderToUse = _cachedVertexShaderSource;
+ if (fragmentShaderToUse == null)
+ fragmentShaderToUse = _cachedFragmentShaderSource;
+
+ Compile(vertexShaderToUse, fragmentShaderToUse);
+ Link();
+
+ LoadUniformInfo();
+ LoadAttributeInfo();
+ }
+
+ protected void LoadCompileAndLinkInlineSources(string vertexShaderSource, string fragmentShaderSource)
+ {
+ if (String.IsNullOrEmpty(vertexShaderSource))
+ throw new ArgumentNullException("vertexShaderSource");
+ if (String.IsNullOrEmpty(fragmentShaderSource))
+ throw new ArgumentNullException("fragmentShaderSource");
+
+ LoadCompileAndLink(vertexShaderSource, fragmentShaderSource);
+ _inlineVertexShaderSource = vertexShaderSource;
+ _inlineFragmentShaderSource = fragmentShaderSource;
+ }
+
+ protected void ReloadCompileAndLink(string vertexShaderSource, string fragmentShaderSource)
+ {
+ // clear out all existing data that will be reset during the compile/link/init process
+ IsBound = false;
+ ProgramID = -1;
+ VertexShaderID = -1;
+ FragmentShaderID = -1;
+ IsLinked = false;
+ IsVertexShaderCompiled = false;
+ IsFragmentShaderCompiled = false;
+ _uniforms = null;
+ _attributes = null;
+ _cachedUniforms = null;
+
+ // TODO: leaving the attribute type mappings intact. This could maybe be a problem?
+ // I think only if the attribute ID's can be assigned randomly by OpenGL even if
+ // the source remains the same each time would it ever be a problem to keep the
+ // old type mappings intact. Since OpenGL assigns the attribute index a zero-based
+ // number I have a feeling it is based on the declaration order in the shader
+ // source... so as long as the source doesn't change it should be the same
+
+ LoadCompileAndLink(vertexShaderSource, fragmentShaderSource);
+ }
+
+ protected void CacheShaderSources(string vertexShaderSource, string fragmentShaderSource)
+ {
+ if (String.IsNullOrEmpty(vertexShaderSource))
+ throw new ArgumentException("vertexShaderSource");
+ if (String.IsNullOrEmpty(fragmentShaderSource))
+ throw new ArgumentException("fragmentShaderSource");
+
+ // ensure we have our own copy of the source strings...
+ _cachedVertexShaderSource = StringExtensions.Copy(vertexShaderSource);
+ _cachedFragmentShaderSource = StringExtensions.Copy(fragmentShaderSource);
+
+ _inlineVertexShaderSource = null;
+ _inlineFragmentShaderSource = null;
+ }
+
+ #endregion
+
+ #region Uniform and Attribute info loading
+
+ private void LoadUniformInfo()
+ {
+ if (ProgramID == -1)
+ throw new InvalidOperationException();
+
+ int numUniforms = Platform.GL.glGetProgramiv(ProgramID, GL20.GL_ACTIVE_UNIFORMS);
+ NumUniforms = numUniforms;
+
+ if (NumUniforms == 0)
+ return;
+
+ _uniforms = new ShaderUniform[NumUniforms];
+ _cachedUniforms = new ShaderCachedUniform[NumUniforms];
+
+ // collect information about each uniform
+ for (int i = 0; i < NumUniforms; ++i)
+ {
+ var uniform = new ShaderUniform();
+
+ int uniformType;
+ uniform.Name = Platform.GL.glGetActiveUniform(ProgramID, i, out uniform.Size, out uniformType);
+ uniform.Location = Platform.GL.glGetUniformLocation(ProgramID, uniform.Name);
+ uniform.Type = (int)uniformType;
+
+ // it seems Windows/Mac (possibly Linux too) have differing opinions on
+ // including "[0]" in the uniform name for uniforms that are arrays
+ // we'll just chop any "[0]" off if found in the uniform name before we
+ // add it to our list
+ int arraySubscriptPos = uniform.Name.IndexOf("[0]");
+ if (arraySubscriptPos != -1)
+ uniform.Name = uniform.Name.Substring(0, arraySubscriptPos);
+
+ _uniforms[i] = uniform;
+
+ // add an empty cached uniform value object as well
+ var cachedUniform = new ShaderCachedUniform();
+ cachedUniform.Name = StringExtensions.Copy(uniform.Name);
+ _cachedUniforms[i] = cachedUniform;
+ }
+ }
+
+ private void LoadAttributeInfo()
+ {
+ if (ProgramID == -1)
+ throw new InvalidOperationException();
+
+ int numAttributes = Platform.GL.glGetProgramiv(ProgramID, GL20.GL_ACTIVE_ATTRIBUTES);
+
+ // sanity checking, which only matters for shader reloading (e.g. when a context is lost)
+ if (_attributeMapping != null)
+ System.Diagnostics.Debug.Assert(numAttributes == NumAttributes);
+
+ NumAttributes = numAttributes;
+
+ if (NumAttributes == 0)
+ return;
+
+ _attributes = new ShaderAttribute[NumAttributes];
+
+ // leave existing attribute type mappings (they will already be there if, e.g. the context is lost and reloaded)
+ // (so, this array will only be allocated here on the first load)
+ if (_attributeMapping == null)
+ {
+ _attributeMapping = new ShaderAttributeMapInfo[NumAttributes];
+
+ // fill with a bunch of empty mapping objects to start out with
+ for (int i = 0; i < _attributeMapping.Length; ++i)
+ _attributeMapping[i] = new ShaderAttributeMapInfo();
+ }
+
+ // collect information about each attribute
+ for (int i = 0; i < NumAttributes; ++i)
+ {
+ var attribute = new ShaderAttribute();
+
+ int attributeType;
+ attribute.Name = Platform.GL.glGetActiveAttrib(ProgramID, i, out attribute.Size, out attributeType);
+ attribute.Location = Platform.GL.glGetAttribLocation(ProgramID, attribute.Name);
+ attribute.Type = (int)attributeType;
+
+ _attributes[i] = attribute;
+ }
+ }
+
+ #endregion
+
+ #region General Uniform and Attribute access
+
+ public bool HasUniform(string name)
+ {
+ if (_uniforms == null)
+ return false;
+
+ for (int i = 0; i < _uniforms.Length; ++i)
+ {
+ if (_uniforms[i].Name == name)
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool HasAttribute(string name)
+ {
+ if (_attributes == null)
+ return false;
+
+ for (int i = 0; i < _attributes.Length; ++i)
+ {
+ if (_attributes[i].Name == name)
+ return true;
+ }
+
+ return false;
+ }
+
+ protected ShaderUniform GetUniform(string name)
+ {
+ if (_uniforms == null)
+ return null;
+
+ for (int i = 0; i < _uniforms.Length; ++i)
+ {
+ if (_uniforms[i].Name == name)
+ return _uniforms[i];
+ }
+
+ return null;
+ }
+
+ protected ShaderAttribute GetAttribute(string name)
+ {
+ if (_attributes == null)
+ return null;
+
+ for (int i = 0; i < _attributes.Length; ++i)
+ {
+ if (_attributes[i].Name == name)
+ return _attributes[i];
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ #region Shader-To-VBO Attribute Mapping
+
+ public bool IsMappedToVBOStandardAttribute(int attributeIndex)
+ {
+ return _attributeMapping[attributeIndex].UsesStandardType;
+ }
+
+ public int GetMappedVBOAttributeIndexFor(int attributeIndex)
+ {
+ return _attributeMapping[attributeIndex].AttributeIndex;
+ }
+
+ public VertexStandardAttributes GetMappedVBOStandardAttribute(int attributeIndex)
+ {
+ return _attributeMapping[attributeIndex].StandardType;
+ }
+
+ public void MapToVBOAttribute(string name, int vboAttributeIndex)
+ {
+ var shaderAttrib = GetAttribute(name);
+ if (shaderAttrib == null)
+ throw new InvalidOperationException("Attribute does not exist.");
+ System.Diagnostics.Debug.Assert(shaderAttrib.Location < NumAttributes);
+
+ var mappingInfo = _attributeMapping[shaderAttrib.Location];
+ mappingInfo.UsesStandardType = false;
+ mappingInfo.AttributeIndex = vboAttributeIndex;
+
+ shaderAttrib.IsTypeBound = true;
+ }
+
+ public void MapToVBOStandardAttribute(string name, VertexStandardAttributes type)
+ {
+ var shaderAttrib = GetAttribute(name);
+ if (shaderAttrib == null)
+ throw new InvalidOperationException("Attribute does not exist.");
+ System.Diagnostics.Debug.Assert(shaderAttrib.Location < NumAttributes);
+
+ var mappingInfo = _attributeMapping[shaderAttrib.Location];
+ mappingInfo.UsesStandardType = true;
+ mappingInfo.StandardType = type;
+
+ shaderAttrib.IsTypeBound = true;
+ }
+
+ #endregion
+
+ #region Callbacks
+
+ internal virtual void OnBind()
+ {
+ System.Diagnostics.Debug.Assert(IsBound == false);
+ IsBound = true;
+ FlushCachedUniforms();
+ }
+
+ internal virtual void OnUnbind()
+ {
+ System.Diagnostics.Debug.Assert(IsBound == true);
+ IsBound = false;
+ if (_cachedUniforms != null)
+ {
+ for (int i = 0; i < _cachedUniforms.Length; ++i)
+ _cachedUniforms[i].HasNewValue = false;
+ }
+ }
+
+ #endregion
+
+ #region Cached Uniform value management
+
+ private ShaderCachedUniform GetCachedUniform(string name)
+ {
+ if (_cachedUniforms == null)
+ return null;
+
+ for (int i = 0; i < _cachedUniforms.Length; ++i)
+ {
+ if (_cachedUniforms[i].Name == name)
+ return _cachedUniforms[i];
+ }
+
+ return null;
+ }
+
+ private void FlushCachedUniforms()
+ {
+ if (!IsBound)
+ throw new InvalidOperationException();
+ if (_cachedUniforms == null || _cachedUniforms.Length == 0)
+ return;
+
+ for (int i = 0; i < _cachedUniforms.Length; ++i)
+ {
+ var uniform = _cachedUniforms[i];
+ if (!uniform.HasNewValue)
+ continue;
+
+ switch (uniform.Type)
+ {
+ case ShaderCachedUniformType.Float1: SetUniform(uniform.Name, uniform.Floats[0]); break;
+ case ShaderCachedUniformType.Int1: SetUniform(uniform.Name, uniform.Ints[0]); break;
+ case ShaderCachedUniformType.Float2: SetUniform(uniform.Name, uniform.Floats[0], uniform.Floats[1]); break;
+ case ShaderCachedUniformType.Int2: SetUniform(uniform.Name, uniform.Ints[0], uniform.Ints[1]); break;
+ case ShaderCachedUniformType.Float3: SetUniform(uniform.Name, uniform.Floats[0], uniform.Floats[1], uniform.Floats[2]); break;
+ case ShaderCachedUniformType.Int3: SetUniform(uniform.Name, uniform.Ints[0], uniform.Ints[1], uniform.Ints[2]); break;
+ case ShaderCachedUniformType.Float4: SetUniform(uniform.Name, uniform.Floats[0], uniform.Floats[1], uniform.Floats[2], uniform.Floats[3]); break;
+ case ShaderCachedUniformType.Int4: SetUniform(uniform.Name, uniform.Ints[0], uniform.Ints[1], uniform.Ints[2], uniform.Ints[3]); break;
+ case ShaderCachedUniformType.Float9:
+ Matrix3x3 m3x3 = new Matrix3x3(uniform.Floats);
+ SetUniform(uniform.Name, ref m3x3);
+ break;
+ case ShaderCachedUniformType.Float16:
+ Matrix4x4 m4x4 = new Matrix4x4(uniform.Floats);
+ SetUniform(uniform.Name, ref m4x4);
+ break;
+ }
+
+ uniform.HasNewValue = false;
+ }
+ }
+
+ #endregion
+
+ #region Uniform Setters
+
+ public void SetUniform(string name, float x)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform1f(uniform.Location, x);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float1;
+ cachedUniform.Floats[0] = x;
+ }
+ }
+
+ public void SetUniform(string name, int x)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform1i(uniform.Location, x);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Int1;
+ cachedUniform.Ints[0] = x;
+ }
+ }
+
+ public void SetUniform(string name, float x, float y)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform2f(uniform.Location, x, y);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float2;
+ cachedUniform.Floats[0] = x;
+ cachedUniform.Floats[1] = y;
+ }
+ }
+
+ public void SetUniform(string name, int x, int y)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform2i(uniform.Location, x, y);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Int2;
+ cachedUniform.Ints[0] = x;
+ cachedUniform.Ints[1] = y;
+ }
+ }
+
+ public void SetUniform(string name, Vector2 v)
+ {
+ SetUniform(name, ref v);
+ }
+
+ public void SetUniform(string name, ref Vector2 v)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform2f(uniform.Location, v.X, v.Y);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float2;
+ cachedUniform.Floats[0] = v.X;
+ cachedUniform.Floats[1] = v.Y;
+ }
+ }
+
+ public void SetUniform(string name, Point2 p)
+ {
+ SetUniform(name, ref p);
+ }
+
+ public void SetUniform(string name, ref Point2 p)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform2i(uniform.Location, p.X, p.Y);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Int2;
+ cachedUniform.Ints[0] = p.X;
+ cachedUniform.Ints[1] = p.Y;
+ }
+ }
+
+ public void SetUniform(string name, float x, float y, float z)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform3f(uniform.Location, x, y, z);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float3;
+ cachedUniform.Floats[0] = x;
+ cachedUniform.Floats[1] = y;
+ cachedUniform.Floats[2] = z;
+ }
+ }
+
+ public void SetUniform(string name, int x, int y, int z)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform3i(uniform.Location, x, y, z);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Int3;
+ cachedUniform.Ints[0] = x;
+ cachedUniform.Ints[1] = y;
+ cachedUniform.Ints[2] = z;
+ }
+ }
+
+ public void SetUniform(string name, Vector3 v)
+ {
+ SetUniform(name, ref v);
+ }
+
+ public void SetUniform(string name, ref Vector3 v)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform3f(uniform.Location, v.X, v.Y, v.Z);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float3;
+ cachedUniform.Floats[0] = v.X;
+ cachedUniform.Floats[1] = v.Y;
+ cachedUniform.Floats[2] = v.Z;
+ }
+ }
+
+ public void SetUniform(string name, Point3 p)
+ {
+ SetUniform(name, ref p);
+ }
+
+ public void SetUniform(string name, ref Point3 p)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform3i(uniform.Location, p.X, p.Y, p.Z);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Int3;
+ cachedUniform.Ints[0] = p.X;
+ cachedUniform.Ints[1] = p.Y;
+ cachedUniform.Ints[2] = p.Z;
+ }
+ }
+
+ public void SetUniform(string name, float x, float y, float z, float w)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform4f(uniform.Location, x, y, z, w);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float4;
+ cachedUniform.Floats[0] = x;
+ cachedUniform.Floats[1] = y;
+ cachedUniform.Floats[2] = z;
+ cachedUniform.Floats[3] = w;
+ }
+ }
+
+ public void SetUniform(string name, int x, int y, int z, int w)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform4i(uniform.Location, x, y, z, w);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Int4;
+ cachedUniform.Ints[0] = x;
+ cachedUniform.Ints[1] = y;
+ cachedUniform.Ints[2] = z;
+ cachedUniform.Ints[3] = w;
+ }
+ }
+
+ public void SetUniform(string name, Vector4 v)
+ {
+ SetUniform(name, ref v);
+ }
+
+ public void SetUniform(string name, ref Vector4 v)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform4f(uniform.Location, v.X, v.Y, v.Z, v.W);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float4;
+ cachedUniform.Floats[0] = v.X;
+ cachedUniform.Floats[1] = v.Y;
+ cachedUniform.Floats[2] = v.Z;
+ cachedUniform.Floats[3] = v.W;
+ }
+ }
+
+ public void SetUniform(string name, Quaternion q)
+ {
+ SetUniform(name, ref q);
+ }
+
+ public void SetUniform(string name, ref Quaternion q)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform4f(uniform.Location, q.X, q.Y, q.Z, q.W);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float4;
+ cachedUniform.Floats[0] = q.X;
+ cachedUniform.Floats[1] = q.Y;
+ cachedUniform.Floats[2] = q.Z;
+ cachedUniform.Floats[3] = q.W;
+ }
+ }
+
+ public void SetUniform(string name, Color c)
+ {
+ SetUniform(name, ref c);
+ }
+
+ public void SetUniform(string name, ref Color c)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform4f(uniform.Location, c.R, c.G, c.B, c.A);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float4;
+ cachedUniform.Floats[0] = c.R;
+ cachedUniform.Floats[1] = c.G;
+ cachedUniform.Floats[2] = c.B;
+ cachedUniform.Floats[3] = c.A;
+ }
+ }
+
+ public void SetUniform(string name, Matrix3x3 m)
+ {
+ SetUniform(name, ref m);
+ }
+
+ public void SetUniform(string name, ref Matrix3x3 m)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniformMatrix3fv(uniform.Location, 1, false, ref m.M11);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float9;
+ m.ToArray(cachedUniform.Floats);
+ }
+ }
+
+ public void SetUniform(string name, Matrix4x4 m)
+ {
+ SetUniform(name, ref m);
+ }
+
+ public void SetUniform(string name, ref Matrix4x4 m)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size != 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniformMatrix4fv(uniform.Location, 1, false, ref m.M11);
+ }
+ else
+ {
+ var cachedUniform = GetCachedUniform(name);
+ if (cachedUniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ cachedUniform.Type = ShaderCachedUniformType.Float16;
+ m.ToArray(cachedUniform.Floats);
+ }
+ }
+
+ public void SetUniform(string name, float[] x)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+ Platform.GL.glUniform1fv(uniform.Location, x.Length, x);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, int[] x)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ unsafe
+ {
+ fixed (int *p = x)
+ {
+ Platform.GL.glUniform1fv(uniform.Location, x.Length, new IntPtr((long)p));
+ }
+ }
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Vector2[] v)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniform2fv(uniform.Location, v.Length, ref v[0].X);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Vector3[] v)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniform3fv(uniform.Location, v.Length, ref v[0].X);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Vector4[] v)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniform4fv(uniform.Location, v.Length, ref v[0].X);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Quaternion[] q)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniform4fv(uniform.Location, q.Length, ref q[0].X);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Color[] c)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniform4fv(uniform.Location, c.Length, ref c[0].R);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Matrix3x3[] m)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniformMatrix3fv(uniform.Location, m.Length, false, ref m[0].M11);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ public void SetUniform(string name, Matrix4x4[] m)
+ {
+ if (IsBound)
+ {
+ var uniform = GetUniform(name);
+ if (uniform == null)
+ throw new ArgumentException("Invalid uniform.");
+ if (uniform.Size <= 1)
+ throw new InvalidOperationException();
+
+ Platform.GL.glUniformMatrix4fv(uniform.Location, m.Length, false, ref m[0].M11);
+ }
+ else
+ {
+ throw new NotImplementedException("Caching uniform value arrays not implemented yet.");
+ }
+ }
+
+ #endregion
+
+ #region GraphicsContextResource
+
+ public override void OnNewContext()
+ {
+ if (!String.IsNullOrEmpty(_inlineVertexShaderSource) && !String.IsNullOrEmpty(_inlineFragmentShaderSource))
+ ReloadCompileAndLink(_inlineVertexShaderSource, _inlineFragmentShaderSource);
+ else
+ ReloadCompileAndLink(null, null);
+ }
+
+ public override void OnLostContext()
+ {
+ ProgramID = -1;
+ VertexShaderID = -1;
+ FragmentShaderID = -1;
+ }
+
+ protected override bool ReleaseResource()
+ {
+ // this needs to happen before the OpenGL context is destroyed
+ // which is not guaranteed if we rely 100% on the GC to clean
+ // everything up. best solution is to ensure all Shader
+ // objects are not being referenced before the window is
+ // closed and do a GC.Collect()
+
+ if (VertexShaderID != -1)
+ {
+ Platform.GL.glDeleteShader(VertexShaderID);
+ VertexShaderID = -1;
+ }
+ if (FragmentShaderID != -1)
+ {
+ Platform.GL.glDeleteShader(FragmentShaderID);
+ FragmentShaderID = -1;
+ }
+ if (ProgramID != -1)
+ {
+ Platform.GL.glDeleteProgram(ProgramID);
+ ProgramID = -1;
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/SpriteFont.cs b/Blarg.GameFramework/Graphics/SpriteFont.cs
new file mode 100644
index 0000000..172fa37
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/SpriteFont.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Text;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class SpriteFont : GraphicsContextResource
+ {
+ private static StringBuilder _buffer = new StringBuilder(8192);
+
+ public const int LowGlyphAscii = 32;
+ public const int HighGlyphAscii = 127;
+
+ private TextureAtlas _glyphs;
+
+ public int Size { get; private set; }
+ public int LetterHeight { get; private set; }
+
+ public Texture Texture { get; private set; }
+
+ public bool IsInvalidated { get; private set; }
+
+ #region Instantiation, (Re-)loading, new/lost context
+
+ // this class is designed to be instantiated and have it's lifecycle maintained
+ // by a sprite font content loader *only*
+ // therefore, the following methods are all internal
+
+ internal SpriteFont(GraphicsDevice graphicsDevice, int fontSize, Texture texture, TextureAtlas glyphs)
+ : base(graphicsDevice)
+ {
+ if (fontSize < 1)
+ throw new ArgumentOutOfRangeException("fontSize");
+ if (texture == null)
+ throw new ArgumentNullException("texture");
+ if (texture.IsInvalidated)
+ throw new ArgumentException("Texture is invalidated.");
+ if (glyphs == null)
+ throw new ArgumentNullException("glyphs");
+ if (glyphs.NumTiles == 0)
+ throw new ArgumentException("TextureAtlas is empty.");
+
+ Size = fontSize;
+ Texture = texture;
+ _glyphs = glyphs;
+
+ Rect charSize = new Rect();
+ _glyphs.GetTileDimensions(GetIndexOfChar(' '), out charSize);
+ LetterHeight = charSize.Height;
+
+ IsInvalidated = false;
+ }
+
+ internal void Reload(Image fontBitmap)
+ {
+ if (!IsInvalidated)
+ throw new InvalidOperationException();
+ if (fontBitmap == null)
+ throw new ArgumentNullException("fontBitmap");
+ if (fontBitmap.Width != Texture.Width || fontBitmap.Height != Texture.Height)
+ throw new ArgumentException("Font bitmap dimensions do not match texture dimensions.");
+
+ Texture.Update(fontBitmap);
+
+ IsInvalidated = false;
+ }
+
+ public override void OnNewContext()
+ {
+ }
+
+ public override void OnLostContext()
+ {
+ IsInvalidated = true;
+ }
+
+ #endregion
+
+ private int GetIndexOfChar(char c)
+ {
+ if ((int)c < LowGlyphAscii || (int)c > HighGlyphAscii)
+ return HighGlyphAscii - LowGlyphAscii;
+ else
+ return (int)c - LowGlyphAscii;
+ }
+
+ public void GetCharDimensions(char c, out Rect dimensions)
+ {
+ int index = GetIndexOfChar(c);
+ _glyphs.GetTileDimensions(index, out dimensions);
+ }
+
+ public void GetCharTexCoords(char c, out RectF texCoords)
+ {
+ int index = GetIndexOfChar(c);
+ _glyphs.GetTileTexCoords(index, out texCoords);
+ }
+
+ public void MeasureString(out int width, out int height, string format, params object[] args)
+ {
+ _buffer.Clear();
+ _buffer.AppendFormat(format, args);
+
+ int textLength = _buffer.Length;
+
+ int currentMaxWidth = 0;
+ int left = 0;
+ int numLines = 1;
+
+ for (int i = 0; i < textLength; ++i)
+ {
+ char c = _buffer[i];
+ if (c == '\n')
+ {
+ // new line
+ left = 0;
+ ++numLines;
+ }
+ else
+ {
+ Rect charSize = new Rect();
+ GetCharDimensions(c, out charSize);
+ left += charSize.Width;
+ }
+
+ currentMaxWidth = Math.Max(left, currentMaxWidth);
+ }
+
+ width = currentMaxWidth;
+ height = numLines * LetterHeight;
+ }
+
+ public void MeasureString(out int width, out int height, float scale, string format, params object[] args)
+ {
+ _buffer.Clear();
+ _buffer.AppendFormat(format, args);
+
+ int textLength = _buffer.Length;
+
+ float scaledLetterHeight = (float)LetterHeight * scale;
+
+ float currentMaxWidth = 0.0f;
+ float left = 0.0f;
+ int numLines = 1;
+
+ for (int i = 0; i < textLength; ++i)
+ {
+ char c = _buffer[i];
+ if (c == '\n')
+ {
+ // new line
+ left = 0.0f;
+ ++numLines;
+ }
+ else
+ {
+ Rect charSize = new Rect();
+ GetCharDimensions(c, out charSize);
+ left += (float)charSize.Width * scale;
+ }
+
+ currentMaxWidth = Math.Max(left, currentMaxWidth);
+ }
+
+ width = (int)Math.Ceiling(currentMaxWidth);
+ height = (int)(numLines * scaledLetterHeight);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/SpriteFontTrueTypeLoader.cs b/Blarg.GameFramework/Graphics/SpriteFontTrueTypeLoader.cs
new file mode 100644
index 0000000..ad095cf
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/SpriteFontTrueTypeLoader.cs
@@ -0,0 +1,196 @@
+using System;
+using System.IO;
+using TrueTypeSharp;
+
+namespace Blarg.GameFramework.Graphics
+{
+ internal static class SpriteFontTrueTypeLoader
+ {
+ private struct SpriteFontGlyphMetrics
+ {
+ public uint Index;
+ public float Scale;
+ public Rect Dimensions;
+ public int Ascent;
+ public int Descent;
+ public int LineGap;
+ public int Advance;
+ public int LetterWidth;
+ public int LetterHeight;
+ }
+
+ public static SpriteFont Load(GraphicsDevice graphicsDevice, Stream file, int size, SpriteFont existingFont = null)
+ {
+ const int FontBitmapWidth = 512;
+ const int FontBitmapHeight = 512;
+ const int NumGlyphs = SpriteFont.HighGlyphAscii - SpriteFont.LowGlyphAscii;
+
+ if (graphicsDevice == null)
+ throw new ArgumentNullException("graphicsDevice");
+ if (file == null)
+ throw new ArgumentNullException("file");
+
+ var binaryReader = new BinaryReader(file);
+ var fontBytes = binaryReader.ReadBytes((int)file.Length);
+
+ var ttf = new TrueTypeFont(fontBytes, 0);
+
+ // get glyph metrics for the "maximum size" glyph as an indicator of the
+ // general size of each glyph in this font. Uppercase 'W' seems to be a
+ // pretty good glyph to represent this "maximum size".
+ var maxMetrics = new SpriteFontGlyphMetrics();
+ if (!GetGlyphMetrics(ttf, 'W', size, ref maxMetrics))
+ throw new Exception("Failed getting initial 'large glyph' metrics.");
+
+ var fontBitmap = new Image(FontBitmapWidth, FontBitmapHeight, ImageFormat.A);
+ fontBitmap.Clear();
+
+ CustomTextureAtlas glyphs = null;
+
+ // if an existing font was provided, then we're just rebuilding the bitmap
+ // and the font will continue using the texture atlas is was provided on
+ // initial creation again
+ if (existingFont == null)
+ glyphs = new CustomTextureAtlas(fontBitmap.Width, fontBitmap.Height);
+
+ // NOTE TO SELF: a lot of this feels "hackish" and that some weird font isn't going to play nice with this. clean this up at some point!
+
+ // current position to draw the glyph to on the bitmap
+ int x = 0;
+ int y = 0;
+
+ // total line height for each row of glyphs on the bitmap. this is not really the font height.
+ // it is likely slightly larger then that. this adds the font's descent a second time to make
+ // vertical room for a few of the glyphs (e.g. '{}' or '()', or even '$') which go off the
+ // top/bottom by a slight bit
+ int lineHeight = (maxMetrics.Ascent - maxMetrics.Descent) + maxMetrics.LineGap - maxMetrics.Descent;
+
+ var metrics = new SpriteFontGlyphMetrics();
+ var position = new Rect();
+
+ // temporary bitmap to hold each rendered glyph (since TrueTypeSharp can't render into
+ // our the pixel buffer in the fontBitmap object directly)
+ // setting the initial size like this is slightly overkill for what will actually be used
+ // but it seems to be big enough for even the largest glyphs (minimal-to-no resizing needed
+ // in the render loop below)
+ var glyphBitmap = new FontBitmap(lineHeight, lineHeight);
+
+ for (int i = 0; i < NumGlyphs; ++i)
+ {
+ char c = (char)(i + SpriteFont.LowGlyphAscii);
+
+ if (!GetGlyphMetrics(ttf, c, size, ref metrics))
+ throw new Exception(String.Format("Failed getting metrics for glyph \"{0}\".", c));
+
+ // adjust each glyph's rect so that it has it's own space that doesn't
+ // collide with any of it's neighbour glyphs (neighbour as seen on the
+ // font bitmap)
+ if (metrics.Dimensions.Left < 0)
+ {
+ // bump the glyph over to the right by the same amount it was over to the left
+ metrics.Advance += -metrics.Dimensions.Left;
+ metrics.Dimensions.Left = 0;
+ }
+
+ // do we need to move to the next row?
+ if ((x + metrics.Advance + metrics.Dimensions.Left) >= fontBitmap.Width)
+ {
+ // yes
+ x = 0;
+ y += lineHeight;
+ System.Diagnostics.Debug.Assert((y + lineHeight) < fontBitmap.Height);
+ }
+
+ // the destination bitmap pixel coordinates of this glyph. these are the
+ // pixel coordinates that will be stored in the font's texture atlas
+ // which will be used by the texture atlas to build texture coords
+ position.Left = x;
+ position.Top = y;
+ position.Right = x + metrics.Advance;
+ position.Bottom = y + lineHeight;
+
+ // top-left coords and dimensions to have stb_truetype draw the glyph at in the font bitmap
+ int drawX = position.Left + metrics.Dimensions.Left;
+ int drawY = (position.Bottom + metrics.Descent) + metrics.Descent - metrics.Dimensions.Bottom + maxMetrics.LineGap;
+ int drawWidth = position.Width;
+ int drawHeight = position.Height;
+
+ // resize the glyph bitmap only when necessary
+ if (drawWidth > glyphBitmap.Width || drawHeight > glyphBitmap.Height)
+ glyphBitmap = new FontBitmap(drawWidth, drawHeight);
+
+ // render glyph
+ ttf.MakeGlyphBitmap(metrics.Index, metrics.Scale, metrics.Scale, glyphBitmap);
+
+ // copy from the temp bitmap to our full font bitmap (unfortunately TrueTypeSharp doesn't give us
+ // a way to do this directly out of the box)
+ fontBitmap.Copy(glyphBitmap.Buffer, glyphBitmap.Width, glyphBitmap.Height, 8, 0, 0, drawWidth, drawHeight, drawX, drawY);
+
+ if (glyphs != null)
+ {
+ // add the glyph position to the texture atlas (which will calc the texture coords for us)
+ int newIndex = glyphs.Add(position);
+ System.Diagnostics.Debug.Assert(newIndex == ((int)c - SpriteFont.LowGlyphAscii));
+ }
+
+ // move to the next glyph's position in the bitmap
+ x += maxMetrics.Advance;
+ }
+
+ SpriteFont font;
+ if (existingFont == null)
+ {
+ var texture = new Texture(graphicsDevice, fontBitmap, TextureParameters.Pixelated);
+ font = new SpriteFont(graphicsDevice, size, texture, glyphs);
+ }
+ else
+ {
+ font = existingFont;
+
+ // texture will have been reallocated already by SpriteFont, just need to provide the bitmap
+ // and the Reload method will handle updating the texture itself
+ font.Reload(fontBitmap);
+ }
+
+ return font;
+ }
+
+ private static bool GetGlyphMetrics(TrueTypeFont font, char glyph, int size, ref SpriteFontGlyphMetrics metrics)
+ {
+ uint index = font.FindGlyphIndex(glyph);
+ if (index == 0)
+ return false;
+
+ metrics.Scale = font.GetScaleForPixelHeight((float)size);
+ metrics.LetterHeight = size;
+ metrics.Index = index;
+
+ font.GetGlyphBox(index, out metrics.Dimensions.Left, out metrics.Dimensions.Top, out metrics.Dimensions.Right, out metrics.Dimensions.Bottom);
+ font.GetFontVMetrics(out metrics.Ascent, out metrics.Descent, out metrics.LineGap);
+ int leftSideBearing;
+ font.GetGlyphHMetrics(index, out metrics.Advance, out leftSideBearing);
+
+ // adjust all the metrics we got by the font size scale value
+ // (I guess this puts them from whatever units they were in to pixel units)
+ metrics.Dimensions.Left = (int)Math.Ceiling((double)metrics.Dimensions.Left * metrics.Scale);
+ metrics.Dimensions.Top = (int)Math.Ceiling((double)metrics.Dimensions.Top * metrics.Scale);
+ metrics.Dimensions.Right = (int)Math.Ceiling((double)metrics.Dimensions.Right * metrics.Scale);
+ metrics.Dimensions.Bottom = (int)Math.Ceiling((double)metrics.Dimensions.Bottom * metrics.Scale);
+ metrics.Ascent = (int)Math.Ceiling((double)metrics.Ascent * metrics.Scale);
+ metrics.Descent = (int)Math.Ceiling((double)metrics.Descent * metrics.Scale);
+ metrics.LineGap = (int)Math.Ceiling((double)metrics.LineGap * metrics.Scale);
+ metrics.Advance = (int)Math.Ceiling((double)metrics.Advance * metrics.Scale);
+ metrics.LetterWidth = metrics.Dimensions.Right - metrics.Dimensions.Left;
+
+ // seen some pixel/bitmap fonts that have the total ascent/descent calculated height
+ // greater then the pixel height. this just figures out this difference, if present,
+ // and sets an appropriate line gap equal to it (in these cases, linegap was 0)
+ int calculatedHeight = metrics.Ascent - metrics.Descent;
+ int heightDifference = Math.Abs(calculatedHeight - metrics.LetterHeight);
+ if (heightDifference != metrics.LineGap && metrics.LineGap == 0)
+ metrics.LineGap = heightDifference;
+
+ return true;
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/SpriteShader.cs b/Blarg.GameFramework/Graphics/SpriteShader.cs
new file mode 100644
index 0000000..0db1554
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/SpriteShader.cs
@@ -0,0 +1,40 @@
+using System;
+using System.IO;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public abstract class SpriteShader : StandardShader
+ {
+ protected string TextureHasAlphaOnlyUniformName { get; set; }
+
+ protected SpriteShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ Initialize();
+ }
+
+ protected SpriteShader(GraphicsDevice graphicsDevice, string vertexShaderSource, string fragmentShaderSource)
+ : base(graphicsDevice, vertexShaderSource, fragmentShaderSource)
+ {
+ Initialize();
+ }
+
+ protected SpriteShader(GraphicsDevice graphicsDevice, TextReader vertexShaderSourceReader, TextReader fragmentShaderSourceReader)
+ : base(graphicsDevice, vertexShaderSourceReader, fragmentShaderSourceReader)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ TextureHasAlphaOnlyUniformName = "u_textureHasAlphaOnly";
+ }
+
+ public void SetTextureHasAlphaOnly(bool hasAlphaOnly)
+ {
+ if (!IsReadyForUse)
+ throw new InvalidOperationException();
+ SetUniform(TextureHasAlphaOnlyUniformName, Convert.ToInt32(hasAlphaOnly));
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/StandardShader.cs b/Blarg.GameFramework/Graphics/StandardShader.cs
new file mode 100644
index 0000000..47f667b
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/StandardShader.cs
@@ -0,0 +1,59 @@
+using System;
+using System.IO;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public abstract class StandardShader : Shader
+ {
+ protected string ModelViewMatrixUniformName { get; set; }
+ protected string ProjectionMatrixUniformName { get; set; }
+
+ protected StandardShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ Initialize();
+ }
+
+ protected StandardShader(GraphicsDevice graphicsDevice, string vertexShaderSource, string fragmentShaderSource)
+ : base(graphicsDevice, vertexShaderSource, fragmentShaderSource)
+ {
+ Initialize();
+ }
+
+ protected StandardShader(GraphicsDevice graphicsDevice, TextReader vertexShaderSourceReader, TextReader fragmentShaderSourceReader)
+ : base(graphicsDevice, vertexShaderSourceReader, fragmentShaderSourceReader)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ ModelViewMatrixUniformName = "u_modelViewMatrix";
+ ProjectionMatrixUniformName = "u_projectionMatrix";
+ }
+
+ public void SetModelViewMatrix(Matrix4x4 m)
+ {
+ SetModelViewMatrix(ref m);
+ }
+
+ public void SetModelViewMatrix(ref Matrix4x4 m)
+ {
+ if (!IsReadyForUse)
+ throw new InvalidOperationException();
+ SetUniform(ModelViewMatrixUniformName, ref m);
+ }
+
+ public void SetProjectionMatrix(Matrix4x4 m)
+ {
+ SetProjectionMatrix(ref m);
+ }
+
+ public void SetProjectionMatrix(ref Matrix4x4 m)
+ {
+ if (!IsReadyForUse)
+ throw new InvalidOperationException();
+ SetUniform(ProjectionMatrixUniformName, ref m);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/Texture.cs b/Blarg.GameFramework/Graphics/Texture.cs
new file mode 100644
index 0000000..44972e9
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/Texture.cs
@@ -0,0 +1,286 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum TextureFormat
+ {
+ RGB,
+ RGBA,
+ Alpha,
+ Depth
+ }
+
+ public class Texture : GraphicsContextResource
+ {
+ private TextureParameters _textureParams;
+
+ public int ID { get; private set; }
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public TextureFormat Format { get; private set; }
+
+ public bool IsInvalidated
+ {
+ get { return ID == -1; }
+ }
+
+ public Texture(GraphicsDevice graphicsDevice, Image image, TextureParameters textureParameters = null)
+ : base(graphicsDevice)
+ {
+ // if this was specified, we should make our own separate copy of it as these
+ // parameters should stay the same when/if the texture is recreated
+ if (textureParameters != null)
+ _textureParams = (TextureParameters)textureParameters.Clone();
+
+ CreateTexture(image);
+ }
+
+ public Texture(GraphicsDevice graphicsDevice, int width, int height, TextureFormat format, TextureParameters textureParameters = null)
+ : base(graphicsDevice)
+ {
+ // if this was specified, we should make our own separate copy of it as these
+ // parameters should stay the same when/if the texture is recreated
+ if (textureParameters != null)
+ _textureParams = (TextureParameters)textureParameters.Clone();
+
+ CreateTexture(width, height, format);
+ }
+
+ #region OpenGL Texture Creation
+
+ private void CreateTexture(Image image)
+ {
+ if (image == null)
+ throw new ArgumentNullException("image");
+
+ if (!GraphicsDevice.IsNonPowerOfTwoTextureSupported)
+ {
+ if (!MathHelpers.IsPowerOf2(image.Width) || !MathHelpers.IsPowerOf2(image.Height))
+ throw new InvalidOperationException();
+ }
+ if (image.BitsPerPixel == 8 && image.PixelFormat != ImageFormat.A)
+ throw new InvalidOperationException();
+ if (image.BitsPerPixel == 16)
+ throw new InvalidOperationException();
+
+ TextureFormat textureFormat;
+ int internalFormat;
+ int pixelFormat;
+ int pixelType = GL20.GL_UNSIGNED_BYTE;
+
+ if (image.PixelFormat == ImageFormat.A)
+ {
+ textureFormat = TextureFormat.Alpha;
+ internalFormat = GL20.GL_ALPHA;
+ pixelFormat = GL20.GL_ALPHA;
+ }
+ else
+ {
+ if (image.BitsPerPixel == 24)
+ {
+ textureFormat = TextureFormat.RGB;
+ internalFormat = GL20.GL_RGB;
+ pixelFormat = GL20.GL_RGB;
+ }
+ else if (image.BitsPerPixel == 32)
+ {
+ textureFormat = TextureFormat.RGBA;
+ internalFormat = GL20.GL_RGBA;
+ pixelFormat = GL20.GL_RGBA;
+ }
+ else
+ throw new InvalidOperationException();
+ }
+
+ Width = image.Width;
+ Height = image.Height;
+ Format = textureFormat;
+
+ ID = Platform.GL.glGenTextures();
+
+ GraphicsDevice.BindTexture(this, 0);
+
+ if (_textureParams == null)
+ _textureParams = GraphicsDevice.GetCopyOfTextureParameters();
+ _textureParams.Apply();
+
+ Platform.GL.glTexImage2D(GL20.GL_TEXTURE_2D, 0, internalFormat, Width, Height, 0, pixelFormat, pixelType, image.Pixels);
+
+ Platform.Logger.Info("Graphics", "Created texture from image. ID = {0}, bpp = {1}, size = {2}x{3}.", ID, image.BitsPerPixel, Width, Height);
+ }
+
+ private void CreateTexture(int width, int height, TextureFormat format, bool useExistingTextureParams = false)
+ {
+ if (!GraphicsDevice.IsNonPowerOfTwoTextureSupported)
+ {
+ if (!MathHelpers.IsPowerOf2(width) || !MathHelpers.IsPowerOf2(height))
+ throw new InvalidOperationException("Texture dimensions must be a power of 2.");
+ }
+
+ int bpp = 0;
+ int internalFormat;
+ int pixelFormat;
+ int pixelType;
+ GetTextureSpecsFromFormat(format, out bpp, out internalFormat, out pixelFormat, out pixelType);
+
+ if (bpp == 0)
+ throw new InvalidOperationException();
+
+ Width = width;
+ Height = height;
+ Format = format;
+
+ ID = Platform.GL.glGenTextures();
+
+ GraphicsDevice.BindTexture(this, 0);
+
+ if (!useExistingTextureParams || _textureParams == null)
+ _textureParams = GraphicsDevice.GetCopyOfTextureParameters();
+ _textureParams.Apply();
+
+ Platform.GL.glTexImage2D(GL20.GL_TEXTURE_2D, 0, internalFormat, Width, Height, 0, pixelFormat, pixelType, IntPtr.Zero);
+
+ if (Format == TextureFormat.Depth)
+ Platform.Logger.Info("Graphics", "Created uninitialized texture. ID = {0}, depth component only, size = {1}x{2}", ID, Width, Height);
+ else
+ Platform.Logger.Info("Graphics", "Created uninitialized texture. ID = {0}, bpp = {1}, size = {2}x{3}", ID, bpp, Width, Height);
+ }
+
+ #endregion
+
+ public void Update(Image image, int destX = 0, int destY = 0)
+ {
+ if (IsInvalidated)
+ throw new InvalidOperationException();
+ if (Format == TextureFormat.Depth)
+ throw new InvalidOperationException();
+
+ if (image == null)
+ throw new ArgumentNullException("image");
+ if (destX < 0 || destX >= Width)
+ throw new InvalidOperationException();
+ if (destY < 0 || destY >= Height)
+ throw new InvalidOperationException();
+ if (image.Width > Width)
+ throw new InvalidOperationException();
+ if (image.Height > Height)
+ throw new InvalidOperationException();
+ if ((destX + image.Width) > Width)
+ throw new InvalidOperationException();
+ if ((destY + image.Height) > Height)
+ throw new InvalidOperationException();
+
+ if (image.BitsPerPixel == 8 && image.PixelFormat != ImageFormat.A)
+ throw new InvalidOperationException();
+ if (image.BitsPerPixel == 16)
+ throw new InvalidOperationException();
+
+ int pixelFormat;
+ int pixelType = GL20.GL_UNSIGNED_BYTE;
+
+ if (image.PixelFormat == ImageFormat.A)
+ pixelFormat = GL20.GL_ALPHA;
+ else
+ {
+ if (image.BitsPerPixel == 24)
+ pixelFormat = GL20.GL_RGB;
+ else if (image.BitsPerPixel == 32)
+ pixelFormat = GL20.GL_RGBA;
+ else
+ throw new InvalidOperationException();
+ }
+
+ GraphicsDevice.BindTexture(this, 0);
+ Platform.GL.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, destX, destY, image.Width, image.Height, pixelFormat, pixelType, image.Pixels);
+ }
+
+ private void GetTextureSpecsFromFormat(TextureFormat textureFormat, out int bpp, out int internalFormat, out int pixelFormat, out int type)
+ {
+ switch (textureFormat)
+ {
+ case TextureFormat.Alpha:
+ bpp = 8;
+ internalFormat = GL20.GL_ALPHA;
+ pixelFormat = GL20.GL_ALPHA;
+ type = GL20.GL_UNSIGNED_BYTE;
+ break;
+
+ case TextureFormat.RGB:
+ bpp = 24;
+ internalFormat = GL20.GL_RGB;
+ pixelFormat = GL20.GL_RGB;
+ type = GL20.GL_UNSIGNED_BYTE;
+ break;
+
+ case TextureFormat.RGBA:
+ bpp = 32;
+ internalFormat = GL20.GL_RGBA;
+ pixelFormat = GL20.GL_RGBA;
+ type = GL20.GL_UNSIGNED_BYTE;
+ break;
+
+ case TextureFormat.Depth:
+ bpp = 0; // doesn't really matter for this one... ?
+ internalFormat = GL20.GL_DEPTH_COMPONENT;
+ pixelFormat = GL20.GL_DEPTH_COMPONENT;
+
+ // TODO: check that these are correct ...
+ if (Platform.Application.Type == PlatformType.Mobile)
+ type = GL20.GL_UNSIGNED_SHORT;
+ else
+ type = GL20.GL_FLOAT;
+ break;
+
+ default:
+ bpp = 0;
+
+ // junk -- just to appease the compiler
+ internalFormat = GL20.GL_RGBA;
+ pixelFormat = GL20.GL_RGBA;
+ type = GL20.GL_UNSIGNED_BYTE;
+ break;
+ }
+ }
+
+ #region GraphicsContextResource
+
+ public override void OnNewContext()
+ {
+ // TODO: recreate empty texture with same width/height/format
+ // (it is up to the application code to refill proper image data,
+ // or the ContentManager if loaded that way)
+ CreateTexture(Width, Height, Format, true);
+ }
+
+ public override void OnLostContext()
+ {
+ ID = -1;
+ }
+
+ protected override bool ReleaseResource()
+ {
+ // this needs to happen before the OpenGL context is destroyed
+ // which is not guaranteed if we rely 100% on the GC to clean
+ // everything up. best solution is to ensure all Texture
+ // objects are not being referenced before the window is
+ // closed and do a GC.Collect()
+
+ if (!IsInvalidated)
+ {
+ if (GraphicsDevice != null)
+ GraphicsDevice.UnbindTexture(this);
+
+ Platform.GL.glDeleteTextures(ID);
+
+ Platform.Logger.Info("Graphics", "Deleted Texture ID = {0}.", ID);
+
+ ID = -1;
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/TextureAtlas.cs b/Blarg.GameFramework/Graphics/TextureAtlas.cs
new file mode 100644
index 0000000..00a5a01
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/TextureAtlas.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public abstract class TextureAtlas
+ {
+ public const float TexCoordEdgeBleedOffset = 0.02f;
+
+ private Texture _texture;
+
+ public int Width { get; protected set; }
+ public int Height { get; protected set; }
+
+ protected float TexCoordEdgeOffset { get; private set; }
+ protected List Tiles { get; private set; }
+
+ public Texture Texture
+ {
+ get
+ {
+ return _texture;
+ }
+ set
+ {
+ if (value == null)
+ throw new ArgumentNullException();
+
+ if (value.Width != Width || value.Height != Height)
+ throw new InvalidOperationException();
+
+ _texture = value;
+ }
+ }
+
+ public TextureAtlas(int textureWidth, int textureHeight, float texCoordEdgeOffset = TexCoordEdgeBleedOffset)
+ {
+ if (textureWidth < 0)
+ throw new ArgumentOutOfRangeException("textureWidth");
+ if (textureHeight < 0)
+ throw new ArgumentOutOfRangeException("textureHeight");
+
+ Width = textureWidth;
+ Height = textureHeight;
+ TexCoordEdgeOffset = texCoordEdgeOffset;
+
+ Tiles = new List();
+ }
+
+ public TextureAtlas(Texture texture, float texCoordEdgeOffset = TexCoordEdgeBleedOffset)
+ {
+ if (texture == null)
+ throw new ArgumentNullException("texture");
+
+ _texture = texture;
+ Width = _texture.Width;
+ Height = _texture.Height;
+ TexCoordEdgeOffset = texCoordEdgeOffset;
+
+ Tiles = new List();
+ }
+
+ public int NumTiles
+ {
+ get { return Tiles.Count; }
+ }
+
+ public TextureRegion GetTile(int index)
+ {
+ return Tiles[index];
+ }
+
+ public void GetTileDimensions(int index, out Rect dimensions)
+ {
+ dimensions = Tiles[index].Dimensions;
+ }
+
+ public void GetTileTexCoords(int index, out RectF texCoords)
+ {
+ texCoords = Tiles[index].TexCoords;
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/TextureParameters.cs b/Blarg.GameFramework/Graphics/TextureParameters.cs
new file mode 100644
index 0000000..f34a684
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/TextureParameters.cs
@@ -0,0 +1,115 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum MinificationFilter
+ {
+ Nearest,
+ Linear,
+ NearestMipmapNearest,
+ LinearMipmapNearest,
+ NearestMipmapLinear,
+ LinearMipmapLinear
+ }
+
+ public enum MagnificationFilter
+ {
+ Nearest,
+ Linear
+ }
+
+ public enum WrapMode
+ {
+ ClampToEdge,
+ Repeat
+ }
+
+ public class TextureParameters
+ {
+ public static readonly TextureParameters Default;
+ public static readonly TextureParameters Pixelated;
+
+ public MinificationFilter MinFilter { get; set; }
+ public MagnificationFilter MagFilter { get; set; }
+ public WrapMode WrapS { get; set; }
+ public WrapMode WrapT { get; set; }
+
+ static TextureParameters()
+ {
+ Default = new TextureParameters();
+ Pixelated = new TextureParameters(MinificationFilter.Nearest, MagnificationFilter.Nearest);
+ }
+
+ public TextureParameters()
+ {
+ Init();
+ }
+
+ public TextureParameters(MinificationFilter minFilter, MagnificationFilter magFilter)
+ {
+ Init();
+ MinFilter = minFilter;
+ MagFilter = magFilter;
+ }
+
+ public void Apply()
+ {
+ int minFilter = GL20.GL_NEAREST;
+ int magFilter = GL20.GL_LINEAR;
+ int wrapS = GL20.GL_REPEAT;
+ int wrapT = GL20.GL_REPEAT;
+
+ switch (MinFilter)
+ {
+ case MinificationFilter.Nearest: minFilter = GL20.GL_NEAREST; break;
+ case MinificationFilter.Linear: minFilter = GL20.GL_LINEAR; break;
+ case MinificationFilter.NearestMipmapNearest: minFilter = GL20.GL_NEAREST_MIPMAP_NEAREST; break;
+ case MinificationFilter.LinearMipmapNearest: minFilter = GL20.GL_LINEAR_MIPMAP_NEAREST; break;
+ case MinificationFilter.NearestMipmapLinear: minFilter = GL20.GL_NEAREST_MIPMAP_LINEAR; break;
+ case MinificationFilter.LinearMipmapLinear: minFilter = GL20.GL_LINEAR_MIPMAP_LINEAR; break;
+ }
+
+ switch (MagFilter)
+ {
+ case MagnificationFilter.Nearest: magFilter = GL20.GL_NEAREST; break;
+ case MagnificationFilter.Linear: magFilter = GL20.GL_LINEAR; break;
+ }
+
+ switch (WrapS)
+ {
+ case WrapMode.ClampToEdge: wrapS = GL20.GL_CLAMP_TO_EDGE; break;
+ case WrapMode.Repeat: wrapS = GL20.GL_REPEAT; break;
+ }
+
+ switch (WrapT)
+ {
+ case WrapMode.ClampToEdge: wrapT = GL20.GL_CLAMP_TO_EDGE; break;
+ case WrapMode.Repeat: wrapT = GL20.GL_REPEAT; break;
+ }
+
+ Platform.GL.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, (int)minFilter);
+ Platform.GL.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, (int)magFilter);
+ Platform.GL.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, (int)wrapS);
+ Platform.GL.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, (int)wrapT);
+ }
+
+ private void Init()
+ {
+ MinFilter = MinificationFilter.Nearest;
+ MagFilter = MagnificationFilter.Linear;
+ WrapS = WrapMode.Repeat;
+ WrapT = WrapMode.Repeat;
+ }
+
+ public TextureParameters Clone()
+ {
+ var clone = new TextureParameters();
+ clone.MagFilter = MagFilter;
+ clone.MinFilter = MinFilter;
+ clone.WrapS = WrapS;
+ clone.WrapT = WrapT;
+ return clone;
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/TextureRegion.cs b/Blarg.GameFramework/Graphics/TextureRegion.cs
new file mode 100644
index 0000000..c989c4e
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/TextureRegion.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public struct TextureRegion
+ {
+ public Rect Dimensions;
+ public RectF TexCoords;
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/VertexAttributes.cs b/Blarg.GameFramework/Graphics/VertexAttributes.cs
new file mode 100644
index 0000000..8c33ac0
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/VertexAttributes.cs
@@ -0,0 +1,81 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public enum VertexStandardAttributes
+ {
+ // high byte = standard type bitmask
+ // low byte = number of floats needed
+ Position2D = 0x0102,
+ Position3D = 0x0203,
+ Normal = 0x0403,
+ Color = 0x0804,
+ TexCoord = 0x1002
+ }
+
+ public enum VertexAttributes
+ {
+ // standard types
+ // high byte = standard type bitmask
+ // low byte = number of floats needed
+ Position2D = VertexStandardAttributes.Position2D,
+ Position3D = VertexStandardAttributes.Position3D,
+ Normal = VertexStandardAttributes.Normal,
+ Color = VertexStandardAttributes.Color,
+ TexCoord = VertexStandardAttributes.TexCoord,
+
+ // generic types, value is equal to number of floats needed
+ Float1 = 1,
+ Float2 = 2,
+ Float3 = 3,
+ Float4 = 4,
+ Vector2 = 2,
+ Vector3 = 3,
+ Vector4 = 4,
+ Matrix3x3 = 9,
+ Matrix4x4 = 16
+ }
+
+ public static class VertexAttributeDeclarations
+ {
+ public static readonly VertexAttributes[] ColorPosition3D = new VertexAttributes[]
+ {
+ VertexAttributes.Color,
+ VertexAttributes.Position3D
+ };
+
+ public static readonly VertexAttributes[] TexturePosition3D = new VertexAttributes[]
+ {
+ VertexAttributes.TexCoord,
+ VertexAttributes.Position3D
+ };
+
+ public static readonly VertexAttributes[] ColorNormalPosition3D = new VertexAttributes[]
+ {
+ VertexAttributes.Color,
+ VertexAttributes.Normal,
+ VertexAttributes.Position3D
+ };
+
+ public static readonly VertexAttributes[] TextureNormalPosition3D = new VertexAttributes[]
+ {
+ VertexAttributes.TexCoord,
+ VertexAttributes.Normal,
+ VertexAttributes.Position3D
+ };
+
+ public static readonly VertexAttributes[] TextureColorPosition2D = new VertexAttributes[]
+ {
+ VertexAttributes.TexCoord,
+ VertexAttributes.Color,
+ VertexAttributes.Position2D
+ };
+
+ public static readonly VertexAttributes[] TextureColorPosition3D = new VertexAttributes[]
+ {
+ VertexAttributes.TexCoord,
+ VertexAttributes.Color,
+ VertexAttributes.Position3D
+ };
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/VertexBuffer.cs b/Blarg.GameFramework/Graphics/VertexBuffer.cs
new file mode 100644
index 0000000..ebbc0b3
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/VertexBuffer.cs
@@ -0,0 +1,1196 @@
+using System;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class VertexBuffer : BufferObject
+ {
+ public struct AttributeInfo
+ {
+ public VertexAttributes type;
+ public int offset;
+ public int size;
+ }
+
+ private float[] _buffer;
+ private int _standardVertexAttribs;
+ private AttributeInfo[] _attributes;
+
+ public int CurrentPosition { get; private set; }
+ public int ElementWidth { get; private set; }
+
+ public int RemainingElements
+ {
+ get { return (NumElements - 1) - CurrentPosition; }
+ }
+
+ public override int NumElements
+ {
+ get { return (_buffer.Length / ElementWidth); }
+ }
+
+ public override int ElementWidthInBytes
+ {
+ get { return ElementWidth * sizeof(float); }
+ }
+
+ public override float[] Data
+ {
+ get { return _buffer; }
+ }
+
+ public int NumAttributes
+ {
+ get { return _attributes.Length; }
+ }
+
+ public VertexAttributes GetAttributeType(int index)
+ {
+ return _attributes[index].type;
+ }
+
+ public int GetAttributeOffset(int index)
+ {
+ return _attributes[index].offset;
+ }
+
+ public int GetAttributeSize(int index)
+ {
+ return _attributes[index].size;
+ }
+
+ public int OffsetPosition2D { get; private set; }
+ public int OffsetPosition3D { get; private set; }
+ public int OffsetNormal { get; private set; }
+ public int OffsetColor { get; private set; }
+ public int OffsetTexCoord { get; private set; }
+
+ public int OffsetBytesPosition2D { get { return (OffsetPosition2D * sizeof(float)); } }
+ public int OffsetBytesPosition3D { get { return (OffsetPosition3D * sizeof(float)); } }
+ public int OffsetBytesNormal { get { return (OffsetNormal * sizeof(float)); } }
+ public int OffsetBytesColor { get { return (OffsetColor * sizeof(float)); } }
+ public int OffsetBytesTexCoord { get { return (OffsetTexCoord * sizeof(float)); } }
+
+ public VertexBuffer(VertexAttributes[] attributes, int numVertices, BufferObjectUsage usage)
+ : base(BufferObjectType.Vertex, usage)
+ {
+ Initialize(attributes, numVertices);
+ }
+
+ public VertexBuffer(GraphicsDevice graphicsDevice, VertexAttributes[] attributes, int numVertices, BufferObjectUsage usage)
+ : base(graphicsDevice, BufferObjectType.Vertex, usage)
+ {
+ Initialize(attributes, numVertices);
+ CreateOnGpu();
+ }
+
+ public VertexBuffer(VertexBuffer source)
+ : base(BufferObjectType.Vertex, source.Usage)
+ {
+ Initialize(source);
+ }
+
+ public VertexBuffer(GraphicsDevice graphicsDevice, VertexBuffer source)
+ : base(graphicsDevice, BufferObjectType.Vertex, source.Usage)
+ {
+ Initialize(source);
+ CreateOnGpu();
+ }
+
+ private void Initialize(VertexAttributes[] attributes, int numVertices)
+ {
+ if (attributes == null || attributes.Length == 0)
+ throw new ArgumentException("attributes");
+ if (numVertices <= 0)
+ throw new ArgumentOutOfRangeException("numVertices");
+
+ SetSizesAndOffsets(attributes);
+ Resize(numVertices);
+ }
+
+ private void Initialize(VertexBuffer source)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+
+ var attributes = new VertexAttributes[source.NumAttributes];
+ for (int i = 0; i < attributes.Length; ++i)
+ attributes[i] = source.GetAttributeType(i);
+
+ SetSizesAndOffsets(attributes);
+ Resize(source.NumElements);
+ Copy(source, 0);
+ }
+
+ public bool HasStandardAttribute(VertexStandardAttributes attribute)
+ {
+ return (_standardVertexAttribs & (int)attribute) > 0;
+ }
+
+ public int GetIndexOfStandardAttribute(VertexStandardAttributes attribute)
+ {
+ for (int i = 0; i < _attributes.Length; ++i)
+ {
+ if ((int)_attributes[i].type == (int)attribute)
+ return i;
+ }
+
+ return -1;
+ }
+
+ public bool MoveNext()
+ {
+ ++CurrentPosition;
+ if (CurrentPosition >= NumElements)
+ {
+ --CurrentPosition;
+ return false;
+ }
+ else
+ return true;
+ }
+
+ public bool MovePrevious()
+ {
+ if (CurrentPosition == 0)
+ return false;
+ else
+ {
+ --CurrentPosition;
+ return true;
+ }
+ }
+
+ public void Move(int amount)
+ {
+ CurrentPosition += amount;
+ if (CurrentPosition < 0)
+ CurrentPosition = 0;
+ else if (CurrentPosition >= NumElements)
+ CurrentPosition = NumElements - 1;
+ }
+
+ public void MoveToStart()
+ {
+ CurrentPosition = 0;
+ }
+
+ public void MoveToEnd()
+ {
+ CurrentPosition = NumElements - 1;
+ }
+
+ public void MoveTo(int index)
+ {
+ CurrentPosition = index;
+ }
+
+ public int GetVertexStartIndex(int vertexIndex)
+ {
+ return (vertexIndex * ElementWidth);
+ }
+
+ public int GetVertexAttributeStartIndex(int attributeIndex, int vertexIndex)
+ {
+ return (GetVertexStartIndex(vertexIndex) + _attributes[attributeIndex].offset);
+ }
+
+ private void SetSizesAndOffsets(VertexAttributes[] attributes)
+ {
+ const int FloatsPerGpuAttribSlot = 4;
+ const int MaxGpuAttribSlots = 8;
+
+ if (attributes == null || attributes.Length == 0)
+ throw new ArgumentException("attributes");
+ if (_standardVertexAttribs != 0)
+ throw new InvalidOperationException();
+ if (_attributes != null)
+ throw new InvalidOperationException();
+
+ _standardVertexAttribs = 0;
+
+ int offset = 0;
+ int numGpuAttribSlotsUsed = 0;
+
+ _attributes = new AttributeInfo[attributes.Length];
+
+ for (int i = 0; i < attributes.Length; ++i)
+ {
+ var attrib = attributes[i];
+ byte size = (byte)attrib;
+ byte standardTypeBitMask = (byte)((ushort)attrib >> 8);
+
+ // using integer division that rounds up (so given size = 13, result is 4, not 3)
+ int numGpuSlots = ((int)size + (FloatsPerGpuAttribSlot - 1)) / FloatsPerGpuAttribSlot;
+ if ((numGpuAttribSlotsUsed + numGpuSlots) > MaxGpuAttribSlots)
+ throw new InvalidOperationException("Exceeded maximum GPU attribute space.");
+
+ if (standardTypeBitMask > 0)
+ {
+ if ((_standardVertexAttribs & standardTypeBitMask) > 0)
+ throw new InvalidOperationException("Duplicate standard attribute type specified.");
+
+ _standardVertexAttribs |= standardTypeBitMask;
+
+ switch ((VertexStandardAttributes)attrib)
+ {
+ case VertexStandardAttributes.Position2D: OffsetPosition2D = offset; break;
+ case VertexStandardAttributes.Position3D: OffsetPosition3D = offset; break;
+ case VertexStandardAttributes.Normal: OffsetNormal = offset; break;
+ case VertexStandardAttributes.Color: OffsetColor = offset; break;
+ case VertexStandardAttributes.TexCoord: OffsetTexCoord = offset; break;
+ }
+ }
+
+ _attributes[i].offset = offset;
+ _attributes[i].size = size;
+ _attributes[i].type = attrib;
+
+ ElementWidth += size;
+ offset += size;
+ numGpuAttribSlotsUsed += numGpuSlots;
+ }
+ }
+
+ public void Resize(int numVertices)
+ {
+ if (numVertices <= 0)
+ throw new ArgumentOutOfRangeException("numIndices");
+
+ int newSizeInFloats = numVertices * ElementWidth;
+
+ if (_buffer == null)
+ _buffer = new float[newSizeInFloats];
+ else
+ Array.Resize(ref _buffer, newSizeInFloats);
+
+ if (!IsClientSide)
+ SizeBufferObject();
+
+ if (CurrentPosition >= NumElements)
+ CurrentPosition = NumElements - 1;
+ }
+
+ public void Extend(int amount)
+ {
+ int newSize = NumElements + amount;
+ if (newSize <= 0)
+ throw new ArgumentOutOfRangeException("amount");
+
+ Resize(newSize);
+ }
+
+ public void Copy(VertexBuffer source, int destVertexIndex)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+
+ Copy(source, destVertexIndex, 0, source.NumElements);
+ }
+
+ public void Copy(VertexBuffer source, int destVertexIndex, int sourceVertexIndex, int count)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+ if (destVertexIndex <= 0 || destVertexIndex >= NumElements)
+ throw new ArgumentOutOfRangeException("destVertexIndex");
+ if (sourceVertexIndex <= 0 || sourceVertexIndex >= source.NumElements)
+ throw new ArgumentOutOfRangeException("sourceVertexStart");
+ if ((destVertexIndex + count) >= NumElements)
+ throw new ArgumentOutOfRangeException("count");
+ if ((sourceVertexIndex + count) >= source.NumElements)
+ throw new ArgumentOutOfRangeException("count");
+ if (NumAttributes != source.NumAttributes)
+ throw new InvalidOperationException("Cannot copy different vertex data types.");
+
+ for (int i = 0; i < NumAttributes; ++i)
+ {
+ if (GetAttributeType(i) != source.GetAttributeType(i))
+ throw new InvalidOperationException("Cannot copy different vertex data types.");
+ }
+
+ int destIndex = GetVertexStartIndex(destVertexIndex);
+ int sourceIndex = source.GetVertexStartIndex(sourceVertexIndex);
+ int copyLength = count * ElementWidth;
+ Array.Copy(source.Data, sourceIndex, _buffer, destIndex, copyLength);
+
+ IsDirty = true;
+ }
+
+ #region Attribute Getters and Setters
+
+ #region Getters
+
+ public Color GetColor(int index)
+ {
+ Color result;
+ GetColor(index, out result);
+ return result;
+ }
+
+ public void GetColor(int index, out Color result)
+ {
+ int p = GetVertexStartIndex(index) + OffsetColor;
+ result.R = _buffer[p];
+ result.G = _buffer[p + 1];
+ result.B = _buffer[p + 2];
+ result.A = _buffer[p + 3];
+ }
+
+ public Vector3 GetPosition3D(int index)
+ {
+ Vector3 result;
+ GetPosition3D(index, out result);
+ return result;
+ }
+
+ public void GetPosition3D(int index, out Vector3 result)
+ {
+ int p = GetVertexStartIndex(index) + OffsetPosition3D;
+ result.X = _buffer[p];
+ result.Y = _buffer[p + 1];
+ result.Z = _buffer[p + 2];
+ }
+
+ public Vector2 GetPosition2D(int index)
+ {
+ Vector2 result;
+ GetPosition2D(index, out result);
+ return result;
+ }
+
+ public void GetPosition2D(int index, out Vector2 result)
+ {
+ int p = GetVertexStartIndex(index) + OffsetPosition2D;
+ result.X = _buffer[p];
+ result.Y = _buffer[p + 1];
+ }
+
+ public Vector3 GetNormal(int index)
+ {
+ Vector3 result;
+ GetNormal(index, out result);
+ return result;
+ }
+
+ public void GetNormal(int index, out Vector3 result)
+ {
+ int p = GetVertexStartIndex(index) + OffsetNormal;
+ result.X = _buffer[p];
+ result.Y = _buffer[p + 1];
+ result.Z = _buffer[p + 2];
+ }
+
+ public Vector2 GetTexCoord(int index)
+ {
+ Vector2 result;
+ GetTexCoord(index, out result);
+ return result;
+ }
+
+ public void GetTexCoord(int index, out Vector2 result)
+ {
+ int p = GetVertexStartIndex(index) + OffsetTexCoord;
+ result.X = _buffer[p];
+ result.Y = _buffer[p + 1];
+ }
+
+ public float Get1f(int attrib, int index)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ return _buffer[p];
+ }
+
+ public void Get1f(int attrib, int index, out float x)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ x = _buffer[p];
+ }
+
+ public void Get2f(int attrib, int index, out float x, out float y)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ x = _buffer[p];
+ y = _buffer[p + 1];
+ }
+
+ public Vector2 Get2f(int attrib, int index)
+ {
+ Vector2 result;
+ Get2f(attrib, index, out result);
+ return result;
+ }
+
+ public void Get2f(int attrib, int index, out Vector2 v)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ v.X = _buffer[p];
+ v.Y = _buffer[p + 1];
+ }
+
+ public void Get3f(int attrib, int index, out float x, out float y, out float z)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ x = _buffer[p];
+ y = _buffer[p + 1];
+ z = _buffer[p + 2];
+ }
+
+ public Vector3 Get3f(int attrib, int index)
+ {
+ Vector3 result;
+ Get3f(attrib, index, out result);
+ return result;
+ }
+
+ public void Get3f(int attrib, int index, out Vector3 v)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ v.X = _buffer[p];
+ v.Y = _buffer[p + 1];
+ v.Z = _buffer[p + 2];
+ }
+
+ public void Get4f(int attrib, int index, out float x, out float y, out float z, out float w)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ x = _buffer[p];
+ y = _buffer[p + 1];
+ z = _buffer[p + 2];
+ w = _buffer[p + 3];
+ }
+
+ public Vector4 Get4f(int attrib, int index)
+ {
+ Vector4 result;
+ Get4f(attrib, index, out result);
+ return result;
+ }
+
+ public void Get4f(int attrib, int index, out Vector4 v)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ v.X = _buffer[p];
+ v.Y = _buffer[p + 1];
+ v.Z = _buffer[p + 2];
+ v.W = _buffer[p + 3];
+ }
+
+ public void Get4f(int attrib, int index, out Quaternion q)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ q.X = _buffer[p];
+ q.Y = _buffer[p + 1];
+ q.Z = _buffer[p + 2];
+ q.W = _buffer[p + 3];
+ }
+
+ public void Get4f(int attrib, int index, out Color c)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ c.R = _buffer[p];
+ c.G = _buffer[p + 1];
+ c.B = _buffer[p + 2];
+ c.A = _buffer[p + 3];
+ }
+
+ public void Get9f(int attrib, int index, float[] x)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ Array.Copy(_buffer, p, x, 0, 9);
+ }
+
+ public Matrix3x3 Get9f(int attrib, int index)
+ {
+ Matrix3x3 result = new Matrix3x3();
+ Get9f(attrib, index, ref result);
+ return result;
+ }
+
+ public void Get9f(int attrib, int index, ref Matrix3x3 m)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ m.M11 = _buffer[p];
+ m.M21 = _buffer[p + 1];
+ m.M31 = _buffer[p + 2];
+ m.M12 = _buffer[p + 3];
+ m.M22 = _buffer[p + 4];
+ m.M32 = _buffer[p + 5];
+ m.M13 = _buffer[p + 6];
+ m.M23 = _buffer[p + 7];
+ m.M33 = _buffer[p + 8];
+ }
+
+ public void Get16f(int attrib, int index, float[] x)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ Array.Copy(_buffer, p, x, 0, 16);
+ }
+
+ public Matrix4x4 Get16f(int attrib, int index)
+ {
+ Matrix4x4 result = new Matrix4x4();
+ Get16f(attrib, index, ref result);
+ return result;
+ }
+
+ public void Get16f(int attrib, int index, ref Matrix4x4 m)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ m.M11 = _buffer[p];
+ m.M21 = _buffer[p + 1];
+ m.M31 = _buffer[p + 2];
+ m.M41 = _buffer[p + 3];
+ m.M12 = _buffer[p + 4];
+ m.M22 = _buffer[p + 5];
+ m.M32 = _buffer[p + 6];
+ m.M42 = _buffer[p + 7];
+ m.M13 = _buffer[p + 8];
+ m.M23 = _buffer[p + 9];
+ m.M33 = _buffer[p + 10];
+ m.M43 = _buffer[p + 11];
+ m.M14 = _buffer[p + 12];
+ m.M24 = _buffer[p + 13];
+ m.M34 = _buffer[p + 14];
+ m.M44 = _buffer[p + 15];
+ }
+
+ public Color GetCurrentColor()
+ {
+ Color result;
+ GetColor(CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrentColor(out Color result)
+ {
+ GetColor(CurrentPosition, out result);
+ }
+
+ public Vector3 GetCurrentPosition3D()
+ {
+ Vector3 result;
+ GetPosition3D(CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrentPosition3D(out Vector3 result)
+ {
+ GetPosition3D(CurrentPosition, out result);
+ }
+
+ public Vector2 GetCurrentPosition2D()
+ {
+ Vector2 result;
+ GetPosition2D(CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrentPosition2D(out Vector2 result)
+ {
+ GetPosition2D(CurrentPosition, out result);
+ }
+
+ public Vector3 GetCurrentNormal()
+ {
+ Vector3 result;
+ GetNormal(CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrentNormal(out Vector3 result)
+ {
+ GetNormal(CurrentPosition, out result);
+ }
+
+ public Vector2 GetCurrentTexCoord()
+ {
+ Vector2 result;
+ GetTexCoord(CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrentTexCoord(out Vector2 result)
+ {
+ GetTexCoord(CurrentPosition, out result);
+ }
+
+ public float GetCurrent1f(int attrib)
+ {
+ return Get1f(attrib, CurrentPosition);
+ }
+
+ public void GetCurrent1f(int attrib, out float x)
+ {
+ Get1f(attrib, CurrentPosition, out x);
+ }
+
+ public void GetCurrent2f(int attrib, out float x, out float y)
+ {
+ Get2f(attrib, CurrentPosition, out x, out y);
+ }
+
+ public Vector2 GetCurrent2f(int attrib)
+ {
+ Vector2 result;
+ Get2f(attrib, CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrent2f(int attrib, out Vector2 v)
+ {
+ Get2f(attrib, CurrentPosition, out v);
+ }
+
+ public void GetCurrent3f(int attrib, out float x, out float y, out float z)
+ {
+ Get3f(attrib, CurrentPosition, out x, out y, out z);
+ }
+
+ public Vector3 GetCurrent3f(int attrib)
+ {
+ Vector3 result;
+ Get3f(attrib, CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrent3f(int attrib, out Vector3 v)
+ {
+ Get3f(attrib, CurrentPosition, out v);
+ }
+
+ public void GetCurrent4f(int attrib, out float x, out float y, out float z, out float w)
+ {
+ Get4f(attrib, CurrentPosition, out x, out y, out z, out w);
+ }
+
+ public Vector4 GetCurrent4f(int attrib)
+ {
+ Vector4 result;
+ Get4f(attrib, CurrentPosition, out result);
+ return result;
+ }
+
+ public void GetCurrent4f(int attrib, out Vector4 v)
+ {
+ Get4f(attrib, CurrentPosition, out v);
+ }
+
+ public void GetCurrent4f(int attrib, out Quaternion q)
+ {
+ Get4f(attrib, CurrentPosition, out q);
+ }
+
+ public void GetCurrent4f(int attrib, out Color c)
+ {
+ Get4f(attrib, CurrentPosition, out c);
+ }
+
+ public Matrix3x3 GetCurrent9f(int attrib)
+ {
+ Matrix3x3 result = new Matrix3x3();
+ Get9f(attrib, CurrentPosition, ref result);
+ return result;
+ }
+
+ public void GetCurrent9f(int attrib, float[] x)
+ {
+ Get9f(attrib, CurrentPosition, x);
+ }
+
+ public void GetCurrent9f(int attrib, ref Matrix3x3 m)
+ {
+ Get9f(attrib, CurrentPosition, ref m);
+ }
+
+ public void GetCurrent16f(int attrib, float[] x)
+ {
+ Get16f(attrib, CurrentPosition, x);
+ }
+
+ public Matrix4x4 GetCurrent16f(int attrib)
+ {
+ Matrix4x4 result = new Matrix4x4();
+ Get16f(attrib, CurrentPosition, ref result);
+ return result;
+ }
+
+ public void GetCurrent16f(int attrib, ref Matrix4x4 m)
+ {
+ Get16f(attrib, CurrentPosition, ref m);
+ }
+
+ #endregion
+
+ #region Setters
+
+ public void SetColor(int index, float r, float g, float b, float a)
+ {
+ int p = GetVertexStartIndex(index) + OffsetColor;
+ _buffer[p] = r;
+ _buffer[p + 1] = g;
+ _buffer[p + 2] = b;
+ _buffer[p + 3] = a;
+ IsDirty = true;
+ }
+
+ public void SetColor(int index, float r, float g, float b)
+ {
+ int p = GetVertexStartIndex(index) + OffsetColor;
+ _buffer[p] = r;
+ _buffer[p + 1] = g;
+ _buffer[p + 2] = b;
+ _buffer[p + 3] = Color.AlphaOpaque;
+ IsDirty = true;
+ }
+
+ public void SetColor(int index, Color c)
+ {
+ SetColor(index, ref c);
+ }
+
+ public void SetColor(int index, ref Color c)
+ {
+ int p = GetVertexStartIndex(index) + OffsetColor;
+ _buffer[p] = c.R;
+ _buffer[p + 1] = c.G;
+ _buffer[p + 2] = c.B;
+ _buffer[p + 3] = c.A;
+ IsDirty = true;
+ }
+
+ public void SetPosition3D(int index, float x, float y, float z)
+ {
+ int p = GetVertexStartIndex(index) + OffsetPosition3D;
+ _buffer[p] = x;
+ _buffer[p + 1] = y;
+ _buffer[p + 2] = z;
+ IsDirty = true;
+ }
+
+ public void SetPosition3D(int index, Vector3 v)
+ {
+ SetPosition3D(index, ref v);
+ }
+
+ public void SetPosition3D(int index, ref Vector3 v)
+ {
+ int p = GetVertexStartIndex(index) + OffsetPosition3D;
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ _buffer[p + 2] = v.Z;
+ IsDirty = true;
+ }
+
+ public void SetPosition2D(int index, float x, float y)
+ {
+ int p = GetVertexStartIndex(index) + OffsetPosition2D;
+ _buffer[p] = x;
+ _buffer[p + 1] = y;
+ IsDirty = true;
+ }
+
+ public void SetPosition2D(int index, Vector2 v)
+ {
+ SetPosition2D(index, ref v);
+ }
+
+ public void SetPosition2D(int index, ref Vector2 v)
+ {
+ int p = GetVertexStartIndex(index) + OffsetPosition2D;
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ IsDirty = true;
+ }
+
+ public void SetNormal(int index, float x, float y, float z)
+ {
+ int p = GetVertexStartIndex(index) + OffsetNormal;
+ _buffer[p] = x;
+ _buffer[p + 1] = y;
+ _buffer[p + 2] = z;
+ IsDirty = true;
+ }
+
+ public void SetNormal(int index, Vector3 v)
+ {
+ SetNormal(index, ref v);
+ }
+
+ public void SetNormal(int index, ref Vector3 v)
+ {
+ int p = GetVertexStartIndex(index) + OffsetNormal;
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ _buffer[p + 2] = v.Z;
+ IsDirty = true;
+ }
+
+ public void SetTexCoord(int index, float u, float v)
+ {
+ int p = GetVertexStartIndex(index) + OffsetTexCoord;
+ _buffer[p] = u;
+ _buffer[p + 1] = v;
+ IsDirty = true;
+ }
+
+ public void SetTexCoord(int index, Vector2 v)
+ {
+ SetTexCoord(index, ref v);
+ }
+
+ public void SetTexCoord(int index, ref Vector2 v)
+ {
+ int p = GetVertexStartIndex(index) + OffsetTexCoord;
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ IsDirty = true;
+ }
+
+ public void Set1f(int attrib, int index, float x)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = x;
+ IsDirty = true;
+ }
+
+ public void Set2f(int attrib, int index, float x, float y)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = x;
+ _buffer[p + 1] = y;
+ IsDirty = true;
+ }
+
+ public void Set2f(int attrib, int index, Vector2 v)
+ {
+ Set2f(attrib, index, ref v);
+ }
+
+ public void Set2f(int attrib, int index, ref Vector2 v)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ IsDirty = true;
+ }
+
+ public void Set3f(int attrib, int index, float x, float y, float z)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = x;
+ _buffer[p + 1] = y;
+ _buffer[p + 2] = z;
+ IsDirty = true;
+ }
+
+ public void Set3f(int attrib, int index, Vector3 v)
+ {
+ Set3f(attrib, index, ref v);
+ }
+
+ public void Set3f(int attrib, int index, ref Vector3 v)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ _buffer[p + 2] = v.Z;
+ IsDirty = true;
+ }
+
+ public void Set4f(int attrib, int index, float x, float y, float z, float w)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = x;
+ _buffer[p + 1] = y;
+ _buffer[p + 2] = z;
+ _buffer[p + 3] = z;
+ IsDirty = true;
+ }
+
+ public void Set4f(int attrib, int index, Vector4 v)
+ {
+ Set4f(attrib, index, ref v);
+ }
+
+ public void Set4f(int attrib, int index, ref Vector4 v)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = v.X;
+ _buffer[p + 1] = v.Y;
+ _buffer[p + 2] = v.Z;
+ _buffer[p + 3] = v.W;
+ IsDirty = true;
+ }
+
+ public void Set4f(int attrib, int index, Quaternion q)
+ {
+ Set4f(attrib, index, ref q);
+ }
+
+ public void Set4f(int attrib, int index, ref Quaternion q)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = q.X;
+ _buffer[p + 1] = q.Y;
+ _buffer[p + 2] = q.Z;
+ _buffer[p + 3] = q.W;
+ IsDirty = true;
+ }
+
+ public void Set4f(int attrib, int index, Color c)
+ {
+ Set4f(attrib, index, ref c);
+ }
+
+ public void Set4f(int attrib, int index, ref Color c)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = c.R;
+ _buffer[p + 1] = c.G;
+ _buffer[p + 2] = c.B;
+ _buffer[p + 3] = c.A;
+ IsDirty = true;
+ }
+
+ public void Set9f(int attrib, int index, float[] x)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ Array.Copy(x, 0, _buffer, p, 9);
+ }
+
+ public void Set9f(int attrib, int index, Matrix3x3 m)
+ {
+ Set9f(attrib, index, ref m);
+ }
+
+ public void Set9f(int attrib, int index, ref Matrix3x3 m)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = m.M11;
+ _buffer[p + 1] = m.M21;
+ _buffer[p + 2] = m.M31;
+ _buffer[p + 3] = m.M12;
+ _buffer[p + 4] = m.M22;
+ _buffer[p + 5] = m.M32;
+ _buffer[p + 6] = m.M13;
+ _buffer[p + 7] = m.M23;
+ _buffer[p + 8] = m.M33;
+ }
+
+ public void Set16f(int attrib, int index, float[] x)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ Array.Copy(x, 0, _buffer, p, 16);
+ }
+
+ public void Set16f(int attrib, int index, Matrix4x4 m)
+ {
+ Set16f(attrib, index, ref m);
+ }
+
+ public void Set16f(int attrib, int index, ref Matrix4x4 m)
+ {
+ int p = GetVertexAttributeStartIndex(attrib, index);
+ _buffer[p] = m.M11;
+ _buffer[p + 1] = m.M21;
+ _buffer[p + 2] = m.M31;
+ _buffer[p + 3] = m.M41;
+ _buffer[p + 4] = m.M12;
+ _buffer[p + 5] = m.M22;
+ _buffer[p + 6] = m.M32;
+ _buffer[p + 7] = m.M42;
+ _buffer[p + 8] = m.M13;
+ _buffer[p + 9] = m.M23;
+ _buffer[p + 10] = m.M33;
+ _buffer[p + 11] = m.M43;
+ _buffer[p + 12] = m.M14;
+ _buffer[p + 13] = m.M24;
+ _buffer[p + 14] = m.M34;
+ _buffer[p + 15] = m.M44;
+ }
+
+ public void SetCurrentColor(float r, float g, float b, float a)
+ {
+ SetColor(CurrentPosition, r, g, b, a);
+ }
+
+ public void SetCurrentColor(float r, float g, float b)
+ {
+ SetColor(CurrentPosition, r, g, b);
+ }
+
+ public void SetCurrentColor(Color c)
+ {
+ SetColor(CurrentPosition, ref c);
+ }
+
+ public void SetCurrentColor(ref Color c)
+ {
+ SetColor(CurrentPosition, ref c);
+ }
+
+ public void SetCurrentPosition3D(float x, float y, float z)
+ {
+ SetPosition3D(CurrentPosition, x, y, z);
+ }
+
+ public void SetCurrentPosition3D(Vector3 v)
+ {
+ SetPosition3D(CurrentPosition, ref v);
+ }
+
+ public void SetCurrentPosition3D(ref Vector3 v)
+ {
+ SetPosition3D(CurrentPosition, ref v);
+ }
+
+ public void SetCurrentPosition2D(float x, float y)
+ {
+ SetPosition2D(CurrentPosition, x, y);
+ }
+
+ public void SetCurrentPosition2D(Vector2 v)
+ {
+ SetPosition2D(CurrentPosition, ref v);
+ }
+
+ public void SetCurrentPosition2D(ref Vector2 v)
+ {
+ SetPosition2D(CurrentPosition, ref v);
+ }
+
+ public void SetCurrentNormal(float x, float y, float z)
+ {
+ SetNormal(CurrentPosition, x, y, z);
+ }
+
+ public void SetCurrentNormal(Vector3 n)
+ {
+ SetNormal(CurrentPosition, ref n);
+ }
+
+ public void SetCurrentNormal(ref Vector3 n)
+ {
+ SetNormal(CurrentPosition, ref n);
+ }
+
+ public void SetCurrentTexCoord(float u, float v)
+ {
+ SetTexCoord(CurrentPosition, u, v);
+ }
+
+ public void SetCurrentTexCoord(Vector2 t)
+ {
+ SetTexCoord(CurrentPosition, ref t);
+ }
+
+ public void SetCurrentTexCoord(ref Vector2 t)
+ {
+ SetTexCoord(CurrentPosition, ref t);
+ }
+
+ public void SetCurrent1f(int attrib, float x)
+ {
+ Set1f(attrib, CurrentPosition, x);
+ }
+
+ public void SetCurrent2f(int attrib, float x, float y)
+ {
+ Set2f(attrib, CurrentPosition, x, y);
+ }
+
+ public void SetCurrent2f(int attrib, Vector2 v)
+ {
+ Set2f(attrib, CurrentPosition, ref v);
+ }
+
+ public void SetCurrent2f(int attrib, ref Vector2 v)
+ {
+ Set2f(attrib, CurrentPosition, ref v);
+ }
+
+ public void SetCurrent3f(int attrib, float x, float y, float z)
+ {
+ Set3f(attrib, CurrentPosition, x, y, z);
+ }
+
+ public void SetCurrent3f(int attrib, Vector3 v)
+ {
+ Set3f(attrib, CurrentPosition, ref v);
+ }
+
+ public void SetCurrent3f(int attrib, ref Vector3 v)
+ {
+ Set3f(attrib, CurrentPosition, ref v);
+ }
+
+ public void SetCurrent4f(int attrib, float x, float y, float z, float w)
+ {
+ Set4f(attrib, CurrentPosition, x, y, z, w);
+ }
+
+ public void SetCurrent4f(int attrib, Vector4 v)
+ {
+ Set4f(attrib, CurrentPosition, ref v);
+ }
+
+ public void SetCurrent4f(int attrib, ref Vector4 v)
+ {
+ Set4f(attrib, CurrentPosition, ref v);
+ }
+
+ public void SetCurrent4f(int attrib, Quaternion q)
+ {
+ Set4f(attrib, CurrentPosition, ref q);
+ }
+
+ public void SetCurrent4f(int attrib, ref Quaternion q)
+ {
+ Set4f(attrib, CurrentPosition, ref q);
+ }
+
+ public void SetCurrent4f(int attrib, Color c)
+ {
+ Set4f(attrib, CurrentPosition, ref c);
+ }
+
+ public void SetCurrent4f(int attrib, ref Color c)
+ {
+ Set4f(attrib, CurrentPosition, ref c);
+ }
+
+ public void SetCurrent9f(int attrib, float[] x)
+ {
+ Set9f(attrib, CurrentPosition, x);
+ }
+
+ public void SetCurrent9f(int attrib, Matrix3x3 m)
+ {
+ Set9f(attrib, CurrentPosition, ref m);
+ }
+
+ public void SetCurrent9f(int attrib, ref Matrix3x3 m)
+ {
+ Set9f(attrib, CurrentPosition, ref m);
+ }
+
+ public void SetCurrent16f(int attrib, float[] x)
+ {
+ Set16f(attrib, CurrentPosition, x);
+ }
+
+ public void SetCurrent16f(int attrib, Matrix4x4 m)
+ {
+ Set16f(attrib, CurrentPosition, ref m);
+ }
+
+ public void SetCurrent16f(int attrib, ref Matrix4x4 m)
+ {
+ Set16f(attrib, CurrentPosition, ref m);
+ }
+
+ #endregion
+
+ #endregion
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/VertexLerpShader.cs b/Blarg.GameFramework/Graphics/VertexLerpShader.cs
new file mode 100644
index 0000000..2748082
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/VertexLerpShader.cs
@@ -0,0 +1,40 @@
+using System;
+using System.IO;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public abstract class VertexLerpShader : StandardShader
+ {
+ protected string LerpUniformName { get; set; }
+
+ protected VertexLerpShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ Initialize();
+ }
+
+ protected VertexLerpShader(GraphicsDevice graphicsDevice, string vertexShaderSource, string fragmentShaderSource)
+ : base(graphicsDevice, vertexShaderSource, fragmentShaderSource)
+ {
+ Initialize();
+ }
+
+ protected VertexLerpShader(GraphicsDevice graphicsDevice, TextReader vertexShaderSourceReader, TextReader fragmentShaderSourceReader)
+ : base(graphicsDevice, vertexShaderSourceReader, fragmentShaderSourceReader)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ LerpUniformName = "u_lerp";
+ }
+
+ public void SetLerp(float t)
+ {
+ if (!IsReadyForUse)
+ throw new InvalidOperationException();
+ SetUniform(LerpUniformName, t);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/VertexSkinningShader.cs b/Blarg.GameFramework/Graphics/VertexSkinningShader.cs
new file mode 100644
index 0000000..6b72ea8
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/VertexSkinningShader.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public abstract class VertexSkinningShader : StandardShader
+ {
+ protected string JointPositionsUniformName { get; set; }
+ protected string JointRotationsUniformName { get; set; }
+
+ protected VertexSkinningShader(GraphicsDevice graphicsDevice)
+ : base(graphicsDevice)
+ {
+ Initialize();
+ }
+
+ protected VertexSkinningShader(GraphicsDevice graphicsDevice, string vertexShaderSource, string fragmentShaderSource)
+ : base(graphicsDevice, vertexShaderSource, fragmentShaderSource)
+ {
+ Initialize();
+ }
+
+ protected VertexSkinningShader(GraphicsDevice graphicsDevice, TextReader vertexShaderSourceReader, TextReader fragmentShaderSourceReader)
+ : base(graphicsDevice, vertexShaderSourceReader, fragmentShaderSourceReader)
+ {
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ JointPositionsUniformName = "u_jointPositions";
+ JointRotationsUniformName = "u_jointRotations";
+ }
+
+ public void SetJointPositions(Vector3[] positions)
+ {
+ if (!IsReadyForUse)
+ throw new InvalidOperationException();
+ SetUniform(JointPositionsUniformName, positions);
+ }
+
+ public void SetJointRotations(Quaternion[] rotations)
+ {
+ if (!IsReadyForUse)
+ throw new InvalidOperationException();
+ SetUniform(JointRotationsUniformName, rotations);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Graphics/ViewContext.cs b/Blarg.GameFramework/Graphics/ViewContext.cs
new file mode 100644
index 0000000..b655b49
--- /dev/null
+++ b/Blarg.GameFramework/Graphics/ViewContext.cs
@@ -0,0 +1,226 @@
+using System;
+using PortableGL;
+
+namespace Blarg.GameFramework.Graphics
+{
+ public class ViewContext
+ {
+ private GraphicsDevice _graphicsDevice;
+ private Rect _viewport;
+ private bool _viewportIsFixedSize;
+ private ScreenOrientation _screenOrientation;
+ private Camera _camera;
+ private bool _isUsingDefaultCamera;
+
+ private Matrix4x4 _modelViewMatrix;
+ private Matrix4x4 _projectionMatrix;
+
+ public int ViewportTop { get { return _viewport.Top; } }
+ public int ViewportLeft { get { return _viewport.Left; } }
+ public int ViewportBottom { get { return _viewport.Bottom; } }
+ public int ViewportRight { get { return _viewport.Right; } }
+ public int ViewportWidth { get { return _viewport.Width; } }
+ public int ViewportHeight { get { return _viewport.Height; } }
+
+ public bool IsViewportFixedSize { get { return _viewportIsFixedSize; } }
+ public bool IgnoringScreenRotation { get { return _viewportIsFixedSize; } }
+
+ public Camera Camera
+ {
+ get
+ {
+ return _camera;
+ }
+ set
+ {
+ bool cameraWasChanged = false;
+
+ // using the default camera but a new camera is being provided?
+ if (_isUsingDefaultCamera && value != null)
+ {
+ _isUsingDefaultCamera = false;
+ _camera = value;
+ cameraWasChanged = true;
+ }
+
+ // not using the default camera already, but setting a new camera
+ else if (!_isUsingDefaultCamera && value != null)
+ {
+ _camera = value;
+ cameraWasChanged = true;
+ }
+
+ // not using the default camera, and clearing ("nulling") the camera
+ else if (!_isUsingDefaultCamera && value == null)
+ {
+ _camera = new Camera(this);
+ _isUsingDefaultCamera = true;
+ cameraWasChanged = true;
+ }
+
+ // update our local projection matrix if a new camera was applied
+ // (otherwise, we wouldn't get it until the next OnResize...)
+ if (cameraWasChanged)
+ ProjectionMatrix = _camera.Projection;
+ }
+ }
+
+ public Matrix4x4 ProjectionMatrix
+ {
+ get
+ {
+ return _projectionMatrix;
+ }
+ set
+ {
+ if (!IgnoringScreenRotation && _screenOrientation != ScreenOrientation.Rotation0)
+ {
+ // apply a rotation immediately _after_ the projection matrix transform
+ Matrix4x4 rotation;
+ Matrix4x4.CreateRotationZ(MathHelpers.DegreesToRadians(-((float)_screenOrientation)), out rotation);
+ Matrix4x4.Multiply(ref rotation, ref value, out _projectionMatrix);
+ }
+ else
+ _projectionMatrix = value;
+ }
+ }
+
+ public Matrix4x4 ModelViewMatrix
+ {
+ get
+ {
+ return _modelViewMatrix;
+ }
+ set
+ {
+ _modelViewMatrix = value;
+ }
+ }
+
+ public Matrix4x4 OrthographicProjectionMatrix
+ {
+ get
+ {
+ Matrix4x4 ortho;
+ Matrix4x4.CreateOrthographic((float)_viewport.Left, (float)_viewport.Right, (float)_viewport.Top, (float)_viewport.Bottom, 0.0f, 1.0f, out ortho);
+
+ if (!IgnoringScreenRotation && _screenOrientation != ScreenOrientation.Rotation0)
+ {
+ // apply a rotation immediately _after_ the projection matrix transform
+ Matrix4x4 rotation;
+ Matrix4x4.CreateRotationZ(MathHelpers.DegreesToRadians(-((float)_screenOrientation)), out rotation);
+ Matrix4x4.Multiply(ref rotation, ref ortho, out ortho);
+ }
+
+ return ortho;
+ }
+ }
+
+ public ViewContext(GraphicsDevice graphicsDevice)
+ {
+ if (graphicsDevice == null)
+ throw new ArgumentNullException("graphicsDevice");
+
+ _graphicsDevice = graphicsDevice;
+ /*
+ var r = new Rect(
+ _graphicsDevice.Window.ClientRectangle.Left,
+ _graphicsDevice.Window.ClientRectangle.Top,
+ _graphicsDevice.Window.ClientRectangle.Right,
+ _graphicsDevice.Window.ClientRectangle.Bottom
+ );
+ */
+ var r = new Rect(0, 0, Platform.Application.Window.ClientWidth, Platform.Application.Window.ClientHeight);
+ Init(r, false);
+ }
+
+ public ViewContext(GraphicsDevice graphicsDevice, Rect fixedViewportSize)
+ {
+ if (graphicsDevice == null)
+ throw new ArgumentNullException("graphicsDevice");
+
+ _graphicsDevice = graphicsDevice;
+ Init(fixedViewportSize, true);
+ }
+
+ private void Init(Rect viewport, bool isFixedSizeViewport)
+ {
+ _viewport = viewport;
+ _viewportIsFixedSize = isFixedSizeViewport;
+ _screenOrientation = ScreenOrientation.Rotation0;
+ _camera = new Camera(this);
+ _isUsingDefaultCamera = true;
+ }
+
+ public void OnNewContext()
+ {
+ _modelViewMatrix = Matrix4x4.Identity;
+ _projectionMatrix = Matrix4x4.Identity;
+ }
+
+ public void OnLostContext()
+ {
+ }
+
+ public void OnResize(ref Rect size, ScreenOrientation screenOrientation = ScreenOrientation.Rotation0)
+ {
+ SetupViewport(ref size, screenOrientation);
+ }
+
+ public void OnRender(float delta)
+ {
+ if (_camera != null)
+ _camera.OnRender(delta);
+ }
+
+ public void OnApply(ref Rect size, ScreenOrientation screenOrientation = ScreenOrientation.Rotation0)
+ {
+ SetupViewport(ref size, screenOrientation);
+
+ // ensures it's set up for rendering immediately when this call returns
+ // NOTE: we assume OnApply() is going to be called in some other class's
+ // OnRender() event only (like, e.g. if a new framebuffer is bound)
+ if (_camera != null)
+ _camera.OnRender(0.0f);
+ }
+
+ private void SetupViewport(ref Rect size, ScreenOrientation screenOrientation)
+ {
+ Rect viewport;
+
+ if (_viewportIsFixedSize)
+ {
+ viewport = _viewport;
+ _screenOrientation = ScreenOrientation.Rotation0;
+ }
+ else
+ {
+ // based on the orientation, we may need to swap the width/height
+ // of the passed viewport dimensions
+ // (we don't do viewport rotation if the viewport is fixed)
+ if (!IgnoringScreenRotation && (screenOrientation == ScreenOrientation.Rotation90 || screenOrientation == ScreenOrientation.Rotation270))
+ {
+ // swap width and height
+ viewport.Left = size.Top;
+ viewport.Top = size.Left;
+ viewport.Right = size.Bottom;
+ viewport.Bottom = size.Right;
+ }
+ else
+ viewport = size;
+
+ // we **don't** want this to be rotated
+ _viewport = size;
+
+ _screenOrientation = screenOrientation;
+ }
+
+ // we **do** obviously want this to be rotated (if there is a rotation)
+ Platform.GL.glViewport(viewport.Left, viewport.Top, viewport.Width, viewport.Height);
+
+ // we also **don't** want the camera to work with a rotated viewport
+ if (_camera != null)
+ _camera.OnResize(ref _viewport);
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Resources/Fonts/Vera.ttf b/Blarg.GameFramework/Resources/Fonts/Vera.ttf
new file mode 100644
index 0000000..58cd6b5
Binary files /dev/null and b/Blarg.GameFramework/Resources/Fonts/Vera.ttf differ
diff --git a/Blarg.GameFramework/Resources/Fonts/VeraMono.ttf b/Blarg.GameFramework/Resources/Fonts/VeraMono.ttf
new file mode 100644
index 0000000..139f0b4
Binary files /dev/null and b/Blarg.GameFramework/Resources/Fonts/VeraMono.ttf differ
diff --git a/Blarg.GameFramework/Resources/ResourceUtils.cs b/Blarg.GameFramework/Resources/ResourceUtils.cs
new file mode 100644
index 0000000..a4bd710
--- /dev/null
+++ b/Blarg.GameFramework/Resources/ResourceUtils.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+using System.Reflection;
+
+namespace Blarg.GameFramework.Resources
+{
+ public static class ResourceUtils
+ {
+ public static Stream GetResource(string filename)
+ {
+ if (String.IsNullOrEmpty(filename))
+ throw new ArgumentNullException("filename");
+
+ return Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
+ }
+
+ public static string GetTextResource(string filename)
+ {
+ using (var stream = GetResource(filename))
+ {
+ if (stream == null)
+ return null;
+
+ using (var reader = new StreamReader(stream))
+ {
+ return reader.ReadToEnd();
+ }
+ }
+ }
+ }
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/debug.frag.glsl b/Blarg.GameFramework/Resources/Shaders/debug.frag.glsl
new file mode 100644
index 0000000..eae5113
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/debug.frag.glsl
@@ -0,0 +1,13 @@
+#ifdef GL_ES
+ #define LOWP lowp
+ precision mediump float;
+#else
+ #define LOWP
+#endif
+varying LOWP vec4 v_color;
+
+void main()
+{
+ gl_FragColor = v_color;
+}
+
diff --git a/Blarg.GameFramework/Resources/Shaders/debug.vert.glsl b/Blarg.GameFramework/Resources/Shaders/debug.vert.glsl
new file mode 100644
index 0000000..fa64145
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/debug.vert.glsl
@@ -0,0 +1,12 @@
+attribute vec4 a_position;
+attribute vec4 a_color;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+varying vec4 v_color;
+
+void main()
+{
+ v_color = a_color;
+ gl_PointSize = 4.0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/simple_color.frag.glsl b/Blarg.GameFramework/Resources/Shaders/simple_color.frag.glsl
new file mode 100644
index 0000000..8aad038
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/simple_color.frag.glsl
@@ -0,0 +1,12 @@
+#ifdef GL_ES
+ #define LOWP lowp
+ precision mediump float;
+#else
+ #define LOWP
+#endif
+varying LOWP vec4 v_color;
+
+void main()
+{
+ gl_FragColor = v_color;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/simple_color.vert.glsl b/Blarg.GameFramework/Resources/Shaders/simple_color.vert.glsl
new file mode 100644
index 0000000..685eed0
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/simple_color.vert.glsl
@@ -0,0 +1,11 @@
+attribute vec4 a_position;
+attribute vec4 a_color;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+varying vec4 v_color;
+
+void main()
+{
+ v_color = a_color;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/simple_color_texture.frag.glsl b/Blarg.GameFramework/Resources/Shaders/simple_color_texture.frag.glsl
new file mode 100644
index 0000000..45c0634
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/simple_color_texture.frag.glsl
@@ -0,0 +1,14 @@
+#ifdef GL_ES
+ #define LOWP lowp
+ precision mediump float;
+#else
+ #define LOWP
+#endif
+varying LOWP vec4 v_color;
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+
+void main()
+{
+ gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/simple_color_texture.vert.glsl b/Blarg.GameFramework/Resources/Shaders/simple_color_texture.vert.glsl
new file mode 100644
index 0000000..a6b882f
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/simple_color_texture.vert.glsl
@@ -0,0 +1,14 @@
+attribute vec4 a_position;
+attribute vec4 a_color;
+attribute vec2 a_texcoord0;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+
+void main()
+{
+ v_color = a_color;
+ v_texCoords = a_texcoord0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/simple_texture.frag.glsl b/Blarg.GameFramework/Resources/Shaders/simple_texture.frag.glsl
new file mode 100644
index 0000000..9362271
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/simple_texture.frag.glsl
@@ -0,0 +1,10 @@
+#ifdef GL_ES
+ precision mediump float;
+#endif
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+
+void main()
+{
+ gl_FragColor = texture2D(u_texture, v_texCoords);
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/simple_texture.vert.glsl b/Blarg.GameFramework/Resources/Shaders/simple_texture.vert.glsl
new file mode 100644
index 0000000..1d4886a
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/simple_texture.vert.glsl
@@ -0,0 +1,12 @@
+attribute vec4 a_position;
+attribute vec2 a_texcoord0;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+
+void main()
+{
+ v_texCoords = a_texcoord0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/sprite2d.frag.glsl b/Blarg.GameFramework/Resources/Shaders/sprite2d.frag.glsl
new file mode 100644
index 0000000..5e75683
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/sprite2d.frag.glsl
@@ -0,0 +1,20 @@
+#ifdef GL_ES
+ #define LOWP lowp
+ precision mediump float;
+#else
+ #define LOWP
+#endif
+varying LOWP vec4 v_color;
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+uniform bool u_textureHasAlphaOnly;
+
+void main()
+{
+ vec4 finalColor;
+ if (u_textureHasAlphaOnly)
+ finalColor = vec4(v_color.xyz, (v_color.a * texture2D(u_texture, v_texCoords).a));
+ else
+ finalColor = v_color * texture2D(u_texture, v_texCoords);
+ gl_FragColor = finalColor;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/sprite2d.vert.glsl b/Blarg.GameFramework/Resources/Shaders/sprite2d.vert.glsl
new file mode 100644
index 0000000..a6b882f
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/sprite2d.vert.glsl
@@ -0,0 +1,14 @@
+attribute vec4 a_position;
+attribute vec4 a_color;
+attribute vec2 a_texcoord0;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+
+void main()
+{
+ v_color = a_color;
+ v_texCoords = a_texcoord0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/sprite3d.frag.glsl b/Blarg.GameFramework/Resources/Shaders/sprite3d.frag.glsl
new file mode 100644
index 0000000..99ef608
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/sprite3d.frag.glsl
@@ -0,0 +1,26 @@
+#ifdef GL_ES
+ #define LOWP lowp
+ precision mediump float;
+#else
+ #define LOWP
+#endif
+varying LOWP vec4 v_color;
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+uniform bool u_textureHasAlphaOnly;
+
+void main()
+{
+ vec4 texColor = texture2D(u_texture, v_texCoords);
+ if (texColor.a > 0.0)
+ {
+ vec4 finalColor;
+ if (u_textureHasAlphaOnly)
+ finalColor = vec4(v_color.xyz, (v_color.a * texColor.a));
+ else
+ finalColor = v_color * texColor;
+ gl_FragColor = finalColor;
+ }
+ else
+ discard;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/sprite3d.vert.glsl b/Blarg.GameFramework/Resources/Shaders/sprite3d.vert.glsl
new file mode 100644
index 0000000..a6b882f
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/sprite3d.vert.glsl
@@ -0,0 +1,14 @@
+attribute vec4 a_position;
+attribute vec4 a_color;
+attribute vec2 a_texcoord0;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+
+void main()
+{
+ v_color = a_color;
+ v_texCoords = a_texcoord0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * a_position;
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/vertexlerp_texture.frag.glsl b/Blarg.GameFramework/Resources/Shaders/vertexlerp_texture.frag.glsl
new file mode 100644
index 0000000..9362271
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/vertexlerp_texture.frag.glsl
@@ -0,0 +1,10 @@
+#ifdef GL_ES
+ precision mediump float;
+#endif
+varying vec2 v_texCoords;
+uniform sampler2D u_texture;
+
+void main()
+{
+ gl_FragColor = texture2D(u_texture, v_texCoords);
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/vertexlerp_texture.vert.glsl b/Blarg.GameFramework/Resources/Shaders/vertexlerp_texture.vert.glsl
new file mode 100644
index 0000000..b767c5d
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/vertexlerp_texture.vert.glsl
@@ -0,0 +1,14 @@
+attribute vec4 a_position1;
+attribute vec4 a_position2;
+attribute vec2 a_texcoord0;
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+uniform float u_lerp;
+varying vec4 v_color;
+varying vec2 v_texCoords;
+
+void main()
+{
+ v_texCoords = a_texcoord0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * mix(a_position1, a_position2, u_lerp);
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/vertexskinning_texture.frag.glsl b/Blarg.GameFramework/Resources/Shaders/vertexskinning_texture.frag.glsl
new file mode 100644
index 0000000..b2ddfa9
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/vertexskinning_texture.frag.glsl
@@ -0,0 +1,10 @@
+#ifdef GL_ES
+ precision mediump float;
+#endif
+varying vec2 v_texCoord;
+uniform sampler2D u_texture;
+
+void main()
+{
+ gl_FragColor = texture2D(u_texture, v_texCoord);
+}
diff --git a/Blarg.GameFramework/Resources/Shaders/vertexskinning_texture.vert.glsl b/Blarg.GameFramework/Resources/Shaders/vertexskinning_texture.vert.glsl
new file mode 100644
index 0000000..3ae9d67
--- /dev/null
+++ b/Blarg.GameFramework/Resources/Shaders/vertexskinning_texture.vert.glsl
@@ -0,0 +1,32 @@
+#ifdef GL_ES
+ precision mediump float;
+#endif
+
+const int MAX_BONES = 50;
+
+attribute vec4 a_position;
+attribute vec2 a_texcoord0;
+attribute float a_jointIndex;
+
+uniform mat4 u_modelViewMatrix;
+uniform mat4 u_projectionMatrix;
+uniform vec3 u_jointPositions[MAX_BONES];
+uniform vec4 u_jointRotations[MAX_BONES];
+
+varying vec2 v_texCoord;
+
+vec3 qtransform(vec4 q, vec3 v)
+{
+ vec3 temp = cross(q.xyz, v) + q.w * v;
+ return cross(temp, -q.xyz) + dot(q.xyz,v) * q.xyz + q.w * temp;
+}
+
+void main()
+{
+ int j = int(a_jointIndex);
+
+ vec4 skinnedPosition = vec4(qtransform(u_jointRotations[j], a_position.xyz) + u_jointPositions[j], 1.0);
+
+ v_texCoord = a_texcoord0;
+ gl_Position = u_projectionMatrix * u_modelViewMatrix * skinnedPosition;
+}
diff --git a/Blarg.GameFramework/Support/StringExtensions.cs b/Blarg.GameFramework/Support/StringExtensions.cs
new file mode 100644
index 0000000..b9f57b9
--- /dev/null
+++ b/Blarg.GameFramework/Support/StringExtensions.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Blarg.GameFramework.Support
+{
+ internal static class StringExtensions
+ {
+ public static string Copy(string source)
+ {
+ if (source == null)
+ throw new ArgumentNullException("source");
+
+ var result = new char[source.Length];
+ for (int i = 0; i < source.Length; ++i)
+ result[i] = source[i];
+
+ return new string(result);
+ }
+ }
+}
+