/* Copyright (c) 2013-2018 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #define GLYPH_HEIGHT 24 #define CELL_HEIGHT 32 #define CELL_WIDTH 32 #define MAX_GLYPHS 1024 static const GLfloat _offsets[] = { 0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f, }; static const GLchar* const _gles3Header = "#version 300 es\n" "precision mediump float;\n"; static const char* const _vertexShader = "in vec2 offset;\n" "in vec3 origin;\n" "in vec2 glyph;\n" "in vec2 dims;\n" "in mat2 transform;\n" "in vec4 color;\n" "out vec4 fragColor;\n" "out vec2 texCoord;\n" "void main() {\n" " texCoord = (glyph + offset * dims) / 512.0;\n" " vec2 scaledOffset = (transform * (offset * 2.0 - vec2(1.0)) + vec2(1.0)) / 2.0 * dims;\n" " fragColor = color;\n" " gl_Position = vec4((origin.x + scaledOffset.x) / 640.0 - 1.0, -(origin.y + scaledOffset.y) / 360.0 + 1.0, origin.z, 1.0);\n" "}"; static const char* const _fragmentShader = "in vec2 texCoord;\n" "in vec4 fragColor;\n" "out vec4 outColor;\n" "uniform sampler2D tex;\n" "uniform float cutoff;\n" "uniform vec3 colorModulus;\n" "void main() {\n" " vec4 texColor = texture2D(tex, texCoord);\n" " texColor.a = clamp((texColor.a - cutoff) / (1.0 - cutoff), 0.0, 1.0);\n" " texColor.rgb = fragColor.rgb * colorModulus;\n" " texColor.a *= fragColor.a;\n" " outColor = texColor;\n" "}"; struct GUIFont { GLuint font; int currentGlyph; GLuint program; GLuint vbo; GLuint vao; GLuint texLocation; GLuint cutoffLocation; GLuint colorModulusLocation; GLuint originLocation; GLuint glyphLocation; GLuint dimsLocation; GLuint transformLocation[2]; GLuint colorLocation; GLuint originVbo; GLuint glyphVbo; GLuint dimsVbo; GLuint transformVbo[2]; GLuint colorVbo; GLfloat originData[MAX_GLYPHS][3]; GLfloat glyphData[MAX_GLYPHS][2]; GLfloat dimsData[MAX_GLYPHS][2]; GLfloat transformData[2][MAX_GLYPHS][2]; GLfloat colorData[MAX_GLYPHS][4]; }; static bool _loadTexture(const char* path) { struct VFile* vf = VFileOpen(path, O_RDONLY); if (!vf) { return false; } png_structp png = PNGReadOpen(vf, 0); png_infop info = png_create_info_struct(png); png_infop end = png_create_info_struct(png); bool success = false; if (png && info && end) { success = PNGReadHeader(png, info); } void* pixels = NULL; if (success) { unsigned height = png_get_image_height(png, info); unsigned width = png_get_image_width(png, info); pixels = malloc(width * height); if (pixels) { success = PNGReadPixels8(png, info, pixels, width, height, width); success = success && PNGReadFooter(png, end); } else { success = false; } if (success) { glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); } } PNGReadClose(png, info, end); if (pixels) { free(pixels); } vf->close(vf); return success; } struct GUIFont* GUIFontCreate(void) { struct GUIFont* font = malloc(sizeof(struct GUIFont)); if (!font) { return NULL; } glGenTextures(1, &font->font); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, font->font); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if (!_loadTexture("romfs:/font-new.png")) { GUIFontDestroy(font); return NULL; } font->currentGlyph = 0; font->program = glCreateProgram(); GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); const GLchar* shaderBuffer[2]; shaderBuffer[0] = _gles3Header; shaderBuffer[1] = _vertexShader; glShaderSource(vertexShader, 2, shaderBuffer, NULL); shaderBuffer[1] = _fragmentShader; glShaderSource(fragmentShader, 2, shaderBuffer, NULL); glAttachShader(font->program, vertexShader); glAttachShader(font->program, fragmentShader); glCompileShader(fragmentShader); GLint success; glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { GLchar msg[512]; glGetShaderInfoLog(fragmentShader, sizeof(msg), NULL, msg); puts(msg); } glCompileShader(vertexShader); glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { GLchar msg[512]; glGetShaderInfoLog(vertexShader, sizeof(msg), NULL, msg); puts(msg); } glLinkProgram(font->program); glGetProgramiv(font->program, GL_LINK_STATUS, &success); if (!success) { GLchar msg[512]; glGetProgramInfoLog(font->program, sizeof(msg), NULL, msg); puts(msg); } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); font->texLocation = glGetUniformLocation(font->program, "tex"); font->cutoffLocation = glGetUniformLocation(font->program, "cutoff"); font->colorModulusLocation = glGetUniformLocation(font->program, "colorModulus"); font->originLocation = glGetAttribLocation(font->program, "origin"); font->glyphLocation = glGetAttribLocation(font->program, "glyph"); font->dimsLocation = glGetAttribLocation(font->program, "dims"); font->transformLocation[0] = glGetAttribLocation(font->program, "transform"); font->transformLocation[1] = font->transformLocation[0] + 1; font->colorLocation = glGetAttribLocation(font->program, "color"); GLuint offsetLocation = glGetAttribLocation(font->program, "offset"); glGenVertexArrays(1, &font->vao); glBindVertexArray(font->vao); glGenBuffers(1, &font->vbo); glBindBuffer(GL_ARRAY_BUFFER, font->vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(_offsets), _offsets, GL_STATIC_DRAW); glVertexAttribPointer(offsetLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(offsetLocation, 0); glEnableVertexAttribArray(offsetLocation); glGenBuffers(1, &font->originVbo); glBindBuffer(GL_ARRAY_BUFFER, font->originVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glVertexAttribPointer(font->originLocation, 3, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(font->originLocation, 1); glEnableVertexAttribArray(font->originLocation); glGenBuffers(1, &font->glyphVbo); glBindBuffer(GL_ARRAY_BUFFER, font->glyphVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glVertexAttribPointer(font->glyphLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(font->glyphLocation, 1); glEnableVertexAttribArray(font->glyphLocation); glGenBuffers(1, &font->dimsVbo); glBindBuffer(GL_ARRAY_BUFFER, font->dimsVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glVertexAttribPointer(font->dimsLocation, 2, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(font->dimsLocation, 1); glEnableVertexAttribArray(font->dimsLocation); glGenBuffers(2, font->transformVbo); glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glVertexAttribPointer(font->transformLocation[0], 2, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(font->transformLocation[0], 1); glEnableVertexAttribArray(font->transformLocation[0]); glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glVertexAttribPointer(font->transformLocation[1], 2, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(font->transformLocation[1], 1); glEnableVertexAttribArray(font->transformLocation[1]); glGenBuffers(1, &font->colorVbo); glBindBuffer(GL_ARRAY_BUFFER, font->colorVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glVertexAttribPointer(font->colorLocation, 4, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribDivisor(font->colorLocation, 1); glEnableVertexAttribArray(font->colorLocation); glBindVertexArray(0); return font; } void GUIFontDestroy(struct GUIFont* font) { glDeleteBuffers(1, &font->vbo); glDeleteBuffers(1, &font->originVbo); glDeleteBuffers(1, &font->glyphVbo); glDeleteBuffers(1, &font->dimsVbo); glDeleteBuffers(2, font->transformVbo); glDeleteBuffers(1, &font->colorVbo); glDeleteProgram(font->program); glDeleteTextures(1, &font->font); glDeleteVertexArrays(1, &font->vao); free(font); } unsigned GUIFontHeight(const struct GUIFont* font) { UNUSED(font); return GLYPH_HEIGHT; } unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { UNUSED(font); if (glyph > 0x7F) { glyph = '?'; } return defaultFontMetrics[glyph].width * 2; } void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) { UNUSED(font); if (icon >= GUI_ICON_MAX) { if (w) { *w = 0; } if (h) { *h = 0; } } else { if (w) { *w = defaultIconMetrics[icon].width * 2; } if (h) { *h = defaultIconMetrics[icon].height * 2; } } } void GUIFontDrawGlyph(struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) { if (glyph > 0x7F) { glyph = '?'; } struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; if (font->currentGlyph >= MAX_GLYPHS) { GUIFontDrawSubmit(font); } int offset = font->currentGlyph; font->originData[offset][0] = x; font->originData[offset][1] = y - GLYPH_HEIGHT + metric.padding.top * 2; font->originData[offset][2] = 0; font->glyphData[offset][0] = (glyph & 15) * CELL_WIDTH + metric.padding.left * 2; font->glyphData[offset][1] = (glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2; font->dimsData[offset][0] = CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2; font->dimsData[offset][1] = CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2; font->transformData[0][offset][0] = 1.0f; font->transformData[0][offset][1] = 0.0f; font->transformData[1][offset][0] = 0.0f; font->transformData[1][offset][1] = 1.0f; font->colorData[offset][0] = (color & 0xFF) / 255.0f; font->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f; font->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f; font->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f; ++font->currentGlyph; } void GUIFontDrawIcon(struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) { if (icon >= GUI_ICON_MAX) { return; } struct GUIIconMetric metric = defaultIconMetrics[icon]; float hFlip = 1.0f; float vFlip = 1.0f; switch (align & GUI_ALIGN_HCENTER) { case GUI_ALIGN_HCENTER: x -= metric.width; break; case GUI_ALIGN_RIGHT: x -= metric.width * 2; break; } switch (align & GUI_ALIGN_VCENTER) { case GUI_ALIGN_VCENTER: y -= metric.height; break; case GUI_ALIGN_BOTTOM: y -= metric.height * 2; break; } switch (orient) { case GUI_ORIENT_HMIRROR: hFlip = -1.0; break; case GUI_ORIENT_VMIRROR: vFlip = -1.0; break; case GUI_ORIENT_0: default: // TODO: Rotate break; } if (font->currentGlyph >= MAX_GLYPHS) { GUIFontDrawSubmit(font); } int offset = font->currentGlyph; font->originData[offset][0] = x; font->originData[offset][1] = y; font->originData[offset][2] = 0; font->glyphData[offset][0] = metric.x * 2; font->glyphData[offset][1] = metric.y * 2 + 256; font->dimsData[offset][0] = metric.width * 2; font->dimsData[offset][1] = metric.height * 2; font->transformData[0][offset][0] = hFlip; font->transformData[0][offset][1] = 0.0f; font->transformData[1][offset][0] = 0.0f; font->transformData[1][offset][1] = vFlip; font->colorData[offset][0] = (color & 0xFF) / 255.0f; font->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f; font->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f; font->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f; ++font->currentGlyph; } void GUIFontDrawIconSize(struct GUIFont* font, int x, int y, int w, int h, uint32_t color, enum GUIIcon icon) { if (icon >= GUI_ICON_MAX) { return; } struct GUIIconMetric metric = defaultIconMetrics[icon]; if (!w) { w = metric.width * 2; } if (!h) { h = metric.height * 2; } if (font->currentGlyph >= MAX_GLYPHS) { GUIFontDrawSubmit(font); } int offset = font->currentGlyph; font->originData[offset][0] = x + w / 2 - metric.width; font->originData[offset][1] = y + h / 2 - metric.height; font->originData[offset][2] = 0; font->glyphData[offset][0] = metric.x * 2; font->glyphData[offset][1] = metric.y * 2 + 256; font->dimsData[offset][0] = metric.width * 2; font->dimsData[offset][1] = metric.height * 2; font->transformData[0][offset][0] = w * 0.5f / metric.width; font->transformData[0][offset][1] = 0.0f; font->transformData[1][offset][0] = 0.0f; font->transformData[1][offset][1] = h * 0.5f / metric.height; font->colorData[offset][0] = (color & 0xFF) / 255.0f; font->colorData[offset][1] = ((color >> 8) & 0xFF) / 255.0f; font->colorData[offset][2] = ((color >> 16) & 0xFF) / 255.0f; font->colorData[offset][3] = ((color >> 24) & 0xFF) / 255.0f; ++font->currentGlyph; } void GUIFontDrawSubmit(struct GUIFont* font) { glUseProgram(font->program); glBindVertexArray(font->vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, font->font); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glUniform1i(font->texLocation, 0); glBindBuffer(GL_ARRAY_BUFFER, font->originVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 3 * font->currentGlyph, font->originData); glBindBuffer(GL_ARRAY_BUFFER, font->glyphVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->glyphData); glBindBuffer(GL_ARRAY_BUFFER, font->dimsVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->dimsData); glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->transformData[0]); glBindBuffer(GL_ARRAY_BUFFER, font->transformVbo[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 2 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 2 * font->currentGlyph, font->transformData[1]); glBindBuffer(GL_ARRAY_BUFFER, font->colorVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4 * MAX_GLYPHS, NULL, GL_STREAM_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * 4 * font->currentGlyph, font->colorData); glUniform1f(font->cutoffLocation, 0.1f); glUniform3f(font->colorModulusLocation, 0.f, 0.f, 0.f); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, font->currentGlyph); glUniform1f(font->cutoffLocation, 0.7f); glUniform3f(font->colorModulusLocation, 1.f, 1.f, 1.f); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, font->currentGlyph); font->currentGlyph = 0; glBindVertexArray(0); glUseProgram(0); }