all repos — mgba @ cd6209d7f07cbbc881e647c536cac50291222552

mGBA Game Boy Advance Emulator

src/platform/opengl/gles2.c (view raw)

   1/* Copyright (c) 2013-2015 Jeffrey Pfau
   2 *
   3 * This Source Code Form is subject to the terms of the Mozilla Public
   4 * License, v. 2.0. If a copy of the MPL was not distributed with this
   5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
   6#include "gles2.h"
   7
   8#include <mgba/core/log.h>
   9#include <mgba-util/configuration.h>
  10#include <mgba-util/formatting.h>
  11#include <mgba-util/math.h>
  12#include <mgba-util/vector.h>
  13#include <mgba-util/vfs.h>
  14
  15mLOG_DECLARE_CATEGORY(OPENGL);
  16mLOG_DEFINE_CATEGORY(OPENGL, "OpenGL", "video.ogl");
  17
  18#define MAX_PASSES 8
  19
  20static const GLchar* const _gles2Header =
  21	"#version 100\n"
  22	"precision mediump float;\n";
  23
  24static const GLchar* const _gl32VHeader =
  25	"#version 150 core\n"
  26	"#define attribute in\n"
  27	"#define varying out\n";
  28
  29static const GLchar* const _gl32FHeader =
  30	"#version 150 core\n"
  31	"#define varying in\n"
  32	"#define texture2D texture\n"
  33	"out vec4 compat_FragColor;\n"
  34	"#define gl_FragColor compat_FragColor\n";
  35
  36static const char* const _vertexShader =
  37	"attribute vec4 position;\n"
  38	"varying vec2 texCoord;\n"
  39
  40	"void main() {\n"
  41	"	gl_Position = position;\n"
  42	"	texCoord = (position.st + vec2(1.0, -1.0)) * vec2(0.5, -0.5);\n"
  43	"}";
  44
  45static const char* const _nullVertexShader =
  46	"attribute vec4 position;\n"
  47	"varying vec2 texCoord;\n"
  48
  49	"void main() {\n"
  50	"	gl_Position = position;\n"
  51	"	texCoord = (position.st + vec2(1.0, 1.0)) * vec2(0.5, 0.5);\n"
  52	"}";
  53
  54static const char* const _fragmentShader =
  55	"varying vec2 texCoord;\n"
  56	"uniform sampler2D tex;\n"
  57	"uniform float gamma;\n"
  58	"uniform vec3 desaturation;\n"
  59	"uniform vec3 scale;\n"
  60	"uniform vec3 bias;\n"
  61
  62	"void main() {\n"
  63	"	vec4 color = texture2D(tex, texCoord);\n"
  64	"	color.a = 1.;\n"
  65	"	float average = dot(color.rgb, vec3(1.)) / 3.;\n"
  66	"	color.rgb = mix(color.rgb, vec3(average), desaturation);\n"
  67	"	color.rgb = scale * pow(color.rgb, vec3(gamma, gamma, gamma)) + bias;\n"
  68	"	gl_FragColor = color;\n"
  69	"}";
  70
  71static const char* const _nullFragmentShader =
  72	"varying vec2 texCoord;\n"
  73	"uniform sampler2D tex;\n"
  74
  75	"void main() {\n"
  76	"	vec4 color = texture2D(tex, texCoord);\n"
  77	"	color.a = 1.;\n"
  78	"	gl_FragColor = color;\n"
  79	"}";
  80
  81static const char* const _interframeFragmentShader =
  82	"varying vec2 texCoord;\n"
  83	"uniform sampler2D tex;\n"
  84
  85	"void main() {\n"
  86	"	vec4 color = texture2D(tex, texCoord);\n"
  87	"	color.a = 0.5;\n"
  88	"	gl_FragColor = color;\n"
  89	"}";
  90
  91static const GLfloat _vertices[] = {
  92	-1.f, -1.f,
  93	-1.f, 1.f,
  94	1.f, 1.f,
  95	1.f, -1.f,
  96};
  97
  98static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
  99	UNUSED(handle);
 100	struct mGLES2Context* context = (struct mGLES2Context*) v;
 101	v->width = 1;
 102	v->height = 1;
 103	glGenTextures(1, &context->tex);
 104	glBindTexture(GL_TEXTURE_2D, context->tex);
 105	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 106	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 107
 108	glGenBuffers(1, &context->vbo);
 109	glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
 110	glBufferData(GL_ARRAY_BUFFER, sizeof(_vertices), _vertices, GL_STATIC_DRAW);
 111
 112	struct mGLES2Uniform* uniforms = malloc(sizeof(struct mGLES2Uniform) * 4);
 113	uniforms[0].name = "gamma";
 114	uniforms[0].readableName = "Gamma";
 115	uniforms[0].type = GL_FLOAT;
 116	uniforms[0].value.f = 1.0f;
 117	uniforms[0].min.f = 0.1f;
 118	uniforms[0].max.f = 3.0f;
 119	uniforms[1].name = "scale";
 120	uniforms[1].readableName = "Scale";
 121	uniforms[1].type = GL_FLOAT_VEC3;
 122	uniforms[1].value.fvec3[0] = 1.0f;
 123	uniforms[1].value.fvec3[1] = 1.0f;
 124	uniforms[1].value.fvec3[2] = 1.0f;
 125	uniforms[1].min.fvec3[0] = -1.0f;
 126	uniforms[1].min.fvec3[1] = -1.0f;
 127	uniforms[1].min.fvec3[2] = -1.0f;
 128	uniforms[1].max.fvec3[0] = 2.0f;
 129	uniforms[1].max.fvec3[1] = 2.0f;
 130	uniforms[1].max.fvec3[2] = 2.0f;
 131	uniforms[2].name = "bias";
 132	uniforms[2].readableName = "Bias";
 133	uniforms[2].type = GL_FLOAT_VEC3;
 134	uniforms[2].value.fvec3[0] = 0.0f;
 135	uniforms[2].value.fvec3[1] = 0.0f;
 136	uniforms[2].value.fvec3[2] = 0.0f;
 137	uniforms[2].min.fvec3[0] = -1.0f;
 138	uniforms[2].min.fvec3[1] = -1.0f;
 139	uniforms[2].min.fvec3[2] = -1.0f;
 140	uniforms[2].max.fvec3[0] = 1.0f;
 141	uniforms[2].max.fvec3[1] = 1.0f;
 142	uniforms[2].max.fvec3[2] = 1.0f;
 143	uniforms[3].name = "desaturation";
 144	uniforms[3].readableName = "Desaturation";
 145	uniforms[3].type = GL_FLOAT_VEC3;
 146	uniforms[3].value.fvec3[0] = 0.0f;
 147	uniforms[3].value.fvec3[1] = 0.0f;
 148	uniforms[3].value.fvec3[2] = 0.0f;
 149	uniforms[3].min.fvec3[0] = 0.0f;
 150	uniforms[3].min.fvec3[1] = 0.0f;
 151	uniforms[3].min.fvec3[2] = 0.0f;
 152	uniforms[3].max.fvec3[0] = 1.0f;
 153	uniforms[3].max.fvec3[1] = 1.0f;
 154	uniforms[3].max.fvec3[2] = 1.0f;
 155	mGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4);
 156	mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0);
 157	mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, 0, 0);
 158
 159	if (context->initialShader.vao != (GLuint) -1) {
 160		glBindVertexArray(context->initialShader.vao);
 161		glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
 162		glBindVertexArray(context->finalShader.vao);
 163		glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
 164		glBindVertexArray(context->interframeShader.vao);
 165		glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
 166		glBindVertexArray(0);
 167	}
 168
 169	glDeleteFramebuffers(1, &context->finalShader.fbo);
 170	glDeleteTextures(1, &context->finalShader.tex);
 171	context->finalShader.fbo = 0;
 172	context->finalShader.tex = 0;
 173}
 174
 175static void mGLES2ContextSetDimensions(struct VideoBackend* v, unsigned width, unsigned height) {
 176	struct mGLES2Context* context = (struct mGLES2Context*) v;
 177	if (width == v->width && height == v->height) {
 178		return;
 179	}
 180	v->width = width;
 181	v->height = height;
 182
 183	glBindTexture(GL_TEXTURE_2D, context->tex);
 184#ifdef COLOR_16_BIT
 185#ifdef COLOR_5_6_5
 186	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
 187#else
 188	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0);
 189#endif
 190#elif defined(__BIG_ENDIAN__)
 191	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0);
 192#else
 193	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
 194#endif
 195
 196	size_t n;
 197	for (n = 0; n < context->nShaders; ++n) {
 198		if (context->shaders[n].width < 0 || context->shaders[n].height < 0) {
 199			context->shaders[n].dirty = true;
 200		}
 201	}
 202	context->initialShader.dirty = true;
 203	context->interframeShader.dirty = true;
 204}
 205
 206static void mGLES2ContextDeinit(struct VideoBackend* v) {
 207	struct mGLES2Context* context = (struct mGLES2Context*) v;
 208	glDeleteTextures(1, &context->tex);
 209	glDeleteBuffers(1, &context->vbo);
 210	mGLES2ShaderDeinit(&context->initialShader);
 211	mGLES2ShaderDeinit(&context->finalShader);
 212	mGLES2ShaderDeinit(&context->interframeShader);
 213	free(context->initialShader.uniforms);
 214}
 215
 216static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) {
 217	struct mGLES2Context* context = (struct mGLES2Context*) v;
 218	unsigned drawW = w;
 219	unsigned drawH = h;
 220	if (v->lockAspectRatio) {
 221		if (w * v->height > h * v->width) {
 222			drawW = h * v->width / v->height;
 223		} else if (w * v->height < h * v->width) {
 224			drawH = w * v->height / v->width;
 225		}
 226	}
 227	if (v->lockIntegerScaling) {
 228		if (drawW >= v->width) {
 229			drawW -= drawW % v->width;
 230		}
 231		if (drawH >= v->height) {
 232			drawH -= drawH % v->height;
 233		}
 234	}
 235	size_t n;
 236	for (n = 0; n < context->nShaders; ++n) {
 237		if (context->shaders[n].width == 0 || context->shaders[n].height == 0) {
 238			context->shaders[n].dirty = true;
 239		}
 240	}
 241	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 242	glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
 243}
 244
 245static void mGLES2ContextClear(struct VideoBackend* v) {
 246	UNUSED(v);
 247	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 248	glClearColor(0.f, 0.f, 0.f, 1.f);
 249	glClear(GL_COLOR_BUFFER_BIT);
 250}
 251
 252void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) {
 253	GLint viewport[4];
 254	glGetIntegerv(GL_VIEWPORT, viewport);
 255	int drawW = shader->width;
 256	int drawH = shader->height;
 257	int padW = 0;
 258	int padH = 0;
 259	if (!drawW) {
 260		drawW = viewport[2];
 261		padW = viewport[0];
 262	} else if (shader->width < 0) {
 263		drawW = context->d.width * -shader->width;
 264	}
 265	if (!drawH) {
 266		drawH = viewport[3];
 267		padH = viewport[1];
 268	} else if (shader->height < 0) {
 269		drawH = context->d.height * -shader->height;
 270	}
 271	if (shader->integerScaling) {
 272		padW = 0;
 273		padH = 0;
 274		drawW -= drawW % context->d.width;
 275		drawH -= drawH % context->d.height;
 276	}
 277
 278	if (shader->dirty) {
 279		if (shader->tex && (shader->width <= 0 || shader->height <= 0)) {
 280			GLint oldTex;
 281			glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTex);
 282			glBindTexture(GL_TEXTURE_2D, shader->tex);
 283			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, drawW, drawH, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
 284			glBindTexture(GL_TEXTURE_2D, oldTex);
 285		}
 286		shader->dirty = false;
 287	}
 288
 289	glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo);
 290	glViewport(padW, padH, drawW, drawH);
 291	if (shader->blend) {
 292		glEnable(GL_BLEND);
 293		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 294	} else {
 295		glDisable(GL_BLEND);
 296		glClearColor(0.f, 0.f, 0.f, 1.f);
 297		glClear(GL_COLOR_BUFFER_BIT);
 298	}
 299
 300	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
 301	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST);
 302	glUseProgram(shader->program);
 303	glUniform1i(shader->texLocation, 0);
 304	glUniform2f(shader->texSizeLocation, context->d.width - padW, context->d.height - padH);
 305	if (shader->vao != (GLuint) -1) {
 306		glBindVertexArray(shader->vao);
 307	} else {
 308		glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
 309		glEnableVertexAttribArray(shader->positionLocation);
 310		glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
 311	}
 312	size_t u;
 313	for (u = 0; u < shader->nUniforms; ++u) {
 314		struct mGLES2Uniform* uniform = &shader->uniforms[u];
 315		switch (uniform->type) {
 316		case GL_FLOAT:
 317			glUniform1f(uniform->location, uniform->value.f);
 318			break;
 319		case GL_INT:
 320			glUniform1i(uniform->location, uniform->value.i);
 321			break;
 322		case GL_BOOL:
 323			glUniform1i(uniform->location, uniform->value.b);
 324			break;
 325		case GL_FLOAT_VEC2:
 326			glUniform2fv(uniform->location, 1, uniform->value.fvec2);
 327			break;
 328		case GL_FLOAT_VEC3:
 329			glUniform3fv(uniform->location, 1, uniform->value.fvec3);
 330			break;
 331		case GL_FLOAT_VEC4:
 332			glUniform4fv(uniform->location, 1, uniform->value.fvec4);
 333			break;
 334		case GL_INT_VEC2:
 335			glUniform2iv(uniform->location, 1, uniform->value.ivec2);
 336			break;
 337		case GL_INT_VEC3:
 338			glUniform3iv(uniform->location, 1, uniform->value.ivec3);
 339			break;
 340		case GL_INT_VEC4:
 341			glUniform4iv(uniform->location, 1, uniform->value.ivec4);
 342			break;
 343		case GL_BOOL_VEC2:
 344			glUniform2i(uniform->location, uniform->value.bvec2[0], uniform->value.bvec2[1]);
 345			break;
 346		case GL_BOOL_VEC3:
 347			glUniform3i(uniform->location, uniform->value.bvec3[0], uniform->value.bvec3[1], uniform->value.bvec3[2]);
 348			break;
 349		case GL_BOOL_VEC4:
 350			glUniform4i(uniform->location, uniform->value.bvec4[0], uniform->value.bvec4[1], uniform->value.bvec4[2], uniform->value.bvec4[3]);
 351			break;
 352		case GL_FLOAT_MAT2:
 353			glUniformMatrix2fv(uniform->location, 1, GL_FALSE, uniform->value.fmat2x2);
 354			break;
 355		case GL_FLOAT_MAT3:
 356			glUniformMatrix3fv(uniform->location, 1, GL_FALSE, uniform->value.fmat3x3);
 357			break;
 358		case GL_FLOAT_MAT4:
 359			glUniformMatrix4fv(uniform->location, 1, GL_FALSE, uniform->value.fmat4x4);
 360			break;
 361		}
 362	}
 363	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 364	glBindTexture(GL_TEXTURE_2D, shader->tex);
 365}
 366
 367void mGLES2ContextDrawFrame(struct VideoBackend* v) {
 368	struct mGLES2Context* context = (struct mGLES2Context*) v;
 369	glActiveTexture(GL_TEXTURE0);
 370	glBindTexture(GL_TEXTURE_2D, context->tex);
 371
 372	GLint viewport[4];
 373	glGetIntegerv(GL_VIEWPORT, viewport);
 374
 375	context->finalShader.filter = v->filter;
 376	context->finalShader.dirty = true;
 377	_drawShader(context, &context->initialShader);
 378	if (v->interframeBlending) {
 379		context->interframeShader.blend = true;
 380		glViewport(0, 0, viewport[2], viewport[3]);
 381		_drawShader(context, &context->interframeShader);
 382	}
 383	size_t n;
 384	for (n = 0; n < context->nShaders; ++n) {
 385		glViewport(0, 0, viewport[2], viewport[3]);
 386		_drawShader(context, &context->shaders[n]);
 387	}
 388	glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
 389	_drawShader(context, &context->finalShader);
 390	if (v->interframeBlending) {
 391		context->interframeShader.blend = false;
 392		glBindTexture(GL_TEXTURE_2D, context->tex);
 393		_drawShader(context, &context->initialShader);
 394		glViewport(0, 0, viewport[2], viewport[3]);
 395		_drawShader(context, &context->interframeShader);
 396	}
 397	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 398	glUseProgram(0);
 399	if (context->finalShader.vao != (GLuint) -1) {
 400		glBindVertexArray(0);
 401	}
 402}
 403
 404void mGLES2ContextPostFrame(struct VideoBackend* v, const void* frame) {
 405	struct mGLES2Context* context = (struct mGLES2Context*) v;
 406	glBindTexture(GL_TEXTURE_2D, context->tex);
 407#ifdef COLOR_16_BIT
 408#ifdef COLOR_5_6_5
 409	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, frame);
 410#else
 411	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, frame);
 412#endif
 413#elif defined(__BIG_ENDIAN__)
 414	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, frame);
 415#else
 416	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, v->width, v->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame);
 417#endif
 418}
 419
 420void mGLES2ContextCreate(struct mGLES2Context* context) {
 421	context->d.init = mGLES2ContextInit;
 422	context->d.deinit = mGLES2ContextDeinit;
 423	context->d.setDimensions = mGLES2ContextSetDimensions;
 424	context->d.resized = mGLES2ContextResized;
 425	context->d.swap = 0;
 426	context->d.clear = mGLES2ContextClear;
 427	context->d.postFrame = mGLES2ContextPostFrame;
 428	context->d.drawFrame = mGLES2ContextDrawFrame;
 429	context->d.setMessage = 0;
 430	context->d.clearMessage = 0;
 431	context->shaders = 0;
 432	context->nShaders = 0;
 433}
 434
 435void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* fs, int width, int height, bool integerScaling, struct mGLES2Uniform* uniforms, size_t nUniforms) {
 436	shader->width = width;
 437	shader->height = height;
 438	shader->integerScaling = integerScaling;
 439	shader->filter = false;
 440	shader->blend = false;
 441	shader->dirty = true;
 442	shader->uniforms = uniforms;
 443	shader->nUniforms = nUniforms;
 444	glGenFramebuffers(1, &shader->fbo);
 445	glBindFramebuffer(GL_FRAMEBUFFER, shader->fbo);
 446
 447	glGenTextures(1, &shader->tex);
 448	glBindTexture(GL_TEXTURE_2D, shader->tex);
 449	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 450	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 451	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 452	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 453	if (shader->width > 0 && shader->height > 0) {
 454		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, shader->width, shader->height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
 455	} else {
 456		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);		
 457	}
 458
 459	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, shader->tex, 0);
 460	shader->program = glCreateProgram();
 461	shader->vertexShader = glCreateShader(GL_VERTEX_SHADER);
 462	shader->fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
 463	const GLchar* shaderBuffer[2];
 464	const GLubyte* version = glGetString(GL_VERSION);
 465	if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) {
 466		shaderBuffer[0] = _gl32VHeader;
 467	} else {
 468		shaderBuffer[0] = _gles2Header;
 469	}
 470	if (vs) {
 471		shaderBuffer[1] = vs;
 472	} else {
 473		shaderBuffer[1] = _nullVertexShader;
 474	}
 475	glShaderSource(shader->vertexShader, 2, shaderBuffer, 0);
 476
 477	if (strncmp((const char*) version, "OpenGL ES ", strlen("OpenGL ES "))) {
 478		shaderBuffer[0] = _gl32FHeader;
 479	}
 480	if (fs) {
 481		shaderBuffer[1] = fs;
 482	} else {
 483		shaderBuffer[1] = _nullFragmentShader;
 484	}
 485	glShaderSource(shader->fragmentShader, 2, shaderBuffer, 0);
 486
 487	glAttachShader(shader->program, shader->vertexShader);
 488	glAttachShader(shader->program, shader->fragmentShader);
 489	char log[1024];
 490	glCompileShader(shader->fragmentShader);
 491	glGetShaderInfoLog(shader->fragmentShader, 1024, 0, log);
 492	if (log[0]) {
 493		mLOG(OPENGL, ERROR, "%s\n", log);
 494	}
 495	glCompileShader(shader->vertexShader);
 496	glGetShaderInfoLog(shader->vertexShader, 1024, 0, log);
 497	if (log[0]) {
 498		mLOG(OPENGL, ERROR, "%s\n", log);
 499	}
 500	glLinkProgram(shader->program);
 501	glGetProgramInfoLog(shader->program, 1024, 0, log);
 502	if (log[0]) {
 503		mLOG(OPENGL, ERROR, "%s\n", log);
 504	}
 505
 506	shader->texLocation = glGetUniformLocation(shader->program, "tex");
 507	shader->texSizeLocation = glGetUniformLocation(shader->program, "texSize");
 508	shader->positionLocation = glGetAttribLocation(shader->program, "position");
 509	size_t i;
 510	for (i = 0; i < shader->nUniforms; ++i) {
 511		shader->uniforms[i].location = glGetUniformLocation(shader->program, shader->uniforms[i].name);
 512	}
 513
 514	const GLubyte* extensions = glGetString(GL_EXTENSIONS);
 515	if (shaderBuffer[0] == _gles2Header || version[0] >= '3' || (extensions && strstr((const char*) extensions, "_vertex_array_object") != NULL)) {
 516		glGenVertexArrays(1, &shader->vao);
 517		glBindVertexArray(shader->vao);
 518		glEnableVertexAttribArray(shader->positionLocation);
 519		glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
 520		glBindVertexArray(0);
 521	} else {
 522		shader->vao = -1;
 523	}
 524
 525	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 526}
 527
 528void mGLES2ShaderDeinit(struct mGLES2Shader* shader) {
 529	glDeleteTextures(1, &shader->tex);
 530	glDeleteShader(shader->fragmentShader);
 531	glDeleteProgram(shader->program);
 532	glDeleteFramebuffers(1, &shader->fbo);
 533	if (shader->vao != (GLuint) -1) {
 534		glDeleteVertexArrays(1, &shader->vao);
 535	}
 536}
 537
 538void mGLES2ShaderAttach(struct mGLES2Context* context, struct mGLES2Shader* shaders, size_t nShaders) {
 539	if (context->shaders) {
 540		if (context->shaders == shaders && context->nShaders == nShaders) {
 541			return;
 542		}
 543		mGLES2ShaderDetach(context);
 544	}
 545	context->shaders = shaders;
 546	context->nShaders = nShaders;
 547	size_t i;
 548	for (i = 0; i < nShaders; ++i) {
 549		glBindFramebuffer(GL_FRAMEBUFFER, context->shaders[i].fbo);
 550		glClearColor(0.f, 0.f, 0.f, 1.f);
 551		glClear(GL_COLOR_BUFFER_BIT);
 552
 553		if (context->shaders[i].vao != (GLuint) -1) {
 554			glBindVertexArray(context->shaders[i].vao);
 555			glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
 556			glEnableVertexAttribArray(context->shaders[i].positionLocation);
 557			glVertexAttribPointer(context->shaders[i].positionLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
 558		}
 559	}
 560	if (context->initialShader.vao != (GLuint) -1) {
 561		glBindVertexArray(0);
 562	}
 563	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 564}
 565
 566void mGLES2ShaderDetach(struct mGLES2Context* context) {
 567	if (!context->shaders) {
 568		return;
 569	}
 570	context->shaders = 0;
 571	context->nShaders = 0;
 572}
 573
 574static bool _lookupIntValue(const struct Configuration* config, const char* section, const char* key, int* out) {
 575	const char* charValue = ConfigurationGetValue(config, section, key);
 576	if (!charValue) {
 577		return false;
 578	}
 579	char* end;
 580	unsigned long value = strtol(charValue, &end, 10);
 581	if (*end) {
 582		return false;
 583	}
 584	*out = value;
 585	return true;
 586}
 587
 588static bool _lookupFloatValue(const struct Configuration* config, const char* section, const char* key, float* out) {
 589	const char* charValue = ConfigurationGetValue(config, section, key);
 590	if (!charValue) {
 591		return false;
 592	}
 593	char* end;
 594	float value = strtof_u(charValue, &end);
 595	if (*end) {
 596		return false;
 597	}
 598	*out = value;
 599	return true;
 600}
 601
 602static bool _lookupBoolValue(const struct Configuration* config, const char* section, const char* key, GLboolean* out) {
 603	const char* charValue = ConfigurationGetValue(config, section, key);
 604	if (!charValue) {
 605		return false;
 606	}
 607	if (!strcmp(charValue, "true")) {
 608		*out = GL_TRUE;
 609		return true;
 610	}
 611	if (!strcmp(charValue, "false")) {
 612		*out = GL_FALSE;
 613		return true;
 614	}
 615	char* end;
 616	unsigned long value = strtol(charValue, &end, 10);
 617	if (*end) {
 618		return false;
 619	}
 620	*out = value;
 621	return true;
 622}
 623
 624DECLARE_VECTOR(mGLES2UniformList, struct mGLES2Uniform);
 625DEFINE_VECTOR(mGLES2UniformList, struct mGLES2Uniform);
 626
 627static void _uniformHandler(const char* sectionName, void* user) {
 628	struct mGLES2UniformList* uniforms = user;
 629	unsigned passId;
 630	int sentinel;
 631	if (sscanf(sectionName, "pass.%u.uniform.%n", &passId, &sentinel) < 1) {
 632		return;
 633	}
 634	struct mGLES2Uniform* u = mGLES2UniformListAppend(uniforms);
 635	u->name = sectionName;
 636}
 637
 638
 639static void _loadValue(struct Configuration* description, const char* name, GLenum type, const char* field, union mGLES2UniformValue* value) {
 640	char fieldName[16];
 641	switch (type) {
 642	case GL_FLOAT:
 643		value->f = 0;
 644		_lookupFloatValue(description, name, field, &value->f);
 645		break;
 646	case GL_FLOAT_VEC2:
 647		value->fvec2[0] = 0;
 648		value->fvec2[1] = 0;
 649		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 650		_lookupFloatValue(description, name, fieldName, &value->fvec2[0]);
 651		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 652		_lookupFloatValue(description, name, fieldName, &value->fvec2[1]);
 653		break;
 654	case GL_FLOAT_VEC3:
 655		value->fvec3[0] = 0;
 656		value->fvec3[1] = 0;
 657		value->fvec3[2] = 0;
 658		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 659		_lookupFloatValue(description, name, fieldName, &value->fvec3[0]);
 660		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 661		_lookupFloatValue(description, name, fieldName, &value->fvec3[1]);
 662		snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
 663		_lookupFloatValue(description, name, fieldName, &value->fvec3[2]);
 664		break;
 665	case GL_FLOAT_VEC4:
 666		value->fvec4[0] = 0;
 667		value->fvec4[1] = 0;
 668		value->fvec4[2] = 0;
 669		value->fvec4[3] = 0;
 670		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 671		_lookupFloatValue(description, name, fieldName, &value->fvec4[0]);
 672		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 673		_lookupFloatValue(description, name, fieldName, &value->fvec4[1]);
 674		snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
 675		_lookupFloatValue(description, name, fieldName, &value->fvec4[2]);
 676		snprintf(fieldName, sizeof(fieldName), "%s[3]", field);
 677		_lookupFloatValue(description, name, fieldName, &value->fvec4[3]);
 678		break;
 679	case GL_FLOAT_MAT2:
 680		value->fmat2x2[0] = 0;
 681		value->fmat2x2[1] = 0;
 682		value->fmat2x2[2] = 0;
 683		value->fmat2x2[3] = 0;
 684		snprintf(fieldName, sizeof(fieldName), "%s[0,0]", field);
 685		_lookupFloatValue(description, name, fieldName, &value->fmat2x2[0]);
 686		snprintf(fieldName, sizeof(fieldName), "%s[0,1]", field);
 687		_lookupFloatValue(description, name, fieldName, &value->fmat2x2[1]);
 688		snprintf(fieldName, sizeof(fieldName), "%s[1,0]", field);
 689		_lookupFloatValue(description, name, fieldName, &value->fmat2x2[2]);
 690		snprintf(fieldName, sizeof(fieldName), "%s[1,1]", field);
 691		_lookupFloatValue(description, name, fieldName, &value->fmat2x2[3]);
 692		break;
 693	case GL_FLOAT_MAT3:
 694		value->fmat3x3[0] = 0;
 695		value->fmat3x3[1] = 0;
 696		value->fmat3x3[2] = 0;
 697		value->fmat3x3[3] = 0;
 698		value->fmat3x3[4] = 0;
 699		value->fmat3x3[5] = 0;
 700		value->fmat3x3[6] = 0;
 701		value->fmat3x3[7] = 0;
 702		value->fmat3x3[8] = 0;
 703		snprintf(fieldName, sizeof(fieldName), "%s[0,0]", field);
 704		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[0]);
 705		snprintf(fieldName, sizeof(fieldName), "%s[0,1]", field);
 706		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[1]);
 707		snprintf(fieldName, sizeof(fieldName), "%s[0,2]", field);
 708		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[2]);
 709		snprintf(fieldName, sizeof(fieldName), "%s[1,0]", field);
 710		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[3]);
 711		snprintf(fieldName, sizeof(fieldName), "%s[1,1]", field);
 712		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[4]);
 713		snprintf(fieldName, sizeof(fieldName), "%s[1,2]", field);
 714		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[5]);
 715		snprintf(fieldName, sizeof(fieldName), "%s[2,0]", field);
 716		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[6]);
 717		snprintf(fieldName, sizeof(fieldName), "%s[2,1]", field);
 718		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[7]);
 719		snprintf(fieldName, sizeof(fieldName), "%s[2,2]", field);
 720		_lookupFloatValue(description, name, fieldName, &value->fmat3x3[8]);
 721		break;
 722	case GL_FLOAT_MAT4:
 723		value->fmat4x4[0] = 0;
 724		value->fmat4x4[1] = 0;
 725		value->fmat4x4[2] = 0;
 726		value->fmat4x4[3] = 0;
 727		value->fmat4x4[4] = 0;
 728		value->fmat4x4[5] = 0;
 729		value->fmat4x4[6] = 0;
 730		value->fmat4x4[7] = 0;
 731		value->fmat4x4[8] = 0;
 732		value->fmat4x4[9] = 0;
 733		value->fmat4x4[10] = 0;
 734		value->fmat4x4[11] = 0;
 735		value->fmat4x4[12] = 0;
 736		value->fmat4x4[13] = 0;
 737		value->fmat4x4[14] = 0;
 738		value->fmat4x4[15] = 0;
 739		snprintf(fieldName, sizeof(fieldName), "%s[0,0]", field);
 740		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[0]);
 741		snprintf(fieldName, sizeof(fieldName), "%s[0,1]", field);
 742		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[1]);
 743		snprintf(fieldName, sizeof(fieldName), "%s[0,2]", field);
 744		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[2]);
 745		snprintf(fieldName, sizeof(fieldName), "%s[0,3]", field);
 746		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[3]);
 747		snprintf(fieldName, sizeof(fieldName), "%s[1,0]", field);
 748		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[4]);
 749		snprintf(fieldName, sizeof(fieldName), "%s[1,1]", field);
 750		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[5]);
 751		snprintf(fieldName, sizeof(fieldName), "%s[1,2]", field);
 752		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[6]);
 753		snprintf(fieldName, sizeof(fieldName), "%s[1,3]", field);
 754		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[7]);
 755		snprintf(fieldName, sizeof(fieldName), "%s[2,0]", field);
 756		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[8]);
 757		snprintf(fieldName, sizeof(fieldName), "%s[2,1]", field);
 758		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[9]);
 759		snprintf(fieldName, sizeof(fieldName), "%s[2,2]", field);
 760		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[10]);
 761		snprintf(fieldName, sizeof(fieldName), "%s[2,3]", field);
 762		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[11]);
 763		snprintf(fieldName, sizeof(fieldName), "%s[3,0]", field);
 764		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[12]);
 765		snprintf(fieldName, sizeof(fieldName), "%s[3,1]", field);
 766		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[13]);
 767		snprintf(fieldName, sizeof(fieldName), "%s[3,2]", field);
 768		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[14]);
 769		snprintf(fieldName, sizeof(fieldName), "%s[3,3]", field);
 770		_lookupFloatValue(description, name, fieldName, &value->fmat4x4[15]);
 771		break;
 772	case GL_INT:
 773		value->i = 0;
 774		_lookupIntValue(description, name, field, &value->i);
 775		break;
 776	case GL_INT_VEC2:
 777		value->ivec2[0] = 0;
 778		value->ivec2[1] = 0;
 779		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 780		_lookupIntValue(description, name, fieldName, &value->ivec2[0]);
 781		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 782		_lookupIntValue(description, name, fieldName, &value->ivec2[1]);
 783		break;
 784	case GL_INT_VEC3:
 785		value->ivec3[0] = 0;
 786		value->ivec3[1] = 0;
 787		value->ivec3[2] = 0;
 788		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 789		_lookupIntValue(description, name, fieldName, &value->ivec3[0]);
 790		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 791		_lookupIntValue(description, name, fieldName, &value->ivec3[1]);
 792		snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
 793		_lookupIntValue(description, name, fieldName, &value->ivec3[2]);
 794		break;
 795	case GL_INT_VEC4:
 796		value->ivec4[0] = 0;
 797		value->ivec4[1] = 0;
 798		value->ivec4[2] = 0;
 799		value->ivec4[3] = 0;
 800		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 801		_lookupIntValue(description, name, fieldName, &value->ivec4[0]);
 802		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 803		_lookupIntValue(description, name, fieldName, &value->ivec4[1]);
 804		snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
 805		_lookupIntValue(description, name, fieldName, &value->ivec4[2]);
 806		snprintf(fieldName, sizeof(fieldName), "%s[3]", field);
 807		_lookupIntValue(description, name, fieldName, &value->ivec4[3]);
 808		break;
 809	case GL_BOOL:
 810		value->b = 0;
 811		_lookupBoolValue(description, name, field, &value->b);
 812		break;
 813	case GL_BOOL_VEC2:
 814		value->bvec2[0] = 0;
 815		value->bvec2[1] = 0;
 816		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 817		_lookupBoolValue(description, name, fieldName, &value->bvec2[0]);
 818		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 819		_lookupBoolValue(description, name, fieldName, &value->bvec2[1]);
 820		break;
 821	case GL_BOOL_VEC3:
 822		value->bvec3[0] = 0;
 823		value->bvec3[1] = 0;
 824		value->bvec3[2] = 0;
 825		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 826		_lookupBoolValue(description, name, fieldName, &value->bvec3[0]);
 827		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 828		_lookupBoolValue(description, name, fieldName, &value->bvec3[1]);
 829		snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
 830		_lookupBoolValue(description, name, fieldName, &value->bvec3[2]);
 831		break;
 832	case GL_BOOL_VEC4:
 833		value->bvec4[0] = 0;
 834		value->bvec4[1] = 0;
 835		value->bvec4[2] = 0;
 836		value->bvec4[3] = 0;
 837		snprintf(fieldName, sizeof(fieldName), "%s[0]", field);
 838		_lookupBoolValue(description, name, fieldName, &value->bvec4[0]);
 839		snprintf(fieldName, sizeof(fieldName), "%s[1]", field);
 840		_lookupBoolValue(description, name, fieldName, &value->bvec4[1]);
 841		snprintf(fieldName, sizeof(fieldName), "%s[2]", field);
 842		_lookupBoolValue(description, name, fieldName, &value->bvec4[2]);
 843		snprintf(fieldName, sizeof(fieldName), "%s[3]", field);
 844		_lookupBoolValue(description, name, fieldName, &value->bvec4[3]);
 845		break;
 846	}
 847}
 848
 849static bool _loadUniform(struct Configuration* description, size_t pass, struct mGLES2Uniform* uniform) {
 850	unsigned passId;
 851	if (sscanf(uniform->name, "pass.%u.uniform.", &passId) < 1 || passId != pass) {
 852		return false;
 853	}
 854	const char* type = ConfigurationGetValue(description, uniform->name, "type");
 855	if (!type) {
 856		return false;
 857	}
 858	if (!strcmp(type, "float")) {
 859		uniform->type = GL_FLOAT;
 860	} else if (!strcmp(type, "float2")) {
 861		uniform->type = GL_FLOAT_VEC2;
 862	} else if (!strcmp(type, "float3")) {
 863		uniform->type = GL_FLOAT_VEC3;
 864	} else if (!strcmp(type, "float4")) {
 865		uniform->type = GL_FLOAT_VEC4;
 866	} else if (!strcmp(type, "float2x2")) {
 867		uniform->type = GL_FLOAT_MAT2;
 868	} else if (!strcmp(type, "float3x3")) {
 869		uniform->type = GL_FLOAT_MAT3;
 870	} else if (!strcmp(type, "float4x4")) {
 871		uniform->type = GL_FLOAT_MAT4;
 872	} else if (!strcmp(type, "int")) {
 873		uniform->type = GL_INT;
 874	} else if (!strcmp(type, "int2")) {
 875		uniform->type = GL_INT_VEC2;
 876	} else if (!strcmp(type, "int3")) {
 877		uniform->type = GL_INT_VEC3;
 878	} else if (!strcmp(type, "int4")) {
 879		uniform->type = GL_INT_VEC4;
 880	} else if (!strcmp(type, "bool")) {
 881		uniform->type = GL_BOOL;
 882	} else if (!strcmp(type, "bool2")) {
 883		uniform->type = GL_BOOL_VEC2;
 884	} else if (!strcmp(type, "bool3")) {
 885		uniform->type = GL_BOOL_VEC3;
 886	} else if (!strcmp(type, "bool4")) {
 887		uniform->type = GL_BOOL_VEC4;
 888	} else {
 889		return false;
 890	}
 891	_loadValue(description, uniform->name, uniform->type, "default", &uniform->value);
 892	_loadValue(description, uniform->name, uniform->type, "min", &uniform->min);
 893	_loadValue(description, uniform->name, uniform->type, "max", &uniform->max);
 894	const char* readable = ConfigurationGetValue(description, uniform->name, "readableName");
 895	if (readable) {
 896		uniform->readableName = strdup(readable);
 897	} else {
 898		uniform->readableName = 0;
 899	}
 900	uniform->name = strdup(strstr(uniform->name, "uniform.") + strlen("uniform."));
 901	return true;
 902}
 903
 904bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) {
 905	struct VFile* manifest = dir->openFile(dir, "manifest.ini", O_RDONLY);
 906	if (!manifest) {
 907		return false;
 908	}
 909	bool success = false;
 910	struct Configuration description;
 911	ConfigurationInit(&description);
 912	if (ConfigurationReadVFile(&description, manifest)) {
 913		int inShaders;
 914		success = _lookupIntValue(&description, "shader", "passes", &inShaders);
 915		if (inShaders > MAX_PASSES || inShaders < 1) {
 916			success = false;
 917		}
 918		if (success) {
 919			struct mGLES2Shader* shaderBlock = malloc(sizeof(struct mGLES2Shader) * inShaders);
 920			int n;
 921			for (n = 0; n < inShaders; ++n) {
 922				char passName[12];
 923				snprintf(passName, sizeof(passName), "pass.%u", n);
 924				const char* fs = ConfigurationGetValue(&description, passName, "fragmentShader");
 925				const char* vs = ConfigurationGetValue(&description, passName, "vertexShader");
 926				if (fs && (fs[0] == '.' || strstr(fs, PATH_SEP))) {
 927					success = false;
 928					break;
 929				}
 930				if (vs && (vs[0] == '.' || strstr(vs, PATH_SEP))) {
 931					success = false;
 932					break;
 933				}
 934				char* fssrc = 0;
 935				char* vssrc = 0;
 936				if (fs) {
 937					struct VFile* fsf = dir->openFile(dir, fs, O_RDONLY);
 938					if (!fsf) {
 939						success = false;
 940						break;
 941					}
 942					fssrc = malloc(fsf->size(fsf) + 1);
 943					fssrc[fsf->size(fsf)] = '\0';
 944					fsf->read(fsf, fssrc, fsf->size(fsf));
 945					fsf->close(fsf);
 946				}
 947				if (vs) {
 948					struct VFile* vsf = dir->openFile(dir, vs, O_RDONLY);
 949					if (!vsf) {
 950						success = false;
 951						free(fssrc);
 952						break;
 953					}
 954					vssrc = malloc(vsf->size(vsf) + 1);
 955					vssrc[vsf->size(vsf)] = '\0';
 956					vsf->read(vsf, vssrc, vsf->size(vsf));
 957					vsf->close(vsf);
 958				}
 959				int width = 0;
 960				int height = 0;
 961				int scaling = 0;
 962				_lookupIntValue(&description, passName, "width", &width);
 963				_lookupIntValue(&description, passName, "height", &height);
 964				_lookupIntValue(&description, passName, "integerScaling", &scaling);
 965
 966				struct mGLES2UniformList uniformVector;
 967				mGLES2UniformListInit(&uniformVector, 0);
 968				ConfigurationEnumerateSections(&description, _uniformHandler, &uniformVector);
 969				size_t u;
 970				for (u = 0; u < mGLES2UniformListSize(&uniformVector); ++u) {
 971					struct mGLES2Uniform* uniform = mGLES2UniformListGetPointer(&uniformVector, u);
 972					if (!_loadUniform(&description, n, uniform)) {
 973						mGLES2UniformListShift(&uniformVector, u, 1);
 974						--u;
 975					}
 976				}
 977				u = mGLES2UniformListSize(&uniformVector);
 978				struct mGLES2Uniform* uniformBlock = malloc(sizeof(*uniformBlock) * u);
 979				memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
 980				mGLES2UniformListDeinit(&uniformVector);
 981
 982				mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u);
 983				int b = 0;
 984				_lookupIntValue(&description, passName, "blend", &b);
 985				if (b) {
 986					shaderBlock[n].blend = b;
 987				}
 988				b = 0;
 989				_lookupIntValue(&description, passName, "filter", &b);
 990				if (b) {
 991					shaderBlock[n].filter = b;
 992				}
 993				free(fssrc);
 994				free(vssrc);
 995			}
 996			if (success) {
 997				shader->nPasses = inShaders;
 998				shader->passes = shaderBlock;
 999				shader->name = ConfigurationGetValue(&description, "shader", "name");
1000				if (shader->name) {
1001					shader->name = strdup(shader->name);
1002				}
1003				shader->author = ConfigurationGetValue(&description, "shader", "author");
1004				if (shader->author) {
1005					shader->author = strdup(shader->author);
1006				}
1007				shader->description = ConfigurationGetValue(&description, "shader", "description");
1008				if (shader->description) {
1009					shader->description = strdup(shader->description);
1010				}
1011			} else {
1012				inShaders = n;
1013				for (n = 0; n < inShaders; ++n) {
1014					mGLES2ShaderDeinit(&shaderBlock[n]);
1015				}
1016			}
1017		}
1018	}
1019	manifest->close(manifest);
1020	ConfigurationDeinit(&description);
1021	return success;
1022}
1023
1024void mGLES2ShaderFree(struct VideoShader* shader) {
1025	free((void*) shader->name);
1026	free((void*) shader->author);
1027	free((void*) shader->description);
1028	shader->name = 0;
1029	shader->author = 0;
1030	shader->description = 0;
1031	struct mGLES2Shader* shaders = shader->passes;
1032	size_t n;
1033	for (n = 0; n < shader->nPasses; ++n) {
1034		mGLES2ShaderDeinit(&shaders[n]);
1035		size_t u;
1036		for (u = 0; u < shaders[n].nUniforms; ++u) {
1037			free((void*) shaders[n].uniforms[u].name);
1038			free((void*) shaders[n].uniforms[u].readableName);
1039		}
1040	}
1041	free(shaders);
1042	shader->passes = 0;
1043	shader->nPasses = 0;
1044}