GLSLEffect.cs 7.92 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
232
233
234
235

			IntPtr data = unifDataBacking;
			for (int i = 0; i < unifs.Length; ++i) {
				var u = unifs[i];
				//var p = Parameters[i];

				SetUniform(ref u, data);

				data = (IntPtr)((long)data + u.bytesize);
			}
pcy's avatar
pcy committed
236
237
		}

238
239
240
241
		// 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
242
243
244
		public ActiveEffect Bind() { // TODO: how to warn on discarding the retval?
			GL.GetError(); // ignore errors from FNA/mojoshader

245
246
247
248
			// workaround FNA crap
			CurrentTechnique = fakeTech;
			fakeTech.Passes[0].Apply();

249
250
251
252
253
254
			uint oldprgm = unchecked((uint)GL.GetIntegerv(GL.CURRENT_PROGRAM));
			GL.Throw();

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

255
			ApplyUnifs();
256
257
258
259
260
261

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

pcy's avatar
initial  
pcy committed
262
		void DelShdrPrgm() {
pcy's avatar
pcy committed
263
			for (int i = 0; glshdrs != null && i < glshdrs.Length; ++i)
264
265
				if (glshdrs[i] != 0)
					GL.DeleteShader(glshdrs[i]);
pcy's avatar
pcy committed
266
			glshdrs = null;
pcy's avatar
initial  
pcy committed
267

268
269
			if (glprgm != 0)
				GL.DeleteProgram(glprgm);
pcy's avatar
initial  
pcy committed
270
271
272
273
274
		}

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

276
277
278
279
280
281
282
283
284
				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
285
286
287
288
289
290
291
			}

			base.Dispose(disposing);
		}
	}
}