all repos — mgba @ 9ac93662579e12e7a19f1413d137cc5329ed1e32

mGBA Game Boy Advance Emulator

src/platform/switch/gui-font.c (view raw)

  1/* Copyright (c) 2013-2018 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 <mgba-util/gui/font.h>
  7#include <mgba-util/gui/font-metrics.h>
  8#include <mgba-util/png-io.h>
  9#include <mgba-util/string.h>
 10#include <mgba-util/vfs.h>
 11
 12#include <GLES3/gl3.h>
 13
 14#define GLYPH_HEIGHT 24
 15#define CELL_HEIGHT 32
 16#define CELL_WIDTH 32
 17#define MAX_GLYPHS 1024
 18
 19static const GLfloat _offsets[] = {
 20	0.f, 0.f,
 21	1.f, 0.f,
 22	1.f, 1.f,
 23	0.f, 1.f,
 24};
 25
 26static const GLchar* const _gles3Header =
 27	"#version 300 es\n"
 28	"precision mediump float;\n";
 29
 30static const char* const _vertexShader =
 31	"in vec2 offset;\n"
 32	"in vec3 origin;\n"
 33	"in vec2 glyph;\n"
 34	"in vec2 dims;\n"
 35	"in mat2 transform;\n"
 36	"in vec4 color;\n"
 37	"out vec4 fragColor;\n"
 38	"out vec2 texCoord;\n"
 39
 40	"void main() {\n"
 41	"	texCoord = (glyph + offset * dims) / 512.0;\n"
 42	"	vec2 scaledOffset = (transform * (offset * 2.0 - vec2(1.0)) + vec2(1.0)) / 2.0 * dims;\n"
 43	"	fragColor = color;\n"
 44	"	gl_Position = vec4((origin.x + scaledOffset.x) / 640.0 - 1.0, -(origin.y + scaledOffset.y) / 360.0 + 1.0, origin.z, 1.0);\n"
 45	"}";
 46
 47static const char* const _fragmentShader =
 48	"in vec2 texCoord;\n"
 49	"in vec4 fragColor;\n"
 50	"out vec4 outColor;\n"
 51	"uniform sampler2D tex;\n"
 52	"uniform float cutoff;\n"
 53	"uniform vec3 colorModulus;\n"
 54
 55	"void main() {\n"
 56	"	vec4 texColor = texture2D(tex, texCoord);\n"
 57	"	texColor.a = clamp((texColor.a - cutoff) / (1.0 - cutoff), 0.0, 1.0);\n"
 58	"	texColor.rgb = fragColor.rgb * colorModulus;\n"
 59	"	texColor.a *= fragColor.a;\n"
 60	"	outColor = texColor;\n"
 61	"}";
 62
 63struct GUIFont {
 64	GLuint font;
 65	int currentGlyph;
 66	GLuint program;
 67	GLuint vbo;
 68	GLuint vao;
 69	GLuint texLocation;
 70	GLuint cutoffLocation;
 71	GLuint colorModulusLocation;
 72
 73	GLuint originLocation;
 74	GLuint glyphLocation;
 75	GLuint dimsLocation;
 76	GLuint transformLocation[2];
 77	GLuint colorLocation;
 78
 79	GLuint originVbo;
 80	GLuint glyphVbo;
 81	GLuint dimsVbo;
 82	GLuint transformVbo[2];
 83	GLuint colorVbo;
 84
 85	GLfloat originData[MAX_GLYPHS][3];
 86	GLfloat glyphData[MAX_GLYPHS][2];
 87	GLfloat dimsData[MAX_GLYPHS][2];
 88	GLfloat transformData[2][MAX_GLYPHS][2];
 89	GLfloat colorData[MAX_GLYPHS][4];
 90};
 91
 92static bool _loadTexture(const char* path) {
 93	struct VFile* vf = VFileOpen(path, O_RDONLY);
 94	if (!vf) {
 95		return false;
 96	}
 97	png_structp png = PNGReadOpen(vf, 0);
 98	png_infop info = png_create_info_struct(png);
 99	png_infop end = png_create_info_struct(png);
100	bool success = false;
101	if (png && info && end) {
102		success = PNGReadHeader(png, info);
103	}
104	void* pixels = NULL;
105	if (success) {
106		unsigned height = png_get_image_height(png, info);
107		unsigned width = png_get_image_width(png, info);
108		pixels = malloc(width * height);
109		if (pixels) {
110			success = PNGReadPixels8(png, info, pixels, width, height, width);
111			success = success && PNGReadFooter(png, end);
112		} else {
113			success = false;
114		}
115		if (success) {
116			glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels);
117		}
118	}
119	PNGReadClose(png, info, end);
120	if (pixels) {
121		free(pixels);
122	}
123	vf->close(vf);
124	return success;
125}
126
127struct GUIFont* GUIFontCreate(void) {
128	struct GUIFont* font = malloc(sizeof(struct GUIFont));
129	if (!font) {
130		return NULL;
131	}
132	glGenTextures(1, &font->font);
133	glActiveTexture(GL_TEXTURE0);
134	glBindTexture(GL_TEXTURE_2D, font->font);
135	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
136	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
137	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
138	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
139	if (!_loadTexture("romfs:/font-new.png")) {
140		GUIFontDestroy(font);
141		return NULL;
142	}
143
144	font->currentGlyph = 0;
145	font->program = glCreateProgram();
146	GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
147	GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
148	const GLchar* shaderBuffer[2];
149
150	shaderBuffer[0] = _gles3Header;
151
152	shaderBuffer[1] = _vertexShader;
153	glShaderSource(vertexShader, 2, shaderBuffer, NULL);
154
155	shaderBuffer[1] = _fragmentShader;
156	glShaderSource(fragmentShader, 2, shaderBuffer, NULL);
157
158	glAttachShader(font->program, vertexShader);
159	glAttachShader(font->program, fragmentShader);
160
161	glCompileShader(fragmentShader);
162
163	GLint success;
164	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
165	if (!success) {
166		GLchar msg[512];
167		glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg);
168		puts(msg);
169	}
170
171	glCompileShader(vertexShader);
172
173	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
174	if (!success) {
175		GLchar msg[512];
176		glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg);
177		puts(msg);
178	}
179
180	glLinkProgram(font->program);
181
182	glGetProgramiv(font->program, GL_LINK_STATUS, &success);
183	if (!success) {
184		GLchar msg[512];
185		glGetProgramInfoLog(font->program, sizeof(msg), NULL, msg);
186		puts(msg);
187	}
188
189	glDeleteShader(vertexShader);
190	glDeleteShader(fragmentShader);
191
192	font->texLocation = glGetUniformLocation(font->program, "tex");
193	font->cutoffLocation = glGetUniformLocation(font->program, "cutoff");
194	font->colorModulusLocation = glGetUniformLocation(font->program, "colorModulus");
195
196	font->originLocation = glGetAttribLocation(font->program, "origin");
197	font->glyphLocation = glGetAttribLocation(font->program, "glyph");
198	font->dimsLocation = glGetAttribLocation(font->program, "dims");
199	font->transformLocation[0] = glGetAttribLocation(font->program, "transform");
200	font->transformLocation[1] = font->transformLocation[0] + 1;
201	font->colorLocation = glGetAttribLocation(font->program, "color");
202
203	GLuint offsetLocation = glGetAttribLocation(font->program, "offset");
204
205	glGenVertexArrays(1, &font->vao);
206	glBindVertexArray(font->vao);
207
208	glGenBuffers(1, &font->vbo);
209	glBindBuffer(GL_ARRAY_BUFFER, font->vbo);
210	glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW);
211	glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
212	glVertexAttribDivisor(offsetLocation, 0);
213	glEnableVertexAttribArray(offsetLocation);
214
215	glGenBuffers(1, &font->originVbo);
216	glBindBuffer(GL_ARRAY_BUFFER, font->originVbo);
217	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
218	glVertexAttribPointer(font->originLocation, 3, GL_FLOAT, GL_FALSE, 0, NULL);
219	glVertexAttribDivisor(font->originLocation, 1);
220	glEnableVertexAttribArray(font->originLocation);
221
222	glGenBuffers(1, &font->glyphVbo);
223	glBindBuffer(GL_ARRAY_BUFFER, font->glyphVbo);
224	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
225	glVertexAttribPointer(font->glyphLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
226	glVertexAttribDivisor(font->glyphLocation, 1);
227	glEnableVertexAttribArray(font->glyphLocation);
228
229	glGenBuffers(1, &font->dimsVbo);
230	glBindBuffer(GL_ARRAY_BUFFER, font->dimsVbo);
231	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
232	glVertexAttribPointer(font->dimsLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL);
233	glVertexAttribDivisor(font->dimsLocation, 1);
234	glEnableVertexAttribArray(font->dimsLocation);
235
236	glGenBuffers(2, font->transformVbo);
237	glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[0]);
238	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
239	glVertexAttribPointer(font->transformLocation[0], 2, GL_FLOAT, GL_FALSE, 0, NULL);
240	glVertexAttribDivisor(font->transformLocation[0], 1);
241	glEnableVertexAttribArray(font->transformLocation[0]);
242	glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[1]);
243	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
244	glVertexAttribPointer(font->transformLocation[1], 2, GL_FLOAT, GL_FALSE, 0, NULL);
245	glVertexAttribDivisor(font->transformLocation[1], 1);
246	glEnableVertexAttribArray(font->transformLocation[1]);
247
248	glGenBuffers(1, &font->colorVbo);
249	glBindBuffer(GL_ARRAY_BUFFER, font->colorVbo);
250	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
251	glVertexAttribPointer(font->colorLocation, 4, GL_FLOAT, GL_FALSE, 0, NULL);
252	glVertexAttribDivisor(font->colorLocation, 1);
253	glEnableVertexAttribArray(font->colorLocation);
254
255	glBindVertexArray(0);
256
257	return font;
258}
259
260void GUIFontDestroy(struct GUIFont* font) {
261	glDeleteBuffers(1, &font->vbo);
262	glDeleteBuffers(1, &font->originVbo);
263	glDeleteBuffers(1, &font->glyphVbo);
264	glDeleteBuffers(1, &font->dimsVbo);
265	glDeleteBuffers(2, font->transformVbo);
266	glDeleteBuffers(1, &font->colorVbo);
267	glDeleteProgram(font->program);
268	glDeleteTextures(1, &font->font);
269	glDeleteVertexArrays(1, &font->vao);
270	free(font);
271}
272
273unsigned GUIFontHeight(const struct GUIFont* font) {
274	UNUSED(font);
275	return GLYPH_HEIGHT;
276}
277
278unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) {
279	UNUSED(font);
280	if (glyph > 0x7F) {
281		glyph = '?';
282	}
283	return defaultFontMetrics[glyph].width * 2;
284}
285
286void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) {
287	UNUSED(font);
288	if (icon >= GUI_ICON_MAX) {
289		if (w) {
290			*w = 0;
291		}
292		if (h) {
293			*h = 0;
294		}
295	} else {
296		if (w) {
297			*w = defaultIconMetrics[icon].width * 2;
298		}
299		if (h) {
300			*h = defaultIconMetrics[icon].height * 2;
301		}
302	}
303}
304
305void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) {
306	if (glyph > 0x7F) {
307		glyph = '?';
308	}
309	struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph];
310
311	struct GUIFont* mutfont = (struct GUIFont*) font;
312	if (font->currentGlyph >= MAX_GLYPHS) {
313		GUIFontDrawSubmit(mutfont);
314	}
315
316	int offset = font->currentGlyph;
317
318	mutfont->originData[offset][0] = x;
319	mutfont->originData[offset][1] = y - GLYPH_HEIGHT + metric.padding.top * 2;
320	mutfont->originData[offset][2] = 0;
321	mutfont->glyphData[offset][0] = (glyph & 15) * CELL_WIDTH + metric.padding.left * 2;
322	mutfont->glyphData[offset][1] = (glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2;
323	mutfont->dimsData[offset][0] = CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2;
324	mutfont->dimsData[offset][1] = CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2;
325	mutfont->transformData[0][offset][0] = 1.0f;
326	mutfont->transformData[0][offset][1] = 0.0f;
327	mutfont->transformData[1][offset][0] = 0.0f;
328	mutfont->transformData[1][offset][1] = 1.0f;
329	mutfont->colorData[offset][0] = (color & 0xFF) / 255.0f;
330	mutfont->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f;
331	mutfont->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f;
332	mutfont->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f;
333
334	++mutfont->currentGlyph;
335}
336
337void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) {
338	if (icon >= GUI_ICON_MAX) {
339		return;
340	}
341	struct GUIIconMetric metric = defaultIconMetrics[icon];
342
343	float hFlip = 1.0f;
344	float vFlip = 1.0f;
345	switch (align & GUI_ALIGN_HCENTER) {
346	case GUI_ALIGN_HCENTER:
347		x -= metric.width;
348		break;
349	case GUI_ALIGN_RIGHT:
350		x -= metric.width * 2;
351		break;
352	}
353	switch (align & GUI_ALIGN_VCENTER) {
354	case GUI_ALIGN_VCENTER:
355		y -= metric.height;
356		break;
357	case GUI_ALIGN_BOTTOM:
358		y -= metric.height * 2;
359		break;
360	}
361
362	switch (orient) {
363	case GUI_ORIENT_HMIRROR:
364		hFlip = -1.0;
365		break;
366	case GUI_ORIENT_VMIRROR:
367		vFlip = -1.0;
368		break;
369	case GUI_ORIENT_0:
370	default:
371		// TODO: Rotate
372		break;
373	}
374
375	struct GUIFont* mutfont = (struct GUIFont*) font;
376	if (font->currentGlyph >= MAX_GLYPHS) {
377		GUIFontDrawSubmit(mutfont);
378	}
379
380	int offset = font->currentGlyph;
381
382	mutfont->originData[offset][0] = x;
383	mutfont->originData[offset][1] = y;
384	mutfont->originData[offset][2] = 0;
385	mutfont->glyphData[offset][0] = metric.x * 2;
386	mutfont->glyphData[offset][1] = metric.y * 2 + 256;
387	mutfont->dimsData[offset][0] = metric.width * 2;
388	mutfont->dimsData[offset][1] = metric.height * 2;
389	mutfont->transformData[0][offset][0] = hFlip;
390	mutfont->transformData[0][offset][1] = 0.0f;
391	mutfont->transformData[1][offset][0] = 0.0f;
392	mutfont->transformData[1][offset][1] = vFlip;
393	mutfont->colorData[offset][0] = (color & 0xFF) / 255.0f;
394	mutfont->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f;
395	mutfont->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f;
396	mutfont->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f;
397
398	++mutfont->currentGlyph;
399}
400
401void GUIFontDrawIconSize(const struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon) {
402	if (icon >= GUI_ICON_MAX) {
403		return;
404	}
405	struct GUIIconMetric metric = defaultIconMetrics[icon];
406
407	if (!w) {
408		w = metric.width * 2;
409	}
410	if (!h) {
411		h = metric.height * 2;
412	}
413
414	struct GUIFont* mutfont = (struct GUIFont*) font;
415	if (font->currentGlyph >= MAX_GLYPHS) {
416		GUIFontDrawSubmit(mutfont);
417	}
418
419	int offset = font->currentGlyph;
420
421	mutfont->originData[offset][0] = x + w / 2 - metric.width;
422	mutfont->originData[offset][1] = y + h / 2 - metric.height;
423	mutfont->originData[offset][2] = 0;
424	mutfont->glyphData[offset][0] = metric.x * 2;
425	mutfont->glyphData[offset][1] = metric.y * 2 + 256;
426	mutfont->dimsData[offset][0] = metric.width * 2;
427	mutfont->dimsData[offset][1] = metric.height * 2;
428	mutfont->transformData[0][offset][0] = w * 0.5f / metric.width;
429	mutfont->transformData[0][offset][1] = 0.0f;
430	mutfont->transformData[1][offset][0] = 0.0f;
431	mutfont->transformData[1][offset][1] = h * 0.5f / metric.height;
432	mutfont->colorData[offset][0] = (color & 0xFF) / 255.0f;
433	mutfont->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f;
434	mutfont->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f;
435	mutfont->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f;
436
437	++mutfont->currentGlyph;
438}
439
440void GUIFontDrawSubmit(struct GUIFont* font) {
441	glUseProgram(font->program);
442	glBindVertexArray(font->vao);
443	glActiveTexture(GL_TEXTURE0);
444	glBindTexture(GL_TEXTURE_2D, font->font);
445
446	glEnable(GL_BLEND);
447	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
448
449	glUniform1i(font->texLocation, 0);
450
451	glBindBuffer(GL_ARRAY_BUFFER, font->originVbo);
452	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
453	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 3 * font->currentGlyph, font->originData);
454
455	glBindBuffer(GL_ARRAY_BUFFER, font->glyphVbo);
456	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
457	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->glyphData);
458
459	glBindBuffer(GL_ARRAY_BUFFER, font->dimsVbo);
460	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
461	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->dimsData);
462
463	glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[0]);
464	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
465	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->transformData[0]);
466
467	glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[1]);
468	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
469	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->transformData[1]);
470
471	glBindBuffer(GL_ARRAY_BUFFER, font->colorVbo);
472	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
473	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 4 * font->currentGlyph, font->colorData);
474
475	glUniform1f(font->cutoffLocation, 0.1f);
476	glUniform3f(font->colorModulusLocation, 0.f, 0.f, 0.f);
477	glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, font->currentGlyph);
478
479	glUniform1f(font->cutoffLocation, 0.7f);
480	glUniform3f(font->colorModulusLocation, 1.f, 1.f, 1.f);
481	glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, font->currentGlyph);
482
483	font->currentGlyph = 0;
484
485	glBindVertexArray(0);
486	glUseProgram(0);
487}