From 63849dae94251bd96ae1c552d9a2bb79b997ec18 Mon Sep 17 00:00:00 2001 From: rexim Date: Sat, 10 Jul 2021 20:42:42 +0700 Subject: [PATCH 1/9] Rename Glyph -> Tile_Glyph --- src/main.c | 82 +++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/main.c b/src/main.c index 23560b9b..59e30a38 100644 --- a/src/main.c +++ b/src/main.c @@ -71,74 +71,74 @@ typedef struct { int ch; Vec4f fg_color; Vec4f bg_color; -} Glyph; +} Tile_Glyph; typedef enum { - GLYPH_ATTR_TILE = 0, - GLYPH_ATTR_CH, - GLYPH_ATTR_FG_COLOR, - GLYPH_ATTR_BG_COLOR, - COUNT_GLYPH_ATTRS, -} Glyph_Attr; + TILE_GLYPH_ATTR_TILE = 0, + TILE_GLYPH_ATTR_CH, + TILE_GLYPH_ATTR_FG_COLOR, + TILE_GLYPH_ATTR_BG_COLOR, + COUNT_TILE_GLYPH_ATTRS, +} Tile_Glyph_Attr; typedef struct { size_t offset; GLint comps; GLenum type; -} Glyph_Attr_Def; +} Attr_Def; -static const Glyph_Attr_Def glyph_attr_defs[COUNT_GLYPH_ATTRS] = { - [GLYPH_ATTR_TILE] = { - .offset = offsetof(Glyph, tile), +static const Attr_Def glyph_attr_defs[COUNT_TILE_GLYPH_ATTRS] = { + [TILE_GLYPH_ATTR_TILE] = { + .offset = offsetof(Tile_Glyph, tile), .comps = 2, .type = GL_INT }, - [GLYPH_ATTR_CH] = { - .offset = offsetof(Glyph, ch), + [TILE_GLYPH_ATTR_CH] = { + .offset = offsetof(Tile_Glyph, ch), .comps = 1, .type = GL_INT }, - [GLYPH_ATTR_FG_COLOR] = { - .offset = offsetof(Glyph, fg_color), + [TILE_GLYPH_ATTR_FG_COLOR] = { + .offset = offsetof(Tile_Glyph, fg_color), .comps = 4, .type = GL_FLOAT }, - [GLYPH_ATTR_BG_COLOR] = { - .offset = offsetof(Glyph, bg_color), + [TILE_GLYPH_ATTR_BG_COLOR] = { + .offset = offsetof(Tile_Glyph, bg_color), .comps = 4, .type = GL_FLOAT }, }; -static_assert(COUNT_GLYPH_ATTRS == 4, "The amount of glyph vertex attributes have changed"); +static_assert(COUNT_TILE_GLYPH_ATTRS == 4, "The amount of glyph vertex attributes have changed"); -#define GLYPH_BUFFER_CAP (640 * 1024) +#define TILE_GLYPH_BUFFER_CAP (640 * 1024) -Glyph glyph_buffer[GLYPH_BUFFER_CAP]; -size_t glyph_buffer_count = 0; +Tile_Glyph tile_glyph_buffer[TILE_GLYPH_BUFFER_CAP]; +size_t tile_glyph_buffer_count = 0; -void glyph_buffer_clear(void) +void tile_glyph_buffer_clear(void) { - glyph_buffer_count = 0; + tile_glyph_buffer_count = 0; } -void glyph_buffer_push(Glyph glyph) +void tile_glyph_buffer_push(Tile_Glyph glyph) { - assert(glyph_buffer_count < GLYPH_BUFFER_CAP); - glyph_buffer[glyph_buffer_count++] = glyph; + assert(tile_glyph_buffer_count < TILE_GLYPH_BUFFER_CAP); + tile_glyph_buffer[tile_glyph_buffer_count++] = glyph; } -void glyph_buffer_sync(void) +void tile_glyph_buffer_sync(void) { glBufferSubData(GL_ARRAY_BUFFER, 0, - glyph_buffer_count * sizeof(Glyph), - glyph_buffer); + tile_glyph_buffer_count * sizeof(Tile_Glyph), + tile_glyph_buffer); } void gl_render_text_sized(const char *text, size_t text_size, Vec2i tile, Vec4f fg_color, Vec4f bg_color) { for (size_t i = 0; i < text_size; ++i) { - glyph_buffer_push((Glyph) { + tile_glyph_buffer_push((Tile_Glyph) { .tile = vec2i_add(tile, vec2i((int) i, 0)), .ch = text[i], .fg_color = fg_color, @@ -297,11 +297,11 @@ int main(int argc, char **argv) glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, - sizeof(glyph_buffer), - glyph_buffer, + sizeof(tile_glyph_buffer), + tile_glyph_buffer, GL_DYNAMIC_DRAW); - for (Glyph_Attr attr = 0; attr < COUNT_GLYPH_ATTRS; ++attr) { + for (Tile_Glyph_Attr attr = 0; attr < COUNT_TILE_GLYPH_ATTRS; ++attr) { glEnableVertexAttribArray(attr); switch (glyph_attr_defs[attr].type) { case GL_FLOAT: @@ -310,7 +310,7 @@ int main(int argc, char **argv) glyph_attr_defs[attr].comps, glyph_attr_defs[attr].type, GL_FALSE, - sizeof(Glyph), + sizeof(Tile_Glyph), (void*) glyph_attr_defs[attr].offset); break; @@ -319,7 +319,7 @@ int main(int argc, char **argv) attr, glyph_attr_defs[attr].comps, glyph_attr_defs[attr].type, - sizeof(Glyph), + sizeof(Tile_Glyph), (void*) glyph_attr_defs[attr].offset); break; @@ -438,14 +438,14 @@ int main(int argc, char **argv) glUniform2f(resolution_uniform, (float) w, (float) h); } - glyph_buffer_clear(); + tile_glyph_buffer_clear(); { for (size_t row = 0; row < editor.size; ++row) { const Line *line = editor.lines + row; gl_render_text_sized(line->chars, line->size, vec2i(0, -(int)row), vec4fs(1.0f), vec4fs(0.0f)); } } - glyph_buffer_sync(); + tile_glyph_buffer_sync(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); @@ -453,15 +453,15 @@ int main(int argc, char **argv) glUniform1f(time_uniform, (float) SDL_GetTicks() / 1000.0f); glUniform2f(camera_uniform, camera_pos.x, camera_pos.y); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) glyph_buffer_count); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) tile_glyph_buffer_count); - glyph_buffer_clear(); + tile_glyph_buffer_clear(); { gl_render_cursor(); } - glyph_buffer_sync(); + tile_glyph_buffer_sync(); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) glyph_buffer_count); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) tile_glyph_buffer_count); SDL_GL_SwapWindow(window); From 74d8f392326f8a022f095c65f8600169062ab1d5 Mon Sep 17 00:00:00 2001 From: rexim Date: Sat, 10 Jul 2021 21:29:23 +0700 Subject: [PATCH 2/9] Factor out the Tile_Glyph renderer --- build.sh | 2 +- shaders/{font.frag => tile_glyph.frag} | 0 shaders/{font.vert => tile_glyph.vert} | 0 src/main.c | 233 +++---------------------- src/tile_glyph.c | 171 ++++++++++++++++++ src/tile_glyph.h | 73 ++++++++ 6 files changed, 265 insertions(+), 214 deletions(-) rename shaders/{font.frag => tile_glyph.frag} (100%) rename shaders/{font.vert => tile_glyph.vert} (100%) create mode 100644 src/tile_glyph.c create mode 100644 src/tile_glyph.h diff --git a/build.sh b/build.sh index 1416d2d2..74280200 100755 --- a/build.sh +++ b/build.sh @@ -6,7 +6,7 @@ CC="${CXX:-cc}" PKGS="sdl2 glew" CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb" LIBS=-lm -SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c" +SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c src/tile_glyph.c" if [ `uname` = "Darwin" ]; then CFLAGS+=" -framework OpenGL" diff --git a/shaders/font.frag b/shaders/tile_glyph.frag similarity index 100% rename from shaders/font.frag rename to shaders/tile_glyph.frag diff --git a/shaders/font.vert b/shaders/tile_glyph.vert similarity index 100% rename from shaders/font.vert rename to shaders/tile_glyph.vert diff --git a/src/main.c b/src/main.c index 59e30a38..ebf20bfe 100644 --- a/src/main.c +++ b/src/main.c @@ -18,19 +18,13 @@ #include "./la.h" #include "./sdl_extra.h" #include "./gl_extra.h" +#include "./tile_glyph.h" #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 #define FPS 60 #define DELTA_TIME (1.0f / FPS) -#define FONT_SCALE 5 -#define FONT_WIDTH 128 -#define FONT_HEIGHT 64 -#define FONT_COLS 18 -#define FONT_ROWS 7 -#define FONT_CHAR_WIDTH (FONT_WIDTH / FONT_COLS) -#define FONT_CHAR_HEIGHT (FONT_HEIGHT / FONT_ROWS) Editor editor = {0}; Vec2f camera_pos = {0}; @@ -66,99 +60,15 @@ void MessageCallback(GLenum source, type, severity, message); } -typedef struct { - Vec2i tile; - int ch; - Vec4f fg_color; - Vec4f bg_color; -} Tile_Glyph; - -typedef enum { - TILE_GLYPH_ATTR_TILE = 0, - TILE_GLYPH_ATTR_CH, - TILE_GLYPH_ATTR_FG_COLOR, - TILE_GLYPH_ATTR_BG_COLOR, - COUNT_TILE_GLYPH_ATTRS, -} Tile_Glyph_Attr; - -typedef struct { - size_t offset; - GLint comps; - GLenum type; -} Attr_Def; - -static const Attr_Def glyph_attr_defs[COUNT_TILE_GLYPH_ATTRS] = { - [TILE_GLYPH_ATTR_TILE] = { - .offset = offsetof(Tile_Glyph, tile), - .comps = 2, - .type = GL_INT - }, - [TILE_GLYPH_ATTR_CH] = { - .offset = offsetof(Tile_Glyph, ch), - .comps = 1, - .type = GL_INT - }, - [TILE_GLYPH_ATTR_FG_COLOR] = { - .offset = offsetof(Tile_Glyph, fg_color), - .comps = 4, - .type = GL_FLOAT - }, - [TILE_GLYPH_ATTR_BG_COLOR] = { - .offset = offsetof(Tile_Glyph, bg_color), - .comps = 4, - .type = GL_FLOAT - }, -}; -static_assert(COUNT_TILE_GLYPH_ATTRS == 4, "The amount of glyph vertex attributes have changed"); - -#define TILE_GLYPH_BUFFER_CAP (640 * 1024) - -Tile_Glyph tile_glyph_buffer[TILE_GLYPH_BUFFER_CAP]; -size_t tile_glyph_buffer_count = 0; - -void tile_glyph_buffer_clear(void) -{ - tile_glyph_buffer_count = 0; -} - -void tile_glyph_buffer_push(Tile_Glyph glyph) -{ - assert(tile_glyph_buffer_count < TILE_GLYPH_BUFFER_CAP); - tile_glyph_buffer[tile_glyph_buffer_count++] = glyph; -} - -void tile_glyph_buffer_sync(void) -{ - glBufferSubData(GL_ARRAY_BUFFER, - 0, - tile_glyph_buffer_count * sizeof(Tile_Glyph), - tile_glyph_buffer); -} - -void gl_render_text_sized(const char *text, size_t text_size, Vec2i tile, Vec4f fg_color, Vec4f bg_color) -{ - for (size_t i = 0; i < text_size; ++i) { - tile_glyph_buffer_push((Tile_Glyph) { - .tile = vec2i_add(tile, vec2i((int) i, 0)), - .ch = text[i], - .fg_color = fg_color, - .bg_color = bg_color, - }); - } -} - -void gl_render_text(const char *text, Vec2i tile, Vec4f fg_color, Vec4f bg_color) -{ - gl_render_text_sized(text, strlen(text), tile, fg_color, bg_color); -} - -void gl_render_cursor() +void gl_render_cursor(Tile_Glyph_Buffer *tgb) { const char *c = editor_char_under_cursor(&editor); Vec2i tile = vec2i((int) editor.cursor_col, -(int) editor.cursor_row); - gl_render_text_sized(c ? c : " ", 1, tile, vec4fs(0.0f), vec4fs(1.0f)); + tile_glyph_render_line_sized(tgb, c ? c : " ", 1, tile, vec4fs(0.0f), vec4fs(1.0f)); } +static Tile_Glyph_Buffer tgb = {0}; + // OPENGL int main(int argc, char **argv) { @@ -223,113 +133,10 @@ int main(int argc, char **argv) fprintf(stderr, "WARNING! GLEW_ARB_debug_output is not available"); } - GLint time_uniform; - GLint resolution_uniform; - GLint scale_uniform; - GLint camera_uniform; - - // Initialize Shaders - { - GLuint vert_shader = 0; - if (!compile_shader_file("./shaders/font.vert", GL_VERTEX_SHADER, &vert_shader)) { - exit(1); - } - GLuint frag_shader = 0; - if (!compile_shader_file("./shaders/font.frag", GL_FRAGMENT_SHADER, &frag_shader)) { - exit(1); - } - - GLuint program = 0; - if (!link_program(vert_shader, frag_shader, &program)) { - exit(1); - } - - glUseProgram(program); - - time_uniform = glGetUniformLocation(program, "time"); - resolution_uniform = glGetUniformLocation(program, "resolution"); - scale_uniform = glGetUniformLocation(program, "scale"); - camera_uniform = glGetUniformLocation(program, "camera"); - - glUniform2f(scale_uniform, FONT_SCALE, FONT_SCALE); - } - - // Init Font Texture - { - const char *font_file_path = "charmap-oldschool_white.png"; - int width, height, n; - unsigned char *pixels = stbi_load(font_file_path, &width, &height, &n, STBI_rgb_alpha); - if (pixels == NULL) { - fprintf(stderr, "ERROR: could not load file %s: %s\n", - file_path, stbi_failure_reason()); - exit(1); - } - - glActiveTexture(GL_TEXTURE0); - - GLuint font_texture = 0; - glGenTextures(1, &font_texture); - glBindTexture(GL_TEXTURE_2D, font_texture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RGBA, - width, - height, - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - pixels); - } - - // Init Buffers - { - GLuint vao = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint vbo = 0; - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, - sizeof(tile_glyph_buffer), - tile_glyph_buffer, - GL_DYNAMIC_DRAW); - - for (Tile_Glyph_Attr attr = 0; attr < COUNT_TILE_GLYPH_ATTRS; ++attr) { - glEnableVertexAttribArray(attr); - switch (glyph_attr_defs[attr].type) { - case GL_FLOAT: - glVertexAttribPointer( - attr, - glyph_attr_defs[attr].comps, - glyph_attr_defs[attr].type, - GL_FALSE, - sizeof(Tile_Glyph), - (void*) glyph_attr_defs[attr].offset); - break; - - case GL_INT: - glVertexAttribIPointer( - attr, - glyph_attr_defs[attr].comps, - glyph_attr_defs[attr].type, - sizeof(Tile_Glyph), - (void*) glyph_attr_defs[attr].offset); - break; - - default: - assert(false && "unreachable"); - exit(1); - } - glVertexAttribDivisor(attr, 1); - } - } + tile_glyph_buffer_init(&tgb, + "./charmap-oldschool_white.png", + "./shaders/tile_glyph.vert", + "./shaders/tile_glyph.frag"); bool quit = false; while (!quit) { @@ -435,33 +242,33 @@ int main(int argc, char **argv) SDL_GetWindowSize(window, &w, &h); // TODO(#19): update the viewport and the resolution only on actual window change glViewport(0, 0, w, h); - glUniform2f(resolution_uniform, (float) w, (float) h); + glUniform2f(tgb.resolution_uniform, (float) w, (float) h); } - tile_glyph_buffer_clear(); + tile_glyph_buffer_clear(&tgb); { for (size_t row = 0; row < editor.size; ++row) { const Line *line = editor.lines + row; - gl_render_text_sized(line->chars, line->size, vec2i(0, -(int)row), vec4fs(1.0f), vec4fs(0.0f)); + tile_glyph_render_line_sized(&tgb, line->chars, line->size, vec2i(0, -(int)row), vec4fs(1.0f), vec4fs(0.0f)); } } - tile_glyph_buffer_sync(); + tile_glyph_buffer_sync(&tgb); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - glUniform1f(time_uniform, (float) SDL_GetTicks() / 1000.0f); - glUniform2f(camera_uniform, camera_pos.x, camera_pos.y); + glUniform1f(tgb.time_uniform, (float) SDL_GetTicks() / 1000.0f); + glUniform2f(tgb.camera_uniform, camera_pos.x, camera_pos.y); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) tile_glyph_buffer_count); + tile_glyph_buffer_draw(&tgb); - tile_glyph_buffer_clear(); + tile_glyph_buffer_clear(&tgb); { - gl_render_cursor(); + gl_render_cursor(&tgb); } - tile_glyph_buffer_sync(); + tile_glyph_buffer_sync(&tgb); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) tile_glyph_buffer_count); + tile_glyph_buffer_draw(&tgb); SDL_GL_SwapWindow(window); diff --git a/src/tile_glyph.c b/src/tile_glyph.c new file mode 100644 index 00000000..fcfc9e37 --- /dev/null +++ b/src/tile_glyph.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include "./stb_image.h" +#include "./tile_glyph.h" +#include "./gl_extra.h" + +static const Attr_Def glyph_attr_defs[COUNT_TILE_GLYPH_ATTRS] = { + [TILE_GLYPH_ATTR_TILE] = { + .offset = offsetof(Tile_Glyph, tile), + .comps = 2, + .type = GL_INT + }, + [TILE_GLYPH_ATTR_CH] = { + .offset = offsetof(Tile_Glyph, ch), + .comps = 1, + .type = GL_INT + }, + [TILE_GLYPH_ATTR_FG_COLOR] = { + .offset = offsetof(Tile_Glyph, fg_color), + .comps = 4, + .type = GL_FLOAT + }, + [TILE_GLYPH_ATTR_BG_COLOR] = { + .offset = offsetof(Tile_Glyph, bg_color), + .comps = 4, + .type = GL_FLOAT + }, +}; +static_assert(COUNT_TILE_GLYPH_ATTRS == 4, "The amount of glyph vertex attributes have changed"); + +void tile_glyph_buffer_clear(Tile_Glyph_Buffer *tgb) +{ + tgb->glyphs_count = 0; +} + +void tile_glyph_buffer_push(Tile_Glyph_Buffer *tgb, Tile_Glyph glyph) +{ + assert(tgb->glyphs_count < TILE_GLYPH_BUFFER_CAP); + tgb->glyphs[tgb->glyphs_count++] = glyph; +} + +void tile_glyph_buffer_sync(Tile_Glyph_Buffer *tgb) +{ + glBufferSubData(GL_ARRAY_BUFFER, + 0, + tgb->glyphs_count * sizeof(Tile_Glyph), + tgb->glyphs); +} + +void tile_glyph_render_line_sized(Tile_Glyph_Buffer *tgb, const char *text, size_t text_size, Vec2i tile, Vec4f fg_color, Vec4f bg_color) +{ + for (size_t i = 0; i < text_size; ++i) { + tile_glyph_buffer_push(tgb, (Tile_Glyph) { + .tile = vec2i_add(tile, vec2i((int) i, 0)), + .ch = text[i], + .fg_color = fg_color, + .bg_color = bg_color, + }); + } +} + +void tile_glyph_render_line(Tile_Glyph_Buffer *tgb, const char *text, Vec2i tile, Vec4f fg_color, Vec4f bg_color) +{ + tile_glyph_render_line_sized(tgb, text, strlen(text), tile, fg_color, bg_color); +} + +void tile_glyph_buffer_init(Tile_Glyph_Buffer *tgb, const char *texture_file_path, const char *vert_file_path, const char *frag_file_path) +{ + // Init Vertex Attributes + { + glGenVertexArrays(1, &tgb->vao); + glBindVertexArray(tgb->vao); + + glGenBuffers(1, &tgb->vbo); + glBindBuffer(GL_ARRAY_BUFFER, tgb->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(tgb->glyphs), tgb->glyphs, GL_DYNAMIC_DRAW); + + for (Tile_Glyph_Attr attr = 0; attr < COUNT_TILE_GLYPH_ATTRS; ++attr) { + glEnableVertexAttribArray(attr); + switch (glyph_attr_defs[attr].type) { + case GL_FLOAT: + glVertexAttribPointer( + attr, + glyph_attr_defs[attr].comps, + glyph_attr_defs[attr].type, + GL_FALSE, + sizeof(Tile_Glyph), + (void*) glyph_attr_defs[attr].offset); + break; + + case GL_INT: + glVertexAttribIPointer( + attr, + glyph_attr_defs[attr].comps, + glyph_attr_defs[attr].type, + sizeof(Tile_Glyph), + (void*) glyph_attr_defs[attr].offset); + break; + + default: + assert(false && "unreachable"); + exit(1); + } + glVertexAttribDivisor(attr, 1); + } + } + + // Init Shaders + { + GLuint vert_shader = 0; + if (!compile_shader_file(vert_file_path, GL_VERTEX_SHADER, &vert_shader)) { + exit(1); + } + GLuint frag_shader = 0; + if (!compile_shader_file(frag_file_path, GL_FRAGMENT_SHADER, &frag_shader)) { + exit(1); + } + + GLuint program = 0; + if (!link_program(vert_shader, frag_shader, &program)) { + exit(1); + } + + glUseProgram(program); + + tgb->time_uniform = glGetUniformLocation(program, "time"); + tgb->resolution_uniform = glGetUniformLocation(program, "resolution"); + tgb->scale_uniform = glGetUniformLocation(program, "scale"); + tgb->camera_uniform = glGetUniformLocation(program, "camera"); + + glUniform2f(tgb->scale_uniform, FONT_SCALE, FONT_SCALE); + } + + // Init Texture + { + int width, height, n; + unsigned char *pixels = stbi_load(texture_file_path, &width, &height, &n, STBI_rgb_alpha); + if (pixels == NULL) { + fprintf(stderr, "ERROR: could not load file %s: %s\n", + texture_file_path, stbi_failure_reason()); + exit(1); + } + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &tgb->font_texture); + glBindTexture(GL_TEXTURE_2D, tgb->font_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + width, + height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixels); + + stbi_image_free(pixels); + } +} + +void tile_glyph_buffer_draw(Tile_Glyph_Buffer *tgb) +{ + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) tgb->glyphs_count); +} diff --git a/src/tile_glyph.h b/src/tile_glyph.h new file mode 100644 index 00000000..0e65e7ea --- /dev/null +++ b/src/tile_glyph.h @@ -0,0 +1,73 @@ +#ifndef TILE_GLYPH_H_ +#define TILE_GLYPH_H_ + +#include +#include "./la.h" + +#define GLEW_STATIC +#include + +#define GL_GLEXT_PROTOTYPES +#include + +#define FONT_SCALE 5 +#define FONT_WIDTH 128 +#define FONT_HEIGHT 64 +#define FONT_COLS 18 +#define FONT_ROWS 7 +#define FONT_CHAR_WIDTH (FONT_WIDTH / FONT_COLS) +#define FONT_CHAR_HEIGHT (FONT_HEIGHT / FONT_ROWS) + +typedef struct { + Vec2i tile; + int ch; + Vec4f fg_color; + Vec4f bg_color; +} Tile_Glyph; + +typedef enum { + TILE_GLYPH_ATTR_TILE = 0, + TILE_GLYPH_ATTR_CH, + TILE_GLYPH_ATTR_FG_COLOR, + TILE_GLYPH_ATTR_BG_COLOR, + COUNT_TILE_GLYPH_ATTRS, +} Tile_Glyph_Attr; + +typedef struct { + size_t offset; + GLint comps; + GLenum type; +} Attr_Def; + +#define TILE_GLYPH_BUFFER_CAP (640 * 1024) + +typedef struct { + GLuint vao; + GLuint vbo; + + GLuint font_texture; + + GLint time_uniform; + GLint resolution_uniform; + GLint scale_uniform; + GLint camera_uniform; + + size_t glyphs_count; + Tile_Glyph glyphs[TILE_GLYPH_BUFFER_CAP]; +} Tile_Glyph_Buffer; + +void tile_glyph_buffer_init(Tile_Glyph_Buffer *buffer, + const char *texture_file_path, + const char *vert_file_path, + const char *frag_file_path); + +void tile_glyph_buffer_clear(Tile_Glyph_Buffer *buffer); +void tile_glyph_buffer_push(Tile_Glyph_Buffer *buffer, Tile_Glyph glyph); +void tile_glyph_buffer_sync(Tile_Glyph_Buffer *buffer); + +void tile_glyph_render_line_sized(Tile_Glyph_Buffer *buffer, const char *text, size_t text_size, Vec2i tile, Vec4f fg_color, Vec4f bg_color); +void tile_glyph_render_line(Tile_Glyph_Buffer *buffer, const char *text, Vec2i tile, Vec4f fg_color, Vec4f bg_color); + +void tile_glyph_buffer_draw(Tile_Glyph_Buffer *buffer); + +#endif // TILE_GLYPH_H_ From ca556aac646ed548f2e5134abed6b065f04f47cc Mon Sep 17 00:00:00 2001 From: rexim Date: Sat, 10 Jul 2021 22:31:10 +0700 Subject: [PATCH 3/9] Introduce Free_Glyph renderer --- README.md | 4 ++ VictorMono-Regular.ttf | Bin 0 -> 151576 bytes build.sh | 2 +- shaders/free_glyph.frag | 7 +++ shaders/free_glyph.vert | 14 +++++ src/free_glyph.c | 133 ++++++++++++++++++++++++++++++++++++++++ src/free_glyph.h | 53 ++++++++++++++++ src/main.c | 91 ++++++++++++++++----------- src/tile_glyph.c | 6 ++ src/tile_glyph.h | 9 +-- 10 files changed, 274 insertions(+), 45 deletions(-) create mode 100644 VictorMono-Regular.ttf create mode 100644 shaders/free_glyph.frag create mode 100644 shaders/free_glyph.vert create mode 100644 src/free_glyph.c create mode 100644 src/free_glyph.h diff --git a/README.md b/README.md index 31b152e4..debf2947 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,7 @@ $ ./ded src\main.c > .\build_msvc.bat > .\ded.exe src\main.c ``` + +# Font + +Victor Mono: https://rubjo.github.io/victor-mono/ diff --git a/VictorMono-Regular.ttf b/VictorMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..aadcc88af58bebac86d44904e950cbad103edbf5 GIT binary patch literal 151576 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�*9yP}-) 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 S{H!yBKeH3(KH{k0fBqNgZI(O$ literal 0 HcmV?d00001 diff --git a/build.sh b/build.sh index 74280200..cf0b983b 100755 --- a/build.sh +++ b/build.sh @@ -6,7 +6,7 @@ CC="${CXX:-cc}" PKGS="sdl2 glew" CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb" LIBS=-lm -SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c src/tile_glyph.c" +SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c src/tile_glyph.c src/free_glyph.c" if [ `uname` = "Darwin" ]; then CFLAGS+=" -framework OpenGL" diff --git a/shaders/free_glyph.frag b/shaders/free_glyph.frag new file mode 100644 index 00000000..43696342 --- /dev/null +++ b/shaders/free_glyph.frag @@ -0,0 +1,7 @@ +#version 330 core + +uniform sampler2D font; + +void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); +} diff --git a/shaders/free_glyph.vert b/shaders/free_glyph.vert new file mode 100644 index 00000000..62c52392 --- /dev/null +++ b/shaders/free_glyph.vert @@ -0,0 +1,14 @@ +#version 330 core + +layout(location = 0) in vec2 pos; +layout(location = 1) in vec2 size; +layout(location = 2) in int ch; +layout(location = 3) in vec4 fg_color; +layout(location = 4) in vec4 bg_color; + +out vec2 uv; + +void main() { + uv = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); + gl_Position = vec4(uv - vec2(0.5), 0.0, 1.0); +} diff --git a/src/free_glyph.c b/src/free_glyph.c new file mode 100644 index 00000000..d153e97c --- /dev/null +++ b/src/free_glyph.c @@ -0,0 +1,133 @@ +#include +#include +#include "./free_glyph.h" +#include "./gl_extra.h" + +typedef struct { + size_t offset; + GLint comps; + GLenum type; +} Attr_Def; + +static const Attr_Def glyph_attr_defs[COUNT_FREE_GLYPH_ATTRS] = { + [FREE_GLYPH_ATTR_POS] = { + .offset = offsetof(Free_Glyph, pos), + .comps = 2, + .type = GL_FLOAT + }, + [FREE_GLYPH_ATTR_SIZE] = { + .offset = offsetof(Free_Glyph, size), + .comps = 2, + .type = GL_FLOAT + }, + [FREE_GLYPH_ATTR_CH] = { + .offset = offsetof(Free_Glyph, ch), + .comps = 1, + .type = GL_INT + }, + [FREE_GLYPH_ATTR_FG_COLOR] = { + .offset = offsetof(Free_Glyph, fg_color), + .comps = 4, + .type = GL_FLOAT + }, + [FREE_GLYPH_ATTR_BG_COLOR] = { + .offset = offsetof(Free_Glyph, bg_color), + .comps = 4, + .type = GL_FLOAT + }, +}; +static_assert(COUNT_FREE_GLYPH_ATTRS == 5, "The amount of glyph vertex attributes have changed"); + + +void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, + const char *vert_file_path, + const char *frag_file_path) +{ + // Init Vertex Attributes + { + glGenVertexArrays(1, &fgb->vao); + glBindVertexArray(fgb->vao); + + glGenBuffers(1, &fgb->vbo); + glBindBuffer(GL_ARRAY_BUFFER, fgb->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(fgb->glyphs), fgb->glyphs, GL_DYNAMIC_DRAW); + + for (Free_Glyph_Attr attr = 0; attr < COUNT_FREE_GLYPH_ATTRS; ++attr) { + glEnableVertexAttribArray(attr); + switch (glyph_attr_defs[attr].type) { + case GL_FLOAT: + glVertexAttribPointer( + attr, + glyph_attr_defs[attr].comps, + glyph_attr_defs[attr].type, + GL_FALSE, + sizeof(Free_Glyph), + (void*) glyph_attr_defs[attr].offset); + break; + + case GL_INT: + glVertexAttribIPointer( + attr, + glyph_attr_defs[attr].comps, + glyph_attr_defs[attr].type, + sizeof(Free_Glyph), + (void*) glyph_attr_defs[attr].offset); + break; + + default: + assert(false && "unreachable"); + exit(1); + } + glVertexAttribDivisor(attr, 1); + } + } + + // Init Shaders + { + GLuint vert_shader = 0; + if (!compile_shader_file(vert_file_path, GL_VERTEX_SHADER, &vert_shader)) { + exit(1); + } + GLuint frag_shader = 0; + if (!compile_shader_file(frag_file_path, GL_FRAGMENT_SHADER, &frag_shader)) { + exit(1); + } + + GLuint program = 0; + if (!link_program(vert_shader, frag_shader, &program)) { + exit(1); + } + + glUseProgram(program); + + fgb->time_uniform = glGetUniformLocation(program, "time"); + fgb->resolution_uniform = glGetUniformLocation(program, "resolution"); + fgb->camera_uniform = glGetUniformLocation(program, "camera"); + } + +} + +void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb) +{ + (void) fgb; + assert(0); +} + +void free_glyph_buffer_push(Free_Glyph_Buffer *fgb, Free_Glyph glyph) +{ + (void) fgb; + (void) glyph; + assert(0); +} + +void free_glyph_buffer_sync(Free_Glyph_Buffer *fgb) +{ + (void) fgb; + assert(0); +} + +void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb) +{ + (void) fgb; + assert(0); +} diff --git a/src/free_glyph.h b/src/free_glyph.h new file mode 100644 index 00000000..d3392807 --- /dev/null +++ b/src/free_glyph.h @@ -0,0 +1,53 @@ +#ifndef FREE_GLYPH_H_ +#define FREE_GLYPH_H_ + +#include +#include "./la.h" + +#define GLEW_STATIC +#include + +#define GL_GLEXT_PROTOTYPES +#include + +typedef struct { + Vec2f pos; + Vec2f size; + int ch; + Vec4f fg_color; + Vec4f bg_color; +} Free_Glyph; + +typedef enum { + FREE_GLYPH_ATTR_POS = 0, + FREE_GLYPH_ATTR_SIZE, + FREE_GLYPH_ATTR_CH, + FREE_GLYPH_ATTR_FG_COLOR, + FREE_GLYPH_ATTR_BG_COLOR, + COUNT_FREE_GLYPH_ATTRS, +} Free_Glyph_Attr; + +#define FREE_GLYPH_BUFFER_CAP (640 * 1000) + +typedef struct { + GLuint vao; + GLuint vbo; + + GLint time_uniform; + GLint resolution_uniform; + GLint camera_uniform; + + size_t glyphs_count; + Free_Glyph glyphs[FREE_GLYPH_BUFFER_CAP]; +} Free_Glyph_Buffer; + +void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, + const char *vert_file_path, + const char *frag_file_path); +void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb); +void free_glyph_buffer_push(Free_Glyph_Buffer *fgb, Free_Glyph glyph); +void free_glyph_buffer_sync(Free_Glyph_Buffer *fgb); +void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb); + + +#endif // FREE_GLYPH_H_ diff --git a/src/main.c b/src/main.c index ebf20bfe..e0ce9476 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include "./sdl_extra.h" #include "./gl_extra.h" #include "./tile_glyph.h" +#include "./free_glyph.h" #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 @@ -68,6 +69,50 @@ void gl_render_cursor(Tile_Glyph_Buffer *tgb) } static Tile_Glyph_Buffer tgb = {0}; +static Free_Glyph_Buffer fgb = {0}; + +void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor *editor) +{ + { + int w, h; + SDL_GetWindowSize(window, &w, &h); + // TODO(#19): update the viewport and the resolution only on actual window change + glViewport(0, 0, w, h); + glUniform2f(tgb->resolution_uniform, (float) w, (float) h); + } + + tile_glyph_buffer_clear(tgb); + { + for (size_t row = 0; row < editor->size; ++row) { + const Line *line = editor->lines + row; + tile_glyph_render_line_sized(tgb, line->chars, line->size, vec2i(0, -(int)row), vec4fs(1.0f), vec4fs(0.0f)); + } + } + tile_glyph_buffer_sync(tgb); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUniform1f(tgb->time_uniform, (float) SDL_GetTicks() / 1000.0f); + glUniform2f(tgb->camera_uniform, camera_pos.x, camera_pos.y); + + tile_glyph_buffer_draw(tgb); + + tile_glyph_buffer_clear(tgb); + { + gl_render_cursor(tgb); + } + tile_glyph_buffer_sync(tgb); + + tile_glyph_buffer_draw(tgb); +} + +void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *tgb, Editor *editor) +{ + (void) window; + (void) tgb; + (void) editor; +} // OPENGL int main(int argc, char **argv) @@ -133,10 +178,14 @@ int main(int argc, char **argv) fprintf(stderr, "WARNING! GLEW_ARB_debug_output is not available"); } - tile_glyph_buffer_init(&tgb, - "./charmap-oldschool_white.png", - "./shaders/tile_glyph.vert", - "./shaders/tile_glyph.frag"); + // tile_glyph_buffer_init(&tgb, + // "./charmap-oldschool_white.png", + // "./shaders/tile_glyph.vert", + // "./shaders/tile_glyph.frag"); + + free_glyph_buffer_init(&fgb, + "./shaders/free_glyph.vert", + "./shaders/free_glyph.frag"); bool quit = false; while (!quit) { @@ -237,38 +286,8 @@ int main(int argc, char **argv) camera_pos = vec2f_add(camera_pos, vec2f_mul(camera_vel, vec2fs(DELTA_TIME))); } - { - int w, h; - SDL_GetWindowSize(window, &w, &h); - // TODO(#19): update the viewport and the resolution only on actual window change - glViewport(0, 0, w, h); - glUniform2f(tgb.resolution_uniform, (float) w, (float) h); - } - - tile_glyph_buffer_clear(&tgb); - { - for (size_t row = 0; row < editor.size; ++row) { - const Line *line = editor.lines + row; - tile_glyph_render_line_sized(&tgb, line->chars, line->size, vec2i(0, -(int)row), vec4fs(1.0f), vec4fs(0.0f)); - } - } - tile_glyph_buffer_sync(&tgb); - - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - glUniform1f(tgb.time_uniform, (float) SDL_GetTicks() / 1000.0f); - glUniform2f(tgb.camera_uniform, camera_pos.x, camera_pos.y); - - tile_glyph_buffer_draw(&tgb); - - tile_glyph_buffer_clear(&tgb); - { - gl_render_cursor(&tgb); - } - tile_glyph_buffer_sync(&tgb); - - tile_glyph_buffer_draw(&tgb); + // render_editor_into_tgb(window, &tgb, &editor); + render_editor_into_fgb(window, &fgb, &editor); SDL_GL_SwapWindow(window); diff --git a/src/tile_glyph.c b/src/tile_glyph.c index fcfc9e37..8d81d473 100644 --- a/src/tile_glyph.c +++ b/src/tile_glyph.c @@ -5,6 +5,12 @@ #include "./tile_glyph.h" #include "./gl_extra.h" +typedef struct { + size_t offset; + GLint comps; + GLenum type; +} Attr_Def; + static const Attr_Def glyph_attr_defs[COUNT_TILE_GLYPH_ATTRS] = { [TILE_GLYPH_ATTR_TILE] = { .offset = offsetof(Tile_Glyph, tile), diff --git a/src/tile_glyph.h b/src/tile_glyph.h index 0e65e7ea..0c3eac0d 100644 --- a/src/tile_glyph.h +++ b/src/tile_glyph.h @@ -33,12 +33,6 @@ typedef enum { COUNT_TILE_GLYPH_ATTRS, } Tile_Glyph_Attr; -typedef struct { - size_t offset; - GLint comps; - GLenum type; -} Attr_Def; - #define TILE_GLYPH_BUFFER_CAP (640 * 1024) typedef struct { @@ -60,14 +54,13 @@ void tile_glyph_buffer_init(Tile_Glyph_Buffer *buffer, const char *texture_file_path, const char *vert_file_path, const char *frag_file_path); - void tile_glyph_buffer_clear(Tile_Glyph_Buffer *buffer); void tile_glyph_buffer_push(Tile_Glyph_Buffer *buffer, Tile_Glyph glyph); void tile_glyph_buffer_sync(Tile_Glyph_Buffer *buffer); +void tile_glyph_buffer_draw(Tile_Glyph_Buffer *buffer); void tile_glyph_render_line_sized(Tile_Glyph_Buffer *buffer, const char *text, size_t text_size, Vec2i tile, Vec4f fg_color, Vec4f bg_color); void tile_glyph_render_line(Tile_Glyph_Buffer *buffer, const char *text, Vec2i tile, Vec4f fg_color, Vec4f bg_color); -void tile_glyph_buffer_draw(Tile_Glyph_Buffer *buffer); #endif // TILE_GLYPH_H_ From c0afe67783e19c35ebc95a624d0243e28efa03c5 Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 11 Jul 2021 00:33:12 +0700 Subject: [PATCH 4/9] Implement and Integrate free glyph renderer --- build.sh | 2 +- shaders/free_glyph.frag | 19 +- shaders/free_glyph.vert | 25 +- src/free_glyph.c | 133 ++- src/free_glyph.h | 34 +- src/main.c | 77 +- src/stb_image_write.h | 1690 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 1951 insertions(+), 29 deletions(-) create mode 100644 src/stb_image_write.h diff --git a/build.sh b/build.sh index cf0b983b..ff1c7d9e 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ set -xe CC="${CXX:-cc}" -PKGS="sdl2 glew" +PKGS="sdl2 glew freetype2" CFLAGS="-Wall -Wextra -std=c11 -pedantic -ggdb" LIBS=-lm SRC="src/main.c src/la.c src/editor.c src/sdl_extra.c src/file.c src/gl_extra.c src/tile_glyph.c src/free_glyph.c" diff --git a/shaders/free_glyph.frag b/shaders/free_glyph.frag index 43696342..dc9dce47 100644 --- a/shaders/free_glyph.frag +++ b/shaders/free_glyph.frag @@ -1,7 +1,24 @@ #version 330 core uniform sampler2D font; +uniform float time; +uniform vec2 resolution; + +in vec2 uv; +in vec2 glyph_uv_pos; +in vec2 glyph_uv_size; +in vec4 glyph_fg_color; +in vec4 glyph_bg_color; + +vec3 hsl2rgb(vec3 c) { + vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0); + return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); +} void main() { - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + vec2 t = glyph_uv_pos + glyph_uv_size * uv; + vec4 tc = texture(font, t); + vec2 frag_uv = gl_FragCoord.xy / resolution; + vec4 rainbow = vec4(hsl2rgb(vec3((time + frag_uv.x + frag_uv.y), 0.5, 0.5)), 1.0); + gl_FragColor = glyph_bg_color * (1.0 - tc.x) + tc.x * glyph_fg_color * rainbow; } diff --git a/shaders/free_glyph.vert b/shaders/free_glyph.vert index 62c52392..c1459603 100644 --- a/shaders/free_glyph.vert +++ b/shaders/free_glyph.vert @@ -1,14 +1,31 @@ #version 330 core +uniform vec2 resolution; +uniform vec2 camera; + layout(location = 0) in vec2 pos; layout(location = 1) in vec2 size; -layout(location = 2) in int ch; -layout(location = 3) in vec4 fg_color; -layout(location = 4) in vec4 bg_color; +layout(location = 2) in vec2 uv_pos; +layout(location = 3) in vec2 uv_size; +layout(location = 4) in vec4 fg_color; +layout(location = 5) in vec4 bg_color; out vec2 uv; +out vec2 glyph_uv_pos; +out vec2 glyph_uv_size; +out vec4 glyph_fg_color; +out vec4 glyph_bg_color; + +vec2 camera_project(vec2 point) +{ + return 2.0 * (point - camera) / resolution; +} void main() { uv = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); - gl_Position = vec4(uv - vec2(0.5), 0.0, 1.0); + gl_Position = vec4(camera_project(uv * size + pos), 0.0, 1.0); + glyph_uv_pos = uv_pos; + glyph_uv_size = uv_size; + glyph_fg_color = fg_color; + glyph_bg_color = bg_color; } diff --git a/src/free_glyph.c b/src/free_glyph.c index d153e97c..7276cbaf 100644 --- a/src/free_glyph.c +++ b/src/free_glyph.c @@ -3,6 +3,9 @@ #include "./free_glyph.h" #include "./gl_extra.h" +#include "./stb_image_write.h" +#include "./stb_image.h" + typedef struct { size_t offset; GLint comps; @@ -20,10 +23,15 @@ static const Attr_Def glyph_attr_defs[COUNT_FREE_GLYPH_ATTRS] = { .comps = 2, .type = GL_FLOAT }, - [FREE_GLYPH_ATTR_CH] = { - .offset = offsetof(Free_Glyph, ch), - .comps = 1, - .type = GL_INT + [FREE_GLYPH_ATTR_UV_POS] = { + .offset = offsetof(Free_Glyph, uv_pos), + .comps = 2, + .type = GL_FLOAT + }, + [FREE_GLYPH_ATTR_UV_SIZE] = { + .offset = offsetof(Free_Glyph, uv_size), + .comps = 2, + .type = GL_FLOAT }, [FREE_GLYPH_ATTR_FG_COLOR] = { .offset = offsetof(Free_Glyph, fg_color), @@ -36,10 +44,11 @@ static const Attr_Def glyph_attr_defs[COUNT_FREE_GLYPH_ATTRS] = { .type = GL_FLOAT }, }; -static_assert(COUNT_FREE_GLYPH_ATTRS == 5, "The amount of glyph vertex attributes have changed"); +static_assert(COUNT_FREE_GLYPH_ATTRS == 6, "The amount of glyph vertex attributes have changed"); void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, + FT_Face face, const char *vert_file_path, const char *frag_file_path) { @@ -105,29 +114,125 @@ void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, fgb->camera_uniform = glGetUniformLocation(program, "camera"); } + // Glyph Texture Atlas + { + for (int i = 32; i < 128; ++i) { + if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { + fprintf(stderr, "ERROR: could not load glyph of a character with code %d\n", i); + exit(1); + } + + fgb->atlas_width += face->glyph->bitmap.width; + if (fgb->atlas_height < face->glyph->bitmap.rows) { + fgb->atlas_height = face->glyph->bitmap.rows; + } + } + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &fgb->glyphs_texture); + glBindTexture(GL_TEXTURE_2D, fgb->glyphs_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RED, + (GLsizei) fgb->atlas_width, + (GLsizei) fgb->atlas_height, + 0, + GL_RED, + GL_UNSIGNED_BYTE, + NULL); + + int x = 0; + for (int i = 32; i < 128; ++i) { + if (FT_Load_Char(face, i, FT_LOAD_RENDER)) { + fprintf(stderr, "ERROR: could not load glyph of a character with code %d\n", i); + exit(1); + } + + if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) { + fprintf(stderr, "ERROR: could not render glyph of a character with code %d\n", i); + exit(1); + } + + fgb->metrics[i].ax = face->glyph->advance.x >> 6; + fgb->metrics[i].ay = face->glyph->advance.y >> 6; + fgb->metrics[i].bw = face->glyph->bitmap.width; + fgb->metrics[i].bh = face->glyph->bitmap.rows; + fgb->metrics[i].bl = face->glyph->bitmap_left; + fgb->metrics[i].bt = face->glyph->bitmap_top; + fgb->metrics[i].tx = (float) x / (float) fgb->atlas_width; + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D( + GL_TEXTURE_2D, + 0, + x, + 0, + face->glyph->bitmap.width, + face->glyph->bitmap.rows, + GL_RED, + GL_UNSIGNED_BYTE, + face->glyph->bitmap.buffer); + x += face->glyph->bitmap.width; + } + } } void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb) { - (void) fgb; - assert(0); + fgb->glyphs_count = 0; } void free_glyph_buffer_push(Free_Glyph_Buffer *fgb, Free_Glyph glyph) { - (void) fgb; - (void) glyph; - assert(0); + assert(fgb->glyphs_count < FREE_GLYPH_BUFFER_CAP); + fgb->glyphs[fgb->glyphs_count++] = glyph; } void free_glyph_buffer_sync(Free_Glyph_Buffer *fgb) { - (void) fgb; - assert(0); + glBufferSubData(GL_ARRAY_BUFFER, + 0, + fgb->glyphs_count * sizeof(Free_Glyph), + fgb->glyphs); } void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb) { - (void) fgb; - assert(0); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei) fgb->glyphs_count); +} + +void free_glyph_buffer_render_line_sized(Free_Glyph_Buffer *fgb, const char *text, size_t text_size, Vec2f pos, Vec4f fg_color, Vec4f bg_color) +{ + for (size_t i = 0; i < text_size; ++i) { + Glyph_Metric metric = fgb->metrics[(int) text[i]]; + float x2 = pos.x + metric.bl; + float y2 = -pos.y - metric.bt; + float w = metric.bw; + float h = metric.bh; + + pos.x += metric.ax; + pos.y += metric.ay; + + Free_Glyph glyph = {0}; + glyph.pos = vec2f(x2, -y2); + glyph.size = vec2f(w, -h); + glyph.uv_pos = vec2f(metric.tx, 0.0f); + glyph.uv_size = vec2f(metric.bw / (float) fgb->atlas_width, metric.bh / (float) fgb->atlas_height); + glyph.fg_color = fg_color; + glyph.bg_color = bg_color; + free_glyph_buffer_push(fgb, glyph); + } +} + +void free_glyph_buffer_render_line(Free_Glyph_Buffer *fgb, const char *text, Vec2f pos, Vec4f fg_color, Vec4f bg_color) +{ + free_glyph_buffer_render_line_sized(fgb, text, strlen(text), pos, fg_color, bg_color); } diff --git a/src/free_glyph.h b/src/free_glyph.h index d3392807..7ab228db 100644 --- a/src/free_glyph.h +++ b/src/free_glyph.h @@ -10,10 +10,16 @@ #define GL_GLEXT_PROTOTYPES #include +#include +#include FT_FREETYPE_H + +// https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Text_Rendering_02 + typedef struct { Vec2f pos; Vec2f size; - int ch; + Vec2f uv_pos; + Vec2f uv_size; Vec4f fg_color; Vec4f bg_color; } Free_Glyph; @@ -21,27 +27,49 @@ typedef struct { typedef enum { FREE_GLYPH_ATTR_POS = 0, FREE_GLYPH_ATTR_SIZE, - FREE_GLYPH_ATTR_CH, + FREE_GLYPH_ATTR_UV_POS, + FREE_GLYPH_ATTR_UV_SIZE, FREE_GLYPH_ATTR_FG_COLOR, FREE_GLYPH_ATTR_BG_COLOR, COUNT_FREE_GLYPH_ATTRS, } Free_Glyph_Attr; +typedef struct { + float ax; // advance.x + float ay; // advance.y + + float bw; // bitmap.width; + float bh; // bitmap.rows; + + float bl; // bitmap_left; + float bt; // bitmap_top; + + float tx; // x offset of glyph in texture coordinates +} Glyph_Metric; + #define FREE_GLYPH_BUFFER_CAP (640 * 1000) typedef struct { GLuint vao; GLuint vbo; + FT_UInt atlas_width; + FT_UInt atlas_height; + + GLuint glyphs_texture; + GLint time_uniform; GLint resolution_uniform; GLint camera_uniform; size_t glyphs_count; Free_Glyph glyphs[FREE_GLYPH_BUFFER_CAP]; + + Glyph_Metric metrics[128]; } Free_Glyph_Buffer; void free_glyph_buffer_init(Free_Glyph_Buffer *fgb, + FT_Face face, const char *vert_file_path, const char *frag_file_path); void free_glyph_buffer_clear(Free_Glyph_Buffer *fgb); @@ -49,5 +77,7 @@ void free_glyph_buffer_push(Free_Glyph_Buffer *fgb, Free_Glyph glyph); void free_glyph_buffer_sync(Free_Glyph_Buffer *fgb); void free_glyph_buffer_draw(Free_Glyph_Buffer *fgb); +void free_glyph_buffer_render_line_sized(Free_Glyph_Buffer *fgb, const char *text, size_t text_size, Vec2f pos, Vec4f fg_color, Vec4f bg_color); +void free_glyph_buffer_render_line(Free_Glyph_Buffer *fgb, const char *text, Vec2f pos, Vec4f fg_color, Vec4f bg_color); #endif // FREE_GLYPH_H_ diff --git a/src/main.c b/src/main.c index e0ce9476..0cb14d96 100644 --- a/src/main.c +++ b/src/main.c @@ -8,9 +8,15 @@ #define GL_GLEXT_PROTOTYPES #include +#include +#include FT_FREETYPE_H + #define STB_IMAGE_IMPLEMENTATION #include "./stb_image.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "./stb_image_write.h" + #define SV_IMPLEMENTATION #include "./sv.h" @@ -85,14 +91,12 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * { for (size_t row = 0; row < editor->size; ++row) { const Line *line = editor->lines + row; + tile_glyph_render_line_sized(tgb, line->chars, line->size, vec2i(0, -(int)row), vec4fs(1.0f), vec4fs(0.0f)); } } tile_glyph_buffer_sync(tgb); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glUniform1f(tgb->time_uniform, (float) SDL_GetTicks() / 1000.0f); glUniform2f(tgb->camera_uniform, camera_pos.x, camera_pos.y); @@ -107,16 +111,71 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * tile_glyph_buffer_draw(tgb); } -void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *tgb, Editor *editor) +#define FREE_GLYPH_FONT_SIZE 64 + +// TODO: Free_Glyph renderer does not support cursor +// TODO: Camera location is broken in Free_Glyph Renderer + +void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *fgb, Editor *editor) { - (void) window; - (void) tgb; (void) editor; + + { + int w, h; + SDL_GetWindowSize(window, &w, &h); + // TODO(#19): update the viewport and the resolution only on actual window change + glViewport(0, 0, w, h); + glUniform2f(fgb->resolution_uniform, (float) w, (float) h); + } + + glUniform1f(fgb->time_uniform, (float) SDL_GetTicks() / 1000.0f); + glUniform2f(fgb->camera_uniform, camera_pos.x, camera_pos.y); + + free_glyph_buffer_clear(fgb); + + { + for (size_t row = 0; row < editor->size; ++row) { + const Line *line = editor->lines + row; + free_glyph_buffer_render_line_sized( + fgb, line->chars, line->size, + vec2f(0, -(float)row * FREE_GLYPH_FONT_SIZE), + vec4fs(1.0f), vec4fs(0.0f)); + } + } + + free_glyph_buffer_sync(fgb); + free_glyph_buffer_draw(fgb); } -// OPENGL int main(int argc, char **argv) { + FT_Library library = {0}; + + FT_Error error = FT_Init_FreeType(&library); + if (error) { + fprintf(stderr, "ERROR: could initialize FreeType2 library\n"); + exit(1); + } + + const char *const font_file_path = "./VictorMono-Regular.ttf"; + + FT_Face face; + error = FT_New_Face(library, font_file_path, 0, &face); + if (error == FT_Err_Unknown_File_Format) { + fprintf(stderr, "ERROR: `%s` has an unknown format\n", font_file_path); + exit(1); + } else if (error) { + fprintf(stderr, "ERROR: could not load file `%s`\n", font_file_path); + exit(1); + } + + FT_UInt pixel_size = FREE_GLYPH_FONT_SIZE; + error = FT_Set_Pixel_Sizes(face, 0, pixel_size); + if (error) { + fprintf(stderr, "ERROR: could not set pixel size to %u\n", pixel_size); + exit(1); + } + const char *file_path = NULL; if (argc > 1) { @@ -184,6 +243,7 @@ int main(int argc, char **argv) // "./shaders/tile_glyph.frag"); free_glyph_buffer_init(&fgb, + face, "./shaders/free_glyph.vert", "./shaders/free_glyph.frag"); @@ -286,6 +346,9 @@ int main(int argc, char **argv) camera_pos = vec2f_add(camera_pos, vec2f_mul(camera_vel, vec2fs(DELTA_TIME))); } + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + // render_editor_into_tgb(window, &tgb, &editor); render_editor_into_fgb(window, &fgb, &editor); diff --git a/src/stb_image_write.h b/src/stb_image_write.h new file mode 100644 index 00000000..a14ee9e4 --- /dev/null +++ b/src/stb_image_write.h @@ -0,0 +1,1690 @@ +/* stb_image_write - v1.15 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBI_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if (s->buf_used + 1 > (int) sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if (s->buf_used + 3 > (int) sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_WANT_SECURE_LIB__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ From 84772a242d2b10d0457027bde240dde8871a1cfb Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 11 Jul 2021 02:50:21 +0700 Subject: [PATCH 5/9] Move glViewport() call outside of the render functions --- src/main.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main.c b/src/main.c index 0cb14d96..9cf9932d 100644 --- a/src/main.c +++ b/src/main.c @@ -82,8 +82,6 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * { int w, h; SDL_GetWindowSize(window, &w, &h); - // TODO(#19): update the viewport and the resolution only on actual window change - glViewport(0, 0, w, h); glUniform2f(tgb->resolution_uniform, (float) w, (float) h); } @@ -118,13 +116,9 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *fgb, Editor *editor) { - (void) editor; - { int w, h; SDL_GetWindowSize(window, &w, &h); - // TODO(#19): update the viewport and the resolution only on actual window change - glViewport(0, 0, w, h); glUniform2f(fgb->resolution_uniform, (float) w, (float) h); } @@ -334,6 +328,13 @@ int main(int argc, char **argv) } } + { + int w, h; + SDL_GetWindowSize(window, &w, &h); + // TODO(#19): update the viewport and the resolution only on actual window change + glViewport(0, 0, w, h); + } + { const Vec2f cursor_pos = vec2f((float) editor.cursor_col * FONT_CHAR_WIDTH * FONT_SCALE, From ff6cb2d65b047621547cc8b2f8b34bf44ea74961 Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 11 Jul 2021 02:52:59 +0700 Subject: [PATCH 6/9] Introduce TILE_GLYPH_RENDER macro switch --- src/main.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main.c b/src/main.c index 9cf9932d..8c9de3b7 100644 --- a/src/main.c +++ b/src/main.c @@ -74,8 +74,13 @@ void gl_render_cursor(Tile_Glyph_Buffer *tgb) tile_glyph_render_line_sized(tgb, c ? c : " ", 1, tile, vec4fs(0.0f), vec4fs(1.0f)); } +// #define TILE_GLYPH_RENDER + +#ifdef TILE_GLYPH_RENDER static Tile_Glyph_Buffer tgb = {0}; +#else static Free_Glyph_Buffer fgb = {0}; +#endif void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor *editor) { @@ -231,15 +236,17 @@ int main(int argc, char **argv) fprintf(stderr, "WARNING! GLEW_ARB_debug_output is not available"); } - // tile_glyph_buffer_init(&tgb, - // "./charmap-oldschool_white.png", - // "./shaders/tile_glyph.vert", - // "./shaders/tile_glyph.frag"); - +#ifdef TILE_GLYPH_RENDER + tile_glyph_buffer_init(&tgb, + "./charmap-oldschool_white.png", + "./shaders/tile_glyph.vert", + "./shaders/tile_glyph.frag"); +#else free_glyph_buffer_init(&fgb, face, "./shaders/free_glyph.vert", "./shaders/free_glyph.frag"); +#endif bool quit = false; while (!quit) { @@ -350,8 +357,11 @@ int main(int argc, char **argv) glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - // render_editor_into_tgb(window, &tgb, &editor); +#ifdef TILE_GLYPH_RENDER + render_editor_into_tgb(window, &tgb, &editor); +#else render_editor_into_fgb(window, &fgb, &editor); +#endif // TILE_GLYPH_RENDER SDL_GL_SwapWindow(window); From 7e85c584945d233346b59b30df1feed1ad184e93 Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 11 Jul 2021 19:50:39 +0700 Subject: [PATCH 7/9] Add TODO(#27) --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 8c9de3b7..868c9adc 100644 --- a/src/main.c +++ b/src/main.c @@ -116,7 +116,7 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * #define FREE_GLYPH_FONT_SIZE 64 -// TODO: Free_Glyph renderer does not support cursor +// TODO(#27): Free_Glyph renderer does not support cursor // TODO: Camera location is broken in Free_Glyph Renderer void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *fgb, Editor *editor) From 88ad25e21c7a6ffe6392c109ceb63f983058db68 Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 11 Jul 2021 19:50:40 +0700 Subject: [PATCH 8/9] Add TODO(#28) --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 868c9adc..1db5320c 100644 --- a/src/main.c +++ b/src/main.c @@ -117,7 +117,7 @@ void render_editor_into_tgb(SDL_Window *window, Tile_Glyph_Buffer *tgb, Editor * #define FREE_GLYPH_FONT_SIZE 64 // TODO(#27): Free_Glyph renderer does not support cursor -// TODO: Camera location is broken in Free_Glyph Renderer +// TODO(#28): Camera location is broken in Free_Glyph Renderer void render_editor_into_fgb(SDL_Window *window, Free_Glyph_Buffer *fgb, Editor *editor) { From b06ac01d463d450524df94de5425bcb901e35693 Mon Sep 17 00:00:00 2001 From: rexim Date: Sun, 11 Jul 2021 19:53:44 +0700 Subject: [PATCH 9/9] Add TODO(#29) --- .github/workflows/ci.yml | 50 +++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b82b51..db6642ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,27 +42,29 @@ jobs: ./build.sh env: CC: clang - build-windows-msvc: - runs-on: windows-2019 - steps: - - uses: actions/checkout@v1 - # this runs vcvarsall for us, so we get the MSVC toolchain in PATH. - - uses: seanmiddleditch/gha-setup-vsdevenv@master - - name: Install dependencies - run: | - ./setup_dependencies.bat - - name: build ded - shell: cmd - run: | - ./build_msvc.bat - - name: Prepare WindowsBinaries artifacts - shell: cmd - run: | - mkdir winbin - copy /B *.exe winbin - copy /B *.png winbin - - name: Upload WindowsBinaries artifacts - uses: actions/upload-artifact@v2 - with: - name: WindowsBinaries - path: ./winbin/ + # TODO(#29): build-windows-msvc is broken + # --- + # build-windows-msvc: + # runs-on: windows-2019 + # steps: + # - uses: actions/checkout@v1 + # # this runs vcvarsall for us, so we get the MSVC toolchain in PATH. + # - uses: seanmiddleditch/gha-setup-vsdevenv@master + # - name: Install dependencies + # run: | + # ./setup_dependencies.bat + # - name: build ded + # shell: cmd + # run: | + # ./build_msvc.bat + # - name: Prepare WindowsBinaries artifacts + # shell: cmd + # run: | + # mkdir winbin + # copy /B *.exe winbin + # copy /B *.png winbin + # - name: Upload WindowsBinaries artifacts + # uses: actions/upload-artifact@v2 + # with: + # name: WindowsBinaries + # path: ./winbin/