#region License /* FNA - XNA4 Reimplementation for Desktop Platforms * Copyright 2009-2020 Ethan Lee and the MonoGame Team * * Released under the Microsoft Public License. * See LICENSE for details. */ #endregion #region THREADED_GL Option // #define THREADED_GL /* Ah, so I see you've run into some issues with threaded GL... * * This class is designed to handle rendering coming from multiple threads, but * if you're too wreckless with how many threads are calling the GL, this will * hang. * * With THREADED_GL we instead allow you to run threaded rendering using * multiple GL contexts. This is more flexible, but much more dangerous. * * Basically, if you have to enable this, you should feel very bad. * -flibit */ #endregion #region DISABLE_THREADING Option // #define DISABLE_THREADING /* Perhaps you read the above option and thought to yourself: * "Wow, only an idiot would need threads for their graphics code!" * * For those of you who are particularly well-behaved with your renderer and * don't ever call anything on a thread at all, you can enable this define and * cut out a _ton_ of garbage generation that's caused by our attempt to force * things to the main thread. * * Enjoy the boost, you've earned it. * -flibit */ #endregion #region Using Statements using System; using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Threading; using SDL2; #endregion namespace Microsoft.Xna.Framework.Graphics { internal partial class ModernGLDevice : IGLDevice { #region OpenGL Texture Container Class private class OpenGLTexture : IGLTexture { public uint Handle { get; private set; } public GLenum Target { get; private set; } public bool HasMipmaps { get; private set; } public OpenGLTexture( uint handle, GLenum target, int levelCount ) { Handle = handle; Target = target; HasMipmaps = levelCount > 1; } // We can't set a SamplerState Texture to null, so use this. private OpenGLTexture() { Handle = 0; Target = GLenum.GL_TEXTURE_2D; // FIXME: Assumption! -flibit } public static readonly OpenGLTexture NullTexture = new OpenGLTexture(); } #endregion #region OpenGL Renderbuffer Container Class private class OpenGLRenderbuffer : IGLRenderbuffer { public uint Handle { get; private set; } public OpenGLRenderbuffer(uint handle) { Handle = handle; } } #endregion #region OpenGL Buffer Container Class private class ModernGLBuffer : IGLBuffer { public uint Handle { get; private set; } public IntPtr BufferSize { get; private set; } public GLenum Flags { get; private set; } public IntPtr Pin; public ModernGLBuffer( uint handle, IntPtr bufferSize, BufferUsage usage ) { Handle = handle; BufferSize = bufferSize; Flags = ( GLenum.GL_MAP_PERSISTENT_BIT | GLenum.GL_MAP_COHERENT_BIT | GLenum.GL_MAP_WRITE_BIT ); if (usage == BufferUsage.None) { Flags |= GLenum.GL_MAP_READ_BIT; } } private ModernGLBuffer(uint handle) { Handle = handle; } public static readonly ModernGLBuffer NullBuffer = new ModernGLBuffer(0); public static readonly ModernGLBuffer ForceUpdate = new ModernGLBuffer(uint.MaxValue); } #endregion #region OpenGL Effect Container Class private class OpenGLEffect : IGLEffect { public IntPtr EffectData { get; private set; } public IntPtr GLEffectData { get; private set; } public OpenGLEffect(IntPtr effect, IntPtr glEffect) { EffectData = effect; GLEffectData = glEffect; } } #endregion #region OpenGL Query Container Class private class OpenGLQuery : IGLQuery { public uint Handle { get; private set; } public OpenGLQuery(uint handle) { Handle = handle; } } #endregion #region Blending State Variables public Color BlendFactor { get { return blendColor; } set { if (value != blendColor) { blendColor = value; glBlendColor( blendColor.R / 255.0f, blendColor.G / 255.0f, blendColor.B / 255.0f, blendColor.A / 255.0f ); } } } public int MultiSampleMask { get { return multisampleMask; } set { if (value != multisampleMask) { if (value == -1) { glDisable(GLenum.GL_SAMPLE_MASK); } else { if (multisampleMask == -1) { glEnable(GLenum.GL_SAMPLE_MASK); } // FIXME: index...? -flibit glSampleMaski(0, (uint) value); } multisampleMask = value; } } } private bool alphaBlendEnable = false; private Color blendColor = Color.Transparent; private BlendFunction blendOp = BlendFunction.Add; private BlendFunction blendOpAlpha = BlendFunction.Add; private Blend srcBlend = Blend.One; private Blend dstBlend = Blend.Zero; private Blend srcBlendAlpha = Blend.One; private Blend dstBlendAlpha = Blend.Zero; private ColorWriteChannels colorWriteEnable = ColorWriteChannels.All; private ColorWriteChannels colorWriteEnable1 = ColorWriteChannels.All; private ColorWriteChannels colorWriteEnable2 = ColorWriteChannels.All; private ColorWriteChannels colorWriteEnable3 = ColorWriteChannels.All; private int multisampleMask = -1; // AKA 0xFFFFFFFF #endregion #region Depth State Variables private bool zEnable = false; private bool zWriteEnable = false; private CompareFunction depthFunc = CompareFunction.Less; #endregion #region Stencil State Variables public int ReferenceStencil { get { return stencilRef; } set { if (value != stencilRef) { stencilRef = value; if (separateStencilEnable) { glStencilFuncSeparate( GLenum.GL_FRONT, XNAToGL.CompareFunc[(int) stencilFunc], stencilRef, stencilMask ); glStencilFuncSeparate( GLenum.GL_BACK, XNAToGL.CompareFunc[(int) ccwStencilFunc], stencilRef, stencilMask ); } else { glStencilFunc( XNAToGL.CompareFunc[(int) stencilFunc], stencilRef, stencilMask ); } } } } private bool stencilEnable = false; private int stencilWriteMask = -1; // AKA 0xFFFFFFFF, ugh -flibit private bool separateStencilEnable = false; private int stencilRef = 0; private int stencilMask = -1; // AKA 0xFFFFFFFF, ugh -flibit private CompareFunction stencilFunc = CompareFunction.Always; private StencilOperation stencilFail = StencilOperation.Keep; private StencilOperation stencilZFail = StencilOperation.Keep; private StencilOperation stencilPass = StencilOperation.Keep; private CompareFunction ccwStencilFunc = CompareFunction.Always; private StencilOperation ccwStencilFail = StencilOperation.Keep; private StencilOperation ccwStencilZFail = StencilOperation.Keep; private StencilOperation ccwStencilPass = StencilOperation.Keep; #endregion #region Rasterizer State Variables private bool scissorTestEnable = false; private CullMode cullFrontFace = CullMode.None; private FillMode fillMode = FillMode.Solid; private float depthBias = 0.0f; private float slopeScaleDepthBias = 0.0f; private bool multiSampleEnable = true; #endregion #region Viewport State Variables /* These two aren't actually empty rects by default in OpenGL, * but we don't _really_ know the starting window size, so * force apply this when the GraphicsDevice is initialized. * -flibit */ private Rectangle scissorRectangle = new Rectangle( 0, 0, 0, 0 ); private Rectangle viewport = new Rectangle( 0, 0, 0, 0 ); private float depthRangeMin = 0.0f; private float depthRangeMax = 1.0f; #endregion #region Sampler State Variables private OpenGLTexture[] Textures; private TextureAddressMode[] SamplersU; private TextureAddressMode[] SamplersV; private TextureAddressMode[] SamplersW; private TextureFilter[] SamplersFilter; private float[] SamplersAnisotropy; private int[] SamplersMaxLevel; private float[] SamplersLODBias; private bool[] SamplersMipped; private uint[] Samplers; #endregion #region Buffer Binding Cache Variables private uint currentVertexBuffer = 0; private uint currentIndexBuffer = 0; // ld, or LastDrawn, effect/vertex attributes private VertexDeclaration ldVertexDeclaration = null; private IntPtr ldPointer = IntPtr.Zero; private IntPtr ldEffect = IntPtr.Zero; private IntPtr ldTechnique = IntPtr.Zero; private uint ldPass = 0; // Some vertex declarations may have overlapping attributes :/ private bool[,] attrUse = new bool[(int) MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TOTAL, 16]; #endregion #region Render Target Cache Variables private uint currentReadFramebuffer = 0; private uint currentDrawFramebuffer = 0; private uint targetFramebuffer = 0; private uint resolveFramebufferRead = 0; private uint resolveFramebufferDraw = 0; private readonly uint[] currentAttachments; private readonly GLenum[] currentAttachmentTypes; private int currentDrawBuffers; private readonly IntPtr drawBuffersArray; private uint currentRenderbuffer; private DepthFormat currentDepthStencilFormat; private readonly uint[] attachments; private readonly GLenum[] attachmentTypes; #endregion #region Clear Cache Variables private Vector4 currentClearColor = new Vector4(0, 0, 0, 0); private float currentClearDepth = 1.0f; private int currentClearStencil = 0; #endregion #region Private OpenGL Context Variable private IntPtr glContext; #endregion #region Faux-Backbuffer Variables public IGLBackbuffer Backbuffer { get; private set; } private GLenum backbufferScaleMode; #endregion #region OpenGL Device Capabilities public bool SupportsDxt1 { get; private set; } public bool SupportsS3tc { get; private set; } public bool SupportsHardwareInstancing { get; private set; } public bool SupportsNoOverwrite { get { return true; } } public int MaxTextureSlots { get; private set; } public int MaxMultiSampleCount { get; private set; } #endregion #region Private Vertex Attribute Cache private class VertexAttribute { public ModernGLBuffer CurrentBuffer; public IntPtr CurrentPointer; public VertexElementFormat CurrentFormat; public bool CurrentNormalized; public int CurrentStride; public VertexAttribute() { CurrentBuffer = ModernGLBuffer.NullBuffer; CurrentPointer = IntPtr.Zero; CurrentFormat = VertexElementFormat.Single; CurrentNormalized = false; CurrentStride = 0; } } private VertexAttribute[] attributes; private bool[] attributeEnabled; private bool[] previousAttributeEnabled; private int[] attributeDivisor; private int[] previousAttributeDivisor; #endregion #region Private MojoShader Interop private string shaderProfile; private IntPtr shaderContext; private IntPtr currentEffect = IntPtr.Zero; private IntPtr currentTechnique = IntPtr.Zero; private uint currentPass = 0; private bool renderTargetBound = false; private bool effectApplied = false; private static IntPtr glGetProcAddress(IntPtr name, IntPtr d) { return SDL.SDL_GL_GetProcAddress(name); } private static MojoShader.MOJOSHADER_glGetProcAddress GLGetProcAddress = glGetProcAddress; #endregion #region Private Graphics Object Disposal Queues private ConcurrentQueue GCTextures = new ConcurrentQueue(); private ConcurrentQueue GCDepthBuffers = new ConcurrentQueue(); private ConcurrentQueue GCVertexBuffers = new ConcurrentQueue(); private ConcurrentQueue GCIndexBuffers = new ConcurrentQueue(); private ConcurrentQueue GCEffects = new ConcurrentQueue(); private ConcurrentQueue GCQueries = new ConcurrentQueue(); #endregion #region Private Profile-specific Variables private bool useCoreProfile; private DepthFormat windowDepthFormat; private uint vao; #endregion #region Public Constructor public ModernGLDevice(PresentationParameters presentationParameters) { // Create OpenGL context glContext = SDL.SDL_GL_CreateContext( presentationParameters.DeviceWindowHandle ); // Check for a possible Core context int flags; int coreFlag = (int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE; SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, out flags); useCoreProfile = (flags & coreFlag) == coreFlag; // Check the window's depth/stencil format int depthSize, stencilSize; SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, out depthSize); SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, out stencilSize); if (depthSize == 0 && stencilSize == 0) { windowDepthFormat = DepthFormat.None; } else if (depthSize == 16 && stencilSize == 0) { windowDepthFormat = DepthFormat.Depth16; } else if (depthSize == 24 && stencilSize == 0) { windowDepthFormat = DepthFormat.Depth24; } else if (depthSize == 24 && stencilSize == 8) { windowDepthFormat = DepthFormat.Depth24Stencil8; } else if (depthSize == 32 && stencilSize == 8) { /* There's like a 99% chance this is GDI, * expect a NoSuitableGraphicsDevice soon after * this line... */ FNALoggerEXT.LogWarn( "Non-standard D32S8 window depth format!" ); windowDepthFormat = DepthFormat.Depth24Stencil8; } else { throw new NotSupportedException(string.Format( "Unrecognized window depth/stencil format: {0} {1}", depthSize, stencilSize )); } // Init threaded GL crap where applicable InitThreadedGL( presentationParameters.DeviceWindowHandle ); // Initialize entry points LoadGLEntryPoints(); shaderProfile = Environment.GetEnvironmentVariable( "FNA_GRAPHICS_MOJOSHADER_PROFILE" ); if (string.IsNullOrEmpty(shaderProfile)) { shaderProfile = MojoShader.MOJOSHADER_glBestProfile( GLGetProcAddress, IntPtr.Zero, null, null, IntPtr.Zero ); /* SPIR-V is very new and not really necessary. */ if (shaderProfile == "glspirv" && !useCoreProfile) { shaderProfile = "glsl120"; } } shaderContext = MojoShader.MOJOSHADER_glCreateContext( shaderProfile, GLGetProcAddress, IntPtr.Zero, null, null, IntPtr.Zero ); MojoShader.MOJOSHADER_glMakeContextCurrent(shaderContext); // Some users might want pixely upscaling... backbufferScaleMode = Environment.GetEnvironmentVariable( "FNA_GRAPHICS_BACKBUFFER_SCALE_NEAREST" ) == "1" ? GLenum.GL_NEAREST : GLenum.GL_LINEAR; // Print GL information FNALoggerEXT.LogInfo("IGLDevice: ModernGLDevice"); FNALoggerEXT.LogInfo("OpenGL Device: " + glGetString(GLenum.GL_RENDERER)); FNALoggerEXT.LogInfo("OpenGL Driver: " + glGetString(GLenum.GL_VERSION)); FNALoggerEXT.LogInfo("OpenGL Vendor: " + glGetString(GLenum.GL_VENDOR)); FNALoggerEXT.LogInfo("MojoShader Profile: " + shaderProfile); // Load the extension list, initialize extension-dependent components string extensions; if (useCoreProfile) { extensions = string.Empty; int numExtensions; glGetIntegerv(GLenum.GL_NUM_EXTENSIONS, out numExtensions); for (uint i = 0; i < numExtensions; i += 1) { extensions += glGetStringi(GLenum.GL_EXTENSIONS, i) + " "; } } else { extensions = glGetString(GLenum.GL_EXTENSIONS); } SupportsS3tc = ( extensions.Contains("GL_EXT_texture_compression_s3tc") || extensions.Contains("GL_OES_texture_compression_S3TC") || extensions.Contains("GL_EXT_texture_compression_dxt3") || extensions.Contains("GL_EXT_texture_compression_dxt5") ); SupportsDxt1 = ( SupportsS3tc || extensions.Contains("GL_EXT_texture_compression_dxt1") ); SupportsHardwareInstancing = true; /* Check the max multisample count, override parameters if necessary */ int maxSamples = 0; glGetIntegerv(GLenum.GL_MAX_SAMPLES, out maxSamples); MaxMultiSampleCount = maxSamples; presentationParameters.MultiSampleCount = Math.Min( presentationParameters.MultiSampleCount, MaxMultiSampleCount ); // Initialize the faux-backbuffer if (UseFauxBackbuffer(presentationParameters)) { Backbuffer = new OpenGLBackbuffer( this, presentationParameters.BackBufferWidth, presentationParameters.BackBufferHeight, presentationParameters.DepthStencilFormat, presentationParameters.MultiSampleCount ); } else { Backbuffer = new NullBackbuffer( presentationParameters.BackBufferWidth, presentationParameters.BackBufferHeight, windowDepthFormat ); } // Initialize texture collection array int numSamplers; glGetIntegerv(GLenum.GL_MAX_TEXTURE_IMAGE_UNITS, out numSamplers); Textures = new OpenGLTexture[numSamplers]; Samplers = new uint[numSamplers]; SamplersU = new TextureAddressMode[numSamplers]; SamplersV = new TextureAddressMode[numSamplers]; SamplersW = new TextureAddressMode[numSamplers]; SamplersFilter = new TextureFilter[numSamplers]; SamplersAnisotropy = new float[numSamplers]; SamplersMaxLevel = new int[numSamplers]; SamplersLODBias = new float[numSamplers]; SamplersMipped = new bool[numSamplers]; GCHandle smpHandle = GCHandle.Alloc(Samplers, GCHandleType.Pinned); glCreateSamplers(numSamplers, smpHandle.AddrOfPinnedObject()); smpHandle.Free(); for (int i = 0; i < numSamplers; i += 1) { Textures[i] = OpenGLTexture.NullTexture; SamplersU[i] = TextureAddressMode.Wrap; SamplersV[i] = TextureAddressMode.Wrap; SamplersW[i] = TextureAddressMode.Wrap; SamplersFilter[i] = TextureFilter.Linear; SamplersAnisotropy[i] = 4.0f; SamplersMaxLevel[i] = 0; SamplersLODBias[i] = 0.0f; SamplersMipped[i] = false; glBindSampler(i, Samplers[i]); } MaxTextureSlots = numSamplers; // Initialize vertex attribute state arrays int numAttributes; glGetIntegerv(GLenum.GL_MAX_VERTEX_ATTRIBS, out numAttributes); numAttributes = Math.Min( numAttributes, GraphicsDevice.MAX_VERTEX_ATTRIBUTES ); attributes = new VertexAttribute[numAttributes]; attributeEnabled = new bool[numAttributes]; previousAttributeEnabled = new bool[numAttributes]; attributeDivisor = new int[numAttributes]; previousAttributeDivisor = new int[numAttributes]; for (int i = 0; i < numAttributes; i += 1) { attributes[i] = new VertexAttribute(); attributeEnabled[i] = false; previousAttributeEnabled[i] = false; attributeDivisor[i] = 0; previousAttributeDivisor[i] = 0; } // Initialize render target FBO and state arrays int numAttachments; glGetIntegerv(GLenum.GL_MAX_DRAW_BUFFERS, out numAttachments); numAttachments = Math.Min( numAttachments, GraphicsDevice.MAX_RENDERTARGET_BINDINGS ); attachments = new uint[numAttachments]; attachmentTypes = new GLenum[numAttachments]; currentAttachments = new uint[numAttachments]; currentAttachmentTypes = new GLenum[numAttachments]; drawBuffersArray = Marshal.AllocHGlobal(sizeof(GLenum) * (numAttachments + 2)); unsafe { GLenum* dba = (GLenum*) drawBuffersArray; for (int i = 0; i < numAttachments; i += 1) { currentAttachments[i] = 0; currentAttachmentTypes[i] = GLenum.GL_TEXTURE_2D; dba[i] = GLenum.GL_COLOR_ATTACHMENT0 + i; } dba[numAttachments] = GLenum.GL_DEPTH_ATTACHMENT; dba[numAttachments + 1] = GLenum.GL_STENCIL_ATTACHMENT; } currentDrawBuffers = 0; currentRenderbuffer = 0; currentDepthStencilFormat = DepthFormat.None; glCreateFramebuffers(1, out targetFramebuffer); glCreateFramebuffers(1, out resolveFramebufferRead); glCreateFramebuffers(1, out resolveFramebufferDraw); // Generate and bind a VAO, to shut Core up if (useCoreProfile) { glGenVertexArrays(1, out vao); glBindVertexArray(vao); } else { // Compat-only, but needed for PSIZE0 accuracy glEnable(GLenum.GL_POINT_SPRITE); glTexEnvi(GLenum.GL_POINT_SPRITE, GLenum.GL_COORD_REPLACE, 1); } } #endregion #region Dispose Method public void Dispose() { if (useCoreProfile) { glBindVertexArray(0); glDeleteVertexArrays(1, ref vao); } glDeleteFramebuffers(1, ref resolveFramebufferRead); resolveFramebufferRead = 0; glDeleteFramebuffers(1, ref resolveFramebufferDraw); resolveFramebufferDraw = 0; glDeleteFramebuffers(1, ref targetFramebuffer); targetFramebuffer = 0; if (Backbuffer is OpenGLBackbuffer) { (Backbuffer as OpenGLBackbuffer).Dispose(); } Backbuffer = null; Marshal.FreeHGlobal(drawBuffersArray); MojoShader.MOJOSHADER_glMakeContextCurrent(IntPtr.Zero); MojoShader.MOJOSHADER_glDestroyContext(shaderContext); #if THREADED_GL SDL.SDL_GL_DeleteContext(BackgroundContext.context); #endif SDL.SDL_GL_DeleteContext(glContext); } #endregion #region Window Backbuffer Reset Method public void ResetBackbuffer(PresentationParameters presentationParameters) { if (UseFauxBackbuffer(presentationParameters)) { if (Backbuffer is NullBackbuffer) { Backbuffer = new OpenGLBackbuffer( this, presentationParameters.BackBufferWidth, presentationParameters.BackBufferHeight, presentationParameters.DepthStencilFormat, presentationParameters.MultiSampleCount ); } else { Backbuffer.ResetFramebuffer( presentationParameters ); } } else { if (Backbuffer is OpenGLBackbuffer) { (Backbuffer as OpenGLBackbuffer).Dispose(); Backbuffer = new NullBackbuffer( presentationParameters.BackBufferWidth, presentationParameters.BackBufferHeight, windowDepthFormat ); } else { Backbuffer.ResetFramebuffer( presentationParameters ); } } } #endregion #region BeginFrame Method public void BeginFrame() { // Do nothing. } #endregion #region Window SwapBuffers Method public void SwapBuffers( Rectangle? sourceRectangle, Rectangle? destinationRectangle, IntPtr overrideWindowHandle ) { /* Only the faux-backbuffer supports presenting * specific regions given to Present(). * -flibit */ if (Backbuffer is OpenGLBackbuffer) { int srcX, srcY, srcW, srcH; int dstX, dstY, dstW, dstH; if (sourceRectangle.HasValue) { srcX = sourceRectangle.Value.X; srcY = sourceRectangle.Value.Y; srcW = sourceRectangle.Value.Width; srcH = sourceRectangle.Value.Height; } else { srcX = 0; srcY = 0; srcW = Backbuffer.Width; srcH = Backbuffer.Height; } if (destinationRectangle.HasValue) { dstX = destinationRectangle.Value.X; dstY = destinationRectangle.Value.Y; dstW = destinationRectangle.Value.Width; dstH = destinationRectangle.Value.Height; } else { dstX = 0; dstY = 0; SDL.SDL_GL_GetDrawableSize( overrideWindowHandle, out dstW, out dstH ); } if (scissorTestEnable) { glDisable(GLenum.GL_SCISSOR_TEST); } uint finalBuffer; if ( Backbuffer.MultiSampleCount > 0 && (srcX != dstX || srcY != dstY || srcW != dstW || srcH != dstH) ) { /* We have to resolve the renderbuffer to a texture first. * For whatever reason, we can't blit a multisample renderbuffer * to the backbuffer. Not sure why, but oh well. * -flibit */ OpenGLBackbuffer glBack = Backbuffer as OpenGLBackbuffer; if (glBack.Texture == 0) { glCreateTextures(GLenum.GL_TEXTURE_2D, 1, out glBack.Texture); glTextureStorage2D( glBack.Texture, 1, GLenum.GL_RGBA, glBack.Width, glBack.Height ); } glNamedFramebufferTexture( glBack.Handle, GLenum.GL_COLOR_ATTACHMENT0, glBack.Texture, 0 ); glBlitNamedFramebuffer( glBack.Handle, resolveFramebufferDraw, 0, 0, glBack.Width, glBack.Height, 0, 0, glBack.Width, glBack.Height, GLenum.GL_COLOR_BUFFER_BIT, GLenum.GL_LINEAR ); /* Invalidate the MSAA faux-backbuffer */ glInvalidateNamedFramebufferData( glBack.Handle, attachments.Length + 2, drawBuffersArray ); finalBuffer = resolveFramebufferDraw; } else { finalBuffer = (Backbuffer as OpenGLBackbuffer).Handle; } glBlitNamedFramebuffer( finalBuffer, 0, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH, GLenum.GL_COLOR_BUFFER_BIT, backbufferScaleMode ); /* Invalidate the faux-backbuffer */ glInvalidateNamedFramebufferData( finalBuffer, attachments.Length + 2, drawBuffersArray ); if (scissorTestEnable) { glEnable(GLenum.GL_SCISSOR_TEST); } SDL.SDL_GL_SwapWindow( overrideWindowHandle ); } else { // Nothing left to do, just swap! SDL.SDL_GL_SwapWindow( overrideWindowHandle ); } #if !DISABLE_THREADING && !THREADED_GL RunActions(); #endif IGLTexture gcTexture; while (GCTextures.TryDequeue(out gcTexture)) { DeleteTexture(gcTexture); } IGLRenderbuffer gcDepthBuffer; while (GCDepthBuffers.TryDequeue(out gcDepthBuffer)) { DeleteRenderbuffer(gcDepthBuffer); } IGLBuffer gcBuffer; while (GCVertexBuffers.TryDequeue(out gcBuffer)) { DeleteVertexBuffer(gcBuffer); } while (GCIndexBuffers.TryDequeue(out gcBuffer)) { DeleteIndexBuffer(gcBuffer); } IGLEffect gcEffect; while (GCEffects.TryDequeue(out gcEffect)) { DeleteEffect(gcEffect); } IGLQuery gcQuery; while (GCQueries.TryDequeue(out gcQuery)) { DeleteQuery(gcQuery); } } #endregion #region GL Object Disposal Wrappers public void AddDisposeTexture(IGLTexture texture) { if (IsOnMainThread()) { DeleteTexture(texture); } else { GCTextures.Enqueue(texture); } } public void AddDisposeRenderbuffer(IGLRenderbuffer renderbuffer) { if (IsOnMainThread()) { DeleteRenderbuffer(renderbuffer); } else { GCDepthBuffers.Enqueue(renderbuffer); } } public void AddDisposeVertexBuffer(IGLBuffer buffer) { if (IsOnMainThread()) { DeleteVertexBuffer(buffer); } else { GCVertexBuffers.Enqueue(buffer); } } public void AddDisposeIndexBuffer(IGLBuffer buffer) { if (IsOnMainThread()) { DeleteIndexBuffer(buffer); } else { GCIndexBuffers.Enqueue(buffer); } } public void AddDisposeEffect(IGLEffect effect) { if (IsOnMainThread()) { DeleteEffect(effect); } else { GCEffects.Enqueue(effect); } } public void AddDisposeQuery(IGLQuery query) { if (IsOnMainThread()) { DeleteQuery(query); } else { GCQueries.Enqueue(query); } } #endregion #region String Marker Method public void SetStringMarker(string text) { #if DEBUG IntPtr chars = Marshal.StringToHGlobalAnsi(text); glStringMarkerGREMEDY(text.Length, chars); Marshal.FreeHGlobal(chars); #endif } #endregion #region Drawing Methods public void DrawIndexedPrimitives( PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount, IGLBuffer indices, IndexElementSize indexElementSize ) { // Bind the index buffer BindIndexBuffer(indices); // Draw! glDrawRangeElementsBaseVertex( XNAToGL.Primitive[(int) primitiveType], minVertexIndex, minVertexIndex + numVertices - 1, XNAToGL.PrimitiveVerts(primitiveType, primitiveCount), XNAToGL.IndexType[(int) indexElementSize], (IntPtr) (startIndex * XNAToGL.IndexSize[(int) indexElementSize]), baseVertex ); } public void DrawInstancedPrimitives( PrimitiveType primitiveType, int baseVertex, int minVertexIndex, int numVertices, int startIndex, int primitiveCount, int instanceCount, IGLBuffer indices, IndexElementSize indexElementSize ) { // Note that minVertexIndex and numVertices are NOT used! // Bind the index buffer BindIndexBuffer(indices); // Draw! glDrawElementsInstancedBaseVertex( XNAToGL.Primitive[(int) primitiveType], XNAToGL.PrimitiveVerts(primitiveType, primitiveCount), XNAToGL.IndexType[(int) indexElementSize], (IntPtr) (startIndex * XNAToGL.IndexSize[(int) indexElementSize]), instanceCount, baseVertex ); } public void DrawPrimitives( PrimitiveType primitiveType, int vertexStart, int primitiveCount ) { // Draw! glDrawArrays( XNAToGL.Primitive[(int) primitiveType], vertexStart, XNAToGL.PrimitiveVerts(primitiveType, primitiveCount) ); } public void DrawUserIndexedPrimitives( PrimitiveType primitiveType, IntPtr vertexData, int vertexOffset, int numVertices, IntPtr indexData, int indexOffset, IndexElementSize indexElementSize, int primitiveCount ) { // Unbind current index buffer. BindIndexBuffer(ModernGLBuffer.NullBuffer); // Draw! glDrawRangeElements( XNAToGL.Primitive[(int) primitiveType], 0, numVertices - 1, XNAToGL.PrimitiveVerts(primitiveType, primitiveCount), XNAToGL.IndexType[(int) indexElementSize], (IntPtr) ( indexData.ToInt64() + (indexOffset * XNAToGL.IndexSize[(int) indexElementSize]) ) ); } public void DrawUserPrimitives( PrimitiveType primitiveType, IntPtr vertexData, int vertexOffset, int primitiveCount ) { // Draw! glDrawArrays( XNAToGL.Primitive[(int) primitiveType], vertexOffset, XNAToGL.PrimitiveVerts(primitiveType, primitiveCount) ); } #endregion #region State Management Methods public void SetPresentationInterval(PresentInterval interval) { if (interval == PresentInterval.Default || interval == PresentInterval.One) { string OSVersion = SDL.SDL_GetPlatform(); bool disableLateSwapTear = ( OSVersion.Equals("Mac OS X") || OSVersion.Equals("WinRT") || Environment.GetEnvironmentVariable("FNA_OPENGL_DISABLE_LATESWAPTEAR") == "1" ); if (disableLateSwapTear) { SDL.SDL_GL_SetSwapInterval(1); } else { if (SDL.SDL_GL_SetSwapInterval(-1) != -1) { FNALoggerEXT.LogInfo("Using EXT_swap_control_tear VSync!"); } else { FNALoggerEXT.LogInfo("EXT_swap_control_tear unsupported. Fall back to standard VSync."); SDL.SDL_ClearError(); SDL.SDL_GL_SetSwapInterval(1); } } } else if (interval == PresentInterval.Immediate) { SDL.SDL_GL_SetSwapInterval(0); } else if (interval == PresentInterval.Two) { SDL.SDL_GL_SetSwapInterval(2); } else { throw new NotSupportedException("Unrecognized PresentInterval!"); } } public void SetViewport(Viewport vp) { // Flip viewport when target is not bound if (!renderTargetBound) { vp.Y = Backbuffer.Height - vp.Y - vp.Height; } if (vp.Bounds != viewport) { viewport = vp.Bounds; glViewport( viewport.X, viewport.Y, viewport.Width, viewport.Height ); } if (vp.MinDepth != depthRangeMin || vp.MaxDepth != depthRangeMax) { depthRangeMin = vp.MinDepth; depthRangeMax = vp.MaxDepth; glDepthRange((double) depthRangeMin, (double) depthRangeMax); } } public void SetScissorRect(Rectangle scissorRect) { // Flip rectangle when target is not bound if (!renderTargetBound) { scissorRect.Y = Backbuffer.Height - scissorRect.Y - scissorRect.Height; } if (scissorRect != scissorRectangle) { scissorRectangle = scissorRect; glScissor( scissorRectangle.X, scissorRectangle.Y, scissorRectangle.Width, scissorRectangle.Height ); } } public void SetBlendState(BlendState blendState) { bool newEnable = ( !( blendState.ColorSourceBlend == Blend.One && blendState.ColorDestinationBlend == Blend.Zero && blendState.AlphaSourceBlend == Blend.One && blendState.AlphaDestinationBlend == Blend.Zero ) ); if (newEnable != alphaBlendEnable) { alphaBlendEnable = newEnable; ToggleGLState(GLenum.GL_BLEND, alphaBlendEnable); } if (alphaBlendEnable) { if (blendState.BlendFactor != blendColor) { blendColor = blendState.BlendFactor; glBlendColor( blendColor.R / 255.0f, blendColor.G / 255.0f, blendColor.B / 255.0f, blendColor.A / 255.0f ); } if ( blendState.ColorSourceBlend != srcBlend || blendState.ColorDestinationBlend != dstBlend || blendState.AlphaSourceBlend != srcBlendAlpha || blendState.AlphaDestinationBlend != dstBlendAlpha ) { srcBlend = blendState.ColorSourceBlend; dstBlend = blendState.ColorDestinationBlend; srcBlendAlpha = blendState.AlphaSourceBlend; dstBlendAlpha = blendState.AlphaDestinationBlend; glBlendFuncSeparate( XNAToGL.BlendMode[(int) srcBlend], XNAToGL.BlendMode[(int) dstBlend], XNAToGL.BlendMode[(int) srcBlendAlpha], XNAToGL.BlendMode[(int) dstBlendAlpha] ); } if ( blendState.ColorBlendFunction != blendOp || blendState.AlphaBlendFunction != blendOpAlpha ) { blendOp = blendState.ColorBlendFunction; blendOpAlpha = blendState.AlphaBlendFunction; glBlendEquationSeparate( XNAToGL.BlendEquation[(int) blendOp], XNAToGL.BlendEquation[(int) blendOpAlpha] ); } } if (blendState.ColorWriteChannels != colorWriteEnable) { colorWriteEnable = blendState.ColorWriteChannels; glColorMask( (colorWriteEnable & ColorWriteChannels.Red) != 0, (colorWriteEnable & ColorWriteChannels.Green) != 0, (colorWriteEnable & ColorWriteChannels.Blue) != 0, (colorWriteEnable & ColorWriteChannels.Alpha) != 0 ); } /* FIXME: So how exactly do we factor in * COLORWRITEENABLE for buffer 0? Do we just assume that * the default is just buffer 0, and all other calls * update the other write masks afterward? Or do we * assume that COLORWRITEENABLE only touches 0, and the * other 3 buffers are left alone unless we don't have * EXT_draw_buffers2? * -flibit */ if (blendState.ColorWriteChannels1 != colorWriteEnable1) { colorWriteEnable1 = blendState.ColorWriteChannels1; glColorMaski( 1, (colorWriteEnable1 & ColorWriteChannels.Red) != 0, (colorWriteEnable1 & ColorWriteChannels.Green) != 0, (colorWriteEnable1 & ColorWriteChannels.Blue) != 0, (colorWriteEnable1 & ColorWriteChannels.Alpha) != 0 ); } if (blendState.ColorWriteChannels2 != colorWriteEnable2) { colorWriteEnable2 = blendState.ColorWriteChannels2; glColorMaski( 2, (colorWriteEnable2 & ColorWriteChannels.Red) != 0, (colorWriteEnable2 & ColorWriteChannels.Green) != 0, (colorWriteEnable2 & ColorWriteChannels.Blue) != 0, (colorWriteEnable2 & ColorWriteChannels.Alpha) != 0 ); } if (blendState.ColorWriteChannels3 != colorWriteEnable3) { colorWriteEnable3 = blendState.ColorWriteChannels3; glColorMaski( 3, (colorWriteEnable3 & ColorWriteChannels.Red) != 0, (colorWriteEnable3 & ColorWriteChannels.Green) != 0, (colorWriteEnable3 & ColorWriteChannels.Blue) != 0, (colorWriteEnable3 & ColorWriteChannels.Alpha) != 0 ); } if (blendState.MultiSampleMask != multisampleMask) { if (blendState.MultiSampleMask == -1) { glDisable(GLenum.GL_SAMPLE_MASK); } else { if (multisampleMask == -1) { glEnable(GLenum.GL_SAMPLE_MASK); } // FIXME: index...? -flibit glSampleMaski(0, (uint) blendState.MultiSampleMask); } multisampleMask = blendState.MultiSampleMask; } } public void SetDepthStencilState(DepthStencilState depthStencilState) { if (depthStencilState.DepthBufferEnable != zEnable) { zEnable = depthStencilState.DepthBufferEnable; ToggleGLState(GLenum.GL_DEPTH_TEST, zEnable); } if (zEnable) { if (depthStencilState.DepthBufferWriteEnable != zWriteEnable) { zWriteEnable = depthStencilState.DepthBufferWriteEnable; glDepthMask(zWriteEnable); } if (depthStencilState.DepthBufferFunction != depthFunc) { depthFunc = depthStencilState.DepthBufferFunction; glDepthFunc(XNAToGL.CompareFunc[(int) depthFunc]); } } if (depthStencilState.StencilEnable != stencilEnable) { stencilEnable = depthStencilState.StencilEnable; ToggleGLState(GLenum.GL_STENCIL_TEST, stencilEnable); } if (stencilEnable) { if (depthStencilState.StencilWriteMask != stencilWriteMask) { stencilWriteMask = depthStencilState.StencilWriteMask; glStencilMask(stencilWriteMask); } // TODO: Can we split StencilFunc/StencilOp up nicely? -flibit if ( depthStencilState.TwoSidedStencilMode != separateStencilEnable || depthStencilState.ReferenceStencil != stencilRef || depthStencilState.StencilMask != stencilMask || depthStencilState.StencilFunction != stencilFunc || depthStencilState.CounterClockwiseStencilFunction != ccwStencilFunc || depthStencilState.StencilFail != stencilFail || depthStencilState.StencilDepthBufferFail != stencilZFail || depthStencilState.StencilPass != stencilPass || depthStencilState.CounterClockwiseStencilFail != ccwStencilFail || depthStencilState.CounterClockwiseStencilDepthBufferFail != ccwStencilZFail || depthStencilState.CounterClockwiseStencilPass != ccwStencilPass ) { separateStencilEnable = depthStencilState.TwoSidedStencilMode; stencilRef = depthStencilState.ReferenceStencil; stencilMask = depthStencilState.StencilMask; stencilFunc = depthStencilState.StencilFunction; stencilFail = depthStencilState.StencilFail; stencilZFail = depthStencilState.StencilDepthBufferFail; stencilPass = depthStencilState.StencilPass; if (separateStencilEnable) { ccwStencilFunc = depthStencilState.CounterClockwiseStencilFunction; ccwStencilFail = depthStencilState.CounterClockwiseStencilFail; ccwStencilZFail = depthStencilState.CounterClockwiseStencilDepthBufferFail; ccwStencilPass = depthStencilState.CounterClockwiseStencilPass; glStencilFuncSeparate( GLenum.GL_FRONT, XNAToGL.CompareFunc[(int) stencilFunc], stencilRef, stencilMask ); glStencilFuncSeparate( GLenum.GL_BACK, XNAToGL.CompareFunc[(int) ccwStencilFunc], stencilRef, stencilMask ); glStencilOpSeparate( GLenum.GL_FRONT, XNAToGL.GLStencilOp[(int) stencilFail], XNAToGL.GLStencilOp[(int) stencilZFail], XNAToGL.GLStencilOp[(int) stencilPass] ); glStencilOpSeparate( GLenum.GL_BACK, XNAToGL.GLStencilOp[(int) ccwStencilFail], XNAToGL.GLStencilOp[(int) ccwStencilZFail], XNAToGL.GLStencilOp[(int) ccwStencilPass] ); } else { glStencilFunc( XNAToGL.CompareFunc[(int) stencilFunc], stencilRef, stencilMask ); glStencilOp( XNAToGL.GLStencilOp[(int) stencilFail], XNAToGL.GLStencilOp[(int) stencilZFail], XNAToGL.GLStencilOp[(int) stencilPass] ); } } } } public void ApplyRasterizerState(RasterizerState rasterizerState) { if (rasterizerState.ScissorTestEnable != scissorTestEnable) { scissorTestEnable = rasterizerState.ScissorTestEnable; ToggleGLState(GLenum.GL_SCISSOR_TEST, scissorTestEnable); } CullMode actualMode; if (renderTargetBound) { actualMode = rasterizerState.CullMode; } else { // When not rendering offscreen the faces change order. if (rasterizerState.CullMode == CullMode.None) { actualMode = rasterizerState.CullMode; } else { actualMode = ( rasterizerState.CullMode == CullMode.CullClockwiseFace ? CullMode.CullCounterClockwiseFace : CullMode.CullClockwiseFace ); } } if (actualMode != cullFrontFace) { if ((actualMode == CullMode.None) != (cullFrontFace == CullMode.None)) { ToggleGLState(GLenum.GL_CULL_FACE, actualMode != CullMode.None); } cullFrontFace = actualMode; if (cullFrontFace != CullMode.None) { glFrontFace(XNAToGL.FrontFace[(int) cullFrontFace]); } } if (rasterizerState.FillMode != fillMode) { fillMode = rasterizerState.FillMode; glPolygonMode( GLenum.GL_FRONT_AND_BACK, XNAToGL.GLFillMode[(int) fillMode] ); } // FIXME: Floating point equality comparisons used for speed -flibit float realDepthBias = rasterizerState.DepthBias * XNAToGL.DepthBiasScale[ renderTargetBound ? (int) currentDepthStencilFormat : (int) Backbuffer.DepthFormat ]; if ( realDepthBias != depthBias || rasterizerState.SlopeScaleDepthBias != slopeScaleDepthBias ) { if ( realDepthBias == 0.0f && rasterizerState.SlopeScaleDepthBias == 0.0f) { // We're changing to disabled bias, disable! glDisable(GLenum.GL_POLYGON_OFFSET_FILL); } else { if (depthBias == 0.0f && slopeScaleDepthBias == 0.0f) { // We're changing away from disabled bias, enable! glEnable(GLenum.GL_POLYGON_OFFSET_FILL); } glPolygonOffset( rasterizerState.SlopeScaleDepthBias, realDepthBias ); } depthBias = realDepthBias; slopeScaleDepthBias = rasterizerState.SlopeScaleDepthBias; } /* If you're reading this, you have a user with broken MSAA! * Here's the deal: On all modern drivers this should work, * but there was a period of time where, for some reason, * IHVs all took a nap and decided that they didn't have to * respect GL_MULTISAMPLE toggles. A couple sources: * * https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_fsaa/opengl_fsaa.html * * https://www.opengl.org/discussion_boards/showthread.php/172025-glDisable(GL_MULTISAMPLE)-has-no-effect * * So yeah. Have em update their driver. If they're on Intel, * tell them to install Linux. Yes, really. * -flibit */ if (rasterizerState.MultiSampleAntiAlias != multiSampleEnable) { multiSampleEnable = rasterizerState.MultiSampleAntiAlias; ToggleGLState(GLenum.GL_MULTISAMPLE, multiSampleEnable); } } public void VerifySampler(int index, Texture texture, SamplerState sampler) { if (texture == null) { if (Textures[index] != OpenGLTexture.NullTexture) { glBindTextureUnit(index, 0); Textures[index] = OpenGLTexture.NullTexture; } return; } OpenGLTexture tex = texture.texture as OpenGLTexture; // Bind the correct texture if (tex != Textures[index]) { if (tex.Target != Textures[index].Target) { // If we're changing targets, unbind the old texture first! glBindTextureUnit(index, 0); } glBindTextureUnit(index, tex.Handle); Textures[index] = tex; } // Apply the sampler states uint slot = Samplers[index]; if (sampler.AddressU != SamplersU[index]) { SamplersU[index] = sampler.AddressU; glSamplerParameteri( slot, GLenum.GL_TEXTURE_WRAP_S, XNAToGL.Wrap[(int) sampler.AddressU] ); } if (sampler.AddressV != SamplersV[index]) { SamplersV[index] = sampler.AddressV; glSamplerParameteri( slot, GLenum.GL_TEXTURE_WRAP_T, XNAToGL.Wrap[(int) sampler.AddressV] ); } if (sampler.AddressW != SamplersW[index]) { SamplersW[index] = sampler.AddressW; glSamplerParameteri( slot, GLenum.GL_TEXTURE_WRAP_R, XNAToGL.Wrap[(int) sampler.AddressW] ); } if ( sampler.Filter != SamplersFilter[index] || sampler.MaxAnisotropy != SamplersAnisotropy[index] || tex.HasMipmaps != SamplersMipped[index] ) { SamplersFilter[index] = sampler.Filter; SamplersAnisotropy[index] = sampler.MaxAnisotropy; SamplersMipped[index] = tex.HasMipmaps; glSamplerParameteri( slot, GLenum.GL_TEXTURE_MAG_FILTER, XNAToGL.MagFilter[(int) sampler.Filter] ); glSamplerParameteri( slot, GLenum.GL_TEXTURE_MIN_FILTER, tex.HasMipmaps ? XNAToGL.MinMipFilter[(int) sampler.Filter] : XNAToGL.MinFilter[(int) sampler.Filter] ); glSamplerParameterf( slot, GLenum.GL_TEXTURE_MAX_ANISOTROPY_EXT, (sampler.Filter == TextureFilter.Anisotropic) ? Math.Max(SamplersAnisotropy[index], 1.0f) : 1.0f ); } if (sampler.MaxMipLevel != SamplersMaxLevel[index]) { SamplersMaxLevel[index] = sampler.MaxMipLevel; glSamplerParameteri( slot, GLenum.GL_TEXTURE_BASE_LEVEL, sampler.MaxMipLevel ); } if (sampler.MipMapLevelOfDetailBias != SamplersLODBias[index]) { SamplersLODBias[index] = sampler.MipMapLevelOfDetailBias; glSamplerParameterf( slot, GLenum.GL_TEXTURE_LOD_BIAS, sampler.MipMapLevelOfDetailBias ); } } #endregion #region Effect Methods public IGLEffect CreateEffect(byte[] effectCode) { IntPtr effect = IntPtr.Zero; IntPtr glEffect = IntPtr.Zero; #if !DISABLE_THREADING ForceToMainThread(() => { #endif effect = MojoShader.MOJOSHADER_parseEffect( shaderProfile, effectCode, (uint) effectCode.Length, null, 0, null, 0, null, null, IntPtr.Zero ); #if DEBUG unsafe { MojoShader.MOJOSHADER_effect *effectPtr = (MojoShader.MOJOSHADER_effect*) effect; MojoShader.MOJOSHADER_error* err = (MojoShader.MOJOSHADER_error*) effectPtr->errors; for (int i = 0; i < effectPtr->error_count; i += 1) { // From the SDL2# LPToUtf8StringMarshaler byte* endPtr = (byte*) err[i].error; while (*endPtr != 0) { endPtr++; } byte[] bytes = new byte[endPtr - (byte*) err[i].error]; Marshal.Copy(err[i].error, bytes, 0, bytes.Length); FNALoggerEXT.LogError( "MOJOSHADER_parseEffect Error: " + System.Text.Encoding.UTF8.GetString(bytes) ); } } #endif glEffect = MojoShader.MOJOSHADER_glCompileEffect(effect); if (glEffect == IntPtr.Zero) { throw new InvalidOperationException( MojoShader.MOJOSHADER_glGetError() ); } #if !DISABLE_THREADING }); #endif return new OpenGLEffect(effect, glEffect); } private void DeleteEffect(IGLEffect effect) { IntPtr glEffectData = (effect as OpenGLEffect).GLEffectData; if (glEffectData == currentEffect) { MojoShader.MOJOSHADER_glEffectEndPass(currentEffect); MojoShader.MOJOSHADER_glEffectEnd(currentEffect); currentEffect = IntPtr.Zero; currentTechnique = IntPtr.Zero; currentPass = 0; } MojoShader.MOJOSHADER_glDeleteEffect(glEffectData); MojoShader.MOJOSHADER_freeEffect(effect.EffectData); } public IGLEffect CloneEffect(IGLEffect cloneSource) { IntPtr effect = IntPtr.Zero; IntPtr glEffect = IntPtr.Zero; #if !DISABLE_THREADING ForceToMainThread(() => { #endif effect = MojoShader.MOJOSHADER_cloneEffect(cloneSource.EffectData); glEffect = MojoShader.MOJOSHADER_glCompileEffect(effect); if (glEffect == IntPtr.Zero) { throw new InvalidOperationException( MojoShader.MOJOSHADER_glGetError() ); } #if !DISABLE_THREADING }); #endif return new OpenGLEffect(effect, glEffect); } public void ApplyEffect( IGLEffect effect, IntPtr technique, uint pass, IntPtr stateChanges ) { effectApplied = true; IntPtr glEffectData = (effect as OpenGLEffect).GLEffectData; if (glEffectData == currentEffect) { if (technique == currentTechnique && pass == currentPass) { MojoShader.MOJOSHADER_glEffectCommitChanges(currentEffect); return; } MojoShader.MOJOSHADER_glEffectEndPass(currentEffect); MojoShader.MOJOSHADER_glEffectBeginPass(currentEffect, pass); currentTechnique = technique; currentPass = pass; return; } else if (currentEffect != IntPtr.Zero) { MojoShader.MOJOSHADER_glEffectEndPass(currentEffect); MojoShader.MOJOSHADER_glEffectEnd(currentEffect); } uint whatever; MojoShader.MOJOSHADER_glEffectBegin( glEffectData, out whatever, 0, stateChanges ); MojoShader.MOJOSHADER_glEffectBeginPass( glEffectData, pass ); currentEffect = glEffectData; currentTechnique = technique; currentPass = pass; } public void BeginPassRestore(IGLEffect effect, IntPtr stateChanges) { IntPtr glEffectData = (effect as OpenGLEffect).GLEffectData; uint whatever; MojoShader.MOJOSHADER_glEffectBegin( glEffectData, out whatever, 1, stateChanges ); MojoShader.MOJOSHADER_glEffectBeginPass( glEffectData, 0 ); effectApplied = true; } public void EndPassRestore(IGLEffect effect) { IntPtr glEffectData = (effect as OpenGLEffect).GLEffectData; MojoShader.MOJOSHADER_glEffectEndPass(glEffectData); MojoShader.MOJOSHADER_glEffectEnd(glEffectData); effectApplied = true; } #endregion #region glVertexAttribPointer/glVertexAttribDivisor Methods public void ApplyVertexAttributes( VertexBufferBinding[] bindings, int numBindings, bool bindingsUpdated, int baseVertex ) { if ( bindingsUpdated || currentEffect != ldEffect || currentTechnique != ldTechnique || currentPass != ldPass || effectApplied ) { /* There's this weird case where you can have overlapping * vertex usage/index combinations. It seems like the first * attrib gets priority, so whenever a duplicate attribute * exists, give it the next available index. If that fails, we * have to crash :/ * -flibit */ Array.Clear(attrUse, 0, attrUse.Length); for (int i = 0; i < numBindings; i += 1) { BindVertexBuffer(bindings[i].VertexBuffer.buffer); VertexDeclaration vertexDeclaration = bindings[i].VertexBuffer.VertexDeclaration; IntPtr basePtr = (IntPtr) ( vertexDeclaration.VertexStride * (bindings[i].VertexOffset) ); foreach (VertexElement element in vertexDeclaration.elements) { int usage = (int) element.VertexElementUsage; int index = element.UsageIndex; if (attrUse[usage, index]) { index = -1; for (int j = 0; j < 16; j += 1) { if (!attrUse[usage, j]) { index = j; break; } } if (index < 0) { throw new InvalidOperationException("Vertex usage collision!"); } } attrUse[usage, index] = true; int attribLoc = MojoShader.MOJOSHADER_glGetVertexAttribLocation( XNAToGL.VertexAttribUsage[usage], index ); if (attribLoc == -1) { // Stream not in use! continue; } attributeEnabled[attribLoc] = true; VertexAttribute attr = attributes[attribLoc]; ModernGLBuffer buffer = bindings[i].VertexBuffer.buffer as ModernGLBuffer; IntPtr ptr = basePtr + element.Offset; VertexElementFormat format = element.VertexElementFormat; bool normalized = XNAToGL.VertexAttribNormalized(element); if ( attr.CurrentBuffer != buffer || attr.CurrentPointer != ptr || attr.CurrentFormat != element.VertexElementFormat || attr.CurrentNormalized != normalized || attr.CurrentStride != vertexDeclaration.VertexStride ) { glVertexAttribPointer( attribLoc, XNAToGL.VertexAttribSize[(int) format], XNAToGL.VertexAttribType[(int) format], normalized, vertexDeclaration.VertexStride, ptr ); attr.CurrentBuffer = buffer; attr.CurrentPointer = ptr; attr.CurrentFormat = format; attr.CurrentNormalized = normalized; attr.CurrentStride = vertexDeclaration.VertexStride; } if (SupportsHardwareInstancing) { attributeDivisor[attribLoc] = bindings[i].InstanceFrequency; } } } FlushGLVertexAttributes(); ldEffect = currentEffect; ldTechnique = currentTechnique; ldPass = currentPass; effectApplied = false; ldVertexDeclaration = null; ldPointer = IntPtr.Zero; } MojoShader.MOJOSHADER_glProgramReady(); MojoShader.MOJOSHADER_glProgramViewportInfo( viewport.Width, viewport.Height, Backbuffer.Width, Backbuffer.Height, renderTargetBound ? 1 : 0 // lol C# ); } public void ApplyVertexAttributes( VertexDeclaration vertexDeclaration, IntPtr ptr, int vertexOffset ) { BindVertexBuffer(ModernGLBuffer.NullBuffer); IntPtr basePtr = ptr + (vertexDeclaration.VertexStride * vertexOffset); if ( vertexDeclaration != ldVertexDeclaration || basePtr != ldPointer || currentEffect != ldEffect || currentTechnique != ldTechnique || currentPass != ldPass || effectApplied ) { /* There's this weird case where you can have overlapping * vertex usage/index combinations. It seems like the first * attrib gets priority, so whenever a duplicate attribute * exists, give it the next available index. If that fails, we * have to crash :/ * -flibit */ Array.Clear(attrUse, 0, attrUse.Length); foreach (VertexElement element in vertexDeclaration.elements) { int usage = (int) element.VertexElementUsage; int index = element.UsageIndex; if (attrUse[usage, index]) { index = -1; for (int j = 0; j < 16; j += 1) { if (!attrUse[usage, j]) { index = j; break; } } if (index < 0) { throw new InvalidOperationException("Vertex usage collision!"); } } attrUse[usage, index] = true; int attribLoc = MojoShader.MOJOSHADER_glGetVertexAttribLocation( XNAToGL.VertexAttribUsage[usage], index ); if (attribLoc == -1) { // Stream not used! continue; } attributeEnabled[attribLoc] = true; VertexAttribute attr = attributes[attribLoc]; IntPtr finalPtr = basePtr + element.Offset; bool normalized = XNAToGL.VertexAttribNormalized(element); if ( attr.CurrentBuffer != ModernGLBuffer.NullBuffer || attr.CurrentPointer != finalPtr || attr.CurrentFormat != element.VertexElementFormat || attr.CurrentNormalized != normalized || attr.CurrentStride != vertexDeclaration.VertexStride ) { glVertexAttribPointer( attribLoc, XNAToGL.VertexAttribSize[(int) element.VertexElementFormat], XNAToGL.VertexAttribType[(int) element.VertexElementFormat], normalized, vertexDeclaration.VertexStride, finalPtr ); attr.CurrentBuffer = ModernGLBuffer.NullBuffer; attr.CurrentPointer = finalPtr; attr.CurrentFormat = element.VertexElementFormat; attr.CurrentNormalized = normalized; attr.CurrentStride = vertexDeclaration.VertexStride; } attributeDivisor[attribLoc] = 0; } FlushGLVertexAttributes(); ldVertexDeclaration = vertexDeclaration; ldPointer = ptr; ldEffect = currentEffect; ldTechnique = currentTechnique; ldPass = currentPass; effectApplied = false; } MojoShader.MOJOSHADER_glProgramReady(); MojoShader.MOJOSHADER_glProgramViewportInfo( viewport.Width, viewport.Height, Backbuffer.Width, Backbuffer.Height, renderTargetBound ? 1 : 0 // lol C# ); } private void FlushGLVertexAttributes() { for (int i = 0; i < attributes.Length; i += 1) { if (attributeEnabled[i]) { attributeEnabled[i] = false; if (!previousAttributeEnabled[i]) { glEnableVertexAttribArray(i); previousAttributeEnabled[i] = true; } } else if (previousAttributeEnabled[i]) { glDisableVertexAttribArray(i); previousAttributeEnabled[i] = false; } int divisor = attributeDivisor[i]; if (divisor != previousAttributeDivisor[i]) { glVertexAttribDivisor(i, divisor); previousAttributeDivisor[i] = divisor; } } } #endregion #region glGenBuffers Methods public IGLBuffer GenVertexBuffer( bool dynamic, BufferUsage usage, int vertexCount, int vertexStride ) { ModernGLBuffer result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif uint handle; glCreateBuffers(1, out handle); result = new ModernGLBuffer( handle, (IntPtr) (vertexStride * vertexCount), usage ); if (dynamic) { glNamedBufferStorage( handle, result.BufferSize, IntPtr.Zero, result.Flags | GLenum.GL_DYNAMIC_STORAGE_BIT ); result.Pin = glMapNamedBufferRange( handle, IntPtr.Zero, result.BufferSize, result.Flags ); } else { glNamedBufferData( handle, result.BufferSize, IntPtr.Zero, GLenum.GL_STATIC_DRAW ); } #if !DISABLE_THREADING }); #endif return result; } public IGLBuffer GenIndexBuffer( bool dynamic, BufferUsage usage, int indexCount, IndexElementSize indexElementSize ) { ModernGLBuffer result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif uint handle; glCreateBuffers(1, out handle); result = new ModernGLBuffer( handle, (IntPtr) (indexCount * XNAToGL.IndexSize[(int) indexElementSize]), usage ); if (dynamic) { glNamedBufferStorage( handle, result.BufferSize, IntPtr.Zero, result.Flags | GLenum.GL_DYNAMIC_STORAGE_BIT ); result.Pin = glMapNamedBufferRange( handle, IntPtr.Zero, result.BufferSize, result.Flags ); } else { glNamedBufferData( handle, result.BufferSize, IntPtr.Zero, GLenum.GL_STATIC_DRAW ); } #if !DISABLE_THREADING }); #endif return result; } #endregion #region glBindBuffer Methods private void BindVertexBuffer(IGLBuffer buffer) { uint handle = (buffer as ModernGLBuffer).Handle; if (handle != currentVertexBuffer) { glBindBuffer(GLenum.GL_ARRAY_BUFFER, handle); currentVertexBuffer = handle; } } private void BindIndexBuffer(IGLBuffer buffer) { uint handle = (buffer as ModernGLBuffer).Handle; if (handle != currentIndexBuffer) { glBindBuffer(GLenum.GL_ELEMENT_ARRAY_BUFFER, handle); currentIndexBuffer = handle; } } #endregion #region glSetBufferData Methods public void SetVertexBufferData( IGLBuffer buffer, int offsetInBytes, IntPtr data, int dataLength, SetDataOptions options ) { ModernGLBuffer buf = buffer as ModernGLBuffer; if (options == SetDataOptions.None) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif /* For static buffers this is the only path, * and it should be "fast" enough over there. * If you are hitting this with a dynamic buffer * you are using dynamic buffers incorrectly. * -flibit */ glNamedBufferSubData( buf.Handle, (IntPtr) offsetInBytes, (IntPtr) dataLength, data ); #if !DISABLE_THREADING }); #endif return; } if (options == SetDataOptions.Discard) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glUnmapNamedBuffer(buf.Handle); glInvalidateBufferData(buf.Handle); buf.Pin = glMapNamedBufferRange( buf.Handle, IntPtr.Zero, buf.BufferSize, buf.Flags ); #if !DISABLE_THREADING }); #endif } SDL.SDL_memcpy( buf.Pin + offsetInBytes, data, (IntPtr) dataLength ); } public void SetIndexBufferData( IGLBuffer buffer, int offsetInBytes, IntPtr data, int dataLength, SetDataOptions options ) { ModernGLBuffer buf = buffer as ModernGLBuffer; if (options == SetDataOptions.None) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif /* For static buffers this is the only path, * and it should be "fast" enough over there. * If you are hitting this with a dynamic buffer * you are using dynamic buffers incorrectly. * -flibit */ glNamedBufferSubData( buf.Handle, (IntPtr) offsetInBytes, (IntPtr) dataLength, data ); #if !DISABLE_THREADING }); #endif return; } if (options == SetDataOptions.Discard) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glUnmapNamedBuffer(buf.Handle); glInvalidateBufferData(buf.Handle); buf.Pin = glMapNamedBufferRange( buf.Handle, IntPtr.Zero, buf.BufferSize, buf.Flags ); #if !DISABLE_THREADING }); #endif } SDL.SDL_memcpy( buf.Pin + offsetInBytes, data, (IntPtr) dataLength ); } #endregion #region glGetBufferData Methods public void GetVertexBufferData( IGLBuffer buffer, int offsetInBytes, IntPtr data, int startIndex, int elementCount, int elementSizeInBytes, int vertexStride ) { IntPtr cpy; bool useStagingBuffer = elementSizeInBytes < vertexStride; if (useStagingBuffer) { cpy = Marshal.AllocHGlobal(elementCount * vertexStride); } else { cpy = data + (startIndex * elementSizeInBytes); } ModernGLBuffer buf = buffer as ModernGLBuffer; if (buf.Pin != IntPtr.Zero) { /* Buffers can't get written to by anyone other than the * application, so we can just memcpy here... right? */ SDL.SDL_memcpy( cpy, buf.Pin + offsetInBytes, (IntPtr) (elementCount * elementSizeInBytes) ); } else { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glGetNamedBufferSubData( buf.Handle, (IntPtr) offsetInBytes, (IntPtr) (elementCount * vertexStride), cpy ); #if !DISABLE_THREADING }); #endif } if (useStagingBuffer) { IntPtr src = cpy; IntPtr dst = data + (startIndex * elementSizeInBytes); for (int i = 0; i < elementCount; i += 1) { SDL.SDL_memcpy(dst, src, (IntPtr) elementSizeInBytes); dst += elementSizeInBytes; src += vertexStride; } Marshal.FreeHGlobal(cpy); } } public void GetIndexBufferData( IGLBuffer buffer, int offsetInBytes, IntPtr data, int startIndex, int elementCount, int elementSizeInBytes ) { ModernGLBuffer buf = buffer as ModernGLBuffer; if (buf.Pin != IntPtr.Zero) { /* Buffers can't get written to by anyone other than the * application, so we can just memcpy here... right? */ SDL.SDL_memcpy( data + (startIndex * elementSizeInBytes), (buffer as ModernGLBuffer).Pin + offsetInBytes, (IntPtr) (elementCount * elementSizeInBytes) ); } else { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glGetNamedBufferSubData( buf.Handle, (IntPtr) offsetInBytes, (IntPtr) (elementCount * elementSizeInBytes), data + (startIndex * elementSizeInBytes) ); #if !DISABLE_THREADING }); #endif } } #endregion #region glDeleteBuffers Methods private void DeleteVertexBuffer(IGLBuffer buffer) { ModernGLBuffer buf = buffer as ModernGLBuffer; uint handle = buf.Handle; if (handle == currentVertexBuffer) { glBindBuffer(GLenum.GL_ARRAY_BUFFER, 0); currentVertexBuffer = 0; } for (int i = 0; i < attributes.Length; i += 1) { if (buf == attributes[i].CurrentBuffer) { // Force the next vertex attrib update! attributes[i].CurrentBuffer = ModernGLBuffer.ForceUpdate; } } glDeleteBuffers(1, ref handle); } private void DeleteIndexBuffer(IGLBuffer buffer) { uint handle = (buffer as ModernGLBuffer).Handle; if (handle == currentIndexBuffer) { glBindBuffer(GLenum.GL_ELEMENT_ARRAY_BUFFER, 0); currentIndexBuffer = 0; } glDeleteBuffers(1, ref handle); } #endregion #region glCreateTexture Methods private OpenGLTexture CreateTexture( GLenum target, int levelCount ) { uint handle; glCreateTextures(target, 1, out handle); OpenGLTexture result = new OpenGLTexture( handle, target, levelCount ); return result; } public IGLTexture CreateTexture2D( SurfaceFormat format, int width, int height, int levelCount, bool isRenderTarget ) { OpenGLTexture result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif result = CreateTexture( GLenum.GL_TEXTURE_2D, levelCount ); glTextureStorage2D( result.Handle, levelCount, XNAToGL.TextureInternalFormat[(int) format], width, height ); if (format == SurfaceFormat.Alpha8) { // Alpha8 needs a swizzle, since GL_ALPHA is unsupported glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_R, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_G, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_B, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_A, GLenum.GL_RED ); } #if !DISABLE_THREADING }); #endif return result; } public IGLTexture CreateTexture3D( SurfaceFormat format, int width, int height, int depth, int levelCount ) { OpenGLTexture result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif result = CreateTexture( GLenum.GL_TEXTURE_3D, levelCount ); glTextureStorage3D( result.Handle, levelCount, XNAToGL.TextureInternalFormat[(int) format], width, height, depth ); if (format == SurfaceFormat.Alpha8) { // Alpha8 needs a swizzle, since GL_ALPHA is unsupported glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_R, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_G, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_B, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_A, GLenum.GL_RED ); } #if !DISABLE_THREADING }); #endif return result; } public IGLTexture CreateTextureCube( SurfaceFormat format, int size, int levelCount, bool isRenderTarget ) { OpenGLTexture result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif result = CreateTexture( GLenum.GL_TEXTURE_CUBE_MAP, levelCount ); glTextureStorage2D( result.Handle, levelCount, XNAToGL.TextureInternalFormat[(int) format], size, size ); if (format == SurfaceFormat.Alpha8) { // Alpha8 needs a swizzle, since GL_ALPHA is unsupported glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_R, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_G, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_B, GLenum.GL_ZERO ); glTextureParameteri( result.Handle, GLenum.GL_TEXTURE_SWIZZLE_A, GLenum.GL_RED ); } #if !DISABLE_THREADING }); #endif return result; } #endregion #region glTexSubImage Methods public void SetTextureData2D( IGLTexture texture, SurfaceFormat format, int x, int y, int w, int h, int level, IntPtr data, int dataLength ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif GLenum glFormat = XNAToGL.TextureFormat[(int) format]; if (glFormat == GLenum.GL_COMPRESSED_TEXTURE_FORMATS) { /* Note that we're using glInternalFormat, not glFormat. * In this case, they should actually be the same thing, * but we use glFormat somewhat differently for * compressed textures. * -flibit */ glCompressedTextureSubImage2D( (texture as OpenGLTexture).Handle, level, x, y, w, h, XNAToGL.TextureInternalFormat[(int) format], dataLength, data ); } else { // Set pixel alignment to match texel size in bytes int packSize = Texture.GetPixelStoreAlignment(format); if (packSize != 4) { glPixelStorei( GLenum.GL_UNPACK_ALIGNMENT, packSize ); } glTextureSubImage2D( (texture as OpenGLTexture).Handle, level, x, y, w, h, glFormat, XNAToGL.TextureDataType[(int) format], data ); // Keep this state sane -flibit if (packSize != 4) { glPixelStorei( GLenum.GL_UNPACK_ALIGNMENT, 4 ); } } #if !DISABLE_THREADING }); #endif } public void SetTextureData3D( IGLTexture texture, SurfaceFormat format, int level, int left, int top, int right, int bottom, int front, int back, IntPtr data, int dataLength ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glTextureSubImage3D( (texture as OpenGLTexture).Handle, level, left, top, front, right - left, bottom - top, back - front, XNAToGL.TextureFormat[(int) format], XNAToGL.TextureDataType[(int) format], data ); #if !DISABLE_THREADING }); #endif } public void SetTextureDataCube( IGLTexture texture, SurfaceFormat format, int xOffset, int yOffset, int width, int height, CubeMapFace cubeMapFace, int level, IntPtr data, int dataLength ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif GLenum glFormat = XNAToGL.TextureFormat[(int) format]; if (glFormat == GLenum.GL_COMPRESSED_TEXTURE_FORMATS) { /* Note that we're using glInternalFormat, not glFormat. * In this case, they should actually be the same thing, * but we use glFormat somewhat differently for * compressed textures. * -flibit */ glCompressedTextureSubImage3D( (texture as OpenGLTexture).Handle, level, xOffset, yOffset, (int) cubeMapFace, width, height, 1, XNAToGL.TextureInternalFormat[(int) format], dataLength, data ); } else { glTextureSubImage3D( (texture as OpenGLTexture).Handle, level, xOffset, yOffset, (int) cubeMapFace, width, height, 1, glFormat, XNAToGL.TextureDataType[(int) format], data ); } #if !DISABLE_THREADING }); #endif } public void SetTextureDataYUV(Texture2D[] textures, IntPtr ptr) { glPixelStorei(GLenum.GL_UNPACK_ALIGNMENT, 1); for (int i = 0; i < 3; i += 1) { Texture2D tex = textures[i]; glTextureSubImage2D( (tex.texture as OpenGLTexture).Handle, 0, 0, 0, tex.Width, tex.Height, GLenum.GL_RED, GLenum.GL_UNSIGNED_BYTE, ptr ); ptr += tex.Width * tex.Height; } glPixelStorei(GLenum.GL_UNPACK_ALIGNMENT, 4); } #endregion #region glGetTexImage Methods public void GetTextureData2D( IGLTexture texture, SurfaceFormat format, int width, int height, int level, int subX, int subY, int subW, int subH, IntPtr data, int startIndex, int elementCount, int elementSizeInBytes ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif if (level == 0 && ReadTargetIfApplicable( texture, width, height, level, data, subX, subY, subW, subH )) { return; } glGetTextureSubImage( (texture as OpenGLTexture).Handle, level, subX, subY, 0, subW, subH, 1, XNAToGL.TextureFormat[(int) format], XNAToGL.TextureDataType[(int) format], elementCount * elementSizeInBytes, data ); #if !DISABLE_THREADING }); #endif } public void GetTextureData3D( IGLTexture texture, SurfaceFormat format, int left, int top, int front, int right, int bottom, int back, int level, IntPtr data, int startIndex, int elementCount, int elementSizeInBytes ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glGetTextureSubImage( (texture as OpenGLTexture).Handle, level, left, top, front, right - left, bottom - top, back - front, XNAToGL.TextureFormat[(int) format], XNAToGL.TextureDataType[(int) format], elementCount * elementSizeInBytes, data ); #if !DISABLE_THREADING }); #endif } public void GetTextureDataCube( IGLTexture texture, SurfaceFormat format, int size, CubeMapFace cubeMapFace, int level, int subX, int subY, int subW, int subH, IntPtr data, int startIndex, int elementCount, int elementSizeInBytes ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif glGetTextureSubImage( (texture as OpenGLTexture).Handle, level, subX, subY, (int) cubeMapFace, subW, subH, 1, XNAToGL.TextureFormat[(int) format], XNAToGL.TextureDataType[(int) format], elementCount * elementSizeInBytes, data ); #if !DISABLE_THREADING }); #endif } #endregion #region glDeleteTexture Method private void DeleteTexture(IGLTexture texture) { uint handle = (texture as OpenGLTexture).Handle; for (int i = 0; i < currentAttachments.Length; i += 1) { if (handle == currentAttachments[i]) { // Force an attachment update, this no longer exists! currentAttachments[i] = uint.MaxValue; } } glDeleteTextures(1, ref handle); } #endregion #region glReadPixels Methods public void ReadBackbuffer( IntPtr data, int dataLen, int startIndex, int elementCount, int elementSizeInBytes, int subX, int subY, int subW, int subH ) { /* FIXME: Right now we're expecting one of the following: * - byte[] * - int[] * - uint[] * - Color[] * Anything else will freak out because we're using * color backbuffers. Maybe check this out when adding * support for more backbuffer types! * -flibit */ if (startIndex > 0 || elementCount != (dataLen / elementSizeInBytes)) { throw new NotImplementedException( "ReadBackbuffer startIndex/elementCount" ); } uint prevReadBuffer = currentReadFramebuffer; if (Backbuffer.MultiSampleCount > 0) { // We have to resolve the renderbuffer to a texture first. OpenGLBackbuffer glBack = Backbuffer as OpenGLBackbuffer; if (glBack.Texture == 0) { glCreateTextures(GLenum.GL_TEXTURE_2D, 1, out glBack.Texture); glTextureStorage2D( glBack.Texture, 1, GLenum.GL_RGBA, glBack.Width, glBack.Height ); } glNamedFramebufferTexture( glBack.Handle, GLenum.GL_COLOR_ATTACHMENT0, glBack.Texture, 0 ); glBlitNamedFramebuffer( glBack.Handle, resolveFramebufferDraw, 0, 0, glBack.Width, glBack.Height, 0, 0, glBack.Width, glBack.Height, GLenum.GL_COLOR_BUFFER_BIT, GLenum.GL_LINEAR ); /* Don't invalidate the backbuffer here! */ BindReadFramebuffer(resolveFramebufferDraw); } else { BindReadFramebuffer( (Backbuffer is OpenGLBackbuffer) ? (Backbuffer as OpenGLBackbuffer).Handle : 0 ); } glReadPixels( subX, subY, subW, subH, GLenum.GL_RGBA, GLenum.GL_UNSIGNED_BYTE, data ); BindReadFramebuffer(prevReadBuffer); // Now we get to do a software-based flip! Yes, really! -flibit int pitch = subW * 4; IntPtr temp = Marshal.AllocHGlobal(pitch); for (int row = 0; row < subH / 2; row += 1) { // Top to temp, bottom to top, temp to bottom SDL.SDL_memcpy(temp, data + (row * pitch), (IntPtr) pitch); SDL.SDL_memcpy(data + (row * pitch), data + ((subH - row - 1) * pitch), (IntPtr) pitch); SDL.SDL_memcpy(data + ((subH - row - 1) * pitch), temp, (IntPtr) pitch); } Marshal.FreeHGlobal(temp); } /// /// Attempts to read the texture data directly from the FBO using glReadPixels /// /// Texture data type /// The texture to read from /// The texture width /// The texture height /// The texture level /// The texture data array /// The portion of the image to read from /// True if we successfully read the texture data private bool ReadTargetIfApplicable( IGLTexture texture, int width, int height, int level, IntPtr data, int subX, int subY, int subW, int subH ) { bool texUnbound = ( currentDrawBuffers != 1 || currentAttachments[0] != (texture as OpenGLTexture).Handle ); if (texUnbound) { return false; } uint prevReadBuffer = currentReadFramebuffer; BindReadFramebuffer(targetFramebuffer); /* glReadPixels should be faster than reading * back from the render target if we are already bound. */ glReadPixels( subX, subY, subW, subH, GLenum.GL_RGBA, // FIXME: Assumption! GLenum.GL_UNSIGNED_BYTE, data ); BindReadFramebuffer(prevReadBuffer); return true; } #endregion #region RenderTarget->Texture Method public void ResolveTarget(RenderTargetBinding target) { if ((target.RenderTarget as IRenderTarget).MultiSampleCount > 0) { // Set up the texture framebuffer int width, height; if (target.RenderTarget is RenderTarget2D) { Texture2D target2D = (target.RenderTarget as Texture2D); width = target2D.Width; height = target2D.Height; glNamedFramebufferTexture( resolveFramebufferDraw, GLenum.GL_COLOR_ATTACHMENT0, (target.RenderTarget.texture as OpenGLTexture).Handle, 0 ); } else { TextureCube targetCube = (target.RenderTarget as TextureCube); width = targetCube.Size; height = targetCube.Size; glNamedFramebufferTextureLayer( resolveFramebufferDraw, GLenum.GL_COLOR_ATTACHMENT0, (target.RenderTarget.texture as OpenGLTexture).Handle, 0, (int) target.CubeMapFace ); } // Set up the renderbuffer framebuffer glNamedFramebufferRenderbuffer( resolveFramebufferRead, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, ((target.RenderTarget as IRenderTarget).ColorBuffer as OpenGLRenderbuffer).Handle ); // Blit! if (scissorTestEnable) { glDisable(GLenum.GL_SCISSOR_TEST); } glBlitNamedFramebuffer( resolveFramebufferRead, resolveFramebufferDraw, 0, 0, width, height, 0, 0, width, height, GLenum.GL_COLOR_BUFFER_BIT, GLenum.GL_LINEAR ); /* Invalidate the MSAA buffer */ glInvalidateNamedFramebufferData( resolveFramebufferRead, attachments.Length + 2, drawBuffersArray ); if (scissorTestEnable) { glEnable(GLenum.GL_SCISSOR_TEST); } } // If the target has mipmaps, regenerate them now if (target.RenderTarget.LevelCount > 1) { glGenerateTextureMipmap((target.RenderTarget.texture as OpenGLTexture).Handle); } } #endregion #region Framebuffer Methods private void BindFramebuffer(uint handle) { if ( currentReadFramebuffer != handle && currentDrawFramebuffer != handle ) { glBindFramebuffer( GLenum.GL_FRAMEBUFFER, handle ); currentReadFramebuffer = handle; currentDrawFramebuffer = handle; } else if (currentReadFramebuffer != handle) { BindReadFramebuffer(handle); } else if (currentDrawFramebuffer != handle) { BindDrawFramebuffer(handle); } } private void BindReadFramebuffer(uint handle) { if (handle == currentReadFramebuffer) { return; } glBindFramebuffer( GLenum.GL_READ_FRAMEBUFFER, handle ); currentReadFramebuffer = handle; } private void BindDrawFramebuffer(uint handle) { if (handle == currentDrawFramebuffer) { return; } glBindFramebuffer( GLenum.GL_DRAW_FRAMEBUFFER, handle ); currentDrawFramebuffer = handle; } #endregion #region Renderbuffer Methods public IGLRenderbuffer GenRenderbuffer( int width, int height, SurfaceFormat format, int multiSampleCount, IGLTexture texture ) { uint handle = 0; #if !DISABLE_THREADING ForceToMainThread(() => { #endif glCreateRenderbuffers(1, out handle); if (multiSampleCount > 0) { glNamedRenderbufferStorageMultisample( handle, multiSampleCount, XNAToGL.TextureInternalFormat[(int) format], width, height ); } else { glNamedRenderbufferStorage( handle, XNAToGL.TextureInternalFormat[(int) format], width, height ); } #if !DISABLE_THREADING }); #endif return new OpenGLRenderbuffer(handle); } public IGLRenderbuffer GenRenderbuffer( int width, int height, DepthFormat format, int multiSampleCount ) { uint handle = 0; #if !DISABLE_THREADING ForceToMainThread(() => { #endif glCreateRenderbuffers(1, out handle); if (multiSampleCount > 0) { glNamedRenderbufferStorageMultisample( handle, multiSampleCount, XNAToGL.DepthStorage[(int) format], width, height ); } else { glNamedRenderbufferStorage( handle, XNAToGL.DepthStorage[(int) format], width, height ); } #if !DISABLE_THREADING }); #endif return new OpenGLRenderbuffer(handle); } private void DeleteRenderbuffer(IGLRenderbuffer renderbuffer) { uint handle = (renderbuffer as OpenGLRenderbuffer).Handle; // Check color attachments for (int i = 0; i < currentAttachments.Length; i += 1) { if (handle == currentAttachments[i]) { // Force an attachment update, this no longer exists! currentAttachments[i] = uint.MaxValue; } } // Check depth/stencil attachment if (handle == currentRenderbuffer) { // Force a renderbuffer update, this no longer exists! currentRenderbuffer = uint.MaxValue; } // Finally. glDeleteRenderbuffers(1, ref handle); } #endregion #region glEnable/glDisable Method private void ToggleGLState(GLenum feature, bool enable) { if (enable) { glEnable(feature); } else { glDisable(feature); } } #endregion #region glClear Method public void Clear(ClearOptions options, Vector4 color, float depth, int stencil) { // glClear depends on the scissor rectangle! if (scissorTestEnable) { glDisable(GLenum.GL_SCISSOR_TEST); } bool clearTarget = (options & ClearOptions.Target) == ClearOptions.Target; bool clearDepth = (options & ClearOptions.DepthBuffer) == ClearOptions.DepthBuffer; bool clearStencil = (options & ClearOptions.Stencil) == ClearOptions.Stencil; // Get the clear mask, set the clear properties if needed GLenum clearMask = GLenum.GL_ZERO; if (clearTarget) { clearMask |= GLenum.GL_COLOR_BUFFER_BIT; if (!color.Equals(currentClearColor)) { glClearColor( color.X, color.Y, color.Z, color.W ); currentClearColor = color; } // glClear depends on the color write mask! if (colorWriteEnable != ColorWriteChannels.All) { // FIXME: ColorWriteChannels1/2/3? -flibit glColorMask(true, true, true, true); } } if (clearDepth) { clearMask |= GLenum.GL_DEPTH_BUFFER_BIT; if (depth != currentClearDepth) { glClearDepth((double) depth); currentClearDepth = depth; } // glClear depends on the depth write mask! if (!zWriteEnable) { glDepthMask(true); } } if (clearStencil) { clearMask |= GLenum.GL_STENCIL_BUFFER_BIT; if (stencil != currentClearStencil) { glClearStencil(stencil); currentClearStencil = stencil; } // glClear depends on the stencil write mask! if (stencilWriteMask != -1) { // AKA 0xFFFFFFFF, ugh -flibit glStencilMask(-1); } } // CLEAR! glClear(clearMask); // Clean up after ourselves. if (scissorTestEnable) { glEnable(GLenum.GL_SCISSOR_TEST); } if (clearTarget && colorWriteEnable != ColorWriteChannels.All) { // FIXME: ColorWriteChannels1/2/3? -flibit glColorMask( (colorWriteEnable & ColorWriteChannels.Red) != 0, (colorWriteEnable & ColorWriteChannels.Green) != 0, (colorWriteEnable & ColorWriteChannels.Blue) != 0, (colorWriteEnable & ColorWriteChannels.Alpha) != 0 ); } if (clearDepth && !zWriteEnable) { glDepthMask(false); } if (clearStencil && stencilWriteMask != -1) // AKA 0xFFFFFFFF, ugh -flibit { glStencilMask(stencilWriteMask); } } #endregion #region SetRenderTargets Method public void SetRenderTargets( RenderTargetBinding[] renderTargets, IGLRenderbuffer renderbuffer, DepthFormat depthFormat ) { // Bind the right framebuffer, if needed if (renderTargets == null) { BindFramebuffer( (Backbuffer is OpenGLBackbuffer) ? (Backbuffer as OpenGLBackbuffer).Handle : 0 ); renderTargetBound = false; return; } else { BindFramebuffer(targetFramebuffer); renderTargetBound = true; } int i; for (i = 0; i < renderTargets.Length; i += 1) { IGLRenderbuffer colorBuffer = (renderTargets[i].RenderTarget as IRenderTarget).ColorBuffer; if (colorBuffer != null) { attachments[i] = (colorBuffer as OpenGLRenderbuffer).Handle; attachmentTypes[i] = GLenum.GL_RENDERBUFFER; } else { attachments[i] = (renderTargets[i].RenderTarget.texture as OpenGLTexture).Handle; if (renderTargets[i].RenderTarget is RenderTarget2D) { attachmentTypes[i] = GLenum.GL_TEXTURE_2D; } else { attachmentTypes[i] = GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int) renderTargets[i].CubeMapFace; } } } // Update the color attachments, DrawBuffers state for (i = 0; i < renderTargets.Length; i += 1) { if (attachments[i] != currentAttachments[i]) { if (currentAttachments[i] != 0) { if ( attachmentTypes[i] != GLenum.GL_RENDERBUFFER && currentAttachmentTypes[i] == GLenum.GL_RENDERBUFFER ) { glNamedFramebufferRenderbuffer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, GLenum.GL_RENDERBUFFER, 0 ); } else if ( attachmentTypes[i] == GLenum.GL_RENDERBUFFER && currentAttachmentTypes[i] != GLenum.GL_RENDERBUFFER ) { if (currentAttachmentTypes[i] == GLenum.GL_TEXTURE_2D) { glNamedFramebufferTexture( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, 0, 0 ); } else { glNamedFramebufferTextureLayer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, 0, 0, (int) currentAttachmentTypes[i] - (int) GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X ); } } } if (attachmentTypes[i] == GLenum.GL_RENDERBUFFER) { glNamedFramebufferRenderbuffer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, GLenum.GL_RENDERBUFFER, attachments[i] ); } else if (attachmentTypes[i] == GLenum.GL_TEXTURE_2D) { glNamedFramebufferTexture( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, attachments[i], 0 ); } else { glNamedFramebufferTextureLayer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, attachments[i], 0, (int) attachmentTypes[i] - (int) GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X ); } currentAttachments[i] = attachments[i]; currentAttachmentTypes[i] = attachmentTypes[i]; } else if (attachmentTypes[i] != currentAttachmentTypes[i]) { // Texture cube face change! glNamedFramebufferTextureLayer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, attachments[i], 0, (int) attachmentTypes[i] - (int) GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X ); currentAttachmentTypes[i] = attachmentTypes[i]; } } while (i < currentAttachments.Length) { if (currentAttachments[i] != 0) { if (currentAttachmentTypes[i] == GLenum.GL_RENDERBUFFER) { glNamedFramebufferRenderbuffer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, GLenum.GL_RENDERBUFFER, 0 ); } else if (currentAttachmentTypes[i] == GLenum.GL_TEXTURE_2D) { glNamedFramebufferTexture( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, 0, 0 ); } else { glNamedFramebufferTextureLayer( targetFramebuffer, GLenum.GL_COLOR_ATTACHMENT0 + i, 0, 0, (int) currentAttachmentTypes[i] - (int) GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X ); } currentAttachments[i] = 0; currentAttachmentTypes[i] = GLenum.GL_TEXTURE_2D; } i += 1; } if (renderTargets.Length != currentDrawBuffers) { glNamedFramebufferDrawBuffers( targetFramebuffer, renderTargets.Length, drawBuffersArray ); currentDrawBuffers = renderTargets.Length; } // Update the depth/stencil attachment /* FIXME: Notice that we do separate attach calls for the stencil. * We _should_ be able to do a single attach for depthstencil, but * some drivers (like Mesa) cannot into GL_DEPTH_STENCIL_ATTACHMENT. * Use XNAToGL.DepthStencilAttachment when this isn't a problem. * -flibit */ uint handle; if (renderbuffer == null) { handle = 0; } else { handle = (renderbuffer as OpenGLRenderbuffer).Handle; } if (handle != currentRenderbuffer) { if (currentDepthStencilFormat == DepthFormat.Depth24Stencil8) { glNamedFramebufferRenderbuffer( targetFramebuffer, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, 0 ); } currentDepthStencilFormat = depthFormat; glNamedFramebufferRenderbuffer( targetFramebuffer, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, handle ); if (currentDepthStencilFormat == DepthFormat.Depth24Stencil8) { glNamedFramebufferRenderbuffer( targetFramebuffer, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, handle ); } currentRenderbuffer = handle; } } #endregion #region Query Object Methods public IGLQuery CreateQuery() { uint handle; glGenQueries(1, out handle); return new OpenGLQuery(handle); } private void DeleteQuery(IGLQuery query) { uint handle = (query as OpenGLQuery).Handle; glDeleteQueries( 1, ref handle ); } public void QueryBegin(IGLQuery query) { glBeginQuery( GLenum.GL_SAMPLES_PASSED, (query as OpenGLQuery).Handle ); } public void QueryEnd(IGLQuery query) { // May need to check active queries...? glEndQuery( GLenum.GL_SAMPLES_PASSED ); } public bool QueryComplete(IGLQuery query) { uint result; glGetQueryObjectuiv( (query as OpenGLQuery).Handle, GLenum.GL_QUERY_RESULT_AVAILABLE, out result ); return result != 0; } public int QueryPixelCount(IGLQuery query) { uint result; glGetQueryObjectuiv( (query as OpenGLQuery).Handle, GLenum.GL_QUERY_RESULT, out result ); return (int) result; } #endregion #region XNA->GL Enum Conversion Class private static class XNAToGL { public static readonly GLenum[] TextureFormat = new GLenum[] { GLenum.GL_RGBA, // SurfaceFormat.Color GLenum.GL_RGB, // SurfaceFormat.Bgr565 GLenum.GL_BGRA, // SurfaceFormat.Bgra5551 GLenum.GL_BGRA, // SurfaceFormat.Bgra4444 GLenum.GL_COMPRESSED_TEXTURE_FORMATS, // SurfaceFormat.Dxt1 GLenum.GL_COMPRESSED_TEXTURE_FORMATS, // SurfaceFormat.Dxt3 GLenum.GL_COMPRESSED_TEXTURE_FORMATS, // SurfaceFormat.Dxt5 GLenum.GL_RG, // SurfaceFormat.NormalizedByte2 GLenum.GL_RGBA, // SurfaceFormat.NormalizedByte4 GLenum.GL_RGBA, // SurfaceFormat.Rgba1010102 GLenum.GL_RG, // SurfaceFormat.Rg32 GLenum.GL_RGBA, // SurfaceFormat.Rgba64 GLenum.GL_RED, // SurfaceFormat.Alpha8 GLenum.GL_RED, // SurfaceFormat.Single GLenum.GL_RG, // SurfaceFormat.Vector2 GLenum.GL_RGBA, // SurfaceFormat.Vector4 GLenum.GL_RED, // SurfaceFormat.HalfSingle GLenum.GL_RG, // SurfaceFormat.HalfVector2 GLenum.GL_RGBA, // SurfaceFormat.HalfVector4 GLenum.GL_RGBA, // SurfaceFormat.HdrBlendable GLenum.GL_BGRA // SurfaceFormat.ColorBgraEXT }; public static readonly GLenum[] TextureInternalFormat = new GLenum[] { GLenum.GL_RGBA8, // SurfaceFormat.Color GLenum.GL_RGB565, // SurfaceFormat.Bgr565 GLenum.GL_RGB5_A1, // SurfaceFormat.Bgra5551 GLenum.GL_RGBA4, // SurfaceFormat.Bgra4444 GLenum.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, // SurfaceFormat.Dxt1 GLenum.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, // SurfaceFormat.Dxt3 GLenum.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, // SurfaceFormat.Dxt5 GLenum.GL_RG8, // SurfaceFormat.NormalizedByte2 GLenum.GL_RGBA8, // SurfaceFormat.NormalizedByte4 GLenum.GL_RGB10_A2_EXT, // SurfaceFormat.Rgba1010102 GLenum.GL_RG16, // SurfaceFormat.Rg32 GLenum.GL_RGBA16, // SurfaceFormat.Rgba64 GLenum.GL_R8, // SurfaceFormat.Alpha8 GLenum.GL_R32F, // SurfaceFormat.Single GLenum.GL_RG32F, // SurfaceFormat.Vector2 GLenum.GL_RGBA32F, // SurfaceFormat.Vector4 GLenum.GL_R16F, // SurfaceFormat.HalfSingle GLenum.GL_RG16F, // SurfaceFormat.HalfVector2 GLenum.GL_RGBA16F, // SurfaceFormat.HalfVector4 GLenum.GL_RGBA16F, // SurfaceFormat.HdrBlendable GLenum.GL_RGBA8, // SurfaceFormat.ColorBgraEXT }; public static readonly GLenum[] TextureDataType = new GLenum[] { GLenum.GL_UNSIGNED_BYTE, // SurfaceFormat.Color GLenum.GL_UNSIGNED_SHORT_5_6_5, // SurfaceFormat.Bgr565 GLenum.GL_UNSIGNED_SHORT_5_5_5_1_REV, // SurfaceFormat.Bgra5551 GLenum.GL_UNSIGNED_SHORT_4_4_4_4_REV, // SurfaceFormat.Bgra4444 GLenum.GL_ZERO, // NOPE GLenum.GL_ZERO, // NOPE GLenum.GL_ZERO, // NOPE GLenum.GL_BYTE, // SurfaceFormat.NormalizedByte2 GLenum.GL_BYTE, // SurfaceFormat.NormalizedByte4 GLenum.GL_UNSIGNED_INT_2_10_10_10_REV, // SurfaceFormat.Rgba1010102 GLenum.GL_UNSIGNED_SHORT, // SurfaceFormat.Rg32 GLenum.GL_UNSIGNED_SHORT, // SurfaceFormat.Rgba64 GLenum.GL_UNSIGNED_BYTE, // SurfaceFormat.Alpha8 GLenum.GL_FLOAT, // SurfaceFormat.Single GLenum.GL_FLOAT, // SurfaceFormat.Vector2 GLenum.GL_FLOAT, // SurfaceFormat.Vector4 GLenum.GL_HALF_FLOAT, // SurfaceFormat.HalfSingle GLenum.GL_HALF_FLOAT, // SurfaceFormat.HalfVector2 GLenum.GL_HALF_FLOAT, // SurfaceFormat.HalfVector4 GLenum.GL_HALF_FLOAT, // SurfaceFormat.HdrBlendable GLenum.GL_UNSIGNED_BYTE, // SurfaceFormat.ColorBgraEXT }; public static readonly GLenum[] BlendMode = new GLenum[] { GLenum.GL_ONE, // Blend.One GLenum.GL_ZERO, // Blend.Zero GLenum.GL_SRC_COLOR, // Blend.SourceColor GLenum.GL_ONE_MINUS_SRC_COLOR, // Blend.InverseSourceColor GLenum.GL_SRC_ALPHA, // Blend.SourceAlpha GLenum.GL_ONE_MINUS_SRC_ALPHA, // Blend.InverseSourceAlpha GLenum.GL_DST_COLOR, // Blend.DestinationColor GLenum.GL_ONE_MINUS_DST_COLOR, // Blend.InverseDestinationColor GLenum.GL_DST_ALPHA, // Blend.DestinationAlpha GLenum.GL_ONE_MINUS_DST_ALPHA, // Blend.InverseDestinationAlpha GLenum.GL_CONSTANT_COLOR, // Blend.BlendFactor GLenum.GL_ONE_MINUS_CONSTANT_COLOR, // Blend.InverseBlendFactor GLenum.GL_SRC_ALPHA_SATURATE // Blend.SourceAlphaSaturation }; public static readonly GLenum[] BlendEquation = new GLenum[] { GLenum.GL_FUNC_ADD, // BlendFunction.Add GLenum.GL_FUNC_SUBTRACT, // BlendFunction.Subtract GLenum.GL_FUNC_REVERSE_SUBTRACT, // BlendFunction.ReverseSubtract GLenum.GL_MAX, // BlendFunction.Max GLenum.GL_MIN // BlendFunction.Min }; public static readonly GLenum[] CompareFunc = new GLenum[] { GLenum.GL_ALWAYS, // CompareFunction.Always GLenum.GL_NEVER, // CompareFunction.Never GLenum.GL_LESS, // CompareFunction.Less GLenum.GL_LEQUAL, // CompareFunction.LessEqual GLenum.GL_EQUAL, // CompareFunction.Equal GLenum.GL_GEQUAL, // CompareFunction.GreaterEqual GLenum.GL_GREATER, // CompareFunction.Greater GLenum.GL_NOTEQUAL // CompareFunction.NotEqual }; public static readonly GLenum[] GLStencilOp = new GLenum[] { GLenum.GL_KEEP, // StencilOperation.Keep GLenum.GL_ZERO, // StencilOperation.Zero GLenum.GL_REPLACE, // StencilOperation.Replace GLenum.GL_INCR_WRAP, // StencilOperation.Increment GLenum.GL_DECR_WRAP, // StencilOperation.Decrement GLenum.GL_INCR, // StencilOperation.IncrementSaturation GLenum.GL_DECR, // StencilOperation.DecrementSaturation GLenum.GL_INVERT // StencilOperation.Invert }; public static readonly GLenum[] FrontFace = new GLenum[] { GLenum.GL_ZERO, // NOPE GLenum.GL_CW, // CullMode.CullClockwiseFace GLenum.GL_CCW // CullMode.CullCounterClockwiseFace }; public static readonly GLenum[] GLFillMode = new GLenum[] { GLenum.GL_FILL, // FillMode.Solid GLenum.GL_LINE // FillMode.WireFrame }; public static readonly int[] Wrap = new int[] { (int) GLenum.GL_REPEAT, // TextureAddressMode.Wrap (int) GLenum.GL_CLAMP_TO_EDGE, // TextureAddressMode.Clamp (int) GLenum.GL_MIRRORED_REPEAT // TextureAddressMode.Mirror }; public static readonly int[] MagFilter = new int[] { (int) GLenum.GL_LINEAR, // TextureFilter.Linear (int) GLenum.GL_NEAREST, // TextureFilter.Point (int) GLenum.GL_LINEAR, // TextureFilter.Anisotropic (int) GLenum.GL_LINEAR, // TextureFilter.LinearMipPoint (int) GLenum.GL_NEAREST, // TextureFilter.PointMipLinear (int) GLenum.GL_NEAREST, // TextureFilter.MinLinearMagPointMipLinear (int) GLenum.GL_NEAREST, // TextureFilter.MinLinearMagPointMipPoint (int) GLenum.GL_LINEAR, // TextureFilter.MinPointMagLinearMipLinear (int) GLenum.GL_LINEAR // TextureFilter.MinPointMagLinearMipPoint }; public static readonly int[] MinMipFilter = new int[] { (int) GLenum.GL_LINEAR_MIPMAP_LINEAR, // TextureFilter.Linear (int) GLenum.GL_NEAREST_MIPMAP_NEAREST, // TextureFilter.Point (int) GLenum.GL_LINEAR_MIPMAP_LINEAR, // TextureFilter.Anisotropic (int) GLenum.GL_LINEAR_MIPMAP_NEAREST, // TextureFilter.LinearMipPoint (int) GLenum.GL_NEAREST_MIPMAP_LINEAR, // TextureFilter.PointMipLinear (int) GLenum.GL_LINEAR_MIPMAP_LINEAR, // TextureFilter.MinLinearMagPointMipLinear (int) GLenum.GL_LINEAR_MIPMAP_NEAREST, // TextureFilter.MinLinearMagPointMipPoint (int) GLenum.GL_NEAREST_MIPMAP_LINEAR, // TextureFilter.MinPointMagLinearMipLinear (int) GLenum.GL_NEAREST_MIPMAP_NEAREST // TextureFilter.MinPointMagLinearMipPoint }; public static readonly int[] MinFilter = new int[] { (int) GLenum.GL_LINEAR, // TextureFilter.Linear (int) GLenum.GL_NEAREST, // TextureFilter.Point (int) GLenum.GL_LINEAR, // TextureFilter.Anisotropic (int) GLenum.GL_LINEAR, // TextureFilter.LinearMipPoint (int) GLenum.GL_NEAREST, // TextureFilter.PointMipLinear (int) GLenum.GL_LINEAR, // TextureFilter.MinLinearMagPointMipLinear (int) GLenum.GL_LINEAR, // TextureFilter.MinLinearMagPointMipPoint (int) GLenum.GL_NEAREST, // TextureFilter.MinPointMagLinearMipLinear (int) GLenum.GL_NEAREST // TextureFilter.MinPointMagLinearMipPoint }; public static readonly GLenum[] DepthStencilAttachment = new GLenum[] { GLenum.GL_ZERO, // NOPE GLenum.GL_DEPTH_ATTACHMENT, // DepthFormat.Depth16 GLenum.GL_DEPTH_ATTACHMENT, // DepthFormat.Depth24 GLenum.GL_DEPTH_STENCIL_ATTACHMENT // DepthFormat.Depth24Stencil8 }; public static readonly GLenum[] DepthStorage = new GLenum[] { GLenum.GL_ZERO, // NOPE GLenum.GL_DEPTH_COMPONENT16, // DepthFormat.Depth16 GLenum.GL_DEPTH_COMPONENT24, // DepthFormat.Depth24 GLenum.GL_DEPTH24_STENCIL8 // DepthFormat.Depth24Stencil8 }; public static readonly float[] DepthBiasScale = new float[] { 0.0f, // DepthFormat.None (float) ((1 << 16) - 1), // DepthFormat.Depth16 (float) ((1 << 24) - 1), // DepthFormat.Depth24 (float) ((1 << 24) - 1) // DepthFormat.Depth24Stencil8 }; public static readonly MojoShader.MOJOSHADER_usage[] VertexAttribUsage = new MojoShader.MOJOSHADER_usage[] { MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_POSITION, // VertexElementUsage.Position MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_COLOR, // VertexElementUsage.Color MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TEXCOORD, // VertexElementUsage.TextureCoordinate MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_NORMAL, // VertexElementUsage.Normal MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_BINORMAL, // VertexElementUsage.Binormal MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TANGENT, // VertexElementUsage.Tangent MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_BLENDINDICES, // VertexElementUsage.BlendIndices MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_BLENDWEIGHT, // VertexElementUsage.BlendWeight MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_DEPTH, // VertexElementUsage.Depth MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_FOG, // VertexElementUsage.Fog MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_POINTSIZE, // VertexElementUsage.PointSize MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_SAMPLE, // VertexElementUsage.Sample MojoShader.MOJOSHADER_usage.MOJOSHADER_USAGE_TESSFACTOR // VertexElementUsage.TessellateFactor }; public static readonly int[] VertexAttribSize = new int[] { 1, // VertexElementFormat.Single 2, // VertexElementFormat.Vector2 3, // VertexElementFormat.Vector3 4, // VertexElementFormat.Vector4 4, // VertexElementFormat.Color 4, // VertexElementFormat.Byte4 2, // VertexElementFormat.Short2 4, // VertexElementFormat.Short4 2, // VertexElementFormat.NormalizedShort2 4, // VertexElementFormat.NormalizedShort4 2, // VertexElementFormat.HalfVector2 4 // VertexElementFormat.HalfVector4 }; public static readonly GLenum[] VertexAttribType = new GLenum[] { GLenum.GL_FLOAT, // VertexElementFormat.Single GLenum.GL_FLOAT, // VertexElementFormat.Vector2 GLenum.GL_FLOAT, // VertexElementFormat.Vector3 GLenum.GL_FLOAT, // VertexElementFormat.Vector4 GLenum.GL_UNSIGNED_BYTE, // VertexElementFormat.Color GLenum.GL_UNSIGNED_BYTE, // VertexElementFormat.Byte4 GLenum.GL_SHORT, // VertexElementFormat.Short2 GLenum.GL_SHORT, // VertexElementFormat.Short4 GLenum.GL_SHORT, // VertexElementFormat.NormalizedShort2 GLenum.GL_SHORT, // VertexElementFormat.NormalizedShort4 GLenum.GL_HALF_FLOAT, // VertexElementFormat.HalfVector2 GLenum.GL_HALF_FLOAT // VertexElementFormat.HalfVector4 }; public static bool VertexAttribNormalized(VertexElement element) { return ( element.VertexElementUsage == VertexElementUsage.Color || element.VertexElementFormat == VertexElementFormat.NormalizedShort2 || element.VertexElementFormat == VertexElementFormat.NormalizedShort4 ); } public static readonly GLenum[] IndexType = new GLenum[] { GLenum.GL_UNSIGNED_SHORT, // IndexElementSize.SixteenBits GLenum.GL_UNSIGNED_INT // IndexElementSize.ThirtyTwoBits }; public static readonly int[] IndexSize = new int[] { 2, // IndexElementSize.SixteenBits 4 // IndexElementSize.ThirtyTwoBits }; public static readonly GLenum[] Primitive = new GLenum[] { GLenum.GL_TRIANGLES, // PrimitiveType.TriangleList GLenum.GL_TRIANGLE_STRIP, // PrimitiveType.TriangleStrip GLenum.GL_LINES, // PrimitiveType.LineList GLenum.GL_LINE_STRIP, // PrimitiveType.LineStrip GLenum.GL_POINTS // PrimitiveType.PointListEXT }; public static int PrimitiveVerts(PrimitiveType primitiveType, int primitiveCount) { switch (primitiveType) { case PrimitiveType.TriangleList: return primitiveCount * 3; case PrimitiveType.TriangleStrip: return primitiveCount + 2; case PrimitiveType.LineList: return primitiveCount * 2; case PrimitiveType.LineStrip: return primitiveCount + 1; case PrimitiveType.PointListEXT: return primitiveCount; } throw new NotSupportedException(); } } #endregion #region The Faux-Backbuffer private bool UseFauxBackbuffer(PresentationParameters presentationParameters) { int drawX, drawY; SDL.SDL_GL_GetDrawableSize( presentationParameters.DeviceWindowHandle, out drawX, out drawY ); bool displayMismatch = ( drawX != presentationParameters.BackBufferWidth || drawY != presentationParameters.BackBufferHeight ); return displayMismatch || (presentationParameters.MultiSampleCount > 0); } private class OpenGLBackbuffer : IGLBackbuffer { public uint Handle { get; private set; } public int Width { get; private set; } public int Height { get; private set; } public DepthFormat DepthFormat { get; private set; } public int MultiSampleCount { get; private set; } public uint Texture; private uint colorAttachment; private uint depthStencilAttachment; private ModernGLDevice glDevice; public OpenGLBackbuffer( ModernGLDevice device, int width, int height, DepthFormat depthFormat, int multiSampleCount ) { Width = width; Height = height; glDevice = device; DepthFormat = depthFormat; MultiSampleCount = multiSampleCount; Texture = 0; // Generate and bind the FBO. uint handle; glDevice.glCreateFramebuffers(1, out handle); Handle = handle; glDevice.BindFramebuffer(Handle); // Create and attach the color buffer glDevice.glCreateRenderbuffers(1, out colorAttachment); if (multiSampleCount > 0) { glDevice.glNamedRenderbufferStorageMultisample( colorAttachment, multiSampleCount, GLenum.GL_RGBA8, width, height ); } else { glDevice.glNamedRenderbufferStorage( colorAttachment, GLenum.GL_RGBA8, width, height ); } glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, colorAttachment ); if (depthFormat == DepthFormat.None) { // Don't bother creating a depth/stencil buffer. depthStencilAttachment = 0; return; } // Create and attach the depth/stencil buffer glDevice.glCreateRenderbuffers(1, out depthStencilAttachment); if (multiSampleCount > 0) { glDevice.glNamedRenderbufferStorageMultisample( depthStencilAttachment, multiSampleCount, XNAToGL.DepthStorage[(int) depthFormat], width, height ); } else { glDevice.glNamedRenderbufferStorage( depthStencilAttachment, XNAToGL.DepthStorage[(int) depthFormat], width, height ); } glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); if (depthFormat == DepthFormat.Depth24Stencil8) { glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); } } public void Dispose() { uint handle = Handle; glDevice.BindFramebuffer(0); glDevice.glDeleteFramebuffers(1, ref handle); glDevice.glDeleteRenderbuffers(1, ref colorAttachment); if (depthStencilAttachment != 0) { glDevice.glDeleteRenderbuffers(1, ref depthStencilAttachment); } if (Texture != 0) { glDevice.glDeleteTextures(1, ref Texture); } glDevice = null; Handle = 0; } public void ResetFramebuffer( PresentationParameters presentationParameters ) { Width = presentationParameters.BackBufferWidth; Height = presentationParameters.BackBufferHeight; DepthFormat depthFormat = presentationParameters.DepthStencilFormat; MultiSampleCount = presentationParameters.MultiSampleCount; if (Texture != 0) { glDevice.glDeleteTextures(1, ref Texture); Texture = 0; } // Detach color attachment glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, 0 ); // Detach depth/stencil attachment, if applicable if (depthStencilAttachment != 0) { glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, 0 ); if (DepthFormat == DepthFormat.Depth24Stencil8) { glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, 0 ); } } // Update our color attachment to the new resolution. if (MultiSampleCount > 0) { glDevice.glNamedRenderbufferStorageMultisample( colorAttachment, MultiSampleCount, GLenum.GL_RGBA8, Width, Height ); } else { glDevice.glNamedRenderbufferStorage( colorAttachment, GLenum.GL_RGBA8, Width, Height ); } glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, colorAttachment ); // Generate/Delete depth/stencil attachment, if needed if (depthFormat == DepthFormat.None) { if (depthStencilAttachment != 0) { glDevice.glDeleteRenderbuffers( 1, ref depthStencilAttachment ); depthStencilAttachment = 0; } } else if (depthStencilAttachment == 0) { glDevice.glCreateRenderbuffers( 1, out depthStencilAttachment ); } // Update the depth/stencil buffer, if applicable if (depthStencilAttachment != 0) { if (MultiSampleCount > 0) { glDevice.glNamedRenderbufferStorageMultisample( depthStencilAttachment, MultiSampleCount, XNAToGL.DepthStorage[(int)depthFormat], Width, Height ); } else { glDevice.glNamedRenderbufferStorage( depthStencilAttachment, XNAToGL.DepthStorage[(int)depthFormat], Width, Height ); } glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); if (depthFormat == DepthFormat.Depth24Stencil8) { glDevice.glNamedFramebufferRenderbuffer( Handle, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); } } DepthFormat = depthFormat; } } #endregion #region The Faux-Faux-Backbuffer private class NullBackbuffer : IGLBackbuffer { public int Width { get; private set; } public int Height { get; private set; } public DepthFormat DepthFormat { get; private set; } public int MultiSampleCount { get { // Constant, per SDL2_GameWindow return 0; } } public NullBackbuffer(int width, int height, DepthFormat depthFormat) { Width = width; Height = height; DepthFormat = depthFormat; } public void ResetFramebuffer( PresentationParameters presentationParameters ) { Width = presentationParameters.BackBufferWidth; Height = presentationParameters.BackBufferHeight; } } #endregion #region Threaded GL Nonsense private int mainThreadId; private bool IsOnMainThread() { return mainThreadId == Thread.CurrentThread.ManagedThreadId; } private void InitThreadedGL(IntPtr window) { mainThreadId = Thread.CurrentThread.ManagedThreadId; #if THREADED_GL // Create a background context SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); WindowInfo = window; BackgroundContext = new GL_ContextHandle() { context = SDL.SDL_GL_CreateContext(window) }; // Make the foreground context current. SDL.SDL_GL_MakeCurrent(window, glContext); // We're going to need glFlush, so load this entry point. glFlush = (Flush) Marshal.GetDelegateForFunctionPointer( SDL.SDL_GL_GetProcAddress("glFlush"), typeof(Flush) ); #endif } #if !DISABLE_THREADING #if THREADED_GL private class GL_ContextHandle { public IntPtr context; } private GL_ContextHandle BackgroundContext; private IntPtr WindowInfo; private delegate void Flush(); private Flush glFlush; #else private System.Collections.Generic.List actions = new System.Collections.Generic.List(); private void RunActions() { lock (actions) { foreach (Action action in actions) { action(); } actions.Clear(); } } #endif private void ForceToMainThread(Action action) { // If we're already on the main thread, just call the action. if (mainThreadId == Thread.CurrentThread.ManagedThreadId) { action(); return; } #if THREADED_GL lock (BackgroundContext) { // Make the context current on this thread. SDL.SDL_GL_MakeCurrent(WindowInfo, BackgroundContext.context); // Execute the action. action(); // Must flush the GL calls now before we release the context. glFlush(); // Free the threaded context for the next threaded call... SDL.SDL_GL_MakeCurrent(WindowInfo, IntPtr.Zero); } #else ManualResetEventSlim resetEvent = new ManualResetEventSlim(false); lock (actions) { actions.Add(() => { action(); resetEvent.Set(); }); } resetEvent.Wait(); #endif } #endif // !DISABLE_THREADING #endregion } }