Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/src/FNAPlatform/OpenGLDevice.cs
4973 lines | 119.5 KiB | text/x-csharp | CSharpLexer
4973 lines | 119.5 KiB | text/x-csharp | CSharpLexer
r0 | #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<IGLTexture> GCTextures = new ConcurrentQueue<IGLTexture>(); | ||||
private ConcurrentQueue<IGLRenderbuffer> GCDepthBuffers = new ConcurrentQueue<IGLRenderbuffer>(); | ||||
private ConcurrentQueue<IGLBuffer> GCVertexBuffers = new ConcurrentQueue<IGLBuffer>(); | ||||
private ConcurrentQueue<IGLBuffer> GCIndexBuffers = new ConcurrentQueue<IGLBuffer>(); | ||||
private ConcurrentQueue<IGLEffect> GCEffects = new ConcurrentQueue<IGLEffect>(); | ||||
private ConcurrentQueue<IGLQuery> GCQueries = new ConcurrentQueue<IGLQuery>(); | ||||
#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); | ||||
} | ||||
/// <summary> | ||||
/// Attempts to read the texture data directly from the FBO using glReadPixels | ||||
/// </summary> | ||||
/// <typeparam name="T">Texture data type</typeparam> | ||||
/// <param name="texture">The texture to read from</param> | ||||
/// <param name="width">The texture width</param> | ||||
/// <param name="height">The texture height</param> | ||||
/// <param name="level">The texture level</param> | ||||
/// <param name="data">The texture data array</param> | ||||
/// <param name="rect">The portion of the image to read from</param> | ||||
/// <returns>True if we successfully read the texture data</returns> | ||||
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<Action> actions = new System.Collections.Generic.List<Action>(); | ||||
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 | ||||
} | ||||
} | ||||