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); + } + } +} +