#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 OpenGLDevice : 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 TextureAddressMode WrapS; public TextureAddressMode WrapT; public TextureAddressMode WrapR; public TextureFilter Filter; public float Anistropy; public int MaxMipmapLevel; public float LODBias; public OpenGLTexture( uint handle, GLenum target, int levelCount ) { Handle = handle; Target = target; HasMipmaps = levelCount > 1; WrapS = TextureAddressMode.Wrap; WrapT = TextureAddressMode.Wrap; WrapR = TextureAddressMode.Wrap; Filter = TextureFilter.Linear; Anistropy = 4.0f; MaxMipmapLevel = 0; LODBias = 0.0f; } // 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 OpenGLBuffer : IGLBuffer { public uint Handle { get; private set; } public IntPtr BufferSize { get; private set; } public GLenum Dynamic { get; private set; } public OpenGLBuffer( uint handle, IntPtr bufferSize, GLenum dynamic ) { Handle = handle; BufferSize = bufferSize; Dynamic = dynamic; } private OpenGLBuffer() { Handle = 0; } public static readonly OpenGLBuffer NullBuffer = new OpenGLBuffer(); } #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 Texture Collection Variables private OpenGLTexture[] Textures; #endregion #region Buffer Binding Cache Variables private uint currentVertexBuffer = 0; private uint currentIndexBuffer = 0; // ld, or LastDrawn, effect/vertex attributes private int ldBaseVertex = -1; 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; private uint realBackbufferFBO; private uint realBackbufferRBO; #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 { // MAP_UNSYNCHRONIZED sucks, oh well! return false; } } public int MaxTextureSlots { get; private set; } public int MaxMultiSampleCount { get; private set; } private bool supportsMultisampling; private bool supportsFauxBackbuffer; private bool supportsFBOInvalidation; private bool supportsBaseVertex; #endregion #region Private Vertex Attribute Cache private class VertexAttribute { public uint CurrentBuffer; public IntPtr CurrentPointer; public VertexElementFormat CurrentFormat; public bool CurrentNormalized; public int CurrentStride; public VertexAttribute() { CurrentBuffer = 0; 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; [ObjCRuntime.MonoPInvokeCallback(typeof(MojoShader.MOJOSHADER_glGetProcAddress))] 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 useES3; private bool useCoreProfile; private bool togglePointSprite; private DepthFormat windowDepthFormat; private uint vao; #endregion #region Private ANGLE Bug Hack /* FIXME: THIS CHECK ABSOLUTELY SHOULD NOT EXIST! FIX THIS BUG: * * https://bugs.chromium.org/p/angleproject/issues/detail?id=3402 * * -flibit */ private bool BUG_HACK_NOTANGLE; #endregion #region Public Constructor public OpenGLDevice(PresentationParameters presentationParameters) { // Create OpenGL context glContext = SDL.SDL_GL_CreateContext( presentationParameters.DeviceWindowHandle ); // Check for a possible ES context int flags; int es3Flag = (int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES; SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, out flags); useES3 = (flags & es3Flag) == es3Flag; // Check for a possible Core context int coreFlag = (int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE; 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 )); } // UIKit needs special treatment for backbuffer behavior SDL.SDL_SysWMinfo wmInfo = new SDL.SDL_SysWMinfo(); SDL.SDL_VERSION(out wmInfo.version); SDL.SDL_GetWindowWMInfo( presentationParameters.DeviceWindowHandle, ref wmInfo ); if (wmInfo.subsystem == SDL.SDL_SYSWM_TYPE.SDL_SYSWM_UIKIT) { realBackbufferFBO = wmInfo.info.uikit.framebuffer; realBackbufferRBO = wmInfo.info.uikit.colorbuffer; } else { realBackbufferFBO = 0; realBackbufferRBO = 0; } // Init threaded GL crap where applicable InitThreadedGL( presentationParameters.DeviceWindowHandle ); // Print GL information LoadGLGetString(); string renderer = glGetString(GLenum.GL_RENDERER); string version = glGetString(GLenum.GL_VERSION); string vendor = glGetString(GLenum.GL_VENDOR); FNALoggerEXT.LogInfo("IGLDevice: OpenGLDevice"); FNALoggerEXT.LogInfo("OpenGL Device: " + renderer); FNALoggerEXT.LogInfo("OpenGL Driver: " + version); FNALoggerEXT.LogInfo("OpenGL Vendor: " + vendor); // FIXME: REMOVE ME ASAP! BUG_HACK_NOTANGLE = !renderer.Contains("Direct3D11"); // Initialize entry points LoadGLEntryPoints(string.Format( "Device: {0}\nDriver: {1}\nVendor: {2}", renderer, version, vendor )); 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); FNALoggerEXT.LogInfo("MojoShader Profile: " + shaderProfile); // Some users might want pixely upscaling... backbufferScaleMode = Environment.GetEnvironmentVariable( "FNA_GRAPHICS_BACKBUFFER_SCALE_NEAREST" ) == "1" ? GLenum.GL_NEAREST : GLenum.GL_LINEAR; // 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") ); /* Check the max multisample count, override parameters if necessary */ int maxSamples = 0; if (supportsMultisampling) { glGetIntegerv(GLenum.GL_MAX_SAMPLES, out maxSamples); } MaxMultiSampleCount = maxSamples; presentationParameters.MultiSampleCount = Math.Min( presentationParameters.MultiSampleCount, MaxMultiSampleCount ); // Initialize the faux-backbuffer if (UseFauxBackbuffer(presentationParameters)) { if (!supportsFauxBackbuffer) { throw new NoSuitableGraphicsDeviceException( "Your hardware does not support the faux-backbuffer!" + "\n\nKeep the window/backbuffer resolution the same." ); } 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]; for (int i = 0; i < numSamplers; i += 1) { Textures[i] = OpenGLTexture.NullTexture; } 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; glGenFramebuffers(1, out targetFramebuffer); glGenFramebuffers(1, out resolveFramebufferRead); glGenFramebuffers(1, out resolveFramebufferDraw); togglePointSprite = false; if (useCoreProfile) { // Generate and bind a VAO, to shut Core up glGenVertexArrays(1, out vao); glBindVertexArray(vao); } else if (!useES3) { /* Compatibility contexts require that point sprites be enabled * explicitly. However, Apple's drivers have a blantant spec * violation that disallows a simple glEnable. So, here we are. * -flibit */ if (SDL.SDL_GetPlatform().Equals("Mac OS X")) { togglePointSprite = true; } else { 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) { if (!supportsFauxBackbuffer) { throw new NoSuitableGraphicsDeviceException( "Your hardware does not support the faux-backbuffer!" + "\n\nKeep the window/backbuffer resolution the same." ); } 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); } 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) { glGenTextures(1, out glBack.Texture); glBindTexture(GLenum.GL_TEXTURE_2D, glBack.Texture); glTexImage2D( GLenum.GL_TEXTURE_2D, 0, (int) GLenum.GL_RGBA, glBack.Width, glBack.Height, 0, GLenum.GL_RGBA, GLenum.GL_UNSIGNED_BYTE, IntPtr.Zero ); glBindTexture(Textures[0].Target, Textures[0].Handle); } BindFramebuffer(resolveFramebufferDraw); glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_TEXTURE_2D, glBack.Texture, 0 ); BindReadFramebuffer(glBack.Handle); glBlitFramebuffer( 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 */ if (supportsFBOInvalidation) { glInvalidateFramebuffer( GLenum.GL_READ_FRAMEBUFFER, attachments.Length + 2, drawBuffersArray ); } BindReadFramebuffer(resolveFramebufferDraw); } else { BindReadFramebuffer((Backbuffer as OpenGLBackbuffer).Handle); } BindDrawFramebuffer(realBackbufferFBO); glBlitFramebuffer( srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH, GLenum.GL_COLOR_BUFFER_BIT, backbufferScaleMode ); /* Invalidate the faux-backbuffer */ if (supportsFBOInvalidation) { glInvalidateFramebuffer( GLenum.GL_READ_FRAMEBUFFER, attachments.Length + 2, drawBuffersArray ); } BindFramebuffer(realBackbufferFBO); if (scissorTestEnable) { glEnable(GLenum.GL_SCISSOR_TEST); } SDL.SDL_GL_SwapWindow( overrideWindowHandle ); BindFramebuffer((Backbuffer as OpenGLBackbuffer).Handle); } 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); bool tps = togglePointSprite && primitiveType == PrimitiveType.PointListEXT; if (tps) { glEnable(GLenum.GL_POINT_SPRITE); } // 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 ); if (tps) { glDisable(GLenum.GL_POINT_SPRITE); } } 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); bool tps = togglePointSprite && primitiveType == PrimitiveType.PointListEXT; if (tps) { glEnable(GLenum.GL_POINT_SPRITE); } // Draw! glDrawElementsInstancedBaseVertex( XNAToGL.Primitive[(int) primitiveType], XNAToGL.PrimitiveVerts(primitiveType, primitiveCount), XNAToGL.IndexType[(int) indexElementSize], (IntPtr) (startIndex * XNAToGL.IndexSize[(int) indexElementSize]), instanceCount, baseVertex ); if (tps) { glDisable(GLenum.GL_POINT_SPRITE); } } public void DrawPrimitives( PrimitiveType primitiveType, int vertexStart, int primitiveCount ) { bool tps = togglePointSprite && primitiveType == PrimitiveType.PointListEXT; if (tps) { glEnable(GLenum.GL_POINT_SPRITE); } // Draw! glDrawArrays( XNAToGL.Primitive[(int) primitiveType], vertexStart, XNAToGL.PrimitiveVerts(primitiveType, primitiveCount) ); if (tps) { glDisable(GLenum.GL_POINT_SPRITE); } } public void DrawUserIndexedPrimitives( PrimitiveType primitiveType, IntPtr vertexData, int vertexOffset, int numVertices, IntPtr indexData, int indexOffset, IndexElementSize indexElementSize, int primitiveCount ) { // Unbind current index buffer. BindIndexBuffer(OpenGLBuffer.NullBuffer); bool tps = togglePointSprite && primitiveType == PrimitiveType.PointListEXT; if (tps) { glEnable(GLenum.GL_POINT_SPRITE); } // 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]) ) ); if (tps) { glDisable(GLenum.GL_POINT_SPRITE); } } public void DrawUserPrimitives( PrimitiveType primitiveType, IntPtr vertexData, int vertexOffset, int primitiveCount ) { bool tps = togglePointSprite && primitiveType == PrimitiveType.PointListEXT; if (tps) { glEnable(GLenum.GL_POINT_SPRITE); } // Draw! glDrawArrays( XNAToGL.Primitive[(int) primitiveType], vertexOffset, XNAToGL.PrimitiveVerts(primitiveType, primitiveCount) ); if (tps) { glDisable(GLenum.GL_POINT_SPRITE); } } #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) { if (index != 0) { glActiveTexture(GLenum.GL_TEXTURE0 + index); } glBindTexture(Textures[index].Target, 0); if (index != 0) { // Keep this state sane. -flibit glActiveTexture(GLenum.GL_TEXTURE0); } Textures[index] = OpenGLTexture.NullTexture; } return; } OpenGLTexture tex = texture.texture as OpenGLTexture; if ( tex == Textures[index] && sampler.AddressU == tex.WrapS && sampler.AddressV == tex.WrapT && sampler.AddressW == tex.WrapR && sampler.Filter == tex.Filter && sampler.MaxAnisotropy == tex.Anistropy && sampler.MaxMipLevel == tex.MaxMipmapLevel && sampler.MipMapLevelOfDetailBias == tex.LODBias ) { // Nothing's changing, forget it. return; } // Set the active texture slot if (index != 0) { glActiveTexture(GLenum.GL_TEXTURE0 + index); } // Bind the correct texture if (tex != Textures[index]) { if (tex.Target != Textures[index].Target) { // If we're changing targets, unbind the old texture first! glBindTexture(Textures[index].Target, 0); } glBindTexture(tex.Target, tex.Handle); Textures[index] = tex; } // Apply the sampler states to the GL texture if (sampler.AddressU != tex.WrapS) { tex.WrapS = sampler.AddressU; glTexParameteri( tex.Target, GLenum.GL_TEXTURE_WRAP_S, XNAToGL.Wrap[(int) tex.WrapS] ); } if (sampler.AddressV != tex.WrapT) { tex.WrapT = sampler.AddressV; glTexParameteri( tex.Target, GLenum.GL_TEXTURE_WRAP_T, XNAToGL.Wrap[(int) tex.WrapT] ); } if (sampler.AddressW != tex.WrapR) { tex.WrapR = sampler.AddressW; glTexParameteri( tex.Target, GLenum.GL_TEXTURE_WRAP_R, XNAToGL.Wrap[(int) tex.WrapR] ); } if ( sampler.Filter != tex.Filter || sampler.MaxAnisotropy != tex.Anistropy ) { tex.Filter = sampler.Filter; tex.Anistropy = sampler.MaxAnisotropy; glTexParameteri( tex.Target, GLenum.GL_TEXTURE_MAG_FILTER, XNAToGL.MagFilter[(int) tex.Filter] ); glTexParameteri( tex.Target, GLenum.GL_TEXTURE_MIN_FILTER, tex.HasMipmaps ? XNAToGL.MinMipFilter[(int) tex.Filter] : XNAToGL.MinFilter[(int) tex.Filter] ); glTexParameterf( tex.Target, GLenum.GL_TEXTURE_MAX_ANISOTROPY_EXT, (tex.Filter == TextureFilter.Anisotropic) ? Math.Max(tex.Anistropy, 1.0f) : 1.0f ); } if (sampler.MaxMipLevel != tex.MaxMipmapLevel) { tex.MaxMipmapLevel = sampler.MaxMipLevel; glTexParameteri( tex.Target, GLenum.GL_TEXTURE_BASE_LEVEL, tex.MaxMipmapLevel ); } if (sampler.MipMapLevelOfDetailBias != tex.LODBias && !useES3) { tex.LODBias = sampler.MipMapLevelOfDetailBias; glTexParameterf( tex.Target, GLenum.GL_TEXTURE_LOD_BIAS, tex.LODBias ); } if (index != 0) { // Keep this state sane. -flibit glActiveTexture(GLenum.GL_TEXTURE0); } } #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 (supportsBaseVertex) { baseVertex = 0; } if ( bindingsUpdated || baseVertex != ldBaseVertex || 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 + baseVertex) ); 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]; uint buffer = (bindings[i].VertexBuffer.buffer as OpenGLBuffer).Handle; 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(); ldBaseVertex = baseVertex; 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(OpenGLBuffer.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 != 0 || 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 = 0; 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; ldBaseVertex = -1; } 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 ) { OpenGLBuffer result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif uint handle; glGenBuffers(1, out handle); result = new OpenGLBuffer( handle, (IntPtr) (vertexStride * vertexCount), dynamic ? GLenum.GL_STREAM_DRAW : GLenum.GL_STATIC_DRAW ); BindVertexBuffer(result); glBufferData( GLenum.GL_ARRAY_BUFFER, result.BufferSize, IntPtr.Zero, result.Dynamic ); #if !DISABLE_THREADING }); #endif return result; } public IGLBuffer GenIndexBuffer( bool dynamic, BufferUsage usage, int indexCount, IndexElementSize indexElementSize ) { OpenGLBuffer result = null; #if !DISABLE_THREADING ForceToMainThread(() => { #endif uint handle; glGenBuffers(1, out handle); result = new OpenGLBuffer( handle, (IntPtr) (indexCount * XNAToGL.IndexSize[(int) indexElementSize]), dynamic ? GLenum.GL_STREAM_DRAW : GLenum.GL_STATIC_DRAW ); BindIndexBuffer(result); glBufferData( GLenum.GL_ELEMENT_ARRAY_BUFFER, result.BufferSize, IntPtr.Zero, result.Dynamic ); #if !DISABLE_THREADING }); #endif return result; } #endregion #region glBindBuffer Methods private void BindVertexBuffer(IGLBuffer buffer) { uint handle = (buffer as OpenGLBuffer).Handle; if (handle != currentVertexBuffer) { glBindBuffer(GLenum.GL_ARRAY_BUFFER, handle); currentVertexBuffer = handle; } } private void BindIndexBuffer(IGLBuffer buffer) { uint handle = (buffer as OpenGLBuffer).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 ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif BindVertexBuffer(buffer); OpenGLBuffer glBuffer = (buffer as OpenGLBuffer); if (options == SetDataOptions.Discard) { glBufferData( GLenum.GL_ARRAY_BUFFER, glBuffer.BufferSize, IntPtr.Zero, glBuffer.Dynamic ); } glBufferSubData( GLenum.GL_ARRAY_BUFFER, (IntPtr) offsetInBytes, (IntPtr) dataLength, data ); #if !DISABLE_THREADING }); #endif } public void SetIndexBufferData( IGLBuffer buffer, int offsetInBytes, IntPtr data, int dataLength, SetDataOptions options ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif BindIndexBuffer(buffer); OpenGLBuffer glBuffer = (buffer as OpenGLBuffer); if (options == SetDataOptions.Discard) { glBufferData( GLenum.GL_ELEMENT_ARRAY_BUFFER, glBuffer.BufferSize, IntPtr.Zero, glBuffer.Dynamic ); } glBufferSubData( GLenum.GL_ELEMENT_ARRAY_BUFFER, (IntPtr) offsetInBytes, (IntPtr) dataLength, data ); #if !DISABLE_THREADING }); #endif } #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); } #if !DISABLE_THREADING ForceToMainThread(() => { #endif BindVertexBuffer(buffer); glGetBufferSubData( GLenum.GL_ARRAY_BUFFER, (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 ) { #if !DISABLE_THREADING ForceToMainThread(() => { #endif BindIndexBuffer(buffer); glGetBufferSubData( GLenum.GL_ELEMENT_ARRAY_BUFFER, (IntPtr) offsetInBytes, (IntPtr) (elementCount * elementSizeInBytes), data + (startIndex * elementSizeInBytes) ); #if !DISABLE_THREADING }); #endif } #endregion #region glDeleteBuffers Methods private void DeleteVertexBuffer(IGLBuffer buffer) { uint handle = (buffer as OpenGLBuffer).Handle; if (handle == currentVertexBuffer) { glBindBuffer(GLenum.GL_ARRAY_BUFFER, 0); currentVertexBuffer = 0; } for (int i = 0; i < attributes.Length; i += 1) { if (handle == attributes[i].CurrentBuffer) { // Force the next vertex attrib update! attributes[i].CurrentBuffer = uint.MaxValue; } } glDeleteBuffers(1, ref handle); } private void DeleteIndexBuffer(IGLBuffer buffer) { uint handle = (buffer as OpenGLBuffer).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; glGenTextures(1, out handle); OpenGLTexture result = new OpenGLTexture( handle, target, levelCount ); BindTexture(result); glTexParameteri( result.Target, GLenum.GL_TEXTURE_WRAP_S, XNAToGL.Wrap[(int) result.WrapS] ); glTexParameteri( result.Target, GLenum.GL_TEXTURE_WRAP_T, XNAToGL.Wrap[(int) result.WrapT] ); glTexParameteri( result.Target, GLenum.GL_TEXTURE_WRAP_R, XNAToGL.Wrap[(int) result.WrapR] ); glTexParameteri( result.Target, GLenum.GL_TEXTURE_MAG_FILTER, XNAToGL.MagFilter[(int) result.Filter] ); glTexParameteri( result.Target, GLenum.GL_TEXTURE_MIN_FILTER, result.HasMipmaps ? XNAToGL.MinMipFilter[(int) result.Filter] : XNAToGL.MinFilter[(int) result.Filter] ); glTexParameterf( result.Target, GLenum.GL_TEXTURE_MAX_ANISOTROPY_EXT, (result.Filter == TextureFilter.Anisotropic) ? Math.Max(result.Anistropy, 1.0f) : 1.0f ); glTexParameteri( result.Target, GLenum.GL_TEXTURE_BASE_LEVEL, result.MaxMipmapLevel ); if (!useES3) { glTexParameterf( result.Target, GLenum.GL_TEXTURE_LOD_BIAS, result.LODBias ); } 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 ); GLenum glFormat = XNAToGL.TextureFormat[(int) format]; GLenum glInternalFormat = XNAToGL.TextureInternalFormat[(int) format]; if (glFormat == GLenum.GL_COMPRESSED_TEXTURE_FORMATS) { for (int i = 0; i < levelCount; i += 1) { int levelWidth = Math.Max(width >> i, 1); int levelHeight = Math.Max(height >> i, 1); glCompressedTexImage2D( GLenum.GL_TEXTURE_2D, i, (int) glInternalFormat, levelWidth, levelHeight, 0, ((levelWidth + 3) / 4) * ((levelHeight + 3) / 4) * Texture.GetFormatSize(format), IntPtr.Zero ); } } else { GLenum glType = XNAToGL.TextureDataType[(int) format]; for (int i = 0; i < levelCount; i += 1) { glTexImage2D( GLenum.GL_TEXTURE_2D, i, (int) glInternalFormat, Math.Max(width >> i, 1), Math.Max(height >> i, 1), 0, glFormat, glType, IntPtr.Zero ); } } #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 ); GLenum glFormat = XNAToGL.TextureFormat[(int) format]; GLenum glInternalFormat = XNAToGL.TextureInternalFormat[(int) format]; GLenum glType = XNAToGL.TextureDataType[(int) format]; for (int i = 0; i < levelCount; i += 1) { glTexImage3D( GLenum.GL_TEXTURE_3D, i, (int) glInternalFormat, Math.Max(width >> i, 1), Math.Max(height >> i, 1), Math.Max(depth >> i, 1), 0, glFormat, glType, IntPtr.Zero ); } #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 ); GLenum glFormat = XNAToGL.TextureFormat[(int) format]; GLenum glInternalFormat = XNAToGL.TextureInternalFormat[(int) format]; if (glFormat == GLenum.GL_COMPRESSED_TEXTURE_FORMATS) { for (int i = 0; i < 6; i += 1) { for (int l = 0; l < levelCount; l += 1) { int levelSize = Math.Max(size >> l, 1); glCompressedTexImage2D( GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, l, (int) glInternalFormat, levelSize, levelSize, 0, ((levelSize + 3) / 4) * ((levelSize + 3) / 4) * Texture.GetFormatSize(format), IntPtr.Zero ); } } } else { GLenum glType = XNAToGL.TextureDataType[(int) format]; for (int i = 0; i < 6; i += 1) { for (int l = 0; l < levelCount; l += 1) { int levelSize = Math.Max(size >> l, 1); glTexImage2D( GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, l, (int) glInternalFormat, levelSize, levelSize, 0, glFormat, glType, IntPtr.Zero ); } } } #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 BindTexture(texture); 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 */ glCompressedTexSubImage2D( GLenum.GL_TEXTURE_2D, 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 ); } glTexSubImage2D( GLenum.GL_TEXTURE_2D, 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 BindTexture(texture); glTexSubImage3D( GLenum.GL_TEXTURE_3D, 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 BindTexture(texture); 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 */ glCompressedTexSubImage2D( GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int) cubeMapFace, level, xOffset, yOffset, width, height, XNAToGL.TextureInternalFormat[(int) format], dataLength, data ); } else { glTexSubImage2D( GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int) cubeMapFace, level, xOffset, yOffset, width, height, 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]; BindTexture(tex.texture); glTexSubImage2D( GLenum.GL_TEXTURE_2D, 0, 0, 0, tex.Width, tex.Height, GLenum.GL_ALPHA, 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; } BindTexture(texture); GLenum glFormat = XNAToGL.TextureFormat[(int) format]; if (glFormat == GLenum.GL_COMPRESSED_TEXTURE_FORMATS) { throw new NotImplementedException("GetData, CompressedTexture"); } else if (subX == 0 && subY == 0 && subW == width && subH == height) { // Just throw the whole texture into the user array. glGetTexImage( GLenum.GL_TEXTURE_2D, level, glFormat, XNAToGL.TextureDataType[(int) format], data ); } else { // Get the whole texture... IntPtr texData = Marshal.AllocHGlobal(width * height * elementSizeInBytes); glGetTexImage( GLenum.GL_TEXTURE_2D, level, glFormat, XNAToGL.TextureDataType[(int) format], texData ); // Now, blit the rect region into the user array. int curPixel = -1; for (int row = subY; row < subY + subH; row += 1) { for (int col = subX; col < subX + subW; col += 1) { curPixel += 1; if (curPixel < startIndex) { // If we're not at the start yet, just keep going... continue; } if (curPixel > elementCount) { // If we're past the end, we're done! return; } // FIXME: Can we copy via pitch instead, or something? -flibit SDL.SDL_memcpy( data + ((curPixel - startIndex) * elementSizeInBytes), texData + (((row * width) + col) * elementSizeInBytes), (IntPtr) elementSizeInBytes ); } } Marshal.FreeHGlobal(texData); } #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 ) { throw new NotImplementedException(); } 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 BindTexture(texture); GLenum glFormat = XNAToGL.TextureFormat[(int) format]; if (glFormat == GLenum.GL_COMPRESSED_TEXTURE_FORMATS) { throw new NotImplementedException("GetData, CompressedTexture"); } else if (subX == 0 && subY == 0 && subW == size && subH == size) { // Just throw the whole texture into the user array. glGetTexImage( GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int) cubeMapFace, level, glFormat, XNAToGL.TextureDataType[(int) format], data ); } else { // Get the whole texture... IntPtr texData = Marshal.AllocHGlobal(size * size * elementSizeInBytes); glGetTexImage( GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int) cubeMapFace, level, glFormat, XNAToGL.TextureDataType[(int) format], texData ); // Now, blit the rect region into the user array. int curPixel = -1; for (int row = subY; row < subY + subH; row += 1) { for (int col = subX; col < subX + subW; col += 1) { curPixel += 1; if (curPixel < startIndex) { // If we're not at the start yet, just keep going... continue; } if (curPixel > elementCount) { // If we're past the end, we're done! return; } // FIXME: Can we copy via pitch instead, or something? -flibit SDL.SDL_memcpy( data + ((curPixel - startIndex) * elementSizeInBytes), texData + (((row * size) + col) * elementSizeInBytes), (IntPtr) elementSizeInBytes ); } } Marshal.FreeHGlobal(texData); } #if !DISABLE_THREADING }); #endif } #endregion #region glBindTexture Method private void BindTexture(IGLTexture texture) { OpenGLTexture tex = texture as OpenGLTexture; if (tex.Target != Textures[0].Target) { glBindTexture(Textures[0].Target, 0); } if (tex != Textures[0]) { glBindTexture( tex.Target, tex.Handle ); } Textures[0] = tex; } #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. uint prevDrawBuffer = currentDrawFramebuffer; OpenGLBackbuffer glBack = Backbuffer as OpenGLBackbuffer; if (glBack.Texture == 0) { glGenTextures(1, out glBack.Texture); glBindTexture(GLenum.GL_TEXTURE_2D, glBack.Texture); glTexImage2D( GLenum.GL_TEXTURE_2D, 0, (int) GLenum.GL_RGBA, glBack.Width, glBack.Height, 0, GLenum.GL_RGBA, GLenum.GL_UNSIGNED_BYTE, IntPtr.Zero ); glBindTexture(Textures[0].Target, Textures[0].Handle); } BindFramebuffer(resolveFramebufferDraw); glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_TEXTURE_2D, glBack.Texture, 0 ); BindReadFramebuffer(glBack.Handle); glBlitFramebuffer( 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! */ BindDrawFramebuffer(prevDrawBuffer); 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 && !useES3) { return false; } uint prevReadBuffer = currentReadFramebuffer; uint prevWriteBuffer = currentDrawFramebuffer; if (texUnbound) { BindFramebuffer(resolveFramebufferRead); glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_TEXTURE_2D, (texture as OpenGLTexture).Handle, level ); } else { 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 ); if (texUnbound) { if (prevReadBuffer == prevWriteBuffer) { BindFramebuffer(prevReadBuffer); } else { BindReadFramebuffer(prevReadBuffer); BindDrawFramebuffer(prevWriteBuffer); } } else { BindReadFramebuffer(prevReadBuffer); } return true; } #endregion #region RenderTarget->Texture Method public void ResolveTarget(RenderTargetBinding target) { if ((target.RenderTarget as IRenderTarget).MultiSampleCount > 0) { uint prevBuffer = currentDrawFramebuffer; // Set up the texture framebuffer GLenum textureTarget; int width, height; if (target.RenderTarget is RenderTarget2D) { textureTarget = GLenum.GL_TEXTURE_2D; Texture2D target2D = (target.RenderTarget as Texture2D); width = target2D.Width; height = target2D.Height; } else { textureTarget = GLenum.GL_TEXTURE_CUBE_MAP_POSITIVE_X + (int) target.CubeMapFace; TextureCube targetCube = (target.RenderTarget as TextureCube); width = targetCube.Size; height = targetCube.Size; } BindFramebuffer(resolveFramebufferDraw); glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, textureTarget, (target.RenderTarget.texture as OpenGLTexture).Handle, 0 ); // Set up the renderbuffer framebuffer BindFramebuffer(resolveFramebufferRead); glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, ((target.RenderTarget as IRenderTarget).ColorBuffer as OpenGLRenderbuffer).Handle ); // Blit! if (scissorTestEnable) { glDisable(GLenum.GL_SCISSOR_TEST); } BindDrawFramebuffer(resolveFramebufferDraw); glBlitFramebuffer( 0, 0, width, height, 0, 0, width, height, GLenum.GL_COLOR_BUFFER_BIT, GLenum.GL_LINEAR ); /* Invalidate the MSAA buffer */ if (supportsFBOInvalidation) { glInvalidateFramebuffer( GLenum.GL_READ_FRAMEBUFFER, attachments.Length + 2, drawBuffersArray ); } if (scissorTestEnable) { glEnable(GLenum.GL_SCISSOR_TEST); } BindFramebuffer(prevBuffer); } // If the target has mipmaps, regenerate them now if (target.RenderTarget.LevelCount > 1) { OpenGLTexture prevTex = Textures[0]; BindTexture(target.RenderTarget.texture); glGenerateMipmap((target.RenderTarget.texture as OpenGLTexture).Target); BindTexture(prevTex); } } #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 glGenRenderbuffers(1, out handle); glBindRenderbuffer( GLenum.GL_RENDERBUFFER, handle ); if (multiSampleCount > 0) { glRenderbufferStorageMultisample( GLenum.GL_RENDERBUFFER, multiSampleCount, XNAToGL.TextureInternalFormat[(int) format], width, height ); } else { glRenderbufferStorage( GLenum.GL_RENDERBUFFER, XNAToGL.TextureInternalFormat[(int) format], width, height ); } glBindRenderbuffer( GLenum.GL_RENDERBUFFER, realBackbufferRBO ); #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 glGenRenderbuffers(1, out handle); glBindRenderbuffer( GLenum.GL_RENDERBUFFER, handle ); if (multiSampleCount > 0) { glRenderbufferStorageMultisample( GLenum.GL_RENDERBUFFER, multiSampleCount, XNAToGL.DepthStorage[(int) format], width, height ); } else { glRenderbufferStorage( GLenum.GL_RENDERBUFFER, XNAToGL.DepthStorage[(int) format], width, height ); } glBindRenderbuffer( GLenum.GL_RENDERBUFFER, realBackbufferRBO ); #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 : realBackbufferFBO ); 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 ) { glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, GLenum.GL_RENDERBUFFER, 0 ); } else if ( attachmentTypes[i] == GLenum.GL_RENDERBUFFER && currentAttachmentTypes[i] != GLenum.GL_RENDERBUFFER ) { glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, currentAttachmentTypes[i], 0, 0 ); } } if (attachmentTypes[i] == GLenum.GL_RENDERBUFFER) { glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, GLenum.GL_RENDERBUFFER, attachments[i] ); } else { glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, attachmentTypes[i], attachments[i], 0 ); } currentAttachments[i] = attachments[i]; currentAttachmentTypes[i] = attachmentTypes[i]; } else if (attachmentTypes[i] != currentAttachmentTypes[i]) { // Texture cube face change! glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, attachmentTypes[i], attachments[i], 0 ); currentAttachmentTypes[i] = attachmentTypes[i]; } } while (i < currentAttachments.Length) { if (currentAttachments[i] != 0) { if (currentAttachmentTypes[i] == GLenum.GL_RENDERBUFFER) { glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, GLenum.GL_RENDERBUFFER, 0 ); } else { glFramebufferTexture2D( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0 + i, currentAttachmentTypes[i], 0, 0 ); } currentAttachments[i] = 0; currentAttachmentTypes[i] = GLenum.GL_TEXTURE_2D; } i += 1; } if (renderTargets.Length != currentDrawBuffers) { glDrawBuffers(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) { glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, 0 ); } currentDepthStencilFormat = depthFormat; glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, handle ); if (currentDepthStencilFormat == DepthFormat.Depth24Stencil8) { glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, 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_ALPHA, // 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_RGB8, // 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_ALPHA, // 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 }; 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 OpenGLDevice glDevice; public OpenGLBackbuffer( OpenGLDevice 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.glGenFramebuffers(1, out handle); Handle = handle; glDevice.BindFramebuffer(Handle); // Create and attach the color buffer glDevice.glGenRenderbuffers(1, out colorAttachment); glDevice.glBindRenderbuffer(GLenum.GL_RENDERBUFFER, colorAttachment); if (multiSampleCount > 0) { glDevice.glRenderbufferStorageMultisample( GLenum.GL_RENDERBUFFER, multiSampleCount, GLenum.GL_RGBA8, width, height ); } else { glDevice.glRenderbufferStorage( GLenum.GL_RENDERBUFFER, GLenum.GL_RGBA8, width, height ); } glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, colorAttachment ); if (depthFormat == DepthFormat.None) { // Don't bother creating a depth/stencil buffer. depthStencilAttachment = 0; // Keep this state sane. glDevice.glBindRenderbuffer( GLenum.GL_RENDERBUFFER, glDevice.realBackbufferRBO ); return; } // Create and attach the depth/stencil buffer glDevice.glGenRenderbuffers(1, out depthStencilAttachment); glDevice.glBindRenderbuffer(GLenum.GL_RENDERBUFFER, depthStencilAttachment); if (multiSampleCount > 0) { glDevice.glRenderbufferStorageMultisample( GLenum.GL_RENDERBUFFER, multiSampleCount, XNAToGL.DepthStorage[(int) depthFormat], width, height ); } else { glDevice.glRenderbufferStorage( GLenum.GL_RENDERBUFFER, XNAToGL.DepthStorage[(int) depthFormat], width, height ); } glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); if (depthFormat == DepthFormat.Depth24Stencil8) { glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); } // Keep this state sane. glDevice.glBindRenderbuffer( GLenum.GL_RENDERBUFFER, glDevice.realBackbufferRBO ); } public void Dispose() { uint handle = Handle; glDevice.BindFramebuffer(glDevice.realBackbufferFBO); 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; } if (glDevice.renderTargetBound) { glDevice.glBindFramebuffer( GLenum.GL_FRAMEBUFFER, Handle ); } // Detach color attachment glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_COLOR_ATTACHMENT0, GLenum.GL_RENDERBUFFER, 0 ); // Detach depth/stencil attachment, if applicable if (depthStencilAttachment != 0) { glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, 0 ); if (DepthFormat == DepthFormat.Depth24Stencil8) { glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, 0 ); } } // Update our color attachment to the new resolution. glDevice.glBindRenderbuffer( GLenum.GL_RENDERBUFFER, colorAttachment ); if (MultiSampleCount > 0) { glDevice.glRenderbufferStorageMultisample( GLenum.GL_RENDERBUFFER, MultiSampleCount, GLenum.GL_RGBA8, Width, Height ); } else { glDevice.glRenderbufferStorage( GLenum.GL_RENDERBUFFER, GLenum.GL_RGBA8, Width, Height ); } glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, 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.glGenRenderbuffers( 1, out depthStencilAttachment ); } // Update the depth/stencil buffer, if applicable if (depthStencilAttachment != 0) { glDevice.glBindRenderbuffer( GLenum.GL_RENDERBUFFER, depthStencilAttachment ); if (MultiSampleCount > 0) { glDevice.glRenderbufferStorageMultisample( GLenum.GL_RENDERBUFFER, MultiSampleCount, XNAToGL.DepthStorage[(int)depthFormat], Width, Height ); } else { glDevice.glRenderbufferStorage( GLenum.GL_RENDERBUFFER, XNAToGL.DepthStorage[(int)depthFormat], Width, Height ); } glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_DEPTH_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); if (depthFormat == DepthFormat.Depth24Stencil8) { glDevice.glFramebufferRenderbuffer( GLenum.GL_FRAMEBUFFER, GLenum.GL_STENCIL_ATTACHMENT, GLenum.GL_RENDERBUFFER, depthStencilAttachment ); } } DepthFormat = depthFormat; if (glDevice.renderTargetBound) { glDevice.glBindFramebuffer( GLenum.GL_FRAMEBUFFER, glDevice.targetFramebuffer ); } // Keep this state sane. glDevice.glBindRenderbuffer( GLenum.GL_RENDERBUFFER, glDevice.realBackbufferRBO ); } } #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 } }