// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT #if !USE_DX && !ANDROID using System; using System.Drawing; using System.Drawing.Imaging; using OpenTK; using OpenTK.Graphics.OpenGL; using BmpPixelFormat = System.Drawing.Imaging.PixelFormat; using GlPixelFormat = OpenTK.Graphics.OpenGL.PixelFormat; namespace ClassicalSharp.GraphicsAPI { /// Implements IGraphicsAPI using OpenGL 1.5, /// or 1.2 with the GL_ARB_vertex_buffer_object extension. public unsafe class OpenGLApi : IGraphicsApi { BeginMode[] modeMappings; public OpenGLApi() { InitFields(); int texDims; GL.GetIntegerv( GetPName.MaxTextureSize, &texDims ); texDimensions = texDims; CheckVboSupport(); base.InitDynamicBuffers(); setupBatchFuncCol4b = SetupVbPos3fCol4b; setupBatchFuncTex2fCol4b = SetupVbPos3fTex2fCol4b; GL.EnableClientState( ArrayCap.VertexArray ); GL.EnableClientState( ArrayCap.ColorArray ); } void CheckVboSupport() { string extensions = new String( (sbyte*)GL.GetString( StringName.Extensions ) ); string version = new String( (sbyte*)GL.GetString( StringName.Version ) ); int major = (int)(version[0] - '0'); // x.y. (and so forth) int minor = (int)(version[2] - '0'); if( (major > 1) || (major == 1 && minor >= 5) ) return; // Supported in core since 1.5 Utils.LogDebug( "Using ARB vertex buffer objects" ); if( !extensions.Contains( "GL_ARB_vertex_buffer_object" ) ) { ErrorHandler.LogError( "OpenGL VBO support check", "Driver does not support OpenGL VBOs, which are required for the OpenGL build." + Environment.NewLine + "You may need to install and/or update video card drivers." + Environment.NewLine + "Alternatively, you can download the Direct3D 9 build." ); throw new InvalidOperationException( "VBO support required for OpenGL build" ); } GL.UseArbVboAddresses(); } public override bool AlphaTest { set { if( value ) GL.Enable( EnableCap.AlphaTest ); else GL.Disable( EnableCap.AlphaTest ); } } public override bool AlphaBlending { set { if( value ) GL.Enable( EnableCap.Blend ); else GL.Disable( EnableCap.Blend ); } } Compare[] compareFuncs; public override void AlphaTestFunc( CompareFunc func, float value ) { GL.AlphaFunc( compareFuncs[(int)func], value ); } BlendingFactor[] blendFuncs; public override void AlphaBlendFunc( BlendFunc srcFunc, BlendFunc dstFunc ) { GL.BlendFunc( blendFuncs[(int)srcFunc], blendFuncs[(int)dstFunc] ); } public override bool Fog { set { if( value ) GL.Enable( EnableCap.Fog ); else GL.Disable( EnableCap.Fog ); } } FastColour lastFogCol = FastColour.Black; public override void SetFogColour( FastColour col ) { if( col != lastFogCol ) { Vector4 colRGBA = new Vector4( col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f ); GL.Fogfv( FogParameter.FogColor, &colRGBA.X ); lastFogCol = col; } } float lastFogEnd = -1, lastFogDensity = -1; public override void SetFogDensity( float value ) { FogParam( FogParameter.FogDensity, value, ref lastFogDensity ); } public override void SetFogStart( float value ) { GL.Fogf( FogParameter.FogStart, value ); } public override void SetFogEnd( float value ) { FogParam( FogParameter.FogEnd, value, ref lastFogEnd ); } static void FogParam( FogParameter param, float value, ref float last ) { if( value == last ) return; GL.Fogf( param, value ); last = value; } Fog lastFogMode = (Fog)999; FogMode[] fogModes; public override void SetFogMode( Fog mode ) { if( mode != lastFogMode ) { GL.Fogi( FogParameter.FogMode, (int)fogModes[(int)mode] ); lastFogMode = mode; } } public override bool FaceCulling { set { if( value ) GL.Enable( EnableCap.CullFace ); else GL.Disable( EnableCap.CullFace ); } } public override void Clear() { GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit ); } FastColour lastClearCol; public override void ClearColour( FastColour col ) { if( col != lastClearCol ) { GL.ClearColor( col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f ); lastClearCol = col; } } public override bool ColourWrite { set { GL.ColorMask( value, value, value, value ); } } public override void DepthTestFunc( CompareFunc func ) { GL.DepthFunc( compareFuncs[(int)func] ); } public override bool DepthTest { set { if( value ) GL.Enable( EnableCap.DepthTest ); else GL.Disable( EnableCap.DepthTest ); } } public override bool DepthWrite { set { GL.DepthMask( value ); } } public override bool AlphaArgBlend { set { } } #region Texturing int texDimensions; public override int MaxTextureDimensions { get { return texDimensions; } } public override bool Texturing { set { if( value ) GL.Enable( EnableCap.Texture2D ); else GL.Disable( EnableCap.Texture2D ); } } protected override int CreateTexture( int width, int height, IntPtr scan0, bool managedPool ) { int texId = 0; GL.GenTextures( 1, &texId ); GL.BindTexture( TextureTarget.Texture2D, texId ); GL.TexParameteri( TextureTarget.Texture2D, TextureParameterName.MinFilter, (int)TextureFilter.Nearest ); GL.TexParameteri( TextureTarget.Texture2D, TextureParameterName.MagFilter, (int)TextureFilter.Nearest ); GL.TexImage2D( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, GlPixelFormat.Bgra, PixelType.UnsignedByte, scan0 ); return texId; } public override void BindTexture( int texture ) { GL.BindTexture( TextureTarget.Texture2D, texture ); } public override void UpdateTexturePart( int texId, int texX, int texY, FastBitmap part ) { GL.BindTexture( TextureTarget.Texture2D, texId ); GL.TexSubImage2D( TextureTarget.Texture2D, 0, texX, texY, part.Width, part.Height, GlPixelFormat.Bgra, PixelType.UnsignedByte, part.Scan0 ); } public override void DeleteTexture( ref int texId ) { if( texId <= 0 ) return; int id = texId; GL.DeleteTextures( 1, &id ); texId = -1; } #endregion #region Vertex/index buffers Action setupBatchFunc, setupBatchFuncCol4b, setupBatchFuncTex2fCol4b; public override int CreateDynamicVb( VertexFormat format, int maxVertices ) { int id = GenAndBind( BufferTarget.ArrayBuffer ); int sizeInBytes = maxVertices * strideSizes[(int)format]; GL.BufferData( BufferTarget.ArrayBuffer, new IntPtr( sizeInBytes ), IntPtr.Zero, BufferUsage.DynamicDraw ); return id; } public override int CreateVb( T[] vertices, VertexFormat format, int count ) { int id = GenAndBind( BufferTarget.ArrayBuffer ); int sizeInBytes = count * strideSizes[(int)format]; GL.BufferData( BufferTarget.ArrayBuffer, new IntPtr( sizeInBytes ), vertices, BufferUsage.StaticDraw ); return id; } public override int CreateVb( IntPtr vertices, VertexFormat format, int count ) { int id = GenAndBind( BufferTarget.ArrayBuffer ); int sizeInBytes = count * strideSizes[(int)format]; GL.BufferData( BufferTarget.ArrayBuffer, new IntPtr( sizeInBytes ), vertices, BufferUsage.StaticDraw ); return id; } public override int CreateIb( ushort[] indices, int indicesCount ) { int id = GenAndBind( BufferTarget.ElementArrayBuffer ); int sizeInBytes = indicesCount * sizeof( ushort ); GL.BufferData( BufferTarget.ElementArrayBuffer, new IntPtr( sizeInBytes ), indices, BufferUsage.StaticDraw ); return id; } public override int CreateIb( IntPtr indices, int indicesCount ) { int id = GenAndBind( BufferTarget.ElementArrayBuffer ); int sizeInBytes = indicesCount * sizeof( ushort ); GL.BufferData( BufferTarget.ElementArrayBuffer, new IntPtr( sizeInBytes ), indices, BufferUsage.StaticDraw ); return id; } static int GenAndBind( BufferTarget target ) { int id = 0; GL.GenBuffers( 1, &id ); GL.BindBuffer( target, id ); return id; } int batchStride; public override void UpdateDynamicVb( DrawMode mode, int id, T[] vertices, int count ) { GL.BindBuffer( BufferTarget.ArrayBuffer, id ); GL.BufferSubData( BufferTarget.ArrayBuffer, IntPtr.Zero, new IntPtr( count * batchStride ), vertices ); setupBatchFunc(); GL.DrawArrays( modeMappings[(int)mode], 0, count ); } public override void UpdateDynamicIndexedVb( DrawMode mode, int id, T[] vertices, int vCount, int indicesCount ) { GL.BindBuffer( BufferTarget.ArrayBuffer, id ); GL.BufferSubData( BufferTarget.ArrayBuffer, IntPtr.Zero, new IntPtr( vCount * batchStride ), vertices ); setupBatchFunc(); GL.DrawElements( modeMappings[(int)mode], indicesCount, indexType, zero ); } public override void SetDynamicVbData( int id, T[] vertices, int count ) { GL.BindBuffer( BufferTarget.ArrayBuffer, id ); GL.BufferSubData( BufferTarget.ArrayBuffer, IntPtr.Zero, new IntPtr( count * batchStride ), vertices ); } public override void DeleteDynamicVb( ref int vb ) { if( vb <= 0 ) return; int id = vb; GL.DeleteBuffers( 1, &id ); vb = -1; } public override void DeleteVb( ref int vb ) { if( vb <= 0 ) return; int id = vb; GL.DeleteBuffers( 1, &id ); vb = -1; } public override void DeleteIb( ref int ib ) { if( ib <= 0 ) return; int id = ib; GL.DeleteBuffers( 1, &id ); ib = -1; } VertexFormat batchFormat = (VertexFormat)999; public override void SetBatchFormat( VertexFormat format ) { if( format == batchFormat ) return; if( batchFormat == VertexFormat.P3fT2fC4b ) { GL.DisableClientState( ArrayCap.TextureCoordArray ); } batchFormat = format; if( format == VertexFormat.P3fT2fC4b ) { GL.EnableClientState( ArrayCap.TextureCoordArray ); setupBatchFunc = setupBatchFuncTex2fCol4b; batchStride = VertexP3fT2fC4b.Size; } else { setupBatchFunc = setupBatchFuncCol4b; batchStride = VertexP3fC4b.Size; } } public override void BindVb( int vb ) { GL.BindBuffer( BufferTarget.ArrayBuffer, vb ); } public override void BindIb( int ib ) { GL.BindBuffer( BufferTarget.ElementArrayBuffer, ib ); } const DrawElementsType indexType = DrawElementsType.UnsignedShort; public override void DrawVb( DrawMode mode, int startVertex, int verticesCount ) { setupBatchFunc(); GL.DrawArrays( modeMappings[(int)mode], startVertex, verticesCount ); } public override void DrawIndexedVb( DrawMode mode, int indicesCount, int startIndex ) { setupBatchFunc(); GL.DrawElements( modeMappings[(int)mode], indicesCount, indexType, new IntPtr( startIndex * 2 ) ); } internal override void DrawIndexedVb_TrisT2fC4b( int indicesCount, int startIndex ) { GL.VertexPointer( 3, PointerType.Float, 24, zero ); GL.ColorPointer( 4, PointerType.UnsignedByte, 24, twelve ); GL.TexCoordPointer( 2, PointerType.Float, 24, sixteen ); GL.DrawElements( BeginMode.Triangles, indicesCount, indexType, new IntPtr( startIndex * 2 ) ); } internal override void DrawIndexedVb_TrisT2fC4b( int indicesCount, int startVertex, int startIndex ) { int offset = startVertex * VertexP3fT2fC4b.Size; GL.VertexPointer( 3, PointerType.Float, 24, new IntPtr( offset ) ); GL.ColorPointer( 4, PointerType.UnsignedByte, 24, new IntPtr( offset + 12 ) ); GL.TexCoordPointer( 2, PointerType.Float, 24, new IntPtr( offset + 16 ) ); GL.DrawElements( BeginMode.Triangles, indicesCount, indexType, new IntPtr( startIndex * 2 ) ); } IntPtr zero = new IntPtr( 0 ), twelve = new IntPtr( 12 ), sixteen = new IntPtr( 16 ); void SetupVbPos3fCol4b() { GL.VertexPointer( 3, PointerType.Float, VertexP3fC4b.Size, zero ); GL.ColorPointer( 4, PointerType.UnsignedByte, VertexP3fC4b.Size, twelve ); } void SetupVbPos3fTex2fCol4b() { GL.VertexPointer( 3, PointerType.Float, VertexP3fT2fC4b.Size, zero ); GL.ColorPointer( 4, PointerType.UnsignedByte, VertexP3fT2fC4b.Size, twelve ); GL.TexCoordPointer( 2, PointerType.Float, VertexP3fT2fC4b.Size, sixteen ); } #endregion #region Matrix manipulation MatrixMode lastMode = 0; MatrixMode[] matrixModes; public override void SetMatrixMode( MatrixType mode ) { MatrixMode glMode = matrixModes[(int)mode]; if( glMode != lastMode ) { GL.MatrixMode( glMode ); lastMode = glMode; } } public override void LoadMatrix( ref Matrix4 matrix ) { fixed( Single* ptr = &matrix.Row0.X ) GL.LoadMatrixf( ptr ); } public override void LoadIdentityMatrix() { GL.LoadIdentity(); } public override void PushMatrix() { GL.PushMatrix(); } public override void PopMatrix() { GL.PopMatrix(); } public override void MultiplyMatrix( ref Matrix4 matrix ) { fixed( Single* ptr = &matrix.Row0.X ) GL.MultMatrixf( ptr ); } #endregion public override void BeginFrame( Game game ) { } public override void EndFrame( Game game ) { game.window.SwapBuffers(); } public override void SetVSync( Game game, bool value ) { game.VSync = value; } bool isIntelRenderer; internal override void MakeApiInfo() { string vendor = new String( (sbyte*)GL.GetString( StringName.Vendor ) ); string renderer = new String( (sbyte*)GL.GetString( StringName.Renderer ) ); string version = new String( (sbyte*)GL.GetString( StringName.Version ) ); int depthBits = 0; GL.GetIntegerv( GetPName.DepthBits, &depthBits ); ApiInfo = new string[] { "--Using OpenGL api--", "Vendor: " + vendor, "Renderer: " + renderer, "GL version: " + version, "Max 2D texture dimensions: " + MaxTextureDimensions, "Depth buffer bits: " + depthBits, }; isIntelRenderer = renderer.Contains( "Intel" ); } public override bool WarnIfNecessary( Chat chat ) { if( !isIntelRenderer ) return false; chat.Add( "&cIntel graphics cards are known to have issues with the OpenGL build." ); chat.Add( "&cVSync may not work, and you may see disappearing clouds and map edges." ); chat.Add( "&cFor Windows, try downloading the Direct3D 9 build instead."); return true; } // Based on http://www.opentk.com/doc/graphics/save-opengl-rendering-to-disk public override void TakeScreenshot( string output, int width, int height ) { using( Bitmap bmp = new Bitmap( width, height, BmpPixelFormat.Format32bppRgb ) ) { // ignore alpha component using( FastBitmap fastBmp = new FastBitmap( bmp, true, false ) ) GL.ReadPixels( 0, 0, width, height, GlPixelFormat.Bgra, PixelType.UnsignedByte, fastBmp.Scan0 ); bmp.RotateFlip( RotateFlipType.RotateNoneFlipY ); bmp.Save( output, ImageFormat.Png ); } } public override void OnWindowResize( Game game ) { GL.Viewport( 0, 0, game.Width, game.Height ); } void InitFields() { // See comment in KeyMap() constructor for why this is necessary. blendFuncs = new BlendingFactor[6]; blendFuncs[0] = BlendingFactor.Zero; blendFuncs[1] = BlendingFactor.One; blendFuncs[2] = BlendingFactor.SrcAlpha; blendFuncs[3] = BlendingFactor.OneMinusSrcAlpha; blendFuncs[4] = BlendingFactor.DstAlpha; blendFuncs[5] = BlendingFactor.OneMinusDstAlpha; compareFuncs = new Compare[8]; compareFuncs[0] = Compare.Always; compareFuncs[1] = Compare.Notequal; compareFuncs[2] = Compare.Never; compareFuncs[3] = Compare.Less; compareFuncs[4] = Compare.Lequal; compareFuncs[5] = Compare.Equal; compareFuncs[6] = Compare.Gequal; compareFuncs[7] = Compare.Greater; modeMappings = new BeginMode[2]; modeMappings[0] = BeginMode.Triangles; modeMappings[1] = BeginMode.Lines; fogModes = new FogMode[3]; fogModes[0] = FogMode.Linear; fogModes[1] = FogMode.Exp; fogModes[2] = FogMode.Exp2; matrixModes = new MatrixMode[3]; matrixModes[0] = MatrixMode.Projection; matrixModes[1] = MatrixMode.Modelview; matrixModes[2] = MatrixMode.Texture; } } } #endif