add GraphicsDevice class and all other classes necessary to support it
based on ported code
This commit is contained in:
parent
63132f52cd
commit
044bf67f46
|
@ -87,6 +87,43 @@
|
|||
<Compile Include="IPlatformConfiguration.cs" />
|
||||
<Compile Include="IApplication.cs" />
|
||||
<Compile Include="BaseApplication.cs" />
|
||||
<Compile Include="Graphics\GraphicsDevice.cs" />
|
||||
<Compile Include="Graphics\GraphicsContextResource.cs" />
|
||||
<Compile Include="Graphics\BufferObject.cs" />
|
||||
<Compile Include="Graphics\Texture.cs" />
|
||||
<Compile Include="Graphics\TextureParameters.cs" />
|
||||
<Compile Include="Graphics\RenderState.cs" />
|
||||
<Compile Include="Graphics\BlendState.cs" />
|
||||
<Compile Include="Graphics\VertexAttributes.cs" />
|
||||
<Compile Include="Graphics\VertexBuffer.cs" />
|
||||
<Compile Include="Graphics\IndexBuffer.cs" />
|
||||
<Compile Include="Graphics\Framebuffer.cs" />
|
||||
<Compile Include="Graphics\Renderbuffer.cs" />
|
||||
<Compile Include="Graphics\Camera.cs" />
|
||||
<Compile Include="Graphics\Frustum.cs" />
|
||||
<Compile Include="Graphics\ViewContext.cs" />
|
||||
<Compile Include="Graphics\Shader.cs" />
|
||||
<Compile Include="Graphics\TextureAtlas.cs" />
|
||||
<Compile Include="Graphics\TextureRegion.cs" />
|
||||
<Compile Include="Graphics\CustomTextureAtlas.cs" />
|
||||
<Compile Include="Graphics\AutoGridTextureAtlas.cs" />
|
||||
<Compile Include="Graphics\SpriteFont.cs" />
|
||||
<Compile Include="Graphics\SpriteFontTrueTypeLoader.cs" />
|
||||
<Compile Include="Resources\ResourceUtils.cs" />
|
||||
<Compile Include="Graphics\StandardShader.cs" />
|
||||
<Compile Include="Graphics\SpriteShader.cs" />
|
||||
<Compile Include="Graphics\VertexSkinningShader.cs" />
|
||||
<Compile Include="Graphics\VertexLerpShader.cs" />
|
||||
<Compile Include="Graphics\GeometryDebugRenderer.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\DebugShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\SimpleColorShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\SimpleColorTextureShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\SimpleTextureShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\SimpleTextureVertexLerpShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\SimpleTextureVertexSkinningShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\Sprite2DShader.cs" />
|
||||
<Compile Include="Graphics\BuiltinShaders\Sprite3DShader.cs" />
|
||||
<Compile Include="Support\StringExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
|
@ -94,5 +131,30 @@
|
|||
<Folder Include="IO\" />
|
||||
<Folder Include="Math\" />
|
||||
<Folder Include="Graphics\" />
|
||||
<Folder Include="Resources\" />
|
||||
<Folder Include="Resources\Fonts\" />
|
||||
<Folder Include="Resources\Shaders\" />
|
||||
<Folder Include="Graphics\BuiltinShaders\" />
|
||||
<Folder Include="Support\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Fonts\Vera.ttf" />
|
||||
<EmbeddedResource Include="Resources\Fonts\VeraMono.ttf" />
|
||||
<EmbeddedResource Include="Resources\Shaders\debug.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\debug.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\simple_color.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\simple_color.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\simple_color_texture.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\simple_color_texture.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\simple_texture.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\simple_texture.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\sprite2d.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\sprite2d.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\sprite3d.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\sprite3d.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\vertexlerp_texture.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\vertexlerp_texture.vert.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\vertexskinning_texture.frag.glsl" />
|
||||
<EmbeddedResource Include="Resources\Shaders\vertexskinning_texture.vert.glsl" />
|
||||
</ItemGroup>
|
||||
</Project>
|
63
Blarg.GameFramework/Graphics/AutoGridTextureAtlas.cs
Normal file
63
Blarg.GameFramework/Graphics/AutoGridTextureAtlas.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
Blarg.GameFramework/Graphics/BlendState.cs
Normal file
120
Blarg.GameFramework/Graphics/BlendState.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
233
Blarg.GameFramework/Graphics/BufferObject.cs
Normal file
233
Blarg.GameFramework/Graphics/BufferObject.cs
Normal file
|
@ -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<T> : 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<T>(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<T>(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
|
||||
}
|
||||
}
|
20
Blarg.GameFramework/Graphics/BuiltinShaders/DebugShader.cs
Normal file
20
Blarg.GameFramework/Graphics/BuiltinShaders/DebugShader.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
180
Blarg.GameFramework/Graphics/Camera.cs
Normal file
180
Blarg.GameFramework/Graphics/Camera.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
63
Blarg.GameFramework/Graphics/CustomTextureAtlas.cs
Normal file
63
Blarg.GameFramework/Graphics/CustomTextureAtlas.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
488
Blarg.GameFramework/Graphics/Framebuffer.cs
Normal file
488
Blarg.GameFramework/Graphics/Framebuffer.cs
Normal file
|
@ -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<FramebufferTextureFormat, Texture> _attachedTextures;
|
||||
private IDictionary<FramebufferRenderbufferFormat, Renderbuffer> _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<FramebufferRenderbufferFormat, Renderbuffer>();
|
||||
_attachedTextures = new Dictionary<FramebufferTextureFormat, Texture>();
|
||||
_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<FramebufferRenderbufferFormat, Renderbuffer>();
|
||||
_attachedTextures = new Dictionary<FramebufferTextureFormat, Texture>();
|
||||
_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
|
||||
}
|
||||
}
|
150
Blarg.GameFramework/Graphics/Frustum.cs
Normal file
150
Blarg.GameFramework/Graphics/Frustum.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
395
Blarg.GameFramework/Graphics/GeometryDebugRenderer.cs
Normal file
395
Blarg.GameFramework/Graphics/GeometryDebugRenderer.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
58
Blarg.GameFramework/Graphics/GraphicsContextResource.cs
Normal file
58
Blarg.GameFramework/Graphics/GraphicsContextResource.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
927
Blarg.GameFramework/Graphics/GraphicsDevice.cs
Normal file
927
Blarg.GameFramework/Graphics/GraphicsDevice.cs
Normal file
|
@ -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<GraphicsContextResource> _managedResources;
|
||||
private IDictionary<int, Texture> _solidColorTextures;
|
||||
private VertexBuffer _boundVertexBuffer;
|
||||
private IndexBuffer _boundIndexBuffer;
|
||||
private Texture[] _boundTextures;
|
||||
private Renderbuffer _boundRenderbuffer;
|
||||
private Framebuffer _boundFramebuffer;
|
||||
private Shader _boundShader;
|
||||
private bool _isShaderVertexAttribsSet;
|
||||
private Stack<int> _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<int>(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<GraphicsContextResource>();
|
||||
_solidColorTextures = new Dictionary<int, Texture>();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -346,4 +346,3 @@ namespace Blarg.GameFramework.Graphics
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
168
Blarg.GameFramework/Graphics/IndexBuffer.cs
Normal file
168
Blarg.GameFramework/Graphics/IndexBuffer.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
|
||||
namespace Blarg.GameFramework.Graphics
|
||||
{
|
||||
public class IndexBuffer : BufferObject<ushort>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
113
Blarg.GameFramework/Graphics/RenderState.cs
Normal file
113
Blarg.GameFramework/Graphics/RenderState.cs
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
109
Blarg.GameFramework/Graphics/Renderbuffer.cs
Normal file
109
Blarg.GameFramework/Graphics/Renderbuffer.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
1246
Blarg.GameFramework/Graphics/Shader.cs
Normal file
1246
Blarg.GameFramework/Graphics/Shader.cs
Normal file
File diff suppressed because it is too large
Load diff
168
Blarg.GameFramework/Graphics/SpriteFont.cs
Normal file
168
Blarg.GameFramework/Graphics/SpriteFont.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
196
Blarg.GameFramework/Graphics/SpriteFontTrueTypeLoader.cs
Normal file
196
Blarg.GameFramework/Graphics/SpriteFontTrueTypeLoader.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
40
Blarg.GameFramework/Graphics/SpriteShader.cs
Normal file
40
Blarg.GameFramework/Graphics/SpriteShader.cs
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
59
Blarg.GameFramework/Graphics/StandardShader.cs
Normal file
59
Blarg.GameFramework/Graphics/StandardShader.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
286
Blarg.GameFramework/Graphics/Texture.cs
Normal file
286
Blarg.GameFramework/Graphics/Texture.cs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
83
Blarg.GameFramework/Graphics/TextureAtlas.cs
Normal file
83
Blarg.GameFramework/Graphics/TextureAtlas.cs
Normal file
|
@ -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<TextureRegion> 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<TextureRegion>();
|
||||
}
|
||||
|
||||
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<TextureRegion>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
115
Blarg.GameFramework/Graphics/TextureParameters.cs
Normal file
115
Blarg.GameFramework/Graphics/TextureParameters.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
10
Blarg.GameFramework/Graphics/TextureRegion.cs
Normal file
10
Blarg.GameFramework/Graphics/TextureRegion.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
|
||||
namespace Blarg.GameFramework.Graphics
|
||||
{
|
||||
public struct TextureRegion
|
||||
{
|
||||
public Rect Dimensions;
|
||||
public RectF TexCoords;
|
||||
}
|
||||
}
|
81
Blarg.GameFramework/Graphics/VertexAttributes.cs
Normal file
81
Blarg.GameFramework/Graphics/VertexAttributes.cs
Normal file
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
1196
Blarg.GameFramework/Graphics/VertexBuffer.cs
Normal file
1196
Blarg.GameFramework/Graphics/VertexBuffer.cs
Normal file
File diff suppressed because it is too large
Load diff
40
Blarg.GameFramework/Graphics/VertexLerpShader.cs
Normal file
40
Blarg.GameFramework/Graphics/VertexLerpShader.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
49
Blarg.GameFramework/Graphics/VertexSkinningShader.cs
Normal file
49
Blarg.GameFramework/Graphics/VertexSkinningShader.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
226
Blarg.GameFramework/Graphics/ViewContext.cs
Normal file
226
Blarg.GameFramework/Graphics/ViewContext.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
BIN
Blarg.GameFramework/Resources/Fonts/Vera.ttf
Normal file
BIN
Blarg.GameFramework/Resources/Fonts/Vera.ttf
Normal file
Binary file not shown.
BIN
Blarg.GameFramework/Resources/Fonts/VeraMono.ttf
Normal file
BIN
Blarg.GameFramework/Resources/Fonts/VeraMono.ttf
Normal file
Binary file not shown.
31
Blarg.GameFramework/Resources/ResourceUtils.cs
Normal file
31
Blarg.GameFramework/Resources/ResourceUtils.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
Blarg.GameFramework/Resources/Shaders/debug.frag.glsl
Normal file
13
Blarg.GameFramework/Resources/Shaders/debug.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
||||
|
12
Blarg.GameFramework/Resources/Shaders/debug.vert.glsl
Normal file
12
Blarg.GameFramework/Resources/Shaders/debug.vert.glsl
Normal file
|
@ -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;
|
||||
}
|
12
Blarg.GameFramework/Resources/Shaders/simple_color.frag.glsl
Normal file
12
Blarg.GameFramework/Resources/Shaders/simple_color.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
11
Blarg.GameFramework/Resources/Shaders/simple_color.vert.glsl
Normal file
11
Blarg.GameFramework/Resources/Shaders/simple_color.vert.glsl
Normal file
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
20
Blarg.GameFramework/Resources/Shaders/sprite2d.frag.glsl
Normal file
20
Blarg.GameFramework/Resources/Shaders/sprite2d.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
14
Blarg.GameFramework/Resources/Shaders/sprite2d.vert.glsl
Normal file
14
Blarg.GameFramework/Resources/Shaders/sprite2d.vert.glsl
Normal file
|
@ -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;
|
||||
}
|
26
Blarg.GameFramework/Resources/Shaders/sprite3d.frag.glsl
Normal file
26
Blarg.GameFramework/Resources/Shaders/sprite3d.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
14
Blarg.GameFramework/Resources/Shaders/sprite3d.vert.glsl
Normal file
14
Blarg.GameFramework/Resources/Shaders/sprite3d.vert.glsl
Normal file
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
20
Blarg.GameFramework/Support/StringExtensions.cs
Normal file
20
Blarg.GameFramework/Support/StringExtensions.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in a new issue