all repos — mgba @ 47bf26ff73d9bdf8a044773b20fc816f6e823e7e

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