using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; 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 { struct UnifData { public int location; public string name; public GLSLType type; public int size; public int rows, cols; public EffectParameterClass epclass; public EffectParameterType eptype; public uint bytesize; public UnifData(int loc, string n, GLSLType t, int sz, int r, int c, EffectParameterClass epc, EffectParameterType ept, uint bsz) { location = loc; name = n; type = t; size = sz; rows = r; cols = c; epclass = epc; eptype = ept; bytesize = bsz; } } public partial 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; IntPtr unifDataBacking; UnifData[] unifs; //readonly static List emptyEffTechList = new List(); public unsafe GLSLEffect(GraphicsDevice gd, IDictionary shaders) : base(/*UGLY hack*/new BasicEffect(gd)) { fakeTech = Techniques[0]; unifDataBacking = IntPtr.Zero; // 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(); GL.ShaderSource(sh, kvp.Value); 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 ; } } } readonly static EffectParameterCollection emptyParamColl = ReflUtil.CreateParamColl(new List()); readonly static EffectAnnotationCollection emptyAnnotColl = ReflUtil.CreateAnnotColl(new List()); void ParseAttribUnif() { /*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 nunifs = GL.GetProgramiv(glprgm, GL.ACTIVE_UNIFORMS); GL.Throw(); unifs = new UnifData[nunifs]; var parms = new List(); uint totalsize = 0; //Console.WriteLine("#unifs = " + nunifs); for (int i = 0; i < nunifs; ++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(); uint bytesize = unchecked((uint)(typ2size[type]*size)); unifs[i] = new UnifData(loc, name, type, size, GetRows(type), GetCols(type), typ2class[type], typ2type[type], bytesize); totalsize += bytesize; } IntPtr bleh = Marshal.AllocHGlobal((IntPtr)totalsize); bool ok = false; try { uint sizepos = 0; for (int i = 0; i < nunifs; ++i) { var u = unifs[i]; IntPtr bps = (IntPtr)((long)bleh + sizepos); parms.Add(ReflUtil.CreateParameter(u.name, null/*semantic: ???*/, u.rows, u.cols, (u.size == 1) ? 0 : u.size, u.epclass, u.eptype, null, null, //emptyParamColl, emptyAnnotColl, bps, u.bytesize)); sizepos += u.bytesize; } ok = true; } finally { if (ok) { unifDataBacking = bleh; this.SetParameters(ReflUtil.CreateParamColl(parms)); } else { Marshal.FreeHGlobal(bleh); unifDataBacking = IntPtr.Zero; } } } void ApplyUnifs() { // TODO } // 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(); ApplyUnifs(); 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(); if (fakeTech != null) { this.SetTechniques(ReflUtil.CreateTechColl(new[]{fakeTech}.ToList())); fakeTech = null; } if (unifDataBacking != IntPtr.Zero) { Marshal.FreeHGlobal(unifDataBacking); unifDataBacking = IntPtr.Zero; } } base.Dispose(disposing); } } }