1 /**
2  * <div class="header">
3  * High-level OpenGL Wrapper/Helpers: Core Package
4  * Authors: S.Percentage
5  * </div>
6  */
7  /**
8 Macros:
9  COPYRIGHT = Copyright 2016 S.Percentage
10  DDOC = <!DOCTYPE html>
11 <html><head>
12 <meta charset="utf-8">
13 <title>$(TITLE)</title>
14 <link rel="stylesheet" href="style.css" type="text/css" />
15 </head><body>
16 <div class="box">
17 <h1>$(TITLE)</h1>
18 $(BODY)
19 <hr>$(SMALL Page generated by $(LINK2 https://dlang.org/ddoc.html, Ddoc). $(COPYRIGHT))
20 </div>
21 </body></html>
22 */
23 module objectivegl.core;
24 
25 // Version Have_dglsl: Enabled dglsl support if required
26 
27 public import derelict.opengl3.gl3;
28 import std..string, std.algorithm, std.range, std.meta, std.traits, std.typecons;
29 version(Have_dglsl)
30 {
31 	import dglsl;
32 }
33 
34 /// Pixel Format for Textures
35 enum PixelFormat
36 {
37 	/// 32bpp full color
38 	RGBA = GL_RGBA,
39 	/// 8bpp single color
40 	Grayscale = GL_RED
41 }
42 
43 /// OpenGL Texture Interfacing
44 abstract class Texture(GLenum TextureType)
45 {
46 	protected GLuint id;
47 	
48 	protected this() { glGenTextures(1, &this.id); }
49 	~this() { glDeleteTextures(1, &this.id); }
50 	
51 	protected void bind() { glBindTexture(TextureType, this.id); }
52 	static class Parameter
53 	{
54 		@disable this();
55 		
56 		static auto opIndexAssign(GLint value, GLenum param) { glTexParameteri(TextureType, param, value); }
57 	}
58 }
59 
60 /// OpenGL Texture2D Representation
61 final class Texture2D : Texture!GL_TEXTURE_2D
62 {
63 	/// Makes empty texture
64 	public static auto newEmpty(int width, int height, PixelFormat format) in { assert(width >= 1 && height >= 1); } body
65 	{
66 		auto obj = new Texture2D();
67 		obj.bind();
68 		glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, null);
69 		Texture2D.Parameter[GL_TEXTURE_MIN_FILTER] = GL_LINEAR;
70 		Texture2D.Parameter[GL_TEXTURE_MAG_FILTER] = GL_LINEAR;
71 		return obj;
72 	}
73 
74 	/// Updates texture
75 	public void update(int x, int y, int width, int height, const(ubyte)* pixels, PixelFormat format)
76 	{
77 		this.bind();
78 		glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, format, GL_UNSIGNED_BYTE, pixels);
79 	}
80 }
81 
82 /// OpenGL Buffer Object Interfacing
83 abstract class Buffer(GLenum BufferType)
84 {
85 	protected GLuint id;
86 	protected this(GLuint buffer) { this.id = buffer; }
87 	~this() { glDeleteBuffers(1, &this.id); }
88 	
89 	protected static auto newOne(BufferDataT)(in BufferDataT* ptr, GLenum usage)
90 	{
91 		GLuint b;
92 		
93 		glGenBuffers(1, &b);
94 		glBindBuffer(BufferType, b); scope(exit) glBindBuffer(BufferType, 0);
95 		glBufferData(BufferType, BufferDataT.sizeof, ptr, usage);
96 		return b;
97 	}
98 }
99 
100 /// OpenGL VertexArrayObject Representation
101 final class VertexArray
102 {
103 	private GLuint aid, bid;
104 	private GLuint vcount;
105 
106 	private this(GLuint array, GLuint buffer, GLuint vcount)
107 	{
108 		this.aid = array;
109 		this.bid = buffer;
110 		this.vcount = vcount;
111 	}
112 	~this() { glDeleteBuffers(1, &this.bid); glDeleteVertexArrays(1, &this.aid); }
113 
114 	/// Makes new Vertex Array Object from slice to be rendered with program
115 	public static auto fromSlice(T)(const T[] slice, const ShaderProgram program)
116 	{
117 		GLuint aid, bid;
118 
119 		glGenVertexArrays(1, &aid);
120 		glGenBuffers(1, &bid);
121 
122 		glBindVertexArray(aid); scope(exit) glBindVertexArray(0);
123 		glBindBuffer(GL_ARRAY_BUFFER, bid); scope(exit) glBindBuffer(GL_ARRAY_BUFFER, 0);
124 		glBufferData(GL_ARRAY_BUFFER, slice.length * T.sizeof, slice.ptr, GL_STATIC_DRAW);
125 		program.applyInputLayouts();
126 		
127 		return new VertexArray(aid, bid, cast(GLuint)slice.length);
128 	}
129 	
130 	/// Instanced drawing shorthand
131 	public void drawInstanced(GLenum primitiveType)(GLint count)
132 	{
133 		GLDevice.Vertices = this;
134 		glDrawArraysInstanced(primitiveType, 0, this.vcount, count);
135 	}
136 
137 	/// Updates buffer data
138 	public void update(T)(const T[] slice)
139 	{
140 		glBindBuffer(GL_ARRAY_BUFFER, this.bid); scope(exit) glBindBuffer(GL_ARRAY_BUFFER, 0);
141 		glBufferData(GL_ARRAY_BUFFER, slice.length * T.sizeof, slice.ptr, GL_STATIC_DRAW);
142 		this.vcount = cast(GLuint)slice.length;
143 	}
144 }
145 
146 /// Type inferred Uniform Buffer factory
147 final class UniformBufferFactory
148 {
149 	@disable this();
150 	
151 	/// Makes new Static(Modified once, used many times) Uniform Buffer with Data
152 	public static auto newStatic(BufferStructureT)(BufferStructureT buffer)
153 	{
154 		return new UniformBuffer!BufferStructureT(Buffer!GL_UNIFORM_BUFFER.newOne(&buffer, GL_STATIC_DRAW));
155 	}
156 }
157 
158 /// OpenGL Uniform Buffer Representation
159 final class UniformBuffer(BufferStructureT) : Buffer!GL_UNIFORM_BUFFER
160 {
161 	private this(GLuint id) { super(id); }
162 	/// Makes new Static(Modified once, used many times) Uniform Buffer with Data
163 	public static auto newStatic(BufferStructureT buffer)
164 	{
165 		return new UniformBuffer(newOne(&buffer, GL_STATIC_DRAW));
166 	}
167 	/// Makes new Static(Modified once, used many times) Uniform Buffer
168 	public static auto newStatic()
169 	{
170 		return new UniformBuffer(newOne!BufferStructureT(null, GL_STATIC_DRAW));
171 	}
172 	
173 	/// Updates buffer data
174 	public void update(BufferStructureT buffer)
175 	{
176 		glBindBuffer(GL_UNIFORM_BUFFER, this.id); scope(exit) glBindBuffer(GL_UNIFORM_BUFFER, 0);
177 		glBufferSubData(GL_UNIFORM_BUFFER, 0, BufferStructureT.sizeof, &buffer);
178 	}
179 }
180 
181 /// UDA: Mark field as Input Element
182 struct element
183 {
184 	/// Attribute Name in shader
185 	string attrName;
186 	/// Is value normalized?(default is false)
187 	bool normalized = false;
188 }
189 /// Type to OpenGL Type Enum
190 private template TypeEnum(T)
191 {
192 	static if(is(T : float)) alias TypeEnum = GL_FLOAT;
193 	else static assert(false, "Unsupported Type");
194 }
195 // Map fields to input element descriptor
196 private template InputElementGen(alias Symbol)
197 {
198 	immutable Attr = getUDAs!(Symbol, element)[0];
199 	immutable InputElementGen = InputElement(Attr.attrName, Symbol.length, TypeEnum!(typeof(Symbol[0])),
200 		Attr.normalized ? GL_TRUE : GL_FALSE, __traits(parent, Symbol).sizeof, cast(const GLvoid*)Symbol.offsetof);
201 }
202 /// Auto generated input element descriptor list from vertex data structure
203 private alias IEDescList(VertexDataT) = staticMap!(InputElementGen, getSymbolsByUDA!(VertexDataT, element));
204 
205 // Vertex(Shader Input) Elements
206 private struct InputElement
207 {
208 	string attrName;
209 	GLint size;
210 	GLenum type;
211 	GLboolean normalized;
212 	GLsizei stride;
213 	const GLvoid* offset;
214 }
215 
216 /// Shader Source Types
217 enum ShaderType : GLenum
218 {
219 	/// Vertex Shader
220 	Vertex = GL_VERTEX_SHADER,
221 	/// Fragment(Pixel) Shader
222 	Fragment = GL_FRAGMENT_SHADER,
223 	/// Geometry Shader
224 	Geometry = GL_GEOMETRY_SHADER
225 }
226 
227 /// OpenGL ShaderProgram Representation
228 final class ShaderProgram
229 {
230 	private struct ResolvedInputElement
231 	{
232 		GLuint index;
233 		GLint size;
234 		GLenum type;
235 		GLboolean normalized;
236 		GLsizei stride;
237 		const GLvoid* offset;
238 	}
239 	private ResolvedInputElement[] elements;
240 	private GLuint pid;
241 
242 	private this(GLuint p, const InputElement[] elements)
243 	{
244 		this.pid = p;
245 		this.elements = elements.map!(x => ResolvedInputElement(glGetAttribLocation(p, x.attrName.toStringz),
246 			x.size, x.type, x.normalized, x.stride, x.offset)).array;
247 		
248 		this.uniforms = new UniformLocations();
249 		this.uniformBlocks = new UniformBlockIndices();
250 	}
251 	~this() { glDeleteProgram(this.pid); }
252 	
253 	version(Have_dglsl)
254 	{
255 		/// Make shader program from dglsl shader classes
256 		public static auto fromDSLClasses(VertexDataT, ShaderT...)()
257 		{
258 			ShaderT shaders;
259 			foreach(i, ST; ShaderT)
260 			{
261 				shaders[i] = new ST();
262 				shaders[i].compile();
263 			}
264 			auto p = new dglsl.Program!ShaderT(shaders);
265 			return new ShaderProgram(p.id, [IEDescList!VertexDataT]);
266 		}
267 	}
268 	/// Make shader program from source codes
269 	public static auto fromSources(VertexDataT, ShaderSources...)()
270 	{
271 		auto compileShader(ShaderType T, string Source)()
272 		{
273 			auto sh = glCreateShader(T);
274 			auto src = Source.toStringz;
275 			auto srcLength = Source.length;
276 			glShaderSource(sh, 1, &src, cast(GLint*)&srcLength);
277 			glCompileShader(sh);
278 			
279 			GLint status;
280 			glGetShaderiv(sh, GL_COMPILE_STATUS, &status);
281 			if(status == GL_FALSE)
282 			{
283 				GLint errlen;
284 				GLchar[] errbuf;
285 				glGetShaderiv(sh, GL_INFO_LOG_LENGTH, &errlen);
286 				errbuf.length = errlen;
287 				glGetShaderInfoLog(sh, cast(GLint)errbuf.length, null, errbuf.ptr);
288 				throw new Exception(errbuf.idup);
289 			}
290 			return sh;
291 		}
292 		template shaderCompilationList(ShaderSources...)
293 		{
294 			static if(ShaderSources.length < 2) alias shaderCompilationList = AliasSeq!();
295 			else alias shaderCompilationList = AliasSeq!(compileShader!(ShaderSources[0], ShaderSources[1]), shaderCompilationList!(ShaderSources[2 .. $]));
296 		}
297 		auto shaders = [shaderCompilationList!ShaderSources];
298 		scope(exit) shaders.each!glDeleteShader;
299 		
300 		auto p = glCreateProgram();
301 		shaders.each!(x => glAttachShader(p, x));
302 		glLinkProgram(p);
303 		
304 		GLint status;
305 		glGetProgramiv(p, GL_LINK_STATUS, &status);
306 		if(status == GL_FALSE)
307 		{
308 			GLint errlen;
309 			GLchar[] errbuf;
310 			glGetProgramiv(p, GL_INFO_LOG_LENGTH, &errlen);
311 			errbuf.length = errlen;
312 			glGetProgramInfoLog(p, cast(GLint)errbuf.length, null, errbuf.ptr);
313 			throw new Exception(errbuf.idup);
314 		}
315 		return new ShaderProgram(p, [IEDescList!VertexDataT]);
316 	}
317 	/// Build shader from imported source
318 	unittest
319 	{
320 		auto shader = ShaderProgram.fromSources!(VertexData,
321 			ShaderType.Vertex, import("vsh.glsl"),
322 			ShaderType.Fragment, import("fsh.glsl"));
323 	}
324 
325 	private void applyInputLayouts() const
326 	{
327 		glUseProgram(this.pid);
328 		foreach(ref e; this.elements)
329 		{
330 			glEnableVertexAttribArray(e.index);
331 			glVertexAttribPointer(e.index, e.size, e.type, e.normalized, e.stride, e.offset);
332 		}
333 		glUseProgram(0);
334 	}
335 	
336 	/// Activates(Uses) shader program
337 	public void activate() const
338 	{
339 		glUseProgram(this.pid);
340 	}
341 	
342 	private class UniformLocations
343 	{
344 		GLuint[string] cache;
345 		
346 		public auto opDispatch(string name)(int v) { this[name] = v; }
347 		public auto opDispatch(string name)(float v) { this[name] = v; }
348 		public auto opDispatch(string name)(float[2] v) { this[name] = v; }
349 		public auto opDispatch(string name)(in float[4] vf) { this[name] = vf; }
350 		public auto opDispatch(string name)(in float[4][4] matr) { this[name] = matr; }
351 		public auto opIndexAssign(int v, string name) { glUniform1i(this.getLocation(name), v); }
352 		public auto opIndexAssign(float v, string name) { glUniform1f(this.getLocation(name), v); }
353 		public auto opIndexAssign(float[2] v, string name) { glUniform2fv(this.getLocation(name), 1, v.ptr); }
354 		public auto opIndexAssign(in float[4] vf, string name) { glUniform4fv(this.getLocation(name), 1, vf.ptr); }
355 		public auto opIndexAssign(in float[4][4] matr, string name) { glUniformMatrix4fv(this.getLocation(name), 1, GL_FALSE, &matr[0][0]); }
356 		version(Have_gl3n)
357 		{
358 			import gl3n.linalg;
359 			public auto opDispatch(string name)(in vec2 vf) { this[name] = vf; }
360 			public auto opDispatch(string name)(in vec4 vf) { this[name] = vf; }
361 			public auto opIndexAssign(in vec2 vf, string name) { glUniform2fv(this.getLocation(name), 1, vf.value_ptr); }
362 			public auto opIndexAssign(in vec4 vf, string name) { glUniform4fv(this.getLocation(name), 1, vf.value_ptr); }
363 		}
364 		
365 		private auto getLocation(string name)
366 		{
367 			if(name !in cache) cache[name] = glGetUniformLocation(this.outer.pid, name.toStringz);
368 			return cache[name];
369 		}
370 	}
371 	/// Field like Uniform Accessors
372 	UniformLocations uniforms;
373 	
374 	private class UniformBlockIndices
375 	{
376 		GLuint[string] cache;
377 		
378 		public auto opDispatch(string name)(int idx) { this[name] = idx; }
379 		public auto opIndexAssign(int idx, string name) { glUniformBlockBinding(this.outer.pid, this.getIndex(name), idx); }
380 		
381 		private auto getIndex(string name)
382 		{
383 			if(name !in cache) cache[name] = glGetUniformBlockIndex(this.outer.pid, name.toStringz);
384 			return cache[name];
385 		}
386 	}
387 	/// Field like Uniform Block Binding
388 	UniformBlockIndices uniformBlocks;
389 }
390 
391 /// Blend Function
392 alias BlendFunc = Tuple!(GLenum, "srcBlend", GLenum, "destBlend");
393 /// Predefined Blend Functions
394 final class BlendFunctions
395 {
396 	@disable this();
397 	
398 	static immutable Alpha = BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
399 }
400 
401 /// OpenGL Device Representation
402 final class GLDevice
403 {
404 	@disable this();
405 	
406 	/// Texture Units
407 	static class TextureUnits
408 	{
409 		@disable this();
410 		
411 		static void opIndexAssign(GLenum TextureType)(in Texture!TextureType tex, int index)
412 		{
413 			glActiveTexture(GL_TEXTURE0 + index);
414 			glBindTexture(TextureType, tex.id);
415 		}
416 	}
417 	/// Accessing texture unit with index
418 	unittest
419 	{
420 		GLDevice.TextureUnits[0] = texture;
421 	}
422 	
423 	/// Binding Point Table
424 	static class BindingPoint
425 	{
426 		@disable this();
427 		
428 		static void opIndexAssign(T)(in UniformBuffer!T buffer, int index)
429 		{
430 			glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer.id);
431 		}
432 	}
433 	
434 	/// Input Assembler: Vertex Buffer and Input Layout
435 	static class Vertices
436 	{
437 		@disable this();
438 		
439 		static void opAssign(in VertexArray varray)
440 		{
441 			glBindVertexArray(varray.aid);
442 		}
443 	}
444 	
445 	/// Rasterizer State
446 	static class RasterizerState
447 	{
448 		@disable this();
449 		
450 		private static class DeviceCaps(GLenum CapEnum)
451 		{
452 			@disable this();
453 			
454 			static void opAssign(bool flag)
455 			{
456 				(flag ? glEnable : glDisable)(CapEnum);
457 			}
458 		}
459 		public alias Blending = DeviceCaps!GL_BLEND;
460 		public alias ScissorTest = DeviceCaps!GL_SCISSOR_TEST;
461 		public alias BackCulling = DeviceCaps!GL_CULL_FACE;
462 		public alias DepthTest = DeviceCaps!GL_DEPTH_TEST;
463 		public alias DepthClamp = DeviceCaps!GL_DEPTH_CLAMP;
464 		static void opDispatch(string name)(BlendFunc blend) if(name == "BlendFunc")
465 		{
466 			glBlendFunc(blend.srcBlend, blend.destBlend);
467 		}
468 	}
469 	
470 	/// Pixel Store
471 	static class PixelStore
472 	{
473 		@disable this();
474 		
475 		static auto opIndexAssign(GLint value, GLenum param) { glPixelStorei(param, value); }
476 	}
477 }