#ifndef RENDERER_H #define RENDERER_H #include #include // trig functions #include // calloc // TODO: decide on extension library //#include #include #include #include #include #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* hexes); void renderFrame(const std::vector *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 *hexes); void fillHexLineBuffer(GLfloat buf[], int len, std::vector* 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* 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* 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* 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 *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 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