mirror of
https://git.shylie.info/shylie/glerminal.git
synced 2024-11-09 20:00:04 +00:00
746 lines
21 KiB
C++
746 lines
21 KiB
C++
#define STB_IMAGE_IMPLEMENTATION
|
|
#define STBI_ONLY_PNG
|
|
#define STBI_MAX_DIMENSIONS 128
|
|
#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"
|
|
|
|
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 460 core\n"
|
|
"layout (location = 0) in vec2 position;\n"
|
|
"layout (location = 1) in vec2 offset;\n"
|
|
"layout (location = 2) in int sprite;\n"
|
|
"uniform vec4 " GRID_SIZE_UNIFORM_NAME ";\n"
|
|
"layout (std430, binding = 0) buffer LayerScales"
|
|
"{\n"
|
|
" float scales[];\n"
|
|
"} lss;\n"
|
|
"out VS_OUT {\n"
|
|
" flat vec2 offset;\n"
|
|
" flat int sprite;\n"
|
|
" flat int layer;\n"
|
|
" vec2 texcoord;\n"
|
|
"} vs_out;\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" const int layer = int(floor(gl_InstanceID / " GRID_SIZE_UNIFORM_NAME ".y));\n"
|
|
" vs_out.sprite = sprite;\n"
|
|
" vs_out.offset = offset * " GRID_SIZE_UNIFORM_NAME ".zw;\n"
|
|
" vs_out.layer = layer;\n"
|
|
" vs_out.texcoord = vec2(position.x + 1, -position.y);\n"
|
|
" vec2 cell_position = vec2(lss.scales[layer] + (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 ".z), -floor((gl_InstanceID % int(" GRID_SIZE_UNIFORM_NAME ".y)) * " GRID_SIZE_UNIFORM_NAME ".z));\n"
|
|
" vec2 temp = ((position + vec2(-0.5, 0.5)) * lss.scales[layer] + cell_position + vec2(0.5, -0.5)) * " GRID_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 460 core\n"
|
|
"layout (triangles) in;\n"
|
|
"layout (triangle_strip, max_vertices = 3) out;\n"
|
|
"in VS_OUT {\n"
|
|
" flat vec2 offset;\n"
|
|
" flat int sprite;\n"
|
|
" flat int layer;\n"
|
|
" vec2 texcoord;\n"
|
|
"} gs_in[];\n"
|
|
"flat out int sprite;\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 * 2, 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 * 2, 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 * 2, 0, 1);\n"
|
|
" sprite = gs_in[2].sprite;\n"
|
|
" texcoord = gs_in[2].texcoord;\n"
|
|
" EmitVertex();\n"
|
|
" EndPrimitive();\n"
|
|
"}";
|
|
|
|
constexpr char* FRAGMENT_SHADER_SOURCE =
|
|
"#version 460 core\n"
|
|
"in vec2 texcoord;\n"
|
|
"flat in int sprite;\n"
|
|
"layout (binding = 0) uniform sampler2DArray " SPRITES_UNIFORM_NAME ";\n"
|
|
"out vec4 FragColor;\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" FragColor = texture(" SPRITES_UNIFORM_NAME ", vec3(texcoord, sprite));\n"
|
|
"}";
|
|
|
|
constexpr char* SCREEN_VERTEX_SHADER_SOURCE =
|
|
"#version 460 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 460 core\n"
|
|
"in vec2 texcoord;\n"
|
|
"layout (binding = 1) uniform sampler2DArray " LAYERS_UNIFORM_NAME ";\n"
|
|
"layout (std430, binding = 1) buffer LayerColors"
|
|
"{\n"
|
|
" vec4 colors[];\n"
|
|
"} lcs;\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 = 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"
|
|
"}";
|
|
}
|
|
|
|
namespace glerminal
|
|
{
|
|
glerminal::glerminal(glerminal_init_cb init, glerminal_main_cb main) :
|
|
m_main(main),
|
|
m_cells{ },
|
|
m_offsets{ },
|
|
m_sprites{ },
|
|
m_layer_colors{ },
|
|
m_layer_scales{ }
|
|
#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.");
|
|
}
|
|
|
|
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();
|
|
|
|
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::quit()
|
|
{
|
|
glfwSetWindowShouldClose(m_window, GLFW_TRUE);
|
|
}
|
|
|
|
void glerminal::flush()
|
|
{
|
|
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, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
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);
|
|
|
|
glViewport(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
glUseProgram(m_screen_program);
|
|
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[x + y * GRID_WIDTH + layer * GRID_AREA] = 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::offset(unsigned char x, unsigned char y, unsigned char layer, float x_offset, float y_offset)
|
|
{
|
|
if (x < GRID_WIDTH && y < GRID_HEIGHT && layer < LAYER_COUNT)
|
|
{
|
|
m_offsets[2 * (x + y * GRID_WIDTH + layer * GRID_AREA) + 0] = x_offset;
|
|
m_offsets[2 * (x + y * GRID_WIDTH + layer * GRID_AREA) + 1] = y_offset;
|
|
}
|
|
}
|
|
|
|
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::load_atlas(unsigned char w, unsigned char h, const unsigned int* data)
|
|
{
|
|
// each row of the atlas
|
|
for (int j = 0; j < h; j++)
|
|
{
|
|
// each column of the atlas
|
|
for (int i = 0; i < w; i++)
|
|
{
|
|
// each row of the individual sprite
|
|
for (int k = 0; k < CELL_SIZE; k++)
|
|
{
|
|
// offset from base address in atlas layout
|
|
const unsigned int src_offset = i + k * w + j * w * CELL_SIZE;
|
|
// offset from base address in array layout
|
|
const unsigned int dst_offset = k + i * CELL_SIZE + j * w * CELL_SIZE;
|
|
|
|
memcpy(m_sprites + CELL_SIZE * dst_offset, data + CELL_SIZE * 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.");
|
|
}
|
|
}
|
|
|
|
#ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT
|
|
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<GLADloadproc>(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<const glerminal*>(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);
|
|
|
|
// -- 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_layer_colors_buffer);
|
|
glGenBuffers(1, &m_layer_scales_buffer);
|
|
|
|
// 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<void*>(0));
|
|
|
|
// set up instanced vertex attributes
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_offsets_instance_vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(m_offsets), m_offsets, GL_STREAM_DRAW);
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(*m_offsets), reinterpret_cast<void*>(0));
|
|
glVertexAttribDivisor(1, 1);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_sprites_instance_vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(m_cells), m_cells, GL_STREAM_DRAW);
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribIPointer(2, 1, GL_UNSIGNED_BYTE, 1 * sizeof(*m_cells), reinterpret_cast<void*>(0));
|
|
glVertexAttribDivisor(2, 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<void*>(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);
|
|
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);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
throw std::runtime_error("Could not compile fragment shader.");
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
glDeleteProgram(m_program);
|
|
|
|
throw std::runtime_error("Could not link shader program.");
|
|
}
|
|
|
|
// setup uniforms
|
|
m_screen_size_uniform_location = glGetUniformLocation(m_program, GRID_SIZE_UNIFORM_NAME);
|
|
|
|
glUseProgram(m_program);
|
|
glUniform4f(m_screen_size_uniform_location, GRID_WIDTH, GRID_AREA, 1.0f / GRID_WIDTH, 1.0f / GRID_HEIGHT);
|
|
|
|
// 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, INFO_LOG_SIZE, 0, info_log);
|
|
|
|
glDeleteShader(screen_vertex_shader);
|
|
glDeleteShader(screen_fragment_shader);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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();
|
|
|
|
glBindTextureUnit(0, m_sprites_texture);
|
|
|
|
// -- 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 uniforms for screen shader
|
|
glUseProgram(m_screen_program);
|
|
|
|
glUniform1i(glGetUniformLocation(m_screen_program, LAYER_COUNT_UNIFORM_NAME), LAYER_COUNT);
|
|
}
|
|
|
|
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_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_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);
|
|
}
|
|
}
|
|
|
|
void glerminal_run(glerminal_init_cb init, glerminal_main_cb main)
|
|
{
|
|
try
|
|
{
|
|
glerminal::glerminal* g = new glerminal::glerminal(init, main);
|
|
g->run();
|
|
delete g;
|
|
}
|
|
catch (const std::runtime_error& e)
|
|
{
|
|
e.what();
|
|
}
|
|
}
|
|
|
|
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 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_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_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_load_sprites_file(const char* filename)
|
|
{
|
|
if (!GLERMINAL_G) { return; }
|
|
|
|
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 (w % glerminal::CELL_SIZE == 0 && h % glerminal::CELL_SIZE == 0)
|
|
{
|
|
GLERMINAL_G->load_atlas(w / glerminal::CELL_SIZE, h / glerminal::CELL_SIZE, reinterpret_cast<unsigned int*>(buffer));
|
|
}
|
|
|
|
stbi_image_free(buffer);
|
|
}
|
|
|
|
void glerminal_load_sprites_buffer(unsigned char width, unsigned char height, const unsigned int* buffer)
|
|
{
|
|
if (!GLERMINAL_G) { return; }
|
|
|
|
// 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);
|
|
}
|
|
} |