#if defined (_WIN32) #include #else #include #endif #include #include #include #include "default_shaders.cpp" #include "dumbLog.h" #include "input.h" #include "render_object.h" #include "renderer.h" #define CLEAR_COL_R 55.f / 255.f #define CLEAR_COL_G 55.f / 255.f #define CLEAR_COL_B 55.f / 255.f #define CLEAR_COL_A 1.f #define USE_SECOND_MONITOR 0 // forward declarations bool createWindow(const char* title, SDL_Handles* handles, glm::vec2& viewport_dims); bool initContext(SDL_Handles* handles); bool initGlOptions(); bool initSDL(SDL_Handles* handles, Uint32 SDL_init_flags); bool initShaders(render_state* rs); void openglDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam); void setDefaults(render_state* rs, glm::vec2 viewport_dims); // interface render_state* renInit(const char* title, glm::vec2 viewport_dims, Uint32 SDL_init_flags) { render_state* rs = UTIL_ALLOC(1, render_state); rs->handles = UTIL_ALLOC(1, SDL_Handles); setDefaults(rs, viewport_dims); if (initSDL(rs->handles, SDL_init_flags) && createWindow(title, rs->handles, rs->viewport_dims) && initContext(rs->handles) && initGlOptions() && initShaders(rs)) { return rs; } LOG(Error) << "renderer initialization failed, aborting\n"; return nullptr; } void renShutdown(render_state* rs) { for (uint i = 0; i < rs->render_group_count; i++) { for (uint j = 0; j < rs->render_groups[i]->entity_count; j++) entFree(rs->render_groups[i]->entities[j]); utilSafeFree(rs->render_groups[i]->entities); } shaderFree(rs->default_shader->program_id); utilSafeFree(rs->default_shader); rs->default_shader = nullptr; shaderFree(rs->simple_shader->program_id); utilSafeFree(rs->simple_shader); rs->simple_shader = nullptr; lightsOut(rs->lights); utilSafeFree(rs->render_groups); rs->render_groups = nullptr; arenaFree(rs->arena); meShutdownAssimp(); SDL_GL_DeleteContext(rs->handles->glContext); SDL_DestroyWindow(rs->handles->window); SDL_Quit(); utilSafeFree(rs->handles); } render_group* renAllocateGroup(uint entity_count, shader_wrapper shader) { assert(entity_count > 0); render_group* rg = UTIL_ALLOC(1, render_group); rg->entities = UTIL_ALLOC(entity_count, entity); rg->entity_count = entity_count; rg->shader = shader; return rg; } void renDoRenderLoop(render_state* rs, uint framerate, frame_callback_fn cb_func_pre, frame_callback_fn cb_func_post) { uint delay = (framerate > 0) ? 1 / framerate : 0; uint frameStart, frameTime; static input_state is = {}; while (rs->running) { frameStart = SDL_GetTicks(); if (cb_func_pre != nullptr) { cb_func_pre(rs); } else { inputProcessEvents(&is); if (is.window_closed || is.escape) { rs->running = false; return; } } renRenderFrame(rs); if (cb_func_post != nullptr) cb_func_post(rs); SDL_GL_SwapWindow(rs->handles->window); frameTime = SDL_GetTicks() - frameStart; if (delay > frameTime) SDL_Delay(delay - frameTime); } } void renRenderFrame(render_state* rs) { glClearColor(rs->clear_col.R, rs->clear_col.G, rs->clear_col.B, rs->clear_col.A); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); for (uint i = 0; i < rs->render_group_count; i++) { render_group* rg = rs->render_groups[i]; for (uint j = 0; j < rg->entity_count; j++) { entity& e = rg->entities[j]; roDraw(e.render_objs, e.world_transform, rs->cam, rg->shader, rs->lights); } } } bool renAddLight(render_state* rs, glm::vec3 pos, glm::vec3 color, float intensity) { if (!lightsAdd(rs->lights, pos, color, intensity)) { LOG(Error) << "Error adding light\n"; return false; } return true; } glm::vec2 renGetWindowDims(render_state* rs) { int x = 0, y = 0; SDL_GetWindowSize(rs->handles->window, &x, &y); glm::vec2 dims(x, y); return dims; } // internal bool createWindow(const char* title, SDL_Handles* handles, glm::vec2& viewport_dims) { uint display_id = 0; if (USE_SECOND_MONITOR && SDL_GetNumVideoDisplays() > 1) display_id = 1; handles->window = SDL_CreateWindow( title, SDL_WINDOWPOS_CENTERED_DISPLAY(display_id), SDL_WINDOWPOS_CENTERED_DISPLAY(display_id), viewport_dims.x, viewport_dims.y, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE ); if (!handles->window) { LOG(Error) << "Error creating window: " << SDL_GetError() << "\n"; return false; } return true; } bool initContext(SDL_Handles* handles) { handles->glContext = SDL_GL_CreateContext(handles->window); if (!handles->glContext) { LOG(Error) << "Error creating glContext: " << SDL_GetError() << "\n"; return false; } // TODO: this doesn't work inside VM with QXL graphics if (SDL_GL_SetSwapInterval(1) != 0) // vsync LOG(Error) << "SDL Errors: " << SDL_GetError() << "\n"; return true; } bool initGlOptions() { if (glewInit()) { LOG(Error) << "failed to initialize OpenGL\n"; return false; } LOG(Info) << "opengl vendor: " << glGetString(GL_VENDOR) << "\n"; LOG(Info)<< "opengl renderer: " << glGetString(GL_RENDERER) << "\n"; LOG(Info) << "opengl version: " << glGetString(GL_VERSION) << "\n"; glEnable(GL_DEPTH_TEST); glEnable(GL_LINE_SMOOTH); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #if 0 // TODO: blending messes up rendering with mesa on intel graphics 4000 glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_ONE, GL_SRC_ALPHA); #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); return true; } bool initSDL(SDL_Handles* handles, Uint32 SDL_init_flags) { Uint32 flags = SDL_INIT_VIDEO | SDL_init_flags; if (SDL_Init(flags) != 0) { LOG(Error) << "Error, SDL_Init: " << SDL_GetError() << "\n"; return false; } 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_GetCurrentDisplayMode(0, &handles->currentDisplayMode); return true; } bool initShaders(render_state* rs) { rs->default_shader = shaderInitDefault(DEFAULT_VERTEX_SHADER, DEFAULT_FRAGMENT_SHADER); rs->simple_shader = shaderInitSimple(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER); if (rs->default_shader == nullptr || rs->simple_shader == nullptr) { LOG(Error) << "shader loading failed\n"; return false; } else { 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 setDefaults(render_state* rs, glm::vec2 viewport_dims) { rs->running = true; rs->viewport_dims = viewport_dims; rs->lights = lightsInit(); rs->clear_col.R = CLEAR_COL_R; rs->clear_col.B = CLEAR_COL_B; rs->clear_col.G = CLEAR_COL_G; rs->clear_col.A = CLEAR_COL_A; }