GLSLEffect.cs 7.71 KB
Newer Older
pcy's avatar
initial  
pcy committed
1
2
3
using System;
using System.Collections.Generic;
using System.Linq;
4
using System.Runtime.InteropServices;
pcy's avatar
initial  
pcy committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
19
20
21
 *       -> set up uniforms and vertex arrays!
 *       --> circumvent? --> HOW??
 *       --> just go with it?? --> glProgramReady ok, glProgramViewportInfo might segfault!
22
 *
pcy's avatar
initial  
pcy committed
23
24
 * * uniforms
 * * samplers, textures
25
26
27
 *   -> 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!
pcy's avatar
initial  
pcy committed
28
29
30
 */

namespace PoroCYon.FNAGLSL {
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
	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 {
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
		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;
			}
		}

pcy's avatar
initial  
pcy committed
67
68
69
		uint[] glshdrs;
		uint glprgm;

70
71
		EffectTechnique fakeTech;

72
73
74
		IntPtr unifDataBacking;
		UnifData[] unifs;

pcy's avatar
pcy committed
75
		//readonly static List<EffectTechnique> emptyEffTechList = new List<EffectTechnique>();
pcy's avatar
initial  
pcy committed
76
77
78
		public unsafe GLSLEffect(GraphicsDevice gd,
					IDictionary<GLSLPurpose, string> shaders)
				: base(/*UGLY hack*/new BasicEffect(gd)) {
79
			fakeTech = Techniques[0];
80
			unifDataBacking = IntPtr.Zero;
81
82
83
84

			// cause NullRefExns when dumb code would try to access the techns
			this.SetTechniques(null/*ReflUtil.CreateTechColl(emptyEffTechList)*/);

pcy's avatar
initial  
pcy committed
85
			GL.Init(gd);
pcy's avatar
pcy committed
86
			GL.GetError(); // ignore errors from FNA/mojoshader
pcy's avatar
initial  
pcy committed
87

88
89
90
91
			// 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);
92
93
			this.SetGLEffectData(IntPtr.Zero);
			this.SetEffectData  (IntPtr.Zero);*/
94
95
96
97
98
99
			// 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.
pcy's avatar
initial  
pcy committed
100
101
102
103
104
105
106
107
108
109
110
111
112
113

			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();

pcy's avatar
pcy committed
114
					GL.ShaderSource(sh, kvp.Value);
pcy's avatar
initial  
pcy committed
115
116
117
118
119
120
					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);
pcy's avatar
pcy committed
121
122
123
						var msg = GL.GetShaderInfoLog(sh, len);
						throw new GLSLCompileException("Failed to compile "
								+ kvp.Key + ": " + msg);
pcy's avatar
initial  
pcy committed
124
125
126
127
128
129
130
131
132
133
134
					}

					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);
pcy's avatar
pcy committed
135
136
					var msg = GL.GetProgramInfoLog(prgm, len);
					throw new GLSLLinkException("Failed to link shader: " + msg);
pcy's avatar
initial  
pcy committed
137
138
				}

pcy's avatar
pcy committed
139
140
141
142
143
				glshdrs = shdrs;
				glprgm  = prgm ;

				ParseAttribUnif();

pcy's avatar
initial  
pcy committed
144
145
146
147
148
149
150
151
152
153
154
155
				ok = true;
			}
			finally {
				if (!ok) {
					DelShdrPrgm();
				} else {
					glshdrs = shdrs;
					glprgm  = prgm ;
				}
			}
		}

156
157
158
159
		readonly static EffectParameterCollection  emptyParamColl
			= ReflUtil.CreateParamColl(new List<EffectParameter >());
		readonly static EffectAnnotationCollection emptyAnnotColl
			= ReflUtil.CreateAnnotColl(new List<EffectAnnotation>());
pcy's avatar
pcy committed
160
		void ParseAttribUnif() {
161
			/*int attrs = GL.GetProgramiv(glprgm, GL.ACTIVE_ATTRIBUTES);
pcy's avatar
pcy committed
162
163
164
165
166
167
168
169
170
171
172
173
			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+"}");
174
			}*/
pcy's avatar
pcy committed
175

176
			int nunifs = GL.GetProgramiv(glprgm, GL.ACTIVE_UNIFORMS);
pcy's avatar
pcy committed
177
			GL.Throw();
178
179
180
181
182
			unifs = new UnifData[nunifs];
			var parms = new List<EffectParameter>();
			uint totalsize = 0;
			//Console.WriteLine("#unifs = " + nunifs);
			for (int i = 0; i < nunifs; ++i) {
pcy's avatar
pcy committed
183
184
185
186
187
188
189
				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();

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
				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;
				}
pcy's avatar
pcy committed
221
			}
222
		}
pcy's avatar
pcy committed
223

224
225
		void ApplyUnifs() {
			// TODO
pcy's avatar
pcy committed
226
227
		}

228
229
230
231
		// TODO: find out a better (i.e. more API-conform) way to do this!
		///<summary>
		///USAGE: using (var eff &eq; glslEffect.Bind()) { ... gd.DrawPrimitives(...); ... }
		///</summary>
pcy's avatar
pcy committed
232
233
234
		public ActiveEffect Bind() { // TODO: how to warn on discarding the retval?
			GL.GetError(); // ignore errors from FNA/mojoshader

235
236
237
238
			// workaround FNA crap
			CurrentTechnique = fakeTech;
			fakeTech.Passes[0].Apply();

239
240
241
242
243
244
			uint oldprgm = unchecked((uint)GL.GetIntegerv(GL.CURRENT_PROGRAM));
			GL.Throw();

			GL.UseProgram(glprgm);
			GL.Throw();

245
			ApplyUnifs();
246
247
248
249
250
251

			return new ActiveEffect(oldprgm);
		}
		[Obsolete("Please use Bind() instead")]
		public ActiveEffect Apply() { return Bind(); }

pcy's avatar
initial  
pcy committed
252
		void DelShdrPrgm() {
pcy's avatar
pcy committed
253
			for (int i = 0; glshdrs != null && i < glshdrs.Length; ++i)
254
255
				if (glshdrs[i] != 0)
					GL.DeleteShader(glshdrs[i]);
pcy's avatar
pcy committed
256
			glshdrs = null;
pcy's avatar
initial  
pcy committed
257

258
259
			if (glprgm != 0)
				GL.DeleteProgram(glprgm);
pcy's avatar
initial  
pcy committed
260
261
262
263
264
		}

		protected override void Dispose(bool disposing) {
			if (!IsDisposed) {
				DelShdrPrgm();
265

266
267
268
269
270
271
272
273
274
				if (fakeTech != null) {
					this.SetTechniques(ReflUtil.CreateTechColl(new[]{fakeTech}.ToList()));
					fakeTech = null;
				}

				if (unifDataBacking != IntPtr.Zero) {
					Marshal.FreeHGlobal(unifDataBacking);
					unifDataBacking = IntPtr.Zero;
				}
pcy's avatar
initial  
pcy committed
275
276
277
278
279
280
281
			}

			base.Dispose(disposing);
		}
	}
}