using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; /* TODO: * * apply * * HOW???? * * OnApply: protecteed internal :( * * INTERNAL_applyEffect: internal... * + calls mojoshader! --> hijack?!! * -> glEffect{Begin,End}: state mgmt | unbind shader! * -> glEffect{Begin,End}Pass: bind shader! | state mgmt * * GD draw calls: ok * * EXCEPT mojoshader: glProgramReady, glProgramViewportInfo * -> set up uniforms and vertex arrays! * --> circumvent? --> HOW?? * --> just go with it?? --> glProgramReady ok, glProgramViewportInfo might segfault! * * * uniforms * * samplers, textures * -> https://www.khronos.org/opengl/wiki/Program_Introspection * -> https://stackoverflow.com/questions/440144/in-opengl-is-there-a-way-to-get-a-list-of-all-uniforms-attribs-used-by-a-shade#442819 * * vertex format verification! */ namespace PoroCYon.FNAGLSL { public class GLSLEffect : Effect { public struct ActiveEffect : IDisposable { internal uint oldprgm; bool disposed; internal ActiveEffect(uint old) { oldprgm = old; disposed = false; } public void Dispose() { if (disposed) return; GL.UseProgram(oldprgm); oldprgm = 0; disposed = true; } } uint[] glshdrs; uint glprgm; EffectTechnique fakeTech; //readonly static List emptyEffTechList = new List(); public unsafe GLSLEffect(GraphicsDevice gd, IDictionary shaders) : base(/*UGLY hack*/new BasicEffect(gd)) { fakeTech = Techniques[0]; // cause NullRefExns when dumb code would try to access the techns this.SetTechniques(null/*ReflUtil.CreateTechColl(emptyEffTechList)*/); GL.Init(gd); GL.GetError(); // ignore errors from FNA/mojoshader // this basically makes FNA think this effect is a noop, so binding // this effect will do the same as binding the 0 program in OpenGL, // so the BasicEffect used won't do much anymore /*gd.DeleteEffect(this); this.SetGLEffectData(IntPtr.Zero); this.SetEffectData (IntPtr.Zero);*/ // turns out we can't do this, or libmojoshader will segfault // because it lacks some null checking in // glEffectEnd, glEffectBeginPass, glProgramViewportInfo :/ ... // so we basically have to make sure the usual route of Apply()ing // the shader happens (unless we can circumvent this *somehow*), // and then apply the shader manually. var shdrs = new uint[shaders.Count]; bool ok = false; uint prgm = 0; try { prgm = GL.CreateProgram(); if (prgm == 0) GL.Throw(); int i = 0; foreach (var kvp in shaders) { uint sh = GL.CreateShader(kvp.Key); if (sh == 0) GL.Throw(); byte[] utf8src = Encoding.UTF8.GetBytes(kvp.Value); fixed (byte* sbuf = utf8src) { sbyte** uurgh = stackalloc sbyte*[1]; *uurgh = (sbyte*)sbuf; int* uurgh2 = stackalloc int[1]; *uurgh2 = utf8src.Length; GL.ShaderSource(sh, 1, uurgh, uurgh2); } GL.Throw(); GL.CompileShader(sh); if (GL.GetError() != 0 || GL.GetShaderiv(sh, GL.COMPILE_STATUS) == 0) { int len = GL.GetShaderiv(sh, GL.INFO_LOG_LENGTH); var msg = GL.GetShaderInfoLog(sh, len); throw new GLSLCompileException("Failed to compile " + kvp.Key + ": " + msg); } GL.AttachShader(prgm, sh); GL.Throw(); shdrs[i++] = sh; } GL.LinkProgram(prgm); if (GL.GetError() != 0 || GL.GetProgramiv(prgm, GL.LINK_STATUS) == 0) { int len = GL.GetProgramiv(prgm, GL.INFO_LOG_LENGTH); var msg = GL.GetProgramInfoLog(prgm, len); throw new GLSLLinkException("Failed to link shader: " + msg); } glshdrs = shdrs; glprgm = prgm ; ParseAttribUnif(); ok = true; } finally { if (!ok) { DelShdrPrgm(); } else { glshdrs = shdrs; glprgm = prgm ; } } } void ParseAttribUnif() { // TODO: build EffectParameter stuff int attrs = GL.GetProgramiv(glprgm, GL.ACTIVE_ATTRIBUTES); GL.Throw(); Console.WriteLine("#attrs = " + attrs); for (int i = 0; i < attrs; ++i) { int size; GLSLType type; string name; GL.GetActiveAttrib(glprgm, unchecked((uint)i), out size, out type, out name); GL.Throw(); int loc = GL.GetAttribLocation(glprgm, name); GL.Throw(); Console.WriteLine("attrs["+i+"] = {size=" + size + ", type=" + type + ", name=" + name + ", loc="+loc+"}"); } int unifs = GL.GetProgramiv(glprgm, GL.ACTIVE_UNIFORMS); GL.Throw(); Console.WriteLine("#unifs = " + unifs); for (int i = 0; i < unifs; ++i) { int size; GLSLType type; string name; GL.GetActiveUniform(glprgm, unchecked((uint)i), out size, out type, out name); GL.Throw(); int loc = GL.GetUniformLocation(glprgm, name); GL.Throw(); Console.WriteLine("unifs["+i+"] = {size=" + size + ", type=" + type + ", name=" + name + ", loc="+loc+"}"); } /* #attrs = 1 * attrs[0] = {size=1, type=FLOAT_VEC3, name=in_pos, loc=0} * #unifs = 2 * unifs[0] = {size=1, type=FLOAT, name=fGlobalTime, loc=0} * unifs[1] = {size=1, type=FLOAT_VEC2, name=v2Resolution, loc=1} */ } // TODO: find out a better (i.e. more API-conform) way to do this! /// ///USAGE: using (var eff &eq; glslEffect.Bind()) { ... gd.DrawPrimitives(...); ... } /// public ActiveEffect Bind() { // TODO: how to warn on discarding the retval? GL.GetError(); // ignore errors from FNA/mojoshader // workaround FNA crap CurrentTechnique = fakeTech; fakeTech.Passes[0].Apply(); uint oldprgm = unchecked((uint)GL.GetIntegerv(GL.CURRENT_PROGRAM)); GL.Throw(); GL.UseProgram(glprgm); GL.Throw(); // TODO: apply uniforms and samplers! (XNA API -> OpenGL state) return new ActiveEffect(oldprgm); } [Obsolete("Please use Bind() instead")] public ActiveEffect Apply() { return Bind(); } void DelShdrPrgm() { for (int i = 0; glshdrs != null && i < glshdrs.Length; ++i) if (glshdrs[i] != 0) GL.DeleteShader(glshdrs[i]); glshdrs = null; if (glprgm != 0) GL.DeleteProgram(glprgm); } protected override void Dispose(bool disposing) { if (!IsDisposed) { DelShdrPrgm(); this.SetTechniques(ReflUtil.CreateTechColl(new[]{fakeTech}.ToList())); fakeTech = null; } base.Dispose(disposing); } } }