diff --git a/examples/basic.cpp b/examples/basic.cpp index fa90ddd..bd0c5df 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -6,15 +6,15 @@ namespace { void init() { - glerminal_update_sprite(0xFF00007F, { - 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, - 0x00FF007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0x00FF007F, - 0x00FF007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0x00FF007F, - 0x00FF007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0x00FF007F, - 0x00FF007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0x00FF007F, - 0x00FF007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0x00FF007F, - 0x00FF007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0xFF00007F, 0x00FF007F, - 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F, 0x00FF007F + glerminal_update_sprite(1, { + 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, + 0x5000FF00, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x5000FF00, + 0x5000FF00, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x5000FF00, + 0x5000FF00, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x5000FF00, + 0x5000FF00, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x5000FF00, + 0x5000FF00, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x5000FF00, + 0x5000FF00, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x500000FF, 0x5000FF00, + 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00, 0x5000FF00 }); } @@ -37,10 +37,10 @@ namespace { for (int j = 0; j < 25; j++) { - for (int k = 0; k < 8; k++) + for (int k = 0; k < 256; k++) { - glerminal_set(i, j, k, rand() % 2); - glerminal_offset(i, j, k, (rand() * rand()) % 100 - 50, (rand() * rand()) % 100 - 50); + glerminal_set(i, j, k, rand() % 8 == 0); + glerminal_offset(i, j, k, (rand() * rand()) % 64 - 32, (rand() * rand()) % 64 - 32); } } } diff --git a/examples/towers.cpp b/examples/towers.cpp index 6a10f1a..87c258b 100644 --- a/examples/towers.cpp +++ b/examples/towers.cpp @@ -7,34 +7,25 @@ namespace { void init() { - for (int i = 1; i < 256; i++) - { - constexpr unsigned char c = 16; - const unsigned char v = (255 - c) * powf((i - 1) / 256.0f, 4.0f) + c; - const unsigned int j = (0xFF << 24) | (v << 16) | (v << 8) | v; - glerminal_update_sprite(i, - { - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - j,j,j,j,j,j,j,j, - }); - } - - for (int i = 0; i < 40; i++) - { - for (int j = 0; j < 25; j++) + glerminal_update_sprite(1, { - const int c = rand() % 224 + 32; - for (int k = 0; k < c; k++) - { - glerminal_set(i, j, k, k); - } - } + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + 0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF, + }); + + for (int i = 0; i < 256; i++) + { + constexpr unsigned char c = 32; + const unsigned char v = (255 - c) * powf((i - 1) / 256.0f, 2.0f) + c; + const unsigned int j = (0xFF << 24) | (v << 16) | (v << 8) | v; + glerminal_layer_color(i, j); + glerminal_layer_scale(i, i / 256.0f + 1); } } @@ -44,7 +35,7 @@ namespace time += dt; - if (time < 1.0f) + if (time < 0.15f) { return; } @@ -60,11 +51,20 @@ namespace { for (int j = 0; j < 25; j++) { + const int c = rand() % 224 + 32; for (int k = 0; k < 256; k++) { - const float ox = 0.025f * powf(k, 0.8f) * (i - cx); - const float oy = 0.025f * powf(k, 0.8f) * (j - cy); - glerminal_offset(i, j, k, ox, oy); + if (k < c) + { + glerminal_set(i, j, k, 1); + const float ox = 0.01f * k * (i - cx); + const float oy = 0.01f * k * (j - cy); + glerminal_offset(i, j, k, ox + (1 + 0.004f * k) * cosf(c * i + k * 6.28f / 128.0f), oy + (1 + 0.004f * k) * sinf(c * j + k * 6.28f / 128.0f)); + } + else + { + glerminal_set(i, j, k, 0); + } } } } diff --git a/include/glerminal.h b/include/glerminal.h index 62528f5..fce99f5 100644 --- a/include/glerminal.h +++ b/include/glerminal.h @@ -58,6 +58,19 @@ unsigned char glerminal_get(unsigned char x, unsigned char y, unsigned char laye */ void glerminal_offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset); +/** + * @brief Set a layer's color + * @param layer The layer to modify + * @param color The new color + */ +void glerminal_layer_color(unsigned char layer, unsigned int color); +/** + * @brief Set a layer's scale + * @param layer The layer to modify + * @param scale The new scale + */ +void glerminal_layer_scale(unsigned char layer, float scale); + /** * @brief Upload sprite to the given sprite ID * @param id The ID of the sprite to change diff --git a/source/glerminal-private.h b/source/glerminal-private.h index 6eadf61..754e003 100644 --- a/source/glerminal-private.h +++ b/source/glerminal-private.h @@ -41,30 +41,46 @@ namespace glerminal void set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite); unsigned char get(unsigned char x, unsigned char y, unsigned char layer) const; void offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset); + void layer_color(unsigned char layer, unsigned int color); + void layer_scale(unsigned char layer, float scale); void update_sprite(unsigned char id, glerminal_sprite sprite); private: + // glfw data + GLFWwindow* m_window; + // opengl handles + unsigned int m_vbo; unsigned int m_sprites_instance_vbo; unsigned int m_offsets_instance_vbo; unsigned int m_vao; - unsigned int m_program; unsigned int m_screen_vao; + unsigned int m_program; unsigned int m_screen_program; unsigned int m_sprites_texture; unsigned int m_framebuffer; unsigned int m_framebuffer_backing_texture; + unsigned int m_layer_colors_buffer; + unsigned int m_layer_scales_buffer; unsigned int m_screen_size_uniform_location; unsigned int m_palette_uniform_location; + // per-cell data + unsigned char m_cells[GRID_AREA * LAYER_COUNT]; float m_offsets[GRID_AREA * LAYER_COUNT * 2]; - unsigned int m_sprites[CELL_SIZE * CELL_SIZE * 256]; + // per-layer data + float m_layer_colors[LAYER_COUNT * 4]; + float m_layer_scales[LAYER_COUNT]; + + // library state + + unsigned int m_sprites[CELL_SIZE * CELL_SIZE * (1 << (8 * sizeof(*m_cells)))]; glerminal_main_cb m_main; #ifdef _DEBUG @@ -79,6 +95,8 @@ namespace glerminal void deinit_gl(); void update_sprites(); + void update_layer_colors(); + void update_layer_scales(); }; } diff --git a/source/glerminal.cpp b/source/glerminal.cpp index e886ab1..0490a74 100644 --- a/source/glerminal.cpp +++ b/source/glerminal.cpp @@ -22,7 +22,7 @@ namespace }; constexpr char* VERTEX_SHADER_SOURCE = - "#version 400 core\n" + "#version 460 core\n" "layout (location = 0) in vec2 position;\n" "layout (location = 1) in vec2 offset;\n" "layout (location = 2) in int sprite;\n" @@ -45,7 +45,7 @@ namespace "}"; constexpr char* GEOMETRY_SHADER_SOURCE = - "#version 400 core\n" + "#version 460 core\n" "layout (triangles) in;\n" "layout (triangle_strip, max_vertices = 3) out;\n" "in VS_OUT {\n" @@ -55,21 +55,25 @@ namespace " vec2 texcoord;\n" "} gs_in[];\n" "flat out int sprite;\n" + "layout (std430, binding = 0) buffer LayerScales" + "{\n" + " float scales[];\n" + "} lss;\n" "out vec2 texcoord;\n" "void main()\n" "{\n" " gl_Layer = gs_in[0].layer;\n" - " gl_Position = vec4(gl_in[0].gl_Position.xy + gs_in[0].offset, 0, 1);\n" + " gl_Position = vec4(gl_in[0].gl_Position.xy * lss.scales[gs_in[0].layer] + gs_in[0].offset, 0, 1);\n" " sprite = gs_in[0].sprite;\n" " texcoord = gs_in[0].texcoord;\n" " EmitVertex();\n" " gl_Layer = gs_in[1].layer;\n" - " gl_Position = vec4(gl_in[1].gl_Position.xy + gs_in[1].offset, 0, 1);\n" + " gl_Position = vec4(gl_in[1].gl_Position.xy * lss.scales[gs_in[1].layer] + gs_in[1].offset, 0, 1);\n" " sprite = gs_in[1].sprite;\n" " texcoord = gs_in[1].texcoord;\n" " EmitVertex();\n" " gl_Layer = gs_in[2].layer;\n" - " gl_Position = vec4(gl_in[2].gl_Position.xy + gs_in[2].offset, 0, 1);\n" + " gl_Position = vec4(gl_in[2].gl_Position.xy * lss.scales[gs_in[2].layer] + gs_in[2].offset, 0, 1);\n" " sprite = gs_in[2].sprite;\n" " texcoord = gs_in[2].texcoord;\n" " EmitVertex();\n" @@ -77,10 +81,10 @@ namespace "}"; constexpr char* FRAGMENT_SHADER_SOURCE = - "#version 400 core\n" + "#version 460 core\n" "in vec2 texcoord;\n" "flat in int sprite;\n" - "uniform sampler2DArray " SPRITES_UNIFORM_NAME ";\n" + "layout (binding = 0) uniform sampler2DArray " SPRITES_UNIFORM_NAME ";\n" "out vec4 FragColor;\n" "void main()\n" "{\n" @@ -88,7 +92,7 @@ namespace "}"; constexpr char* SCREEN_VERTEX_SHADER_SOURCE = - "#version 400 core\n" + "#version 460 core\n" "layout (location = 0) in vec2 position;\n" "out vec2 texcoord;\n" "void main()\n" @@ -98,16 +102,20 @@ namespace "}"; constexpr char* SCREEN_FRAGMENT_SHADER_SOURCE = - "#version 400 core\n" + "#version 460 core\n" "in vec2 texcoord;\n" - "uniform sampler2DArray " LAYERS_UNIFORM_NAME ";\n" + "layout (binding = 1) uniform sampler2DArray " LAYERS_UNIFORM_NAME ";\n" + "layout (std430, binding = 1) buffer LayerColors" + "{\n" + " vec4 colors[];\n" + "} lcs;\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " vec3 current_color = vec3(0);\n" " for (int i = 0; i < 256; i++)\n" " {\n" - " vec4 texsample = texture(" LAYERS_UNIFORM_NAME ", vec3(texcoord, i));\n" + " vec4 texsample = lcs.colors[i] * texture(" LAYERS_UNIFORM_NAME ", vec3(texcoord, i));\n" " current_color = mix(current_color, texsample.rgb, texsample.a);\n" " }\n" " FragColor = vec4(current_color, 1);\n" @@ -120,7 +128,9 @@ namespace glerminal m_main(main), m_cells{ }, m_offsets{ }, - m_sprites{ } + m_sprites{ }, + m_layer_colors{ }, + m_layer_scales{ } #ifdef _DEBUG , m_log("log.txt") #endif @@ -141,6 +151,12 @@ namespace glerminal throw std::runtime_error("No main callback provided."); } + for (int i = 0; i < LAYER_COUNT; i++) + { + m_layer_colors[i * 4 + 0] = m_layer_colors[i * 4 + 1] = m_layer_colors[i * 4 + 2] = m_layer_colors[i * 4 + 3] = 1; + m_layer_scales[i] = 1; + } + init_glfw(); init_gl(); @@ -174,14 +190,14 @@ namespace glerminal void glerminal::flush() { - // use dirty flag later glNamedBufferData(m_sprites_instance_vbo, sizeof(m_cells), m_cells, GL_STREAM_DRAW); glNamedBufferData(m_offsets_instance_vbo, sizeof(m_offsets), m_offsets, GL_STREAM_DRAW); update_sprites(); + update_layer_colors(); + update_layer_scales(); glViewport(0, 0, GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE); glUseProgram(m_program); - glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture); glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); glBindVertexArray(m_vao); glClear(GL_COLOR_BUFFER_BIT); @@ -189,7 +205,6 @@ namespace glerminal glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); glUseProgram(m_screen_program); - glBindTexture(GL_TEXTURE_2D_ARRAY, m_framebuffer_backing_texture); glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindVertexArray(m_screen_vao); glClear(GL_COLOR_BUFFER_BIT); @@ -227,6 +242,19 @@ namespace glerminal } } + void glerminal::layer_color(unsigned char layer, unsigned int color) + { + m_layer_colors[layer * 4 + 0] = ((color >> 0) & 0xFF) / 255.0f; + m_layer_colors[layer * 4 + 1] = ((color >> 8) & 0xFF) / 255.0f; + m_layer_colors[layer * 4 + 2] = ((color >> 16) & 0xFF) / 255.0f; + m_layer_colors[layer * 4 + 3] = ((color >> 24) & 0xFF) / 255.0f; + } + + void glerminal::layer_scale(unsigned char layer, float scale) + { + m_layer_scales[layer] = scale; + } + void glerminal::update_sprite(unsigned char id, glerminal_sprite sprite) { // does this work? @@ -342,6 +370,8 @@ namespace glerminal glGenBuffers(1, &m_vbo); glGenBuffers(1, &m_sprites_instance_vbo); glGenBuffers(1, &m_offsets_instance_vbo); + glGenBuffers(1, &m_layer_colors_buffer); + glGenBuffers(1, &m_layer_scales_buffer); // create vertex array object glGenVertexArrays(1, &m_vao); @@ -374,6 +404,16 @@ namespace glerminal glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*VBO_VERTICES), reinterpret_cast(0)); + glBindVertexArray(0); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_layer_scales_buffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_layer_scales), m_layer_scales, GL_DYNAMIC_READ); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_layer_scales_buffer); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_layer_colors_buffer); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(m_layer_colors), m_layer_colors, GL_DYNAMIC_READ); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, m_layer_colors_buffer); + // -- setup shader program -- // compile const unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER); @@ -388,12 +428,16 @@ namespace glerminal glShaderSource(fragment_shader, 1, &FRAGMENT_SHADER_SOURCE, nullptr); glCompileShader(fragment_shader); + constexpr int INFO_LOG_SIZE = 512; int success; + char info_log[INFO_LOG_SIZE]; // verify compile glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); if (!success) { + glGetShaderInfoLog(vertex_shader, INFO_LOG_SIZE, nullptr, info_log); + glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); @@ -404,6 +448,8 @@ namespace glerminal glGetShaderiv(geometry_shader, GL_COMPILE_STATUS, &success); if (!success) { + glGetShaderInfoLog(geometry_shader, INFO_LOG_SIZE, nullptr, info_log); + glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); @@ -414,6 +460,8 @@ namespace glerminal glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); if (!success) { + glGetShaderInfoLog(fragment_shader, INFO_LOG_SIZE, nullptr, info_log); + glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); @@ -446,8 +494,6 @@ namespace glerminal glUseProgram(m_program); glUniform4f(m_screen_size_uniform_location, GRID_WIDTH, GRID_HEIGHT, 1.0f / GRID_WIDTH, 1.0f / GRID_HEIGHT); - glUniform1i(glGetUniformLocation(m_program, SPRITES_UNIFORM_NAME), 0); - // compile const unsigned int screen_vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(screen_vertex_shader, 1, &SCREEN_VERTEX_SHADER_SOURCE, nullptr); @@ -461,6 +507,8 @@ namespace glerminal glGetShaderiv(screen_vertex_shader, GL_COMPILE_STATUS, &success); if (!success) { + glGetShaderInfoLog(screen_vertex_shader, INFO_LOG_SIZE, 0, info_log); + glDeleteShader(screen_vertex_shader); glDeleteShader(screen_fragment_shader); @@ -470,6 +518,8 @@ namespace glerminal glGetShaderiv(screen_fragment_shader, GL_COMPILE_STATUS, &success); if (!success) { + glGetShaderInfoLog(screen_fragment_shader, INFO_LOG_SIZE, 0, info_log); + glDeleteShader(screen_vertex_shader); glDeleteShader(screen_fragment_shader); @@ -510,6 +560,8 @@ namespace glerminal update_sprites(); + glBindTextureUnit(0, m_sprites_texture); + // -- setup framebuffer -- glGenFramebuffers(1, &m_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); @@ -526,9 +578,11 @@ namespace glerminal glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0); - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE, LAYER_COUNT, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE, LAYER_COUNT); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_framebuffer_backing_texture, 0); + glBindTextureUnit(1, m_framebuffer_backing_texture); + // setup uniforms for screen shader glUseProgram(m_screen_program); } @@ -550,13 +604,25 @@ namespace glerminal glDeleteBuffers(1, &m_vbo); glDeleteBuffers(1, &m_sprites_instance_vbo); glDeleteBuffers(1, &m_offsets_instance_vbo); + glDeleteBuffers(1, &m_layer_colors_buffer); + glDeleteBuffers(1, &m_layer_scales_buffer); glDeleteProgram(m_program); } void glerminal::update_sprites() { glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture); - glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, CELL_SIZE, CELL_SIZE, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_sprites); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, CELL_SIZE, CELL_SIZE, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_sprites); + } + + void glerminal::update_layer_colors() + { + glNamedBufferData(m_layer_colors_buffer, sizeof(m_layer_colors), m_layer_colors, GL_DYNAMIC_READ); + } + + void glerminal::update_layer_scales() + { + glNamedBufferData(m_layer_scales_buffer, sizeof(m_layer_scales), m_layer_scales, GL_DYNAMIC_READ); } } @@ -602,6 +668,20 @@ void glerminal_offset(unsigned char x, unsigned char y, unsigned char layer, flo GLERMINAL_G->offset(x, y, layer, x_offset, y_offset); } +void glerminal_layer_color(unsigned char layer, unsigned int color) +{ + if (!GLERMINAL_G) { return; } + + GLERMINAL_G->layer_color(layer, color); +} + +void glerminal_layer_scale(unsigned char layer, float scale) +{ + if (!GLERMINAL_G) { return; } + + GLERMINAL_G->layer_scale(layer, scale); +} + void glerminal_update_sprite(unsigned char id, glerminal_sprite sprite) { if (!GLERMINAL_G) { return; }