You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

554 lines
16 KiB

#ifndef RENDERER_H
#define RENDERER_H
#include <vector>
#include <cmath> // trig functions
#include <cstdlib> // calloc
// TODO: decide on extension library
//#include <GL/glew.h>
#include <GL/gl3w.h>
#include <SDL.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "hexlib.h"
#include "hexgame.h"
const char * VERTEX_SHADER_CODE =
"#version 330 core\n"
"in vec3 vertexPosition_modelspace;\n"
"in vec3 vertexColor;\n"
"out vec3 fragmentColor;\n"
"uniform mat4 MVP;\n"
"void main()\n"
"{\n"
" gl_Position = MVP * vec4(vertexPosition_modelspace, 1);\n"
" fragmentColor = vertexColor;\n"
"}";
const char * FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"in vec3 fragmentColor;\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = fragmentColor;\n"
"}";
const char * LINE_FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = vec3(0,0,0);\n"
"}";
const char * DEBUG_FRAGMENT_SHADER_CODE =
"#version 330 core\n"
"out vec3 color;\n"
"void main()\n"
"{\n"
" color = vec3(1,0,0);\n"
"}";
typedef struct clear_col
{
real32 R;
real32 G;
real32 B;
real32 A;
} clear_col;
clear_col g_clear_col { 75.f / 255.f, 135.f / 255.f, 135.f / 255.f, 1.f };
typedef struct gl_matrix_info
{
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model;
glm::mat4 MVP;
} gl_matrix_info;
typedef struct gl_buffer
{
GLuint buffer_id;
size_t buffer_len; // NOTE: number of elements in buffer
GLfloat* buffer;
} gl_buffer;
typedef struct gl_render_group
{
// NOTE: For now the renderFrame function will assume these are the same size
gl_buffer vertex_buffer;
gl_buffer color_buffer;
GLuint program_id;
GLuint matrix_id; // NOTE: reference to a vertex shader's MVP matrix uniform
GLuint vertex_array_id;
} gl_render_group;
gl_matrix_info g_scene_matrices;
gl_render_group g_filled_hex_render_group;
gl_render_group g_hex_line_render_group;
gl_render_group g_debug_render_group;
// Interface
bool initRenderer(SDL_Handles &handles, v2i vpDims);
bool createScene(std::vector<hex_info>* hexes);
void renderFrame(const std::vector<hex_info> *hexes);
void renderDebug(hex_info *start_hex, hex_info * current_hex);
void freeBuffers();
// forward declarations
bool initShaderProgram(gl_render_group &rg, const char * vertex_code, const char * frag_code, const char * mat_id);
// enable debug output https://www.khronos.org/opengl/wiki/OpenGL_Error
void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam);
void fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex);
void fillColorBuffer(GLfloat buf[], int len, std::vector<hex_info> *hexes);
void fillHexLineBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes);
bool checkGLBufferSize(GLenum buf_type, int expected_size, int line_num);
bool initGLBufferObject(gl_buffer* buf_obj, int len, GLenum usage, GLfloat data[]);
void drawRenderGroup(gl_render_group* rg, GLenum draw_mode, bool update_vertex_data);
bool
initRenderer(SDL_Handles &handles, v2i vpDims)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetSwapInterval(1); // vsync
SDL_GetCurrentDisplayMode(0, &handles.currentDisplayMode);
handles.window =
SDL_CreateWindow("hexgame", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
vpDims.x, vpDims.y, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
if (handles.window == NULL)
{
// TODO: log error SDL_GetError()
return false;
}
handles.glContext = SDL_GL_CreateContext(handles.window);
// TODO: decide on extension library
gl3wInit();
#if 0
// Initialize GLEW
// glewExperimental is only needed in GLEW <= 1.13.0
// we can require version 2.0.0+
//glewExperimental = true; // Needed for core profile
GLenum err = glewInit();
if (err != GLEW_OK) {
LOG(ERROR) << "Failed to initilize GLEW" << glewGetErrorString(err) << "\n";
return false;
}
#endif
LOG(INFO) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n";
LOG(INFO)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n";
LOG(INFO) << "opengl version: " << glGetString(GL_VERSION) << "\n";
// TODO: blending, these options break the http://www.opengl-tutorial.org tutorials atm
// Setup render state: alpha-blending enabled, polygon fill
#if 0
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
// TODO: glDebugMessageCallback is only availabe from >v4.3
// check and warn if context doesn't support this function here
glEnable (GL_DEBUG_OUTPUT);
glDebugMessageCallback((GLDEBUGPROC) openglDebugCallback, 0);
// hide VRAM debug messages
glDebugMessageControl(GL_DONT_CARE, 33361, GL_DONT_CARE, 0, 0, GL_FALSE);
// TODO: check for errors when compiling shaders
initShaderProgram(g_filled_hex_render_group, VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE, "MVP");
initShaderProgram(g_hex_line_render_group, VERTEX_SHADER_CODE, LINE_FRAGMENT_SHADER_CODE, "MVP");
initShaderProgram(g_debug_render_group, VERTEX_SHADER_CODE, DEBUG_FRAGMENT_SHADER_CODE, "MVP");
return true;
}
// NOTE: mat_id should match the matrix variable name in the shader source
bool
initShaderProgram(gl_render_group &rg, const char * vertex_code, const char * frag_code, const char * mat_id)
{
glGenVertexArrays(1, &rg.vertex_array_id);
glBindVertexArray(rg.vertex_array_id);
GLuint vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
GLuint fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vertex_shader_id, 1, &vertex_code, NULL);
glShaderSource(fragment_shader_id, 1, &frag_code, NULL);
glCompileShader(vertex_shader_id);
glCompileShader(fragment_shader_id);
rg.program_id = glCreateProgram();
glAttachShader(rg.program_id, vertex_shader_id);
glAttachShader(rg.program_id, fragment_shader_id);
glLinkProgram(rg.program_id);
rg.matrix_id = glGetUniformLocation(rg.program_id, mat_id);
glDetachShader(rg.program_id, vertex_shader_id);
glDetachShader(rg.program_id, fragment_shader_id);
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
// TODO: check for errors when compiling shaders
return true;
}
bool
createScene(std::vector<hex_info>* hexes)
{
// Model, View, Projection Matrices
// left, right, bottom, top, zNear, zFar
g_scene_matrices.projection = glm::ortho(0.f, 1280.0f, 0.f, 720.0f, 0.1f, 100.0f);
g_scene_matrices.view = glm::lookAt(
glm::vec3(0.0f, 0.0f, 2.0f), // camera position
glm::vec3(0.0f, 0.0f, 0.0f), // look at position
glm::vec3(0,1,0) // "up" vector
);
g_scene_matrices.model = glm::mat4(1.0f);
g_scene_matrices.MVP = g_scene_matrices.projection * g_scene_matrices.view * g_scene_matrices.model;
// Vertex Data
// TODO: index duplicate vertices
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-9-vbo-indexing/
int hex_count = (int) hexes->size();
// 6 triangles * 3 vertices per triangle * 3 floats per vertex = 54
int vbuf_len = hex_count * 6 * 3 * 3;
// TODO: surely there's a way to use indexed drawing for line vertices eg) line_loop
// and still use one buffer for multiple shapes/paths gldrawarraysintanced?, gldrawelements?
// https://gamedev.stackexchange.com/questions/104310/opengl-4-5-primitive-restart-vs-base-index
int line_vertices_per_hex = 6 * 2; // 12 vertices since we're using line segments atm
//gl_buffer *line_vertex_buffer = &g_hex_line_render_group.vertex_buffer;
int line_buf_len = hexes->size() * line_vertices_per_hex * 3; // 3 floats per vertex
// temporary buffers
GLfloat* vbuf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
GLfloat* cbuf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
GLfloat* line_buf = (GLfloat*) std::calloc(vbuf_len, sizeof(GLfloat));
for (int i = 0; i < hex_count; i++)
fillTriangleBufferFromHex(vbuf, 54 * i, (*hexes)[i]);
if (!initGLBufferObject(&g_filled_hex_render_group.vertex_buffer, vbuf_len, GL_DYNAMIC_DRAW, vbuf))
return false;
// color data for hex vertices
fillColorBuffer(cbuf, vbuf_len, hexes);
if (!initGLBufferObject(&g_filled_hex_render_group.color_buffer, vbuf_len, GL_DYNAMIC_DRAW, cbuf))
return false;
// hex line vertex data
fillHexLineBuffer(line_buf, line_buf_len, hexes);
if (!initGLBufferObject(&g_hex_line_render_group.vertex_buffer, line_buf_len, GL_STATIC_DRAW, line_buf))
return false;
// free temporary buffers
std::free(vbuf);
std::free(cbuf);
std::free(line_buf);
// debug draw vertexes
// 4 vertices, 3 floats per vertex
int len = 4 * 3;
if (!initGLBufferObject(&g_debug_render_group.vertex_buffer, len, GL_DYNAMIC_DRAW, 0))
return false;
return true;
}
void
fillTriangleBufferFromHex(GLfloat buf[], int idx, const hex_info &hex)
{
// triangles
for (int i = 0; i < 6; i++)
{
if (i == 5) // re-use the first point for the last triangle
{
// vertex 0
buf[idx + 0] = (GLfloat) hex.XPos;
buf[idx + 1] = (GLfloat) hex.YPos;
buf[idx + 2] = (GLfloat) 0.f;
// vertex 1
buf[idx + 3] = (GLfloat) hex.vertices[i].x;
buf[idx + 4] = (GLfloat) hex.vertices[i].y;
buf[idx + 5] = (GLfloat) 0.f;
// vertex 2
buf[idx + 6] = (GLfloat) hex.vertices[0].x;
buf[idx + 7] = (GLfloat) hex.vertices[0].y;
buf[idx + 8] = (GLfloat) 0.f;
}
else
{
// vertex 0
buf[idx + 0] = (GLfloat) hex.XPos;
buf[idx + 1] = (GLfloat) hex.YPos;
buf[idx + 2] = (GLfloat) 0.f;
// vertex 1
buf[idx + 3] = (GLfloat) hex.vertices[i].x;
buf[idx + 4] = (GLfloat) hex.vertices[i].y;
buf[idx + 5] = (GLfloat) 0.f;
// vertex 2
buf[idx + 6] = (GLfloat) hex.vertices[i + 1].x;
buf[idx + 7] = (GLfloat) hex.vertices[i + 1].y;
buf[idx + 8] = (GLfloat) 0.f;
}
// we've added 9 GLfloats per loop
idx += 9;
}
}
void
fillColorBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes)
{
int buf_idx;
int buf_len_per_hex = 54; // NOTE: 3 * 3 * 6
for (int i = 0; i < (int) hexes->size(); i++)
{
buf_idx = i * buf_len_per_hex;
hex_info hxi = (*hexes)[i];
GLfloat color = (GLfloat) hxi.current_color / (GLfloat) UINT32_MAX;
for (int j = 0; j < buf_len_per_hex; j++)
buf[buf_idx + j] = color;
}
}
void
fillHexLineBuffer(GLfloat buf[], int len, std::vector<hex_info>* hexes)
{
Point p1, p2;
int idx = 0;
for (int i = 0; i < (int) hexes->size(); i++)
{
hex_info hxi = (*hexes)[i];
for (int j = 0; j < 6; j ++)
{
if (j == 5) // wrap
{
p1 = hxi.vertices[j];
p2 = hxi.vertices[0];
}
else
{
p1 = hxi.vertices[j];
p2 = hxi.vertices[j + 1];
}
buf[idx + 0] = p1.x;
buf[idx + 1] = p1.y;
buf[idx + 2] = 0.f;
buf[idx + 3] = p2.x;
buf[idx + 4] = p2.y;
buf[idx + 5] = 0.f;
idx += 6;
}
}
}
// NOTE: this will only work after a call to glBindBuffer
bool
checkGLBufferSize(GLenum buf_type, int expected_size, int line_num)
{
GLint gl_size;
glGetBufferParameteriv(buf_type, GL_BUFFER_SIZE ,&gl_size);
if (expected_size != gl_size)
{
LOG(ERROR) << "line: " << line_num << "gl buffer size mismatch\n";
return false;
}
return true;
}
bool
initGLBufferObject(gl_buffer* buf_obj, int len, GLenum usage, GLfloat data[])
{
buf_obj->buffer_len = (size_t) len;
buf_obj->buffer = (GLfloat*) std::calloc(len, sizeof(GLfloat));
if (data)
{
for (int i = 0; i < len; i++)
buf_obj->buffer[i] = data[i];
}
glGenBuffers(1, &buf_obj->buffer_id);
glBindBuffer(GL_ARRAY_BUFFER, buf_obj->buffer_id);
glBufferData(GL_ARRAY_BUFFER, len * sizeof(GLfloat), buf_obj->buffer, usage);
if (!checkGLBufferSize(GL_ARRAY_BUFFER, len * sizeof(GLfloat), __LINE__))
return false;
return true;
}
void
openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* userParam)
{
LOG((type == GL_DEBUG_TYPE_ERROR) ? ERROR : DEBUG)
<< (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "")
<< ", type: " << type
<< ", severity: " << severity
<< ", message: " << message << "\n";
}
void
drawRenderGroup(gl_render_group* rg, GLenum draw_mode, bool update_vertex_data = false)
{
glUseProgram(rg->program_id);
// Send our transformation to the currently bound shader, in the "g_MVP" uniform
glUniformMatrix4fv(rg->matrix_id, 1, GL_FALSE, &g_scene_matrices.MVP[0][0]);
// 1st attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, rg->vertex_buffer.buffer_id);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
if (update_vertex_data)
{
glBufferSubData(GL_ARRAY_BUFFER, 0, rg->vertex_buffer.buffer_len * sizeof(GLfloat),
rg->vertex_buffer.buffer);
}
// 2nd attribute buffer : colors
if (rg->color_buffer.buffer)
{
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, rg->color_buffer.buffer_id);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
// TODO: maybe add an option to not send color data every frame?
glBufferSubData(GL_ARRAY_BUFFER, 0, rg->color_buffer.buffer_len * sizeof(GLfloat),
rg->color_buffer.buffer);
}
// draw
glDrawArrays(draw_mode, 0, rg->vertex_buffer.buffer_len / 3);
// cleanup
glDisableVertexAttribArray(0);
if (rg->color_buffer.buffer)
glDisableVertexAttribArray(1);
}
void
renderFrame(std::vector<hex_info> *hexes)
{
glClearColor(g_clear_col.R, g_clear_col.G, g_clear_col.B, g_clear_col.A);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
// filled hexes
// get new colors every frame
gl_render_group* rg = &g_filled_hex_render_group;
fillColorBuffer(rg->color_buffer.buffer, rg->color_buffer.buffer_len, hexes);
drawRenderGroup(rg, GL_TRIANGLES);
// hex lines
drawRenderGroup(&g_hex_line_render_group, GL_LINES);
}
void
renderDebug(hex_info *start_hex, hex_info *current_hex)
{
if (!start_hex || !current_hex)
return;
// fill vertex buffer
Point p1(start_hex->XPos, start_hex->YPos);
Point p2(current_hex->XPos, current_hex->YPos);
real64 angle = std::atan2(p2.y - p1.y, p2.x - p1.x);
real64 len = std::hypot(p2.y - p1.y, p2.x - p1.x);
real64 coneAngle = 24.5f * M_PI / 180; // M_PI is non-standard and may not be portable
// TODO: add matrix math library for rotation transform?
// |x| | cos(a + b) -sin(a + b) 0 |
// |y| * | sin(a + b) cos(a + b) 0 |
// |0| | 0 0 1 |
//
real64 x1 = len * std::cos(angle);
real64 y1 = len * std::sin(angle);
// top of cone
real64 topX = x1 * std::cos(coneAngle) - y1 * std::sin(coneAngle) + p1.x;
real64 topY = x1 * std::sin(coneAngle) + y1 * std::cos(coneAngle) + p1.y;
// bottom of cone
real64 botX = x1 * std::cos(coneAngle) + y1 * std::sin(coneAngle) + p1.x;
real64 botY = x1 * std::sin(-1 * coneAngle) + y1 * std::cos(coneAngle) + p1.y;
// TODO: indexed line drawing
real64 buf[4 * 3] = {
p1.x, p1.y, 0,
botX, botY, 0,
p2.x, p2.y, 0,
topX, topY, 0,
};
// copy vertexes to render group
gl_render_group* rg = &g_debug_render_group;
for (int i = 0; i < 12; i++)
rg->vertex_buffer.buffer[i] = buf[i];
drawRenderGroup(rg, GL_LINE_LOOP, true);
}
void
freeBuffers()
{
std::vector<gl_render_group> groups = {
g_filled_hex_render_group,
g_hex_line_render_group,
g_debug_render_group
};
for (gl_render_group group : groups)
{
if (group.vertex_buffer.buffer)
{
std::free(group.vertex_buffer.buffer);
group.vertex_buffer.buffer = 0;
}
if (group.color_buffer.buffer)
{
std::free(group.color_buffer.buffer);
group.color_buffer.buffer = 0;
}
}
}
#endif // RENDERER_H