GLSLEffect.cs 8.62 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
 * * samplers, textures
24
25
26
 *   -> 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
27
28
29
 */

namespace PoroCYon.FNAGLSL {
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
	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 {
48
49
		public struct ActiveEffect : IDisposable {
			internal uint oldprgm;
pcy's avatar
pcy committed
50
			internal List<uint> texs;
51
52
			bool disposed;

pcy's avatar
pcy committed
53
54
			internal ActiveEffect(uint old, List<uint> ts) {
				oldprgm = old; disposed = false; texs = ts;
55
56
57
58
59
60
61
62
			}

			public void Dispose() {
				if (disposed) return;

				GL.UseProgram(oldprgm);
				oldprgm = 0;

pcy's avatar
pcy committed
63
64
65
66
67
68
				// TODO: release textures
				/*for (int i = 0; i < texs.Length; ++i) {
					
				}*/
				texs.Clear();

69
70
71
72
				disposed = true;
			}
		}

pcy's avatar
initial  
pcy committed
73
74
75
		uint[] glshdrs;
		uint glprgm;

76
77
		EffectTechnique fakeTech;

78
79
80
		IntPtr unifDataBacking;
		UnifData[] unifs;

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

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

pcy's avatar
initial  
pcy committed
91
			GL.Init(gd);
pcy's avatar
pcy committed
92
			GL.GetError(); // ignore errors from FNA/mojoshader
pcy's avatar
initial  
pcy committed
93

94
95
96
97
			// 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);
98
99
			this.SetGLEffectData(IntPtr.Zero);
			this.SetEffectData  (IntPtr.Zero);*/
100
101
102
103
104
105
			// 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119

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

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

pcy's avatar
pcy committed
145
146
147
148
149
				glshdrs = shdrs;
				glprgm  = prgm ;

				ParseAttribUnif();

pcy's avatar
initial  
pcy committed
150
151
152
153
154
155
156
157
158
159
160
161
				ok = true;
			}
			finally {
				if (!ok) {
					DelShdrPrgm();
				} else {
					glshdrs = shdrs;
					glprgm  = prgm ;
				}
			}
		}

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

182
			int nunifs = GL.GetProgramiv(glprgm, GL.ACTIVE_UNIFORMS);
pcy's avatar
pcy committed
183
			GL.Throw();
184
185
186
187
188
			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
189
190
191
192
193
194
195
				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();

196
197
198
199
200
201
202
				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);
pcy's avatar
pcy committed
203
			ILUtil.Initblk(bleh, 0, (IntPtr)totalsize);
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
			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
228
			}
229
		}
pcy's avatar
pcy committed
230

pcy's avatar
pcy committed
231
		List<uint> usedTextures = new List<uint>();
232
		void ApplyUnifs() {
pcy's avatar
pcy committed
233
234
235
			usedTextures.Clear();
			// TODO: get active texture to restore later -> glGetIntegerv(GL_ACTIVE_TEXTURE)
			// TODO: make list to hold used texture slots
pcy's avatar
pcy committed
236
237
238
239

			IntPtr data = unifDataBacking;
			for (int i = 0; i < unifs.Length; ++i) {
				var u = unifs[i];
pcy's avatar
pcy committed
240
241
242
243
244
245
246
247
248
249
250
				var p = Parameters[i];

				// TODO:
				/*if (it's a texture) {
					find empty texture slot, put texture in it
					-> GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
					-> GL_TEXTURE_BINDING_[123]D / _BINDING_CUBE_MAP
					bind uniform to texture slot
					add texture slot to the used slot list
				}
				else*/ SetUniform(ref u, data);
pcy's avatar
pcy committed
251
252
253

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

			// TODO: restore active texture
pcy's avatar
pcy committed
256
257
		}

258
259
260
261
		// 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
262
263
264
		public ActiveEffect Bind() { // TODO: how to warn on discarding the retval?
			GL.GetError(); // ignore errors from FNA/mojoshader

265
266
267
268
			// workaround FNA crap
			CurrentTechnique = fakeTech;
			fakeTech.Passes[0].Apply();

269
270
271
272
273
274
			uint oldprgm = unchecked((uint)GL.GetIntegerv(GL.CURRENT_PROGRAM));
			GL.Throw();

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

275
			ApplyUnifs();
276

pcy's avatar
pcy committed
277
			return new ActiveEffect(oldprgm, usedTextures);
278
279
280
281
		}
		[Obsolete("Please use Bind() instead")]
		public ActiveEffect Apply() { return Bind(); }

pcy's avatar
initial  
pcy committed
282
		void DelShdrPrgm() {
pcy's avatar
pcy committed
283
			for (int i = 0; glshdrs != null && i < glshdrs.Length; ++i)
284
285
				if (glshdrs[i] != 0)
					GL.DeleteShader(glshdrs[i]);
pcy's avatar
pcy committed
286
			glshdrs = null;
pcy's avatar
initial  
pcy committed
287

288
289
			if (glprgm != 0)
				GL.DeleteProgram(glprgm);
pcy's avatar
initial  
pcy committed
290
291
292
293
294
		}

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

296
297
298
299
300
301
302
303
304
				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
305
306
307
308
309
310
311
			}

			base.Dispose(disposing);
		}
	}
}