#define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_PNG #define STBI_MAX_DIMENSIONS 512 #include "glerminal-private.h" #define GRID_SIZE_UNIFORM_NAME "grid_size" #define SPRITES_UNIFORM_NAME "sprites" #define LAYERS_UNIFORM_NAME "layers" #define LAYER_COUNT_UNIFORM_NAME "layer_count" #define ATLAS_WIDTH_UNIFORM_NAME "atlas_width" 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 450 core\n" "layout (location = 0) in vec2 position;\n" "layout (location = 1) in vec2 offset;\n" "layout (location = 2) in int sprite;\n" "layout (location = 3) in vec4 color;\n" "layout (location = 4) in float scale;\n" "uniform vec4 " GRID_SIZE_UNIFORM_NAME ";\n" "uniform int " ATLAS_WIDTH_UNIFORM_NAME ";\n" "out VS_OUT {\n" " flat int sprite;\n" " flat int layer;\n" " flat vec4 layer_color;\n" " vec2 texcoord;\n" "} vs_out;\n" "void main()\n" "{\n" " const int layer = int(floor(gl_InstanceID / " GRID_SIZE_UNIFORM_NAME ".y));\n" " vec2 scaled_offset = 2 * offset * " GRID_SIZE_UNIFORM_NAME ".zw;\n" " vs_out.sprite = sprite;\n" " vs_out.layer = layer;\n" " vs_out.layer_color = color;\n" " vs_out.texcoord = vec2(sprite % " ATLAS_WIDTH_UNIFORM_NAME " + position.x + 1, (sprite / " ATLAS_WIDTH_UNIFORM_NAME ") - position.y) / vec2(" ATLAS_WIDTH_UNIFORM_NAME ") + vec2(-(2 * position.x + 1) * " GRID_SIZE_UNIFORM_NAME ".z / 16, (2 * position.y + 1) * " GRID_SIZE_UNIFORM_NAME ".z / 16);\n" " vec2 cell_position = vec2(scale + (gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) - " GRID_SIZE_UNIFORM_NAME ".x * floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x));\n" " vec2 temp = ((position + vec2(-0.5, 0.5)) * scale + cell_position + vec2(-0.5, 0.5)) * " GRID_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1);\n" " gl_Position = vec4(scaled_offset.x + temp.x, scaled_offset.y - temp.y, 0, 1);\n" "}"; // note: AMD_vertex_shader_layer support required constexpr char VERTEX_SHADER_ARB_SOURCE[] = "#version 450 core\n" "#extension GL_ARB_shader_viewport_layer_array : enable\n" "#extension GL_AMD_vertex_shader_layer : enable\n" "layout (location = 0) in vec2 position;\n" "layout (location = 1) in vec2 offset;\n" "layout (location = 2) in int sprite;\n" "layout (location = 3) in vec4 color;\n" "layout (location = 4) in float scale;\n" "uniform vec4 " GRID_SIZE_UNIFORM_NAME ";\n" "uniform int " ATLAS_WIDTH_UNIFORM_NAME ";\n" "out VS_OUT {\n" " flat int sprite;\n" " flat vec4 layer_color;\n" " vec2 texcoord;\n" "} vs_out;\n" "void main()\n" "{\n" " const int layer = int(floor(gl_InstanceID / " GRID_SIZE_UNIFORM_NAME ".y));\n" " gl_Layer = layer;\n" " vec2 scaled_offset = 2 * offset * " GRID_SIZE_UNIFORM_NAME ".zw;\n" " vs_out.sprite = sprite;\n" " vs_out.texcoord = vec2(sprite % " ATLAS_WIDTH_UNIFORM_NAME " + position.x + 1, (sprite / " ATLAS_WIDTH_UNIFORM_NAME ") - position.y) / vec2(" ATLAS_WIDTH_UNIFORM_NAME ") + vec2(-(2 * position.x + 1) * " GRID_SIZE_UNIFORM_NAME ".z / 16, (2 * position.y + 1) * " GRID_SIZE_UNIFORM_NAME ".z / 16);\n" " vs_out.layer_color = color;\n" " vec2 cell_position = vec2(scale + (gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) - " GRID_SIZE_UNIFORM_NAME ".x * floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) / " GRID_SIZE_UNIFORM_NAME ".x));\n" " vec2 temp = ((position + vec2(-0.5, 0.5)) * scale + cell_position + vec2(-0.5, 0.5)) * " GRID_SIZE_UNIFORM_NAME ".zw * 2 + vec2(-1, 1);\n" " gl_Position = vec4(scaled_offset.x + temp.x, scaled_offset.y - temp.y, 0, 1);\n" "}"; constexpr const char* VERTEX_SHADER_SOURCE_PTR = VERTEX_SHADER_SOURCE; constexpr const char* VERTEX_SHADER_ARB_SOURCE_PTR = VERTEX_SHADER_ARB_SOURCE; constexpr char GEOMETRY_SHADER_SOURCE[] = "#version 450 core\n" "layout (triangles) in;\n" "layout (triangle_strip, max_vertices = 3) out;\n" "in VS_OUT {\n" " flat int sprite;\n" " flat int layer;\n" " flat vec4 layer_color;\n" " vec2 texcoord;\n" "} gs_in[];\n" "flat out int sprite;\n" "flat out vec4 layer_color;\n" "out vec2 texcoord;\n" "void main()\n" "{\n" " gl_Layer = gs_in[0].layer;\n" " gl_Position = gl_in[0].gl_Position;\n" " sprite = gs_in[0].sprite;\n" " layer_color = gs_in[0].layer_color;\n" " texcoord = gs_in[0].texcoord;\n" " EmitVertex();\n" " gl_Layer = gs_in[1].layer;\n" " gl_Position = gl_in[1].gl_Position;\n" " sprite = gs_in[1].sprite;\n" " layer_color = gs_in[1].layer_color;\n" " texcoord = gs_in[1].texcoord;\n" " EmitVertex();\n" " gl_Layer = gs_in[2].layer;\n" " gl_Position = gl_in[2].gl_Position;\n" " sprite = gs_in[2].sprite;\n" " layer_color = gs_in[2].layer_color;\n" " texcoord = gs_in[2].texcoord;\n" " EmitVertex();\n" " EndPrimitive();\n" "}"; constexpr const char* GEOMETRY_SHADER_SOURCE_PTR = GEOMETRY_SHADER_SOURCE; constexpr char FRAGMENT_SHADER_SOURCE[] = "#version 450 core\n" "flat in int sprite;\n" "flat vec4 layer_color;\n" "in vec2 texcoord;\n" "layout (binding = 2) uniform sampler2D " SPRITES_UNIFORM_NAME ";\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " FragColor = layer_color * texture(" SPRITES_UNIFORM_NAME ", texcoord);\n" "}"; // note: AMD_vertex_shader_layer support required constexpr char FRAGMENT_SHADER_ARB_SOURCE[] = "#version 450 core\n" "in VS_OUT {\n" " flat int sprite;\n" " flat vec4 layer_color;\n" " vec2 texcoord;\n" "} fs_in;\n" "layout (binding = 2) uniform sampler2D " SPRITES_UNIFORM_NAME ";\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " FragColor = fs_in.layer_color * texture(" SPRITES_UNIFORM_NAME ", fs_in.texcoord);\n" "}"; constexpr const char* FRAGMENT_SHADER_SOURCE_PTR = FRAGMENT_SHADER_SOURCE; constexpr const char* FRAGMENT_SHADER_ARB_SOURCE_PTR = FRAGMENT_SHADER_ARB_SOURCE; constexpr char SCREEN_VERTEX_SHADER_SOURCE[] = "#version 450 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 const char* SCREEN_VERTEX_SHADER_SOURCE_PTR = SCREEN_VERTEX_SHADER_SOURCE; constexpr char SCREEN_FRAGMENT_SHADER_SOURCE[] = "#version 450 core\n" "in vec2 texcoord;\n" "layout (binding = 1) uniform sampler2DArray " LAYERS_UNIFORM_NAME ";\n" "uniform int " LAYER_COUNT_UNIFORM_NAME ";\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " vec3 current_color = vec3(0);\n" " for (int i = 0; i < " LAYER_COUNT_UNIFORM_NAME "; 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" "}"; constexpr const char* SCREEN_FRAGMENT_SHADER_SOURCE_PTR = SCREEN_FRAGMENT_SHADER_SOURCE; } namespace glerminal { glerminal::glerminal(glerminal_init_params params) : m_main(params.main), m_keypressed(params.keypress), m_keyreleased(params.keyrelease), m_mousemoved(params.moved), m_mousepressed(params.mousepress), m_mousereleased(params.mouserelease), m_cells{ }, m_offsets{ }, m_sprites{ }, m_colors{ }, m_scales{ } #ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT , m_log("log.txt") #endif { if (GLERMINAL_G) { throw std::runtime_error("glerminal is already running."); } // unsure if this should be an error if (!params.init) { throw std::runtime_error("No init callback provided."); } if (!m_main) { throw std::runtime_error("No main callback provided."); } for (int i = 0; i < GRID_AREA_2 * LAYER_COUNT; i++) { m_colors[i * 4 + 0] = m_colors[i * 4 + 1] = m_colors[i * 4 + 2] = m_colors[i * 4 + 3] = 255; m_scales[i] = 1; } init_glfw(); init_gl(); GLERMINAL_G = this; params.init(); } glerminal::~glerminal() { deinit_gl(); deinit_glfw(); GLERMINAL_G = nullptr; } void glerminal::run() { double last = glfwGetTime(); while (!glfwWindowShouldClose(m_window)) { glfwPollEvents(); const double current = glfwGetTime(); m_main(current - last); last = current; } } void glerminal::quit() { glfwSetWindowShouldClose(m_window, GLFW_TRUE); } void glerminal::flush() { glNamedBufferSubData(m_sprites_instance_vbo, 0, sizeof(m_cells), m_cells); glNamedBufferSubData(m_offsets_instance_vbo, 0, sizeof(m_offsets), m_offsets); update_sprites(); update_colors(); update_scales(); glUseProgram(m_program); glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); glBindVertexArray(m_vao); glClear(GL_COLOR_BUFFER_BIT); glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_AREA * LAYER_COUNT); glUseProgram(m_screen_program); glBindFramebuffer(GL_FRAMEBUFFER, m_screen_framebuffer); glBindVertexArray(m_screen_vao); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 6); glBlitNamedFramebuffer(m_screen_framebuffer, 0, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST); glfwSwapInterval(0); glfwSwapBuffers(m_window); glBlitNamedFramebuffer(m_screen_framebuffer, 0, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST); glfwSwapInterval(1); glfwSwapBuffers(m_window); } void glerminal::set(unsigned char x, unsigned char y, unsigned char layer, unsigned short sprite) { if (x < GRID_WIDTH + 2 && y < GRID_HEIGHT + 2 && layer < LAYER_COUNT) { m_cells[x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2] = sprite; } } unsigned short glerminal::get(unsigned char x, unsigned char y, unsigned char layer) const { if (x < GRID_WIDTH + 2 && y < GRID_HEIGHT + 2 && layer < LAYER_COUNT) { return m_cells[x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2]; } else { return 0; } } void glerminal::offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset) { if (x < GRID_WIDTH + 2 && y < GRID_HEIGHT + 2 && layer < LAYER_COUNT) { m_offsets[2 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 0] = x_offset; m_offsets[2 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 1] = y_offset; } } void glerminal::color(unsigned char x, unsigned char y, unsigned char layer, unsigned int color) { if (x < GRID_WIDTH + 2 && y < GRID_HEIGHT + 2 && layer < LAYER_COUNT) { m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 0] = (color >> 0) & 0xFF; m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 1] = (color >> 8) & 0xFF; m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 2] = (color >> 16) & 0xFF; m_colors[4 * (x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2) + 3] = (color >> 24) & 0xFF; } } void glerminal::scale(unsigned char x, unsigned char y, unsigned char layer, float scale) { m_scales[x + y * (GRID_WIDTH + 2) + layer * GRID_AREA_2] = scale; } void glerminal::load_atlas(unsigned char w, unsigned char h, const unsigned int* data) { for (int src_atlas_row = 0; src_atlas_row < h; src_atlas_row++) { for (int src_atlas_col = 0; src_atlas_col < w; src_atlas_col++) { for (int sprite_row = 0; sprite_row < CELL_SIZE; sprite_row++) { const unsigned int sprite_index = src_atlas_col + src_atlas_row * w; const unsigned int dst_atlas_row = sprite_index / MAX_SPRITES_ROW; const unsigned int dst_atlas_col = sprite_index % MAX_SPRITES_ROW; // offset from base address in source atlas layout const unsigned int src_offset = CELL_SIZE * (src_atlas_col + sprite_row * w + src_atlas_row * w * CELL_SIZE); const unsigned int dst_column = dst_atlas_col * (CELL_SIZE + 2) + 1; const unsigned int dst_row = MAX_SPRITES_ROW * (CELL_SIZE + 2) * ((sprite_row + 1) + dst_atlas_row * (CELL_SIZE + 2)); // offset from base address in glerminal atlas layout const unsigned int dst_offset = dst_column + dst_row; memcpy(m_sprites + dst_offset, data + src_offset, CELL_SIZE * sizeof(unsigned int)); } } } } void glerminal::init_glfw() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); 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 GLERMINAL_OPENGL_DEBUG_CONTEXT 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."); } glfwSetWindowUserPointer(m_window, this); glfwSetKeyCallback(m_window, glfw_key_handler); glfwSetCursorPosCallback(m_window, glfw_mousemoved_handler); glfwSetMouseButtonCallback(m_window, glfw_mousepress_handler); } void glerminal::log(GLenum type, GLuint id, GLenum severity, const char* message) const { #ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT glDebugMessageInsert(GL_DEBUG_SOURCE_THIRD_PARTY, type, id, severity, -1, message); #endif } void glerminal::init_gl() { glfwMakeContextCurrent(m_window); if (!gladLoadGLLoader(reinterpret_cast(glfwGetProcAddress))) { throw std::runtime_error("Failed to initialize GLAD."); } #ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT 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 glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // -- setup vertex data -- // create vertex buffer object glGenBuffers(1, &m_vbo); glGenBuffers(1, &m_sprites_instance_vbo); glGenBuffers(1, &m_offsets_instance_vbo); glGenBuffers(1, &m_colors_instance_vbo); glGenBuffers(1, &m_scales_instance_vbo); // create vertex array object glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); // set up static vertex attributes glBindBuffer(GL_ARRAY_BUFFER, m_vbo); glBufferStorage(GL_ARRAY_BUFFER, sizeof(VBO_VERTICES), VBO_VERTICES, GL_NONE); 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_offsets_instance_vbo); glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_offsets), m_offsets, GL_DYNAMIC_STORAGE_BIT); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*m_offsets), reinterpret_cast(0)); glVertexAttribDivisor(1, 1); glBindBuffer(GL_ARRAY_BUFFER, m_sprites_instance_vbo); glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_cells), m_cells, GL_DYNAMIC_STORAGE_BIT); glEnableVertexAttribArray(2); glVertexAttribIPointer(2, 1, GL_UNSIGNED_SHORT, 1 * sizeof(*m_cells), reinterpret_cast(0)); glVertexAttribDivisor(2, 1); glBindBuffer(GL_ARRAY_BUFFER, m_colors_instance_vbo); glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_colors), m_colors, GL_DYNAMIC_STORAGE_BIT); glEnableVertexAttribArray(3); glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 * sizeof(*m_colors), reinterpret_cast(0)); glVertexAttribDivisor(3, 1); glBindBuffer(GL_ARRAY_BUFFER, m_scales_instance_vbo); glBufferStorage(GL_ARRAY_BUFFER, sizeof(m_scales), m_scales, GL_DYNAMIC_STORAGE_BIT); glEnableVertexAttribArray(4); glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, 1 * sizeof(*m_scales), reinterpret_cast(0)); 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)); glBindVertexArray(0); // -- setup shader program -- // test for features const bool vertex_shader_layer_supported = glfwExtensionSupported("GL_ARB_shader_viewport_layer_array") || glfwExtensionSupported("GL_AMD_vertex_shader_layer"); // compile const unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_shader, 1, &(vertex_shader_layer_supported ? VERTEX_SHADER_ARB_SOURCE_PTR : VERTEX_SHADER_SOURCE_PTR), nullptr); glCompileShader(vertex_shader); const unsigned int geometry_shader = glCreateShader(GL_GEOMETRY_SHADER); glShaderSource(geometry_shader, 1, &GEOMETRY_SHADER_SOURCE_PTR, nullptr); glCompileShader(geometry_shader); const unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, &(vertex_shader_layer_supported ? FRAGMENT_SHADER_ARB_SOURCE_PTR : FRAGMENT_SHADER_SOURCE_PTR), nullptr); glCompileShader(fragment_shader); constexpr int INFO_LOG_SIZE = 512; int success; char info_log[INFO_LOG_SIZE + 1] = {}; // 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); log(GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, "Could not compile vertex shader."); log(GL_DEBUG_TYPE_ERROR, 0, GL_DEBUG_SEVERITY_HIGH, info_log); throw std::runtime_error("Could not compile vertex shader."); } 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); log(GL_DEBUG_TYPE_ERROR, 1, GL_DEBUG_SEVERITY_HIGH, "Could not compile geometry shader."); log(GL_DEBUG_TYPE_ERROR, 1, GL_DEBUG_SEVERITY_HIGH, info_log); throw std::runtime_error("Could not compile geometry shader."); } 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); log(GL_DEBUG_TYPE_ERROR, 2, GL_DEBUG_SEVERITY_HIGH, "Could not compile fragment shader."); log(GL_DEBUG_TYPE_ERROR, 2, GL_DEBUG_SEVERITY_HIGH, info_log); throw std::runtime_error("Could not compile fragment shader."); } // link m_program = glCreateProgram(); glAttachShader(m_program, vertex_shader); // only need geometry shader if AMD_vertex_shader_layer is not supported if (!vertex_shader_layer_supported) { 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, INFO_LOG_SIZE, nullptr, info_log); glDeleteProgram(m_program); log(GL_DEBUG_TYPE_ERROR, 3, GL_DEBUG_SEVERITY_HIGH, "Could not link shader program."); log(GL_DEBUG_TYPE_ERROR, 3, GL_DEBUG_SEVERITY_HIGH, info_log); throw std::runtime_error("Could not link shader program."); } // setup uniforms glUseProgram(m_program); glUniform4f(glGetUniformLocation(m_program, GRID_SIZE_UNIFORM_NAME), GRID_WIDTH + 2, GRID_AREA_2, 1.0f / GRID_WIDTH, 1.0f / GRID_HEIGHT); glUniform1i(glGetUniformLocation(m_program, ATLAS_WIDTH_UNIFORM_NAME), MAX_SPRITES_ROW); // compile const unsigned int screen_vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(screen_vertex_shader, 1, &SCREEN_VERTEX_SHADER_SOURCE_PTR, nullptr); glCompileShader(screen_vertex_shader); const unsigned int screen_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(screen_fragment_shader, 1, &SCREEN_FRAGMENT_SHADER_SOURCE_PTR, nullptr); glCompileShader(screen_fragment_shader); // verify compile 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); log(GL_DEBUG_TYPE_ERROR, 4, GL_DEBUG_SEVERITY_HIGH, "Could not compile screen vertex shader."); log(GL_DEBUG_TYPE_ERROR, 4, GL_DEBUG_SEVERITY_HIGH, info_log); throw std::runtime_error("Could not compile screen vertex shader."); } 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); log(GL_DEBUG_TYPE_ERROR, 5, GL_DEBUG_SEVERITY_HIGH, "Could not compile screen fragment shader."); log(GL_DEBUG_TYPE_ERROR, 5, GL_DEBUG_SEVERITY_HIGH, info_log); 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); log(GL_DEBUG_TYPE_ERROR, 6, GL_DEBUG_SEVERITY_HIGH, "Could not link screen shader program."); log(GL_DEBUG_TYPE_ERROR, 6, GL_DEBUG_SEVERITY_HIGH, info_log); throw std::runtime_error("Could not link screen shader program."); } // setup uniforms for screen shader glUseProgram(m_screen_program); glUniform1i(glGetUniformLocation(m_screen_program, LAYER_COUNT_UNIFORM_NAME), LAYER_COUNT); // -- setup textures -- glGenTextures(1, &m_sprites_texture); glBindTexture(GL_TEXTURE_2D, m_sprites_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTextureStorage2D(m_sprites_texture, 1, GL_RGBA8, MAX_SPRITES_ROW * (CELL_SIZE + 2), MAX_SPRITES_ROW * (CELL_SIZE + 2)); update_sprites(); glBindTextureUnit(2, m_sprites_texture); const auto err = glGetError(); // -- 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); glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, SCREEN_WIDTH, SCREEN_HEIGHT, LAYER_COUNT); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_framebuffer_backing_texture, 0); glBindTextureUnit(1, m_framebuffer_backing_texture); // -- setup screen framebuffer -- glGenFramebuffers(1, &m_screen_framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, m_screen_framebuffer); glGenTextures(1, &m_screen_framebuffer_backing_texture); glBindTexture(GL_TEXTURE_2D, m_screen_framebuffer_backing_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, SCREEN_WIDTH, SCREEN_HEIGHT); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_screen_framebuffer_backing_texture, 0); } void glerminal::deinit_glfw() { glfwDestroyWindow(m_window); glfwTerminate(); } void glerminal::deinit_gl() { glDeleteFramebuffers(1, &m_screen_framebuffer); glDeleteTextures(1, &m_screen_framebuffer_backing_texture); 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_sprites_instance_vbo); glDeleteBuffers(1, &m_offsets_instance_vbo); glDeleteBuffers(1, &m_colors_instance_vbo); glDeleteBuffers(1, &m_scales_instance_vbo); glDeleteProgram(m_program); } void glerminal::update_sprites() { glTextureSubImage2D(m_sprites_texture, 0, 0, 0, (CELL_SIZE + 2) * MAX_SPRITES_ROW, (CELL_SIZE + 2) * MAX_SPRITES_ROW, GL_RGBA, GL_UNSIGNED_BYTE, m_sprites); } void glerminal::update_colors() { glNamedBufferSubData(m_colors_instance_vbo, 0, sizeof(m_colors), m_colors); } void glerminal::update_scales() { glNamedBufferSubData(m_scales_instance_vbo, 0, sizeof(m_scales), m_scales); } void glerminal::glfw_key_handler(GLFWwindow* window, int key, int scancode, int action, int mods) { const glerminal* const self = static_cast(glfwGetWindowUserPointer(window)); if (self->m_keypressed && action == GLFW_PRESS) { self->m_keypressed(key); } if (self->m_keyreleased && action == GLFW_RELEASE) { self->m_keyreleased(key); } } void glerminal::glfw_mousemoved_handler(GLFWwindow *window, double x, double y) { const glerminal* const self = static_cast(glfwGetWindowUserPointer(window)); if (self->m_mousemoved) { self->m_mousemoved(x / (CELL_SIZE * CELL_SCALE), y / (CELL_SIZE * CELL_SCALE)); } } void glerminal::glfw_mousepress_handler(GLFWwindow *window, int button, int action, int mods) { const glerminal* const self = static_cast(glfwGetWindowUserPointer(window)); double x, y; glfwGetCursorPos(window, &x, &y); if (self->m_mousepressed && action == GLFW_PRESS) { self->m_mousepressed(button, x / (CELL_SIZE * CELL_SCALE), y / (CELL_SIZE * CELL_SCALE)); } if (self->m_mousereleased && action == GLFW_RELEASE) { self->m_mousereleased(button, x / (CELL_SIZE * CELL_SCALE), y / (CELL_SIZE * CELL_SCALE)); } } } void glerminal_run(glerminal_init_params params) { try { glerminal::glerminal* g = new glerminal::glerminal(params); g->run(); delete g; } catch (const std::runtime_error& e) { std::cout << e.what() << std::endl; } } void glerminal_quit() { if (!GLERMINAL_G) { return; } GLERMINAL_G->quit(); } void glerminal_flush() { if (!GLERMINAL_G) { return; } GLERMINAL_G->flush(); } void glerminal_set(unsigned char x, unsigned char y, unsigned char layer, unsigned short sprite) { if (!GLERMINAL_G) { return; } GLERMINAL_G->set(x, y, layer, sprite); } unsigned char glerminal_get(unsigned char x, unsigned char y, unsigned short layer) { if (!GLERMINAL_G) { return 0; } return GLERMINAL_G->get(x, y, layer); } void glerminal_offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset) { if (!GLERMINAL_G) { return; } GLERMINAL_G->offset(x, y, layer, x_offset, y_offset); } void glerminal_color(unsigned char x, unsigned char y, unsigned char layer, unsigned int color) { if (!GLERMINAL_G) { return; } GLERMINAL_G->color(x, y, layer, color); } void glerminal_scale(unsigned char x, unsigned char y, unsigned char layer, float scale) { if (!GLERMINAL_G) { return; } GLERMINAL_G->scale(x, y, layer, scale); } int glerminal_load_sprites_file(const char* filename) { if (!GLERMINAL_G) { return false; } bool success = false; int w, h; stbi_uc* const buffer = stbi_load(filename, &w, &h, nullptr, 4); // verify atlas size is a multiple of CELL_SIZE in each dimension if (buffer && w % glerminal::CELL_SIZE == 0 && h % glerminal::CELL_SIZE == 0) { GLERMINAL_G->load_atlas(w / glerminal::CELL_SIZE, h / glerminal::CELL_SIZE, reinterpret_cast(buffer)); success = true; } stbi_image_free(buffer); return success; } int glerminal_load_sprites_buffer(unsigned char width, unsigned char height, const unsigned int* buffer) { if (!GLERMINAL_G) { return false; } // verify atlas size is a multiple of CELL_SIZE in each dimension if (width % glerminal::CELL_SIZE == 0 && height % glerminal::CELL_SIZE == 0) { GLERMINAL_G->load_atlas(width / glerminal::CELL_SIZE, height / glerminal::CELL_SIZE, buffer); return true; } else { return false; } }