glerminal/source/glerminal.cpp

612 lines
17 KiB
C++
Raw Normal View History

2024-05-09 19:42:12 +00:00
#include "glerminal-private.h"
#define SCREEN_SIZE_UNIFORM_NAME "screen_size"
#define PALETTE_UNIFORM_NAME "palette"
#define SPRITES_UNIFORM_NAME "sprites"
#define LAYERS_UNIFORM_NAME "layers"
2024-05-09 19:42:12 +00:00
namespace
{
glerminal::glerminal* GLERMINAL_G = nullptr;
constexpr float VBO_VERTICES[] =
{
// first triangle
0, 0, // top right
0, -1, // bottom right
-1, 0, // top left
2024-05-09 19:42:12 +00:00
// second triangle
0, -1, // bottom right
-1, -1, // bottom left
-1, 0 // top left
2024-05-09 19:42:12 +00:00
};
constexpr char* VERTEX_SHADER_SOURCE =
"#version 400 core\n"
"layout (location = 0) in vec2 position;\n"
2024-05-14 22:16:06 +00:00
"layout (location = 1) in ivec4 sprite[4];\n"
"uniform vec4 " SCREEN_SIZE_UNIFORM_NAME ";\n"
"out VS_OUT {\n"
2024-05-14 22:16:06 +00:00
" flat ivec4 sprite[4];\n"
" vec2 texcoord;\n"
"} vs_out;\n"
2024-05-09 19:42:12 +00:00
"void main()\n"
"{\n"
" vs_out.sprite = sprite;\n"
2024-05-14 22:16:06 +00:00
" vs_out.texcoord = vec2(position.x + 1, -position.y);\n"
" vec2 cell_position = vec2(1 + gl_InstanceID - " SCREEN_SIZE_UNIFORM_NAME ".x * floor(gl_InstanceID * " SCREEN_SIZE_UNIFORM_NAME ".z), -floor((gl_InstanceID) * " SCREEN_SIZE_UNIFORM_NAME ".z));\n"
2024-05-14 22:16:06 +00:00
" vec2 temp = vec2((position + cell_position) * " SCREEN_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1));\n"
" gl_Position = vec4(temp.x, -temp.y, 0, 1);\n"
"}";
constexpr char* GEOMETRY_SHADER_SOURCE =
"#version 400 core\n"
"layout (triangles) in;\n"
"layout (triangle_strip, max_vertices = 48) out;\n"
"layout (invocations = 16) in;\n"
"in VS_OUT {\n"
2024-05-14 22:16:06 +00:00
" flat ivec4 sprite[4];\n"
" vec2 texcoord;\n"
"} gs_in[];\n"
"flat out int sprite;\n"
"out vec2 texcoord;\n"
"void main()\n"
"{\n"
" gl_Layer = gl_InvocationID;\n"
" gl_Position = gl_in[0].gl_Position;\n"
2024-05-14 22:16:06 +00:00
" sprite = gs_in[0].sprite[gl_InvocationID / 16][gl_InvocationID % 16];\n"
" texcoord = gs_in[0].texcoord;\n"
" EmitVertex();\n"
" gl_Layer = gl_InvocationID;\n"
" gl_Position = gl_in[1].gl_Position;\n"
2024-05-14 22:16:06 +00:00
" sprite = gs_in[1].sprite[gl_InvocationID / 16][gl_InvocationID % 16];\n"
" texcoord = gs_in[1].texcoord;\n"
" EmitVertex();\n"
" gl_Layer = gl_InvocationID;\n"
" gl_Position = gl_in[2].gl_Position;\n"
2024-05-14 22:16:06 +00:00
" sprite = gs_in[2].sprite[gl_InvocationID / 16][gl_InvocationID % 16];\n"
" texcoord = gs_in[2].texcoord;\n"
" EmitVertex();\n"
" EndPrimitive();\n"
2024-05-09 19:42:12 +00:00
"}";
constexpr char* FRAGMENT_SHADER_SOURCE =
"#version 400 core\n"
"in vec2 texcoord;\n"
"flat in int sprite;\n"
"uniform usampler2DArray " SPRITES_UNIFORM_NAME ";\n"
"uniform vec4 " PALETTE_UNIFORM_NAME "[16];\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = " PALETTE_UNIFORM_NAME "[int(texture(" SPRITES_UNIFORM_NAME ", vec3(texcoord, sprite)))];\n"
"}";
constexpr char* SCREEN_VERTEX_SHADER_SOURCE =
"#version 400 core\n"
"layout (location = 0) in vec2 position;\n"
"out vec2 texcoord;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(position * 2 + 1, 0, 1);\n"
2024-05-14 21:46:30 +00:00
" texcoord = vec2(position.x + 1, -position.y);\n"
"}";
constexpr char* SCREEN_FRAGMENT_SHADER_SOURCE =
"#version 400 core\n"
"in vec2 texcoord;\n"
"uniform sampler2DArray " LAYERS_UNIFORM_NAME ";\n"
2024-05-09 19:42:12 +00:00
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" vec3 current_color = vec3(0);\n"
2024-05-14 22:16:06 +00:00
" for (int i = 0; i < 16; i++)\n"
" {\n"
" vec4 texsample = texture(" LAYERS_UNIFORM_NAME ", vec3(texcoord, i));\n"
2024-05-14 22:16:06 +00:00
" current_color = mix(current_color, texsample.rgb, texsample.a);\n"
" }\n"
" FragColor = vec4(current_color, 1);\n"
2024-05-09 19:42:12 +00:00
"}";
}
namespace glerminal
{
2024-05-14 21:46:30 +00:00
glerminal::glerminal(glerminal_init_cb init, glerminal_main_cb main) :
m_main(main),
m_cells{ },
m_sprites{ },
m_palette{ }
#ifdef _DEBUG
, m_log("log.txt")
#endif
2024-05-09 19:42:12 +00:00
{
if (GLERMINAL_G)
{
throw std::runtime_error("glerminal is already running.");
}
// unsure if this should be an error
2024-05-14 21:46:30 +00:00
if (!init)
{
throw std::runtime_error("No init callback provided.");
}
2024-05-09 19:42:12 +00:00
if (!m_main)
{
throw std::runtime_error("No main callback provided.");
}
init_glfw();
init_gl();
GLERMINAL_G = this;
2024-05-14 21:46:30 +00:00
init();
2024-05-09 19:42:12 +00:00
}
glerminal::~glerminal()
{
deinit_gl();
deinit_glfw();
GLERMINAL_G = nullptr;
}
void glerminal::run()
{
2024-05-14 21:46:30 +00:00
float last = glfwGetTime();
2024-05-09 19:42:12 +00:00
while (!glfwWindowShouldClose(m_window))
{
glfwPollEvents();
2024-05-14 21:46:30 +00:00
const float current = glfwGetTime();
m_main(current - last);
last = current;
2024-05-09 19:42:12 +00:00
}
}
void glerminal::flush()
{
2024-05-14 21:46:30 +00:00
// use dirty flag later
glNamedBufferData(m_instance_vbo, sizeof(m_cells), m_cells, GL_STREAM_DRAW);
2024-05-14 21:46:30 +00:00
update_sprites();
update_palette();
2024-05-09 19:42:12 +00:00
glUseProgram(m_program);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
2024-05-09 19:42:12 +00:00
glBindVertexArray(m_vao);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_AREA);
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);
2024-05-09 19:42:12 +00:00
glDrawArrays(GL_TRIANGLES, 0, 6);
glfwSwapBuffers(m_window);
}
void glerminal::set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite)
{
if (x < GRID_WIDTH && y < GRID_HEIGHT && layer < LAYER_COUNT)
{
2024-05-14 22:16:06 +00:00
m_cells[layer + x * LAYER_COUNT + y * LAYER_COUNT * GRID_WIDTH] = sprite;
}
}
unsigned char glerminal::get(unsigned char x, unsigned char y, unsigned char layer) const
{
if (x < GRID_WIDTH && y < GRID_HEIGHT && layer < LAYER_COUNT)
{
return m_cells[x + y * GRID_WIDTH + layer * GRID_AREA];
}
else
{
return 0;
}
}
2024-05-14 21:46:30 +00:00
void glerminal::update_sprite(unsigned char id, glerminal_sprite sprite)
{
// does this work?
reinterpret_cast<glerminal_sprite*>(m_sprites)[id] = sprite;
}
void glerminal::update_palette_color(unsigned char id, unsigned int color)
{
if (id < 16)
{
m_palette[4 * id + 0] = ((color >> 24) & 0xFF) / 255.0f;
m_palette[4 * id + 1] = ((color >> 16) & 0xFF) / 255.0f;
2024-05-14 22:16:06 +00:00
m_palette[4 * id + 2] = ((color >> 8) & 0xFF) / 255.0f;
m_palette[4 * id + 3] = ((color >> 0) & 0xFF) / 255.0f;
2024-05-14 21:46:30 +00:00
}
}
2024-05-09 19:42:12 +00:00
void glerminal::init_glfw()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
2024-05-09 19:42:12 +00:00
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// not resizable for now.
//
// need to think about how to handle resizing to ensure
// that the window stays an integer number of "tiles" large
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
#ifdef _DEBUG
glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE);
#endif
2024-05-09 19:42:12 +00:00
// non-adjustable size for the same reason as above
m_window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "glerminal", nullptr, nullptr);
2024-05-09 19:42:12 +00:00
if (!m_window)
{
throw std::runtime_error("Failed to create glerminal window.");
}
}
#ifdef _DEBUG
void glerminal::log(GLenum type, GLuint id, GLenum severity, const char* message) const
{
glDebugMessageInsert(GL_DEBUG_SOURCE_THIRD_PARTY, type, id, severity, -1, message);
}
#endif
2024-05-09 19:42:12 +00:00
void glerminal::init_gl()
{
glfwMakeContextCurrent(m_window);
glfwSwapInterval(1);
if (!gladLoadGLLoader(reinterpret_cast<GLADloadproc>(glfwGetProcAddress)))
{
throw std::runtime_error("Failed to initialize GLAD.");
}
#ifdef _DEBUG
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback([](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam)
{
const char* source_str = "[UNKNOWN]";
switch (source)
{
case GL_DEBUG_SOURCE_API:
source_str = "[API]";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
source_str = "[WINDOW]";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
source_str = "[SHADER]";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
source_str = "[GLERMINAL]";
break;
case GL_DEBUG_SOURCE_APPLICATION:
source_str = "[APPLICATION]";
break;
}
const char* type_str = "[UNKNOWN]";
switch (type)
{
case GL_DEBUG_TYPE_ERROR:
type_str = "[ERROR]";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
type_str = "[DEPRECATED]";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
type_str = "[UNDEFINED]";
break;
case GL_DEBUG_TYPE_PORTABILITY:
type_str = "[PORTABILITY]";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
type_str = "[PERFORMANCE]";
break;
}
static_cast<const glerminal*>(userParam)->m_log << source_str << type_str << ' ' << std::string(message, length) << std::endl;
}, this);
#endif
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
glDisable(GL_DEPTH_TEST);
2024-05-09 19:42:12 +00:00
// -- setup vertex data --
// create vertex buffer object
glGenBuffers(1, &m_vbo);
glGenBuffers(1, &m_instance_vbo);
2024-05-09 19:42:12 +00:00
// create vertex array object
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
// set up static vertex attributes
2024-05-09 19:42:12 +00:00
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(VBO_VERTICES), VBO_VERTICES, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*VBO_VERTICES), reinterpret_cast<void*>(0));
// set up instanced vertex attributes
glBindBuffer(GL_ARRAY_BUFFER, m_instance_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(m_cells), m_cells, GL_STREAM_DRAW);
glEnableVertexAttribArray(1);
2024-05-14 22:16:06 +00:00
glVertexAttribIPointer(1, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast<void*>(0));
glVertexAttribDivisor(1, 1);
2024-05-14 22:16:06 +00:00
glEnableVertexAttribArray(2);
glVertexAttribIPointer(2, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast<void*>(4));
glVertexAttribDivisor(2, 1);
glEnableVertexAttribArray(3);
glVertexAttribIPointer(3, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast<void*>(8));
glVertexAttribDivisor(3, 1);
glEnableVertexAttribArray(4);
glVertexAttribIPointer(4, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast<void*>(12));
glVertexAttribDivisor(4, 1);
2024-05-09 19:42:12 +00:00
// set up static vertex attributes
glGenVertexArrays(1, &m_screen_vao);
glBindVertexArray(m_screen_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*VBO_VERTICES), reinterpret_cast<void*>(0));
2024-05-09 19:42:12 +00:00
// -- setup shader program --
// compile
const unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &VERTEX_SHADER_SOURCE, nullptr);
glCompileShader(vertex_shader);
const unsigned int geometry_shader = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry_shader, 1, &GEOMETRY_SHADER_SOURCE, nullptr);
glCompileShader(geometry_shader);
2024-05-09 19:42:12 +00:00
const unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &FRAGMENT_SHADER_SOURCE, nullptr);
glCompileShader(fragment_shader);
int success;
// verify compile
2024-05-09 19:42:12 +00:00
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glDeleteShader(vertex_shader);
glDeleteShader(geometry_shader);
2024-05-09 19:42:12 +00:00
glDeleteShader(fragment_shader);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not compile vertex shader.");
2024-05-09 19:42:12 +00:00
}
glGetShaderiv(geometry_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glDeleteShader(vertex_shader);
glDeleteShader(geometry_shader);
glDeleteShader(fragment_shader);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not compile geometry shader.");
}
2024-05-09 19:42:12 +00:00
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glDeleteShader(vertex_shader);
glDeleteShader(geometry_shader);
2024-05-09 19:42:12 +00:00
glDeleteShader(fragment_shader);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not compile fragment shader.");
2024-05-09 19:42:12 +00:00
}
// link
m_program = glCreateProgram();
glAttachShader(m_program, vertex_shader);
glAttachShader(m_program, geometry_shader);
2024-05-09 19:42:12 +00:00
glAttachShader(m_program, fragment_shader);
glLinkProgram(m_program);
glDeleteShader(vertex_shader);
glDeleteShader(geometry_shader);
2024-05-09 19:42:12 +00:00
glDeleteShader(fragment_shader);
// verify link
2024-05-09 19:42:12 +00:00
glGetProgramiv(m_program, GL_LINK_STATUS, &success);
if (!success)
{
glDeleteProgram(m_program);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not link shader program.");
2024-05-09 19:42:12 +00:00
}
// setup uniforms
m_screen_size_uniform_location = glGetUniformLocation(m_program, SCREEN_SIZE_UNIFORM_NAME);
m_palette_uniform_location = glGetUniformLocation(m_program, PALETTE_UNIFORM_NAME);
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);
update_palette();
// compile
const unsigned int screen_vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(screen_vertex_shader, 1, &SCREEN_VERTEX_SHADER_SOURCE, nullptr);
glCompileShader(screen_vertex_shader);
const unsigned int screen_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(screen_fragment_shader, 1, &SCREEN_FRAGMENT_SHADER_SOURCE, nullptr);
glCompileShader(screen_fragment_shader);
// verify compile
glGetShaderiv(screen_vertex_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glDeleteShader(screen_vertex_shader);
glDeleteShader(screen_fragment_shader);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not compile screen vertex shader.");
}
glGetShaderiv(screen_fragment_shader, GL_COMPILE_STATUS, &success);
if (!success)
{
glDeleteShader(screen_vertex_shader);
glDeleteShader(screen_fragment_shader);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not compile screen fragment shader.");
}
// link
m_screen_program = glCreateProgram();
glAttachShader(m_screen_program, screen_vertex_shader);
glAttachShader(m_screen_program, screen_fragment_shader);
glLinkProgram(m_screen_program);
glDeleteShader(screen_vertex_shader);
glDeleteShader(screen_fragment_shader);
// verify link
glGetProgramiv(m_screen_program, GL_LINK_STATUS, &success);
if (!success)
{
glDeleteProgram(m_screen_program);
2024-05-15 01:07:19 +00:00
throw std::runtime_error("Could not link screen shader program.");
}
// setup uniforms later
// -- setup textures --
glGenTextures(1, &m_sprites_texture);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
update_sprites();
// -- setup framebuffer --
glGenFramebuffers(1, &m_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glGenTextures(1, &m_framebuffer_backing_texture);
glBindTexture(GL_TEXTURE_2D_ARRAY, m_framebuffer_backing_texture);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
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, SCREEN_WIDTH, SCREEN_HEIGHT, LAYER_COUNT, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_framebuffer_backing_texture, 0);
// setup uniforms for screen shader
glUseProgram(m_screen_program);
2024-05-09 19:42:12 +00:00
}
void glerminal::deinit_glfw()
{
glfwDestroyWindow(m_window);
glfwTerminate();
}
void glerminal::deinit_gl()
{
glDeleteFramebuffers(1, &m_framebuffer);
glDeleteTextures(1, &m_framebuffer_backing_texture);
glDeleteTextures(1, &m_sprites_texture);
glDeleteVertexArrays(1, &m_vao);
glDeleteVertexArrays(1, &m_screen_vao);
glDeleteBuffers(1, &m_vbo);
glDeleteBuffers(1, &m_instance_vbo);
2024-05-09 19:42:12 +00:00
glDeleteProgram(m_program);
}
void glerminal::update_sprites()
{
glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8UI, CELL_SIZE, CELL_SIZE, 256, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE, m_sprites);
}
void glerminal::update_palette()
{
glUseProgram(m_program);
glUniform4fv(m_palette_uniform_location, 16, m_palette);
}
2024-05-09 19:42:12 +00:00
}
2024-05-14 21:46:30 +00:00
void glerminal_run(glerminal_init_cb init, glerminal_main_cb main)
2024-05-09 19:42:12 +00:00
{
try
{
2024-05-14 21:46:30 +00:00
glerminal::glerminal(init, main).run();
2024-05-09 19:42:12 +00:00
}
catch (const std::runtime_error& e)
{
}
}
void glerminal_flush()
{
if (!GLERMINAL_G) { return; }
GLERMINAL_G->flush();
}
void glerminal_set(unsigned char x, unsigned char y, unsigned char layer, unsigned char sprite)
{
if (!GLERMINAL_G) { return; }
GLERMINAL_G->set(x, y, layer, sprite);
}
unsigned char glerminal_get(unsigned char x, unsigned char y, unsigned char layer)
{
if (!GLERMINAL_G) { return 0; }
return GLERMINAL_G->get(x, y, layer);
2024-05-14 21:46:30 +00:00
}
void glerminal_update_sprite(unsigned char id, glerminal_sprite sprite)
{
if (!GLERMINAL_G) { return; }
GLERMINAL_G->update_sprite(id, sprite);
}
void glerminal_update_palette(unsigned char id, unsigned int color)
{
if (!GLERMINAL_G) { return; }
GLERMINAL_G->update_palette_color(id, color);
2024-05-09 19:42:12 +00:00
}