1 module dgt.gl_backend;
2 import derelict.opengl;
3 import derelict.sdl2.sdl;
4 
5 import dgt.array : Array;
6 import dgt.color : Color;
7 import dgt.geom;
8 import dgt.io;
9 
10 ///The default vertex shader
11 string DEFAULT_VERTEX_SHADER = "#version 150
12 in vec2 position;
13 in vec2 tex_coord;
14 in vec4 color;
15 uniform mat3 transform;
16 out vec4 Color;
17 out vec2 Tex_coord;
18 void main() {
19 	Color = color;
20 	Tex_coord = tex_coord;
21 	vec3 transformed = vec3(position, 1.0) * transform;
22 	transformed.z = 0;
23 	gl_Position = vec4(transformed, 1.0);
24 }";
25 ///The default fragment shader
26 string DEFAULT_FRAGMENT_SHADER = "#version 150
27 in vec4 Color;
28 in vec2 Tex_coord;
29 out vec4 outColor;
30 uniform sampler2D tex;
31 void main() {
32 	vec4 tex_color = texture(tex, Tex_coord);
33 	outColor = Color * tex_color;
34 }";
35 
36 ///Represents a vertex to pass to OpenGL
37 struct Vertex
38 {
39 	Vector pos, texPos;
40 	Color col;
41 
42 	@nogc nothrow:
43 
44 	void print() const
45 	{
46 		dgt.io.print("Vertex(", pos, ", ", texPos, ", ", col, ")");
47 	}
48 }
49 
50 /**
51 Handles opengl contexts and passing data to OpenGL such as shaders and vertices
52 */
53 struct GLBackend
54 {
55 	//The draw data
56 	private GLuint texture = 0;
57 	public Array!float vertices;
58 	private Array!GLuint indices;
59 
60 	private SDL_GLContext ctx;
61 	//OpenGL objects
62 	private GLuint shader, fragment, vertex, vbo, ebo, vao, texture_location;
63 	private string transformAttribute,
64 		positionAttribute,
65 		texPositionAttribute,
66 		colorAttribute, textureAttribute;
67 	private SDL_Window* window;
68 	private Transform transform;
69 
70 	//The amount of floats per vertex
71 	private static immutable size_t vertex_size = 8;
72 
73     @disable this();
74 	@disable this(this);
75 
76 	@trusted:
77 	package this(SDL_Window* window, bool vsync)
78 	{
79 		DerelictGL3.load();
80 		this.window = window;
81 		SDL_GL_SetSwapInterval(vsync ? 1 : 0);
82         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
83         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
84         SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
85 		ctx = SDL_GL_CreateContext(window);
86 		DerelictGL3.reload();
87         DerelictGL3.loadExtra();
88         glGenVertexArrays(1, &vao);
89         glBindVertexArray(vao);
90 		glGenBuffers(1, &vbo);
91 		glGenBuffers(1, &ebo);
92 		setShader(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER);
93 		glEnable (GL_BLEND);
94 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
95 		vertices = Array!float(1024);
96 		indices = Array!GLuint(1024);
97 	}
98 
99 	@nogc nothrow:
100 
101 	~this()
102 	{
103 		vertices.destroy();
104 		indices.destroy();
105 		glDeleteProgram(shader);
106 		glDeleteShader(fragment);
107 		glDeleteShader(vertex);
108 
109 		glDeleteBuffers(1, &vbo);
110 		glDeleteBuffers(1, &ebo);
111 
112         glDeleteVertexArrays(1, &vao);
113 		SDL_GL_DeleteContext(ctx);
114 	}
115 
116     /**
117 	Set the current shader and its attributes
118 
119 	The GL backend passes a uniform 3x3 matrix to a uniform with a name
120 	given by 'transformAttributeName.' This matrix is a transformation
121 	applied to every vertex. Each vertex receives a vec2 of its position
122 	and texture coordinate, given by 'positionAttributeName' and
123 	'texPositionAttributeName.' Vertices also are blended with a color,
124 	given by 'colorAttributeName.' The texture is passed as a uniform
125 	sampler2D to 'textureAttributeName.' The output of the shader is a
126 	vec4 for a color, given by 'colorOutputName'
127 	*/
128 	public void setShader(in string vertexShader, 
129 		in string fragmentShader,
130 		in string transformAttributeName = "transform",
131 		in string positionAttributeName = "position",
132 		in string texPositionAttributeName = "tex_coord",
133 		in string colorAttributeName = "color",
134 		in string textureAttributeName = "tex",
135 		in string colorOutputName = "outColor")
136 	{
137 		transformAttribute = transformAttributeName;
138 		positionAttribute = positionAttributeName;
139 		texPositionAttribute = texPositionAttributeName,
140 		colorAttribute = colorAttributeName;
141 		textureAttribute = textureAttributeName;
142 		if(shader != 0) glDeleteProgram(shader);
143 		if(vertex != 0) glDeleteShader(vertex);
144 		if(fragment != 0) glDeleteShader(fragment);
145 		vertex = glCreateShader(GL_VERTEX_SHADER);
146 		auto vertexShaderPtr = vertexShader.ptr;
147 		glShaderSource(vertex, 1, cast(GLchar**)(&vertexShaderPtr), null);
148 		glCompileShader(vertex);
149 		GLint status;
150 		glGetShaderiv(vertex, GL_COMPILE_STATUS, &status);
151 		if (status != GL_TRUE)
152 		{
153 			println("Vertex shader compilation failed");
154 			char[512] buffer;
155 			GLsizei length;
156 			glGetShaderInfoLog(vertex, 512, &length, buffer.ptr);
157 			println("Error: ", buffer[0..length]);
158 			setShader(DEFAULT_VERTEX_SHADER, fragmentShader);
159 		}
160 		fragment = glCreateShader(GL_FRAGMENT_SHADER);
161 		auto fragmentShaderPtr = fragmentShader.ptr;
162 		glShaderSource(fragment, 1, cast(GLchar**)(&fragmentShaderPtr), null);
163 		glCompileShader(fragment);
164 		glGetShaderiv(fragment, GL_COMPILE_STATUS, &status);
165 		if (status != GL_TRUE)
166 		{
167 			println("Fragment shader compilation failed\n");
168 			char[512] buffer;
169 			GLsizei length;
170 			glGetShaderInfoLog(vertex, 512, &length, buffer.ptr);
171 			println("Error: ", buffer[0..length]);
172 			setShader(vertexShader, DEFAULT_FRAGMENT_SHADER);
173 		}
174 		shader = glCreateProgram();
175 		glAttachShader(shader, vertex);
176 		glAttachShader(shader, fragment);
177 		glBindFragDataLocation(shader, 0, colorOutputName.ptr);
178 		glLinkProgram(shader);
179 		glUseProgram(shader);
180 	}
181 
182     /**
183     Clear the screen and the vertex and index buffers
184 
185     Any data that hasn't been drawn will be lost
186     */
187 	public void clear(in Color col)
188 	{
189 		vertices.clear();
190 		indices.clear();
191 		glClearColor(col.r, col.g, col.b, col.a);
192 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
193 	}
194 
195     ///Draw the current vertices and indices and clear the buffers
196 	public void flush()
197 	{
198 		GLint transform_attrib = glGetUniformLocation(shader, transformAttribute.ptr);
199 		glUniformMatrix3fv(transform_attrib, 1, GL_FALSE, transform.ptr);
200 		//Bind the vertex data
201 		glBindBuffer(GL_ARRAY_BUFFER, vbo);
202 		glBufferData(GL_ARRAY_BUFFER, vertices.length * float.sizeof, vertices.ptr, GL_STREAM_DRAW);
203 		//Bind the index data
204 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
205 		glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.length * GLuint.sizeof, indices.ptr, GL_STREAM_DRAW);
206 		//Set up the vertex attributes
207 		GLint posAttrib = glGetAttribLocation(shader, positionAttribute.ptr);
208 		glEnableVertexAttribArray(posAttrib);
209 		glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 8 * GLfloat.sizeof, cast(void*)0);
210 		GLint texAttrib = glGetAttribLocation(shader, texPositionAttribute.ptr);
211 		glEnableVertexAttribArray(texAttrib);
212 		glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 8 * GLfloat.sizeof, cast(void*)(2 * GLfloat.sizeof));
213 		GLint colAttrib = glGetAttribLocation(shader, colorAttribute.ptr);
214 		glEnableVertexAttribArray(colAttrib);
215 		glVertexAttribPointer(colAttrib, 4, GL_FLOAT, GL_FALSE, 8 * GLfloat.sizeof, cast(void*)(4 * GLfloat.sizeof));
216 		//Upload the texture to the GPU
217 		texture_location = glGetUniformLocation(shader, textureAttribute.ptr);
218 		glActiveTexture(GL_TEXTURE0);
219 		glBindTexture(GL_TEXTURE_2D, texture);
220 		glUniform1i(texture_location, 0);
221 		//Draw the triangles
222 		glDrawElements(GL_TRIANGLES, cast(int)indices.length, GL_UNSIGNED_INT, cast(void*)0);
223 		vertices.clear();
224 		indices.clear();
225 	}
226 
227     ///Flush the buffers and display the new screen
228 	public void flip()
229 	{
230 		flush();
231 		SDL_GL_SwapWindow(window);
232 	}
233 
234 
235 	private void switchTexture(in GLuint texture)
236 	{
237 		if (this.texture != 0)
238 			flush();
239 		this.texture = texture;
240 	}
241 
242     /**
243     Add some vertices and indices to the backend
244 
245     The 0th index is the first vertex in this add, not since the last flush
246     */
247 	public void add(in GLuint texture,
248 					in Vertex[] newVertices, in GLuint[] newIndices)
249 	{
250 		if(this.texture != texture)
251 			switchTexture(texture);
252 		auto offset = vertices.length / vertex_size;
253 		foreach(v; newVertices)
254 			vertices.addAll(v.pos.x, v.pos.y, v.texPos.x, v.texPos.y,
255 				v.col.r, v.col.g, v.col.b, v.col.a);
256 		foreach(i; newIndices)
257 			indices.add(cast(uint)(i + offset));
258 	}
259 
260     public void setTransform(in Transform transform)
261     {
262         this.transform = transform;
263         if(indices.length != 0)
264             flush();
265     }
266 }
267 unittest
268 {
269 	auto vert = Vertex(Vector(0, 0), Vector(1, 1), Color(1, 1, 1, 1));
270 	println("Should print a white vertex at 0, 0 from 1, 1: ", vert);
271 }