1 /++
2 Authors: Miguel Ángel (quevangel), quevangel@protonmail.com
3 +/
4 
5 module daque.graphics.opengl;
6 
7 import std.string;
8 import std.file;
9 import std.algorithm;
10 
11 import core.stdc.stdlib;
12 
13 import derelict.opengl;
14 import derelict.sdl2.sdl;
15 import derelict.sdl2.image;
16 
17 import daque.math.geometry;
18 
19 /// Renders an Array of vertices already on GPU memory
20 void render(GpuArray vertices)
21 {
22 	vertices.bind();
23 	glDrawArrays(GL_TRIANGLES, 0, cast(int) vertices.size());
24 }
25 
26 /++
27 Initializes required libraries for graphics rendering.
28 
29 Initializes SDL2 and OpenGL libraries.
30 	+/
31 static this()
32 {
33 	DerelictGL3.load();
34 }
35 
36 /++
37 	+/
38 static ~this()
39 {
40 }
41 /++
42 Represents a 'shader' OpenGL object.
43 
44 A shader is *part* of a program ought to be executed by the GPU.
45 This class serves as a way to compile and use those program parts.
46 
47 The "whole" Program is another OpenGL object constructed by assembling
48 many Shader s.
49 +/
50 class Shader
51 {
52 public:
53 	/// Types of shaders there can be
54 	enum Type
55 	{
56 		Vertex,
57 		Fragment
58 	}
59 	/++
60 			Get the type of the shader
61 			Returns: type of the shader
62 			+/
63 	@property Type type()
64 	{
65 		return m_type;
66 	}
67 
68 	/++
69 			Constructs a new shader of the specified type, using as source code the file pointed to by
70 			sourcePath
71 
72 			Params:
73 			type = Type of the shader to be constructed
74 			sourcePath = String representing a path to a file containing the 
75 			source code which will be used as source for the constructed shader
76 			+/
77 	this(Shader.Type type, string sourcePath)
78 	{
79 		m_type = type;
80 		m_shaderGlName = cast(immutable(GLuint)) compileShader(type, sourcePath);
81 	}
82 
83 	~this()
84 	{
85 		glDeleteShader(m_shaderGlName);
86 	}
87 
88 private:
89 	immutable(GLuint) m_shaderGlName;
90 	immutable(Type) m_type;
91 	/++
92 			Compiles a shader of the specified type, using as source code the file pointed to by sourcePath 
93 			and returns the name of the opengl object representing the compiled shader.
94 
95 			Params:
96 			type = Type of shader to be compiled
97 			sourcePath = Path to the shader's source code
98 
99 			Returns:
100 			Opengl Name of the compiled shader
101 			+/
102 	static GLuint compileShader(immutable Type type, string sourcePath)
103 	{
104 		// Create and compile
105 		GLuint shaderName = glCreateShader(typeToGlenum(type));
106 		const char* sourceCodeZ = toStringz(readText(sourcePath));
107 		glShaderSource(shaderName, 1, &sourceCodeZ, null);
108 		glCompileShader(shaderName);
109 
110 		// Error checking
111 		GLint compilationSuccess = 0;
112 		glGetShaderiv(shaderName, GL_COMPILE_STATUS, &compilationSuccess);
113 		// Error case
114 		if (compilationSuccess == GL_FALSE)
115 		{
116 			GLint logSize = 0;
117 			GLchar[] errorLog;
118 
119 			glGetShaderiv(shaderName, GL_INFO_LOG_LENGTH, &logSize);
120 			errorLog.length = logSize;
121 			glGetShaderInfoLog(shaderName, logSize, &logSize, &errorLog[0]);
122 			string info = cast(string) fromStringz(&errorLog[0]);
123 
124 			import std.stdio : writeln;
125 			writeln("COMPILATION ERROR: ", info);
126 
127 			glDeleteShader(shaderName);
128 			shaderName = 0;
129 		}
130 		else // Success case
131 		{
132 		}
133 
134 		return shaderName;
135 	}
136 
137 	/++
138 			Maps Shader.Type to equivalent OpenGL GLenum.
139 
140 			Params:
141 			type = type to be mapped to GLenum
142 			Returns: 
143 			GLenum equivalent of type
144 			+/
145 	static pure GLenum typeToGlenum(Shader.Type type)
146 	{
147 		final switch (type)
148 		{
149 		case Shader.Type.Vertex:
150 			return GL_VERTEX_SHADER;
151 		case Shader.Type.Fragment:
152 			return GL_FRAGMENT_SHADER;
153 		}
154 	}
155 }
156 
157 /++
158 Represents and handles a Program Opengl Object.
159 A Program is a group of Opengl Shaders which will be linked together.
160 A Program is  a program to be executed by the GPU to each of the Vertices of a model.
161 +/
162 class Program
163 {
164 public:
165 	/++
166 			Creates a new empty program
167 			+/
168 	this()
169 	{
170 		m_programGlName = glCreateProgram();
171 	}
172 	/++ 
173 			Creates a program with the specified shaders already attached
174 			+/
175 	this(Shader[] shaders)
176 	{
177 		this();
178 		shaders.each!(s => this.attach(s));
179 	}
180 
181 	~this()
182 	{
183 		glDeleteProgram(m_programGlName);
184 	}
185 	/++
186 			Attaches the shader to this program
187 
188 			Params:
189 			shader = Shader to be attached
190 			+/
191 	void attach(Shader shader)
192 	{
193 		glAttachShader(m_programGlName, shader.m_shaderGlName);
194 	}
195 	/++
196 			Links the currently attached shaders
197 			+/
198 	void link()
199 	{
200 		glLinkProgram(m_programGlName);
201 
202 		GLint isLinked = 0;
203 		glGetProgramiv(m_programGlName, GL_LINK_STATUS, cast(int*)&isLinked);
204 		if (isLinked == GL_FALSE)
205 		{
206 			import std.stdio: writeln;
207 			writeln("LINKING ERROR");
208 		}
209 	}
210 
211 	/++
212 			Binds the program to the current opengl context so that it is used to process new render
213 			commands
214 			+/
215 	void use()
216 	{
217 		glUseProgram(m_programGlName);
218 	}
219 
220 
221 	int getUniformLocation(string name)
222 	{
223 		return glGetUniformLocation(m_programGlName, name.toStringz());
224 	}
225 
226 	template strToType(string typeString)
227 	{
228 		static if(typeString == "i")
229 			alias strToType = int;
230 		else static if(typeString == "f")
231 			alias strToType = float;
232 		else
233 			static assert(0, "unrecognized string type " ~ typeString);
234 	}
235 
236 	import std.conv;
237 	/++
238 			Sets a integer uniform variable inside the program
239 
240 			Params:
241 			uniformName = name of the single-valued uniform integer to be changed
242 			val = new value to be assigned
243 			+/
244 	void setUniform(uint count, string typeString)(int location, void[] data)
245 	{
246 		this.use();
247 		mixin("alias glUniform = " ~ "glUniform" ~ to!string(count) ~ typeString ~ "v;");
248 		glUniform(location, cast(int)(data.length / (strToType!typeString.sizeof * count)), cast(strToType!typeString*)data.ptr);
249 	}
250 
251 	// TODO: setUniformMatrix method
252 
253 	void getUniform(string typeString)(int location, strToType!typeString* output)
254 	{
255 		mixin("alias glGetUniform = glGetUniform" ~ typeString ~ "v;");
256 		glGetUniform(m_programGlName, location, output);
257 	}
258 
259 private:
260 	// associated Opengl Object Program's name
261 	immutable(GLuint) m_programGlName;
262 
263 }
264 
265 /++
266 Represents a buffer opengl object.
267 A buffer opengl object is the mechanism through which data can be stored in the GPU, usually
268 vertex data of the models to be rendered.
269 
270 This class eases/abstracts the interaction with this kind of opengl objects.
271 +/
272 class Buffer
273 {
274 public:
275 	/++
276 			Constructs a new and empty buffer
277 			+/
278 	this()
279 	{
280 		m_name = Buffer.gen();
281 	}
282 
283 	~this()
284 	{
285 		del(m_name);
286 	}
287 
288 	/++
289 			Generates a new opengl buffer and returns it's name
290 			Returns: name of the newly created opengl buffer
291 			+/
292 	static GLuint gen()
293 	{
294 		GLuint buffer;
295 		glGenBuffers(1, &buffer);
296 		return buffer;
297 	}
298 
299 	/++ 
300 			Deletes a opengl buffer given it's name
301 			Params :
302 			buffer = opengl name of the opengl buffer
303 			+/
304 	static void del(GLuint buffer)
305 	{
306 		glDeleteBuffers(1, &buffer);
307 	}
308 
309 	/++
310 			Sends the unformatted data of the specified size to the buffer
311 
312 			Params:
313 			data = Pointer to the data to be sent
314 			size = Size in bytes of the data to be sent
315 			+/
316 	void bufferData(void* data, size_t size)
317 	{
318 		bind();
319 		glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW);
320 	}
321 
322 	/++
323 			Binds the buffer to the current opengl context
324 			+/
325 	void bind()
326 	{
327 		glBindBuffer(GL_ARRAY_BUFFER, m_name);
328 	}
329 
330 private:
331 	// opengl name of the buffer managed by @this
332 	immutable(GLuint) m_name;
333 }
334 
335 /++
336 Data needed to represent a particular attribute for a Vertex.
337 +/
338 struct AttributeFormat
339 {
340 	/// OpenGL identifies each attribute by an @index
341 	GLuint index;
342 	/// No. of components of this attribute
343 	GLint size;
344 	/// Data type of the components of this attribute
345 	GLenum type;
346 	/// Does it need to be _normalized_(Clipped to a range of 0.0 - 1.0)?
347 	GLboolean normalized;
348 	/// Space between each appearance of this attribute in an array of Vertices, equivalently, the
349 	/// size of each Vertex
350 	GLsizei stride;
351 	/// Offset to first appearance of this attribute in an array of Vertices, equivalently, the
352 	/// offset of this member in the Vertex structure
353 	const GLvoid* pointer;
354 }
355 
356 /++
357 Given the Buffer and the VertexArray currently bound to the OpenGL context, this function provides
358 format info about the attribute format.index of the vertices in the VertexArray.
359 
360 This associates the Buffer to the VertexArray.
361 
362 Params:
363 format = attribute format to be given to the VertexArray currently bound
364 	+/
365 void setup(AttributeFormat format)
366 {
367 	glEnableVertexAttribArray(format.index);
368 
369 	glVertexAttribPointer(format.index, format.size, format.type,
370 			format.normalized, format.stride, format.pointer);
371 }
372 
373 /++
374 Represents an opengl Vertex Array Object (VAO).
375 A VAO relates Opengl Buffers and Vertex Formats.
376 +/
377 class VertexArray
378 {
379 private:
380 	// opengl name of the VAO managed by @this
381 	immutable(GLuint) m_name;
382 public:
383 	/++
384 			Generates and empty VAO and saves it's name
385 			+/
386 	this()
387 	{
388 		m_name = genVertexArray();
389 	}
390 
391 	static GLuint genVertexArray()
392 	{
393 		GLuint name;
394 		glGenVertexArrays(1, &name);
395 		return name;
396 	}
397 	/++
398 			Deallocates the VAO
399 			+/
400 	~this()
401 	{
402 		deleteVertexArray(m_name);
403 	}
404 
405 	static void deleteVertexArray(GLuint vertexArrayName)
406 	{
407 		glDeleteVertexArrays(1, &vertexArrayName);
408 	}
409 	/++
410 			Associates this VertexArray with the buffer and the format given by the type
411 			VertexType.
412 
413 			Inputs:
414 			buffer = Buffer to associae with this VertexArray and this format
415 			+/
416 	void use(Buffer buffer, AttributeFormat[] formats)
417 	{
418 		bind();
419 		buffer.bind();
420 		formats.each!setup;
421 	}
422 
423 	/++
424 			Binds this VertexArray to the opengl context
425 			+/
426 	void bind()
427 	{
428 		glBindVertexArray(m_name);
429 	}
430 }
431 
432 /++
433 Represents an array of things to be stored in the GPU
434 Params:
435 DataType = Type of the data to be stored
436 	+/
437 class GpuArray
438 {
439 private:
440 	Buffer m_buffer;
441 	VertexArray m_vao;
442 	uint m_size;
443 
444 public:
445 	/++
446 			Creates and fills a new gpu array
447 
448 			Params:
449 			data = data to be initialy filled with
450 			+/
451 	this(void[] data, uint noElements, AttributeFormat[] attributeFormats)
452 	{
453 		m_buffer = new Buffer();
454 		m_vao = new VertexArray();
455 		m_size = noElements;
456 
457 		m_buffer.bufferData(data.ptr, data.length);
458 		m_vao.use(m_buffer, attributeFormats);
459 	}
460 
461 	/// Binds the associated VertexArray to the opengl context
462 	void bind()
463 	{
464 		m_vao.bind();
465 	}
466 
467 	uint size()
468 	{
469 		return m_size;
470 	}
471 }
472 
473 /++
474 Represents a 2D opengl texture
475 +/
476 class Texture
477 {
478 private:
479 	immutable(GLuint) m_name;
480 	immutable(GLenum) m_type;
481 
482 	immutable(SDL_Surface*) m_surface;
483 	immutable(uint) m_width, m_height;
484 
485 public:
486 	/++
487 			Constructs a Texture from an image file got from imagePath
488 
489 			Params:
490 			imagePath = path to the image to be used as a source to construct the texture
491 			+/
492 	this(string imagePath)
493 	{
494 		m_type = GL_TEXTURE_2D;
495 		m_surface = cast(immutable(SDL_Surface*)) IMG_Load(imagePath.toStringz());
496 		m_width = m_surface.w;
497 		m_height = m_surface.h;
498 
499 		if (!m_surface) // error reading surface
500 		{
501 			return;
502 		}
503 		else if (m_surface.format.format != SDL_PIXELFORMAT_RGBA32) // unsupported pixel format
504 		{
505 			return;
506 		}
507 
508 		m_name = Texture.gen();
509 		this.bind();
510 		this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
511 		this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
512 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_surface.w, m_surface.h, 0,
513 				GL_RGBA, GL_UNSIGNED_BYTE, m_surface.pixels);
514 	}
515 
516 	/++
517 			Constructs an empty Texture with the specified width and height, and fills it with color
518 			clearColor
519 
520 			Params:
521 			width = width of the texture to be constructed
522 			height = height of the texture to be constructed
523 			clearColor = color to be filled with
524 			+/
525 	this(uint width, uint height, uint clearColor = 0xffffffff)
526 	{
527 		m_width = width;
528 		m_height = height;
529 
530 		m_name = Texture.gen();
531 		m_type = GL_TEXTURE_2D;
532 		m_surface = null;
533 
534 		this.bind();
535 		this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
536 		this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
537 		glTexImage2D(m_type, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);
538 	}
539 
540 	~this()
541 	{
542 		Texture.del(m_name);
543 	}
544 
545 	/++
546 			Fills the texture with color clearColor
547 			+/
548 	void clear(uint clearColor)
549 	{
550 		//t glClearTexImage(m_name, 0, GL_RGBA, GL_UNSIGNED_BYTE, &clearColor);
551 	}
552 
553 	private template GLType(string name)
554 	{
555 		static if (name == "f")
556 		{
557 			alias GLType = GLfloat;
558 		}
559 		else static if (name == "i")
560 		{
561 			alias GLType = GLint;
562 		}
563 	}
564 	/// Sets an internal opengl parameter for the texture
565 	void setParameter(string typename)(GLenum parameterName, GLType!typename value)
566 	{
567 		this.bind();
568 
569 		mixin("alias glTexParameter = glTexParameter" ~ typename ~ ";");
570 		glTexParameter(m_type, parameterName, value);
571 	}
572 
573 	/// Returns the width of the texture
574 	uint width()
575 	{
576 		return m_width;
577 	}
578 	/// Returns the height of the texture
579 	uint height()
580 	{
581 		return m_height;
582 	}
583 
584 	/// Binds the texture to the opengl context
585 	void bind()
586 	{
587 		glBindTexture(m_type, m_name);
588 	}
589 
590 	/// Returns the opengl index of the texture ( aka: it's name )
591 	GLuint name()
592 	{
593 		return m_name;
594 	}
595 	/++
596 			Sets the pixels of a specified rectangular region
597 
598 			Params:
599 			offsetx = x coordinate ( from low left texture corner ) of the low left corner of
600 			the region
601 
602 			offsety = y coordinate ( from low left texture corner ) of the low left corner of
603 			the region
604 
605 			width = width of the region
606 			height = height of the region
607 
608 			data = data in row major order of the pixels to be set
609 			+/
610 	void updateRegion(uint offsetx, uint offsety, uint width, uint height, uint[] data)
611 	in
612 	{
613 		assert(data.length >= width * height);
614 	}
615 	out
616 	{
617 	}
618 	do
619 	{
620 		this.bind();
621 		glTexSubImage2D(m_type, 0, offsetx, offsety, width, height, GL_RGBA,
622 				GL_UNSIGNED_BYTE, data.ptr);
623 	}
624 
625 	/++
626 			Generates a new opengl texture and returns it's name
627 
628 			Returns: the name of the newly created texture
629 		+/
630 	static GLuint gen()
631 	{
632 		GLuint name;
633 		glGenTextures(1, &name);
634 		return name;
635 	}
636 
637 	/++
638 			Deletes an opengl texture using it's name
639 			+/
640 	static void del(GLuint texture)
641 	{
642 		glDeleteTextures(1, &texture);
643 	}
644 }
645 
646 /++
647 Assigns texture to textureUnit.
648 
649 Params:
650 textureUnit = texture unit index to be set
651 texture = texture to be assigned
652 	+/
653 void setTextureUnit(int textureUnit, Texture texture)
654 {
655 	glActiveTexture(GL_TEXTURE0 + textureUnit);
656 	glBindTexture(texture.m_type, texture.m_name);
657 }