1 module canvas.backend.opengl;
2 import canvas.backend.common;
3 import canvas.color;
4 import canvas.math;
5 import bindbc.opengl;
6 
7 private void[] flipImage(size_t width, size_t height, const(void)[] data) {
8 	void[] result = new void[4 * width * height];
9 	size_t length = 4 * width;
10 	foreach (j; 0 .. height) {
11 		size_t index1 = j * 4 * width;
12 		size_t index2 = (height - j - 1) * 4 * width;
13 		result[index1 .. index1 + length] = data[index2 .. index2 + length];
14 	}
15 	return result;
16 }
17 
18 private interface OpenGLResource {
19 
20 	void dispose();
21 
22 }
23 
24 final class OpenGLException : Exception {
25 
26 	this(string msg, string file = __FILE__, size_t line = __LINE__) @nogc @safe pure nothrow {
27 		super(msg, file, line);
28 	}
29 
30 }
31 
32 final class OpenGLShader : Shader, OpenGLResource {
33 
34 	private {
35 		OpenGLBackend context;
36 
37 		GLuint program;
38 
39 		GLint[string] uniformLocations;
40 	}
41 
42 	private this(OpenGLBackend context, ShaderSource[] sources) {
43 		import std.conv : to;
44 
45 		debug (resourceTracking) {
46 			import std.stdio : writeln;
47 
48 			writefln!"Create OpenGL shader %p"(cast(void*) this);
49 		}
50 
51 		this.context = context;
52 
53 		program = glCreateProgram();
54 		if (program == 0) {
55 			throw new OpenGLException("Could not create shader program");
56 		}
57 
58 		GLuint[] shaders;
59 
60 		foreach (source; sources) {
61 			GLenum type;
62 			final switch (source.type) {
63 			case ShaderType.Fragment:
64 				type = GL_FRAGMENT_SHADER;
65 				break;
66 			case ShaderType.Vertex:
67 				type = GL_VERTEX_SHADER;
68 				break;
69 			}
70 
71 			GLuint shader = glCreateShader(type);
72 			if (shader == 0) {
73 				foreach (s; shaders) {
74 					glDeleteShader(s);
75 				}
76 
77 				glDeleteProgram(program);
78 
79 				throw new OpenGLException("Could not create " ~ source.type.to!string ~ " shader");
80 			}
81 
82 			const(char)* src = source.source.ptr;
83 
84 			if (source.source.length > GLint.max) {
85 				throw new OpenGLException("Length of " ~ source.type.to!string ~ " shader is too large");
86 			}
87 
88 			GLint length = cast(GLint) source.source.length;
89 
90 			glShaderSource(shader, 1, &src, &length);
91 
92 			glCompileShader(shader);
93 
94 			GLint success;
95 			glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
96 			if (!success) {
97 				import std.exception : assumeUnique;
98 
99 				GLint size;
100 				glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &size);
101 
102 				if (size <= 1) {
103 					throw new OpenGLException(source.type.to!string ~ " shader compilation failed");
104 				}
105 
106 				char[] buf = new char[size - 1];
107 				glGetShaderInfoLog(shader, size - 1, null, buf.ptr);
108 
109 				throw new OpenGLException(source.type.to!string ~ " shader compilation failed: " ~ buf.assumeUnique);
110 			}
111 
112 			shaders ~= shader;
113 		}
114 
115 		foreach (shader; shaders) {
116 			glAttachShader(program, shader);
117 		}
118 
119 		glLinkProgram(program);
120 
121 		GLint success;
122 		glGetProgramiv(program, GL_LINK_STATUS, &success);
123 		if (!success) {
124 			import std.exception : assumeUnique;
125 
126 			GLint size;
127 			glGetProgramiv(program, GL_INFO_LOG_LENGTH, &size);
128 
129 			if (size <= 1) {
130 				throw new OpenGLException("Shader linking failed");
131 			}
132 
133 			char[] buf = new char[size - 1];
134 			glGetProgramInfoLog(program, size - 1, null, buf.ptr);
135 
136 			throw new OpenGLException("Shader linking failed: " ~ buf.assumeUnique);
137 		}
138 
139 		foreach (shader; shaders) {
140 			glDeleteShader(shader);
141 		}
142 	}
143 
144 	override void dispose() {
145 		debug (resourceTracking) {
146 			import std.stdio : writeln;
147 
148 			writefln!"Delete OpenGL shader %p"(cast(void*) this);
149 		}
150 
151 		glDeleteProgram(program);
152 
153 		context.resources.remove(this);
154 	}
155 
156 	private void useShader() {
157 		if (context.shaderInUse !is this) {
158 			context.shaderInUse = this;
159 			glUseProgram(program);
160 		}
161 	}
162 
163 	private GLint getUniformLocation(string name) {
164 		import std..string : toStringz;
165 
166 		if (name in uniformLocations) {
167 			return uniformLocations[name];
168 		}
169 
170 		GLint result = glGetUniformLocation(program, name.toStringz);
171 
172 		uniformLocations[name] = result;
173 		return result;
174 	}
175 
176 	override void setUniform(string name, float value) {
177 		useShader();
178 
179 		GLint uniformLocation = getUniformLocation(name);
180 		if (uniformLocation == -1) {
181 			return;
182 		}
183 
184 		glUniform1f(uniformLocation, value);
185 		if (glGetError() != GL_NO_ERROR) {
186 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to float value");
187 		}
188 	}
189 
190 	override void setUniform(string name, int value) {
191 		useShader();
192 
193 		GLint uniformLocation = getUniformLocation(name);
194 		if (uniformLocation == -1) {
195 			return;
196 		}
197 
198 		glUniform1i(uniformLocation, value);
199 		if (glGetError() != GL_NO_ERROR) {
200 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to int value");
201 		}
202 	}
203 
204 	override void setUniform(string name, FVec2 value) {
205 		useShader();
206 
207 		GLint uniformLocation = getUniformLocation(name);
208 		if (uniformLocation == -1) {
209 			return;
210 		}
211 
212 		glUniform2f(uniformLocation, value.x, value.y);
213 		if (glGetError() != GL_NO_ERROR) {
214 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to FVec2 value");
215 		}
216 	}
217 
218 	override void setUniform(string name, FVec3 value) {
219 		useShader();
220 
221 		GLint uniformLocation = getUniformLocation(name);
222 		if (uniformLocation == -1) {
223 			return;
224 		}
225 
226 		glUniform3f(uniformLocation, value.x, value.y, value.z);
227 		if (glGetError() != GL_NO_ERROR) {
228 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to FVec3 value");
229 		}
230 	}
231 
232 	override void setUniform(string name, FVec4 value) {
233 		useShader();
234 
235 		GLint uniformLocation = getUniformLocation(name);
236 		if (uniformLocation == -1) {
237 			return;
238 		}
239 
240 		glUniform4f(uniformLocation, value.x, value.y, value.z, value.w);
241 		if (glGetError() != GL_NO_ERROR) {
242 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to FVec4 value");
243 		}
244 	}
245 
246 	override void setUniform(string name, Color value) {
247 		useShader();
248 
249 		GLint uniformLocation = getUniformLocation(name);
250 		if (uniformLocation == -1) {
251 			return;
252 		}
253 
254 		glUniform4f(uniformLocation, value.r, value.g, value.b, value.a);
255 		if (glGetError() != GL_NO_ERROR) {
256 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to Color value");
257 		}
258 	}
259 
260 	override void setUniform(string name, IVec2 value) {
261 		useShader();
262 
263 		GLint uniformLocation = getUniformLocation(name);
264 		if (uniformLocation == -1) {
265 			return;
266 		}
267 
268 		glUniform2i(uniformLocation, value.x, value.y);
269 		if (glGetError() != GL_NO_ERROR) {
270 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to IVec2 value");
271 		}
272 	}
273 
274 	override void setUniform(string name, IVec3 value) {
275 		useShader();
276 
277 		GLint uniformLocation = getUniformLocation(name);
278 		if (uniformLocation == -1) {
279 			return;
280 		}
281 
282 		glUniform3i(uniformLocation, value.x, value.y, value.z);
283 		if (glGetError() != GL_NO_ERROR) {
284 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to IVec3 value");
285 		}
286 	}
287 
288 	override void setUniform(string name, IVec4 value) {
289 		useShader();
290 
291 		GLint uniformLocation = getUniformLocation(name);
292 		if (uniformLocation == -1) {
293 			return;
294 		}
295 
296 		glUniform4i(uniformLocation, value.x, value.y, value.z, value.w);
297 		if (glGetError() != GL_NO_ERROR) {
298 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to IVec4 value");
299 		}
300 	}
301 
302 	override void setUniform(string name, FMat3 value) {
303 		useShader();
304 
305 		GLint uniformLocation = getUniformLocation(name);
306 		if (uniformLocation == -1) {
307 			return;
308 		}
309 
310 		glUniformMatrix3fv(uniformLocation, 1, true, cast(const(GLfloat)*)&value);
311 		if (glGetError() != GL_NO_ERROR) {
312 			throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to FMat3 value");
313 		}
314 	}
315 
316 	private {
317 		size_t[string] textureLocations;
318 		Texture[] textures;
319 		int currTexture;
320 
321 		size_t assignTexture(string name) {
322 			if (name !in textureLocations) {
323 				useShader();
324 
325 				GLint uniformLocation = getUniformLocation(name);
326 				if (uniformLocation == -1) {
327 					return -1;
328 				}
329 
330 				glUniform1i(uniformLocation, currTexture);
331 				if (glGetError() != GL_NO_ERROR) {
332 					throw new OpenGLException("An error occurred while setting uniform '" ~ name ~ "' to texture value");
333 				}
334 
335 				textureLocations[name] = currTexture;
336 				textures ~= null;
337 				currTexture += 1;
338 			}
339 			return textureLocations[name];
340 		}
341 	}
342 
343 	override void setUniform(string name, Texture value) {
344 		if (value && !cast(OpenGLFramebuffer) value && !cast(OpenGLTexture) value) {
345 			throw new OpenGLException("Cannot set OpenGL uniform to non-OpenGL texture");
346 		}
347 
348 		size_t texture = assignTexture(name);
349 		if (texture == -1) {
350 			return;
351 		}
352 
353 		textures[texture] = value;
354 	}
355 
356 }
357 
358 final class OpenGLMesh : Mesh, OpenGLResource {
359 
360 	private {
361 		OpenGLBackend context;
362 
363 		GLuint vao, buffer;
364 
365 		MeshUsage usage;
366 	}
367 
368 	private this(OpenGLBackend context, VertexFormat format, MeshUsage usage) {
369 		debug (resourceTracking) {
370 			import std.stdio : writeln;
371 
372 			writefln!"Create OpenGL mesh %p"(cast(void*) this);
373 		}
374 
375 		this.context = context;
376 		this.usage = usage;
377 
378 		glGenVertexArrays(1, &vao);
379 		glBindVertexArray(vao);
380 
381 		glGenBuffers(1, &buffer);
382 		glBindBuffer(GL_ARRAY_BUFFER, buffer);
383 
384 		foreach (i, v; format.attributes) {
385 			GLint size;
386 			GLenum type;
387 			final switch (v.type) {
388 			case AttributeType.Float:
389 				size = 1;
390 				type = GL_FLOAT;
391 				break;
392 			case AttributeType.FVec2:
393 				size = 2;
394 				type = GL_FLOAT;
395 				break;
396 			case AttributeType.FVec3:
397 				size = 3;
398 				type = GL_FLOAT;
399 				break;
400 			case AttributeType.FVec4:
401 				size = 4;
402 				type = GL_FLOAT;
403 				break;
404 			case AttributeType.Int:
405 				size = 1;
406 				type = GL_INT;
407 				break;
408 			case AttributeType.IVec2:
409 				size = 2;
410 				type = GL_INT;
411 				break;
412 			case AttributeType.IVec3:
413 				size = 3;
414 				type = GL_INT;
415 				break;
416 			case AttributeType.IVec4:
417 				size = 4;
418 				type = GL_INT;
419 				break;
420 			}
421 			glVertexAttribPointer(cast(GLuint) i, size, type, false,
422 				cast(GLsizei) format.stride, cast(void*) v.byteOffset);
423 			glEnableVertexAttribArray(cast(GLuint) i);
424 		}
425 	}
426 
427 	override void dispose() {
428 		debug (resourceTracking) {
429 			import std.stdio : writeln;
430 
431 			writefln!"Delete OpenGL mesh %p"(cast(void*) this);
432 		}
433 
434 		glDeleteVertexArrays(1, &vao);
435 		glDeleteBuffers(1, &buffer);
436 
437 		context.resources.remove(this);
438 	}
439 
440 	override void upload(void[] data) {
441 		glBindVertexArray(vao);
442 		glBufferData(GL_ARRAY_BUFFER, cast(GLsizeiptr) data.length, data.ptr,
443 			usage == MeshUsage.Dynamic ? GL_STREAM_DRAW : GL_STATIC_DRAW,
444 		);
445 	}
446 
447 }
448 
449 final class OpenGLTexture : Texture, OpenGLResource {
450 
451 	private {
452 		OpenGLBackend context;
453 
454 		GLuint texture;
455 
456 		IVec2 _size;
457 	}
458 
459 	private this(OpenGLBackend context, IVec2 size, const(void)[] data) {
460 		debug (resourceTracking) {
461 			import std.stdio : writeln;
462 
463 			writefln!"Create OpenGL texture %p"(cast(void*) this);
464 		}
465 
466 		this.context = context;
467 		_size = size;
468 
469 		glGenTextures(1, &texture);
470 		glBindTexture(GL_TEXTURE_2D, texture);
471 
472 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
473 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
474 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
475 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
476 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
477 			cast(GLsizei) size.x, cast(GLsizei) size.y,
478 			0, GL_RGBA, GL_UNSIGNED_BYTE, flipImage(size.x, size.y, data).ptr);
479 	}
480 
481 	override void dispose() {
482 		debug (resourceTracking) {
483 			import std.stdio : writeln;
484 
485 			writefln!"Delete OpenGL texture %p"(cast(void*) this);
486 		}
487 
488 		glDeleteTextures(1, &texture);
489 
490 		context.resources.remove(this);
491 	}
492 
493 	override IVec2 size() {
494 		return _size;
495 	}
496 
497 }
498 
499 final class OpenGLFramebuffer : Framebuffer, OpenGLResource {
500 
501 	private {
502 		OpenGLBackend context;
503 
504 		GLuint fbo, rbo, texColorBuffer;
505 
506 		IVec2 _size;
507 	}
508 
509 	private this(OpenGLBackend context, IVec2 size) {
510 		debug (resourceTracking) {
511 			import std.stdio : writeln;
512 
513 			writefln!"Create OpenGL framebuffer %p"(cast(void*) this);
514 		}
515 
516 		this.context = context;
517 		_size = size;
518 
519 		glGenFramebuffers(1, &fbo);
520 
521 		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
522 
523 		glGenTextures(1, &texColorBuffer);
524 		glBindTexture(GL_TEXTURE_2D, texColorBuffer);
525 
526 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
527 			cast(GLsizei) size.x, cast(GLsizei) size.y,
528 			0, GL_RGBA, GL_UNSIGNED_BYTE, null);
529 
530 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
531 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
532 		glBindTexture(GL_TEXTURE_2D, 0);
533 
534 		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
535 			GL_TEXTURE_2D, texColorBuffer, 0);
536 		
537 		glGenRenderbuffers(1, &rbo);
538 
539 		glBindRenderbuffer(GL_RENDERBUFFER, rbo);
540 		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
541 			cast(GLsizei) size.x, cast(GLsizei) size.y);
542 		glBindRenderbuffer(GL_RENDERBUFFER, 0);
543 
544 		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
545 			GL_RENDERBUFFER, rbo);
546 
547 		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
548 			throw new OpenGLException("An error occurred while creating a framebuffer");
549 		}
550 
551 		if (context._renderTarget) {
552 			glBindFramebuffer(GL_FRAMEBUFFER, (cast(OpenGLFramebuffer) context._renderTarget).fbo);
553 		}
554 		else {
555 			glBindFramebuffer(GL_FRAMEBUFFER, 0);
556 		}
557 	}
558 
559 	override void dispose() {
560 		debug (resourceTracking) {
561 			import std.stdio : writeln;
562 
563 			writefln!"Delete OpenGL framebuffer %p"(cast(void*) this);
564 		}
565 
566 		glDeleteRenderbuffers(1, &rbo);
567 		glDeleteTextures(1, &texColorBuffer);
568 		glDeleteFramebuffers(1, &fbo);
569 
570 		context.resources.remove(this);
571 	}
572 
573 	override IVec2 size() {
574 		return _size;
575 	}
576 
577 }
578 
579 final class OpenGLBackend : CanvasBackend {
580 
581 	private OpenGLShader shaderInUse;
582 
583 	this() {
584 		GLSupport gl = .loadOpenGL(); // TODO: if you wanna do multicontext on Windows,
585 		// apparently loadOpenGL needs to be called after every context switch?
586 		if (gl < GLSupport.gl33) {
587 			throw new Exception("Could not load OpenGL 3.3 or above");
588 		}
589 
590 		glDisable(GL_MULTISAMPLE);
591 	}
592 
593 	private bool[OpenGLResource] resources;
594 
595 	override void dispose() {
596 		import std.array : array;
597 
598 		foreach (resource; resources.byKey.array) {
599 			resource.dispose();
600 		}
601 
602 		// TODO: unloadOpenGL?
603 	}
604 
605 	override Shader shader(ShaderSource[] sources) {
606 		return new OpenGLShader(this, sources);
607 	}
608 
609 	override Mesh mesh(VertexFormat format, MeshUsage usage) {
610 		return new OpenGLMesh(this, format, usage);
611 	}
612 
613 	override Framebuffer framebuffer(IVec2 size) {
614 		return new OpenGLFramebuffer(this, size);
615 	}
616 
617 	override Texture texture(IVec2 size, const(void)[] data) {
618 		return new OpenGLTexture(this, size, data);
619 	}
620 
621 	private Framebuffer _renderTarget;
622 
623 	override void renderTarget(Framebuffer framebuffer) {
624 		if (framebuffer && !cast(OpenGLFramebuffer) framebuffer) {
625 			throw new OpenGLException("Cannot bind non-OpenGL framebuffer to OpenGL context");
626 		}
627 
628 		if (framebuffer !is _renderTarget) {
629 			_renderTarget = framebuffer;
630 			if (framebuffer) {
631 				glBindFramebuffer(GL_FRAMEBUFFER, (cast(OpenGLFramebuffer) framebuffer).fbo);
632 			}
633 			else {
634 				glBindFramebuffer(GL_FRAMEBUFFER, 0);
635 			}
636 		}
637 	}
638 
639 	override Framebuffer renderTarget() {
640 		return _renderTarget;
641 	}
642 
643 	override void clearColor(Color color) {
644 		glClearColor(color.r, color.g, color.b, color.a);
645 		glClear(GL_COLOR_BUFFER_BIT);
646 	}
647 
648 	override void clearStencil(ubyte value) {
649 		glClearStencil(value);
650 		glClear(GL_STENCIL_BUFFER_BIT);
651 	}
652 
653 	override void stencil(Stencil value) {
654 		stencilSeparate(value, value);
655 	}
656 
657 	override void stencilSeparate(Stencil front, Stencil back) {
658 		GLenum convStencilFunc(StencilFunction func) {
659 			final switch (func) {
660 				case StencilFunction.Always: return GL_ALWAYS;
661 				case StencilFunction.Never: return GL_NEVER;
662 				case StencilFunction.Lt: return GL_LESS;
663 				case StencilFunction.Le: return GL_LEQUAL;
664 				case StencilFunction.Gt: return GL_GREATER;
665 				case StencilFunction.Ge: return GL_GEQUAL;
666 				case StencilFunction.Eq: return GL_EQUAL;
667 				case StencilFunction.Neq: return GL_NOTEQUAL;
668 			}
669 		}
670 
671 		GLenum convStencilOp(StencilOp op) {
672 			final switch (op) {
673 				case StencilOp.Nop: return GL_KEEP;
674 				case StencilOp.Zero: return GL_ZERO;
675 				case StencilOp.Set: return GL_REPLACE;
676 				case StencilOp.Inc: return GL_INCR;
677 				case StencilOp.Dec: return GL_DECR;
678 				case StencilOp.IncWrap: return GL_INCR_WRAP;
679 				case StencilOp.DecWrap: return GL_DECR_WRAP;
680 				case StencilOp.Inv: return GL_INVERT;
681 			}
682 		}
683 
684 		if (front.func == StencilFunction.Always
685 				&& front.stencilFail == StencilOp.Nop
686 				&& front.depthFail == StencilOp.Nop
687 				&& front.pass == StencilOp.Nop
688 				&& back.func == StencilFunction.Always
689 				&& back.stencilFail == StencilOp.Nop
690 				&& back.depthFail == StencilOp.Nop
691 				&& back.pass == StencilOp.Nop) {
692 			glDisable(GL_STENCIL_TEST);
693 			return;
694 		}
695 
696 		glEnable(GL_STENCIL_TEST);
697 
698 		if (front.writeMask == back.writeMask) {
699 			glStencilMask(front.writeMask);
700 		}
701 		else {
702 			glStencilMaskSeparate(GL_FRONT, front.writeMask);
703 			glStencilMaskSeparate(GL_BACK, back.writeMask);
704 		}
705 
706 		if (front.func == back.func && front.refValue == back.refValue && front.readMask == back.readMask) {
707 			glStencilFunc(convStencilFunc(front.func), front.refValue, front.readMask);
708 		}
709 		else {
710 			glStencilFuncSeparate(GL_FRONT, convStencilFunc(front.func), front.refValue, front.readMask);
711 			glStencilFuncSeparate(GL_BACK, convStencilFunc(back.func), back.refValue, back.readMask);
712 		}
713 
714 		if (front.stencilFail == back.stencilFail && front.depthFail == back.depthFail && front.pass == back.pass) {
715 			glStencilOp(
716 				convStencilOp(front.stencilFail),
717 				convStencilOp(front.depthFail),
718 				convStencilOp(front.pass),
719 			);
720 		}
721 		else {
722 			glStencilOpSeparate(GL_FRONT,
723 				convStencilOp(front.stencilFail),
724 				convStencilOp(front.depthFail),
725 				convStencilOp(front.pass),
726 			);
727 			glStencilOpSeparate(GL_BACK,
728 				convStencilOp(back.stencilFail),
729 				convStencilOp(back.depthFail),
730 				convStencilOp(back.pass),
731 			);
732 		}
733 	}
734 
735 	private BlendingFunction _colorBlend;
736 	private BlendingFunction _alphaBlend;
737 
738 	override BlendingFunction colorBlend() const { return _colorBlend; }
739 	override BlendingFunction alphaBlend() const { return _alphaBlend; }
740 	override BlendingFunction blend() const { return _colorBlend; }
741 
742 	override void blend(BlendingFunction value) {
743 		blend(value, value);
744 	}
745 
746 	private GLenum convBlendingOperation(BlendingFunction.Operation op) {
747 		final switch (op) {
748 			case BlendingFunction.Operation.Add: return GL_FUNC_ADD;
749 			case BlendingFunction.Operation.SrcMinusDst: return GL_FUNC_SUBTRACT;
750 			case BlendingFunction.Operation.DstMinusSrc: return GL_FUNC_REVERSE_SUBTRACT;
751 			case BlendingFunction.Operation.Min: return GL_MIN;
752 			case BlendingFunction.Operation.Max: return GL_MAX;
753 		}
754 	}
755 
756 	private GLenum convBlendingFactor(BlendingFunction.Factor factor) {
757 		final switch (factor) {
758 			case BlendingFunction.Factor.Zero: return GL_ZERO;
759 			case BlendingFunction.Factor.One: return GL_ONE;
760 			case BlendingFunction.Factor.SrcColor: return GL_SRC_COLOR;
761 			case BlendingFunction.Factor.DstColor: return GL_DST_COLOR;
762 			case BlendingFunction.Factor.SrcAlpha: return GL_SRC_ALPHA;
763 			case BlendingFunction.Factor.DstAlpha: return GL_DST_ALPHA;
764 			case BlendingFunction.Factor.ConstColor: return GL_CONSTANT_COLOR;
765 			case BlendingFunction.Factor.ConstAlpha: return GL_CONSTANT_ALPHA;
766 			case BlendingFunction.Factor.OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR;
767 			case BlendingFunction.Factor.OneMinusDstColor: return GL_ONE_MINUS_DST_COLOR;
768 			case BlendingFunction.Factor.OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA;
769 			case BlendingFunction.Factor.OneMinusDstAlpha: return GL_ONE_MINUS_DST_ALPHA;
770 			case BlendingFunction.Factor.OneMinusConstColor: return GL_ONE_MINUS_CONSTANT_COLOR;
771 			case BlendingFunction.Factor.OneMinusConstAlpha: return GL_ONE_MINUS_CONSTANT_ALPHA;
772 		}
773 	}
774 
775 	override void blend(BlendingFunction color, BlendingFunction alpha) {
776 		_colorBlend = color;
777 		_alphaBlend = alpha;
778 
779 		if (color == BlendingFunctions.Overwrite && alpha == color) {
780 			// technically doesn't catch all functions equivalent to disabling blending
781 			// but it's good enough
782 			glDisable(GL_BLEND);
783 			return;
784 		}
785 
786 		glEnable(GL_BLEND);
787 
788 		if (color.op == alpha.op) {
789 			glBlendEquation(convBlendingOperation(color.op));
790 		}
791 		else {
792 			glBlendEquationSeparate(
793 				convBlendingOperation(color.op),
794 				convBlendingOperation(alpha.op),
795 			);
796 		}
797 
798 		if (color.sfactor == alpha.sfactor && color.dfactor == alpha.dfactor) {
799 			glBlendFunc(
800 				convBlendingFactor(color.sfactor),
801 				convBlendingFactor(color.dfactor),
802 			);
803 		}
804 		else {
805 			glBlendFuncSeparate(
806 				convBlendingFactor(color.sfactor),
807 				convBlendingFactor(color.dfactor),
808 				convBlendingFactor(alpha.sfactor),
809 				convBlendingFactor(alpha.dfactor),
810 			);
811 		}
812 
813 		glBlendColor(
814 			color.constant.r,
815 			color.constant.g,
816 			color.constant.b,
817 			alpha.constant.a,
818 		);
819 	}
820 
821 	override void viewport(IVec2 location, IVec2 size) {
822 		glViewport(location.x, location.y, size.x, size.y);
823 	}
824 
825 	override void colorWriteMask(bool r, bool g, bool b, bool a) {
826 		glColorMask(r, g, b, a);
827 	}
828 
829 	override void draw(DrawMode mode, Shader shader, Mesh mesh, size_t startVertex, size_t numVertices) {
830 		if (!cast(OpenGLMesh) mesh) {
831 			throw new OpenGLException("Cannot draw non-OpenGL mesh in OpenGL context");
832 		}
833 
834 		if (!cast(OpenGLShader) shader) {
835 			throw new OpenGLException("Cannot use non-OpenGL shader in OpenGL context");
836 		}
837 
838 		OpenGLShader glShader = cast(OpenGLShader) shader;
839 
840 		glShader.useShader();
841 
842 		foreach (i, texture; glShader.textures) {
843 			glActiveTexture(GL_TEXTURE0 + cast(int) i);
844 			if (cast(OpenGLFramebuffer) texture) {
845 				glBindTexture(GL_TEXTURE_2D, (cast(OpenGLFramebuffer) texture).texColorBuffer);
846 			}
847 			else if (cast(OpenGLTexture) texture) {
848 				glBindTexture(GL_TEXTURE_2D, (cast(OpenGLTexture) texture).texture);
849 			}
850 			else {
851 				glBindTexture(GL_TEXTURE_2D, 0);
852 			}
853 		}
854 
855 		glBindVertexArray((cast(OpenGLMesh) mesh).vao);
856 
857 		GLenum glMode;
858 		final switch (mode) {
859 		case DrawMode.Triangles:
860 			glMode = GL_TRIANGLES;
861 			break;
862 		case DrawMode.TriangleFan:
863 			glMode = GL_TRIANGLE_FAN;
864 			break;
865 		case DrawMode.TriangleStrip:
866 			glMode = GL_TRIANGLE_STRIP;
867 			break;
868 		}
869 
870 		glDrawArrays(glMode, cast(GLint) startVertex, cast(GLsizei) numVertices);
871 	}
872 
873 }