all repos — mgba @ 7f4ca56af8d543c1dbf7b8ce12c3d7c6f2ce05d6

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