#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" namespace { glerminal::glerminal* GLERMINAL_G = nullptr; constexpr float VBO_VERTICES[] = { // first triangle 0, 0, // top right 0, -1, // bottom right -1, 0, // top left // second triangle 0, -1, // bottom right -1, -1, // bottom left -1, 0 // top left }; constexpr char* VERTEX_SHADER_SOURCE = "#version 400 core\n" "layout (location = 0) in vec2 position;\n" "layout (location = 1) in ivec4 sprite[4];\n" "uniform vec4 " SCREEN_SIZE_UNIFORM_NAME ";\n" "out VS_OUT {\n" " flat ivec4 sprite[4];\n" " vec2 texcoord;\n" "} vs_out;\n" "void main()\n" "{\n" " vs_out.sprite = sprite;\n" " 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" " 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" " 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" " 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" " 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" " sprite = gs_in[2].sprite[gl_InvocationID / 16][gl_InvocationID % 16];\n" " texcoord = gs_in[2].texcoord;\n" " EmitVertex();\n" " EndPrimitive();\n" "}"; 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" " 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" "out vec4 FragColor;\n" "void main()\n" "{\n" " vec3 current_color = vec3(0);\n" " for (int i = 0; i < 16; i++)\n" " {\n" " vec4 texsample = 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" "}"; } namespace glerminal { 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 { if (GLERMINAL_G) { throw std::runtime_error("glerminal is already running."); } // unsure if this should be an error if (!init) { throw std::runtime_error("No init callback provided."); } if (!m_main) { throw std::runtime_error("No main callback provided."); } init_glfw(); init_gl(); GLERMINAL_G = this; init(); } glerminal::~glerminal() { deinit_gl(); deinit_glfw(); GLERMINAL_G = nullptr; } void glerminal::run() { float last = glfwGetTime(); while (!glfwWindowShouldClose(m_window)) { glfwPollEvents(); const float current = glfwGetTime(); m_main(current - last); last = current; } } void glerminal::flush() { // use dirty flag later glNamedBufferData(m_instance_vbo, sizeof(m_cells), m_cells, GL_STREAM_DRAW); update_sprites(); update_palette(); glUseProgram(m_program); glBindTexture(GL_TEXTURE_2D_ARRAY, m_sprites_texture); glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); 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); 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) { 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; } } void glerminal::update_sprite(unsigned char id, glerminal_sprite sprite) { // does this work? reinterpret_cast(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; m_palette[4 * id + 2] = ((color >> 8) & 0xFF) / 255.0f; m_palette[4 * id + 3] = ((color >> 0) & 0xFF) / 255.0f; } } void glerminal::init_glfw() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); 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 // non-adjustable size for the same reason as above m_window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "glerminal", nullptr, nullptr); 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 void glerminal::init_gl() { glfwMakeContextCurrent(m_window); glfwSwapInterval(1); if (!gladLoadGLLoader(reinterpret_cast(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(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); // -- setup vertex data -- // create vertex buffer object glGenBuffers(1, &m_vbo); glGenBuffers(1, &m_instance_vbo); // create vertex array object glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); // set up static vertex attributes 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(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); glVertexAttribIPointer(1, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast(0)); glVertexAttribDivisor(1, 1); glEnableVertexAttribArray(2); glVertexAttribIPointer(2, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast(4)); glVertexAttribDivisor(2, 1); glEnableVertexAttribArray(3); glVertexAttribIPointer(3, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast(8)); glVertexAttribDivisor(3, 1); glEnableVertexAttribArray(4); glVertexAttribIPointer(4, 4, GL_UNSIGNED_BYTE, 16 * sizeof(*m_cells), reinterpret_cast(12)); glVertexAttribDivisor(4, 1); // 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(0)); // -- 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); const unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, &FRAGMENT_SHADER_SOURCE, nullptr); glCompileShader(fragment_shader); int success; char info_log[512] = {}; // verify compile glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertex_shader, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); using namespace std::string_literals; throw std::runtime_error("Could not compile vertex shader: "s + info_log); } glGetShaderiv(geometry_shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(geometry_shader, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); using namespace std::string_literals; throw std::runtime_error("Could not compile geometry shader: "s + info_log); } glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragment_shader, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); using namespace std::string_literals; throw std::runtime_error("Could not compile fragment shader: "s + info_log); } // link m_program = glCreateProgram(); glAttachShader(m_program, vertex_shader); glAttachShader(m_program, geometry_shader); glAttachShader(m_program, fragment_shader); glLinkProgram(m_program); glDeleteShader(vertex_shader); glDeleteShader(geometry_shader); glDeleteShader(fragment_shader); // verify link glGetProgramiv(m_program, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(m_program, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteProgram(m_program); using namespace std::string_literals; throw std::runtime_error("Could not link shader program: "s + info_log); } // 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) { glGetShaderInfoLog(screen_vertex_shader, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteShader(screen_vertex_shader); glDeleteShader(screen_fragment_shader); using namespace std::string_literals; throw std::runtime_error("Could not compile screen vertex shader: "s + info_log); } glGetShaderiv(screen_fragment_shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(screen_fragment_shader, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteShader(screen_vertex_shader); glDeleteShader(screen_fragment_shader); using namespace std::string_literals; throw std::runtime_error("Could not compile screen fragment shader: "s + info_log); } // 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) { glGetProgramInfoLog(m_screen_program, sizeof(info_log) / sizeof(*info_log), nullptr, info_log); glDeleteProgram(m_screen_program); using namespace std::string_literals; throw std::runtime_error("Could not link screen shader program: "s + info_log); } // 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); } 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); 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); } } void glerminal_run(glerminal_init_cb init, glerminal_main_cb main) { try { glerminal::glerminal(init, main).run(); } 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); } 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); }