all repos — mgba @ a5890bfea5dfdb956d3464d0b6c4076e963dffab

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(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	if (font->currentGlyph >= MAX_GLYPHS) {
312		GUIFontDrawSubmit(font);
313	}
314
315	int offset = font->currentGlyph;
316
317	font->originData[offset][0] = x;
318	font->originData[offset][1] = y - GLYPH_HEIGHT + metric.padding.top * 2;
319	font->originData[offset][2] = 0;
320	font->glyphData[offset][0] = (glyph & 15) * CELL_WIDTH + metric.padding.left * 2;
321	font->glyphData[offset][1] = (glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2;
322	font->dimsData[offset][0] = CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2;
323	font->dimsData[offset][1] = CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2;
324	font->transformData[0][offset][0] = 1.0f;
325	font->transformData[0][offset][1] = 0.0f;
326	font->transformData[1][offset][0] = 0.0f;
327	font->transformData[1][offset][1] = 1.0f;
328	font->colorData[offset][0] = (color & 0xFF) / 255.0f;
329	font->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f;
330	font->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f;
331	font->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f;
332
333	++font->currentGlyph;
334}
335
336void GUIFontDrawIcon(struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) {
337	if (icon >= GUI_ICON_MAX) {
338		return;
339	}
340	struct GUIIconMetric metric = defaultIconMetrics[icon];
341
342	float hFlip = 1.0f;
343	float vFlip = 1.0f;
344	switch (align & GUI_ALIGN_HCENTER) {
345	case GUI_ALIGN_HCENTER:
346		x -= metric.width;
347		break;
348	case GUI_ALIGN_RIGHT:
349		x -= metric.width * 2;
350		break;
351	}
352	switch (align & GUI_ALIGN_VCENTER) {
353	case GUI_ALIGN_VCENTER:
354		y -= metric.height;
355		break;
356	case GUI_ALIGN_BOTTOM:
357		y -= metric.height * 2;
358		break;
359	}
360
361	switch (orient) {
362	case GUI_ORIENT_HMIRROR:
363		hFlip = -1.0;
364		break;
365	case GUI_ORIENT_VMIRROR:
366		vFlip = -1.0;
367		break;
368	case GUI_ORIENT_0:
369	default:
370		// TODO: Rotate
371		break;
372	}
373	if (font->currentGlyph >= MAX_GLYPHS) {
374		GUIFontDrawSubmit(font);
375	}
376
377	int offset = font->currentGlyph;
378
379	font->originData[offset][0] = x;
380	font->originData[offset][1] = y;
381	font->originData[offset][2] = 0;
382	font->glyphData[offset][0] = metric.x * 2;
383	font->glyphData[offset][1] = metric.y * 2 + 256;
384	font->dimsData[offset][0] = metric.width * 2;
385	font->dimsData[offset][1] = metric.height * 2;
386	font->transformData[0][offset][0] = hFlip;
387	font->transformData[0][offset][1] = 0.0f;
388	font->transformData[1][offset][0] = 0.0f;
389	font->transformData[1][offset][1] = vFlip;
390	font->colorData[offset][0] = (color & 0xFF) / 255.0f;
391	font->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f;
392	font->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f;
393	font->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f;
394
395	++font->currentGlyph;
396}
397
398void GUIFontDrawIconSize(struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon) {
399	if (icon >= GUI_ICON_MAX) {
400		return;
401	}
402	struct GUIIconMetric metric = defaultIconMetrics[icon];
403
404	if (!w) {
405		w = metric.width * 2;
406	}
407	if (!h) {
408		h = metric.height * 2;
409	}
410
411	if (font->currentGlyph >= MAX_GLYPHS) {
412		GUIFontDrawSubmit(font);
413	}
414
415	int offset = font->currentGlyph;
416
417	font->originData[offset][0] = x + w / 2 - metric.width;
418	font->originData[offset][1] = y + h / 2 - metric.height;
419	font->originData[offset][2] = 0;
420	font->glyphData[offset][0] = metric.x * 2;
421	font->glyphData[offset][1] = metric.y * 2 + 256;
422	font->dimsData[offset][0] = metric.width * 2;
423	font->dimsData[offset][1] = metric.height * 2;
424	font->transformData[0][offset][0] = w * 0.5f / metric.width;
425	font->transformData[0][offset][1] = 0.0f;
426	font->transformData[1][offset][0] = 0.0f;
427	font->transformData[1][offset][1] = h * 0.5f / metric.height;
428	font->colorData[offset][0] = (color & 0xFF) / 255.0f;
429	font->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f;
430	font->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f;
431	font->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f;
432
433	++font->currentGlyph;
434}
435
436void GUIFontDrawSubmit(struct GUIFont* font) {
437	glUseProgram(font->program);
438	glBindVertexArray(font->vao);
439	glActiveTexture(GL_TEXTURE0);
440	glBindTexture(GL_TEXTURE_2D, font->font);
441
442	glEnable(GL_BLEND);
443	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
444
445	glUniform1i(font->texLocation, 0);
446
447	glBindBuffer(GL_ARRAY_BUFFER, font->originVbo);
448	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
449	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 3 * font->currentGlyph, font->originData);
450
451	glBindBuffer(GL_ARRAY_BUFFER, font->glyphVbo);
452	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
453	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->glyphData);
454
455	glBindBuffer(GL_ARRAY_BUFFER, font->dimsVbo);
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->dimsData);
458
459	glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[0]);
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->transformData[0]);
462
463	glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[1]);
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[1]);
466
467	glBindBuffer(GL_ARRAY_BUFFER, font->colorVbo);
468	glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * MAX_GLYPHS, NULL, GL_STREAM_DRAW);
469	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 4 * font->currentGlyph, font->colorData);
470
471	glUniform1f(font->cutoffLocation, 0.1f);
472	glUniform3f(font->colorModulusLocation, 0.f, 0.f, 0.f);
473	glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, font->currentGlyph);
474
475	glUniform1f(font->cutoffLocation, 0.7f);
476	glUniform3f(font->colorModulusLocation, 1.f, 1.f, 1.f);
477	glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, font->currentGlyph);
478
479	font->currentGlyph = 0;
480
481	glBindVertexArray(0);
482	glUseProgram(0);
483}