#include #include #include #include "client/engine/Wrapper.h" DummyClient DummyClient::dummy; IClient* Engine::client = &DummyClient::dummy; // window data GLFWwindow* Engine::window = nullptr; int Engine::scale = 1; int Engine::width = 0; int Engine::height = 0; int Engine::resizeTicks = -1; // projection data float Engine::fovY = 60; float Engine::nearClip = 0.1f; float Engine::farClip = 1000.0f; Matrix3D Engine::projMatrix; // rectangle for framebuffer drawing FramebufferRectangle Engine::rectangle; int Engine::stage = 0; // shader stage 1 - world WorldShader Engine::worldShader; // shader stage 2 - world ssao SSAOShader Engine::ssaoShader; // shader stage 3 - world ssao blur SSAOBlurShader Engine::ssaoBlurShader; // shader stage 4 - world post WorldPostShader Engine::worldPostShader; // shader stage 5 - 2D overlay OverlayShader Engine::overlayShader; bool Engine::init(int width, int height, const char* name) { Engine::width = width; Engine::height = height; updateScale(); if(!glfwInit()) { cout << "could not initialize GLFW" << endl; return false; } glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, 0); glfwWindowHint(GLFW_RESIZABLE, 1); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); window = glfwCreateWindow(width, height, name, nullptr, nullptr); if(!window) { cout << "could not create window" << endl; glfwTerminate(); return false; } glfwMakeContextCurrent(window); glfwSwapInterval(1); glfwShowWindow(window); GLenum err = glewInit(); if(GLEW_OK != err) { cout << "could not initialize GLEW: " << glewGetErrorString(err) << endl; return false; } cout << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << endl; if(!worldShader.init() || !ssaoShader.init() || !ssaoBlurShader.init() || !worldPostShader.init() || !overlayShader.init() || !rectangle.init()) { glfwDestroyWindow(window); glfwTerminate(); return false; } glfwSetKeyCallback(window, onKeyEvent); glfwSetMouseButtonCallback(window, onMouseClick); glfwSetFramebufferSizeCallback(window, onWindowResize); glfwSetCursorPosCallback(window, onMouseMove); return true; } void Engine::sleep(uint64_t nanos) { uint64_t end = glfwGetTimerValue() + nanos - 10000; // non busy wait until a close range while(end > glfwGetTimerValue() + 1000000) { this_thread::sleep_for(chrono::nanoseconds(1000000)); } while(end > glfwGetTimerValue() + 100000) { this_thread::sleep_for(chrono::nanoseconds(100000)); } // busy wait for higher accuracy at the end while(end > glfwGetTimerValue()); } void Engine::start(IClient* client) { if(client != nullptr) { Engine::client = client; } glEnable(GL_CULL_FACE); glDepthFunc(GL_LEQUAL); uint64_t newTime = glfwGetTimerValue(); uint64_t oldTime = newTime; uint64_t lag = 0; uint64_t frameLag = 0; uint64_t lastFrame = 0; while(!glfwWindowShouldClose(window)) { oldTime = newTime; newTime = glfwGetTimerValue(); lag += newTime - oldTime; frameLag += newTime - oldTime; if(lag >= NANOS_PER_TICK || frameLag >= NANOS_PER_FRAME) { int ticksPerFrame = 0; while(lag >= NANOS_PER_TICK) { lag -= NANOS_PER_TICK; Engine::client->tick(); ticksPerFrame++; resizeTicks -= (resizeTicks >= 0); if(resizeTicks == 0) { glViewport(0, 0, width, height); updateScale(); worldShader.resize(); ssaoShader.resize(); ssaoBlurShader.resize(); worldPostShader.resize(); } if(ticksPerFrame >= MAX_TICKS_PER_FRAME) { long skip = lag / NANOS_PER_TICK; lag -= skip * NANOS_PER_TICK; if(skip > 0) { cout << "skipped " << skip << " game ticks " << lag << endl; } break; } } if(frameLag >= NANOS_PER_FRAME) { frameLag -= NANOS_PER_FRAME; // make sure no frames are rendered immediately after each other // this happens if the game tick takes too long if(lastFrame + NANOS_PER_FRAME - 1000000 < glfwGetTimerValue()) { lastFrame = glfwGetTimerValue(); if(resizeTicks == -1) { onRenderTick((float) lag / NANOS_PER_TICK); glfwSwapBuffers(window); } } } glfwPollEvents(); } else { // wait until next frame long waitingTime = min(NANOS_PER_FRAME - frameLag, NANOS_PER_TICK - lag); sleep(waitingTime); } } glfwDestroyWindow(window); glfwTerminate(); } void Engine::stop() { glfwSetWindowShouldClose(window, 1); } void Engine::onKeyEvent(GLFWwindow* w, int key, int scancode, int action, int mods) { client->onKeyEvent(key, scancode, action, mods); } void Engine::onMouseMove(GLFWwindow* w, double x, double y) { client->onMouseMove(x, y); } void Engine::onMouseClick(GLFWwindow* w, int button, int action, int mods) { client->onMouseClick(button, action, mods); } void Engine::onWindowResize(GLFWwindow* w, int width, int height) { Engine::width = width; Engine::height = height; resizeTicks = 10; } void Engine::updateScale() { scale = 1; while(width / (scale + 1) >= 400 && height / (scale + 1) >= 300) { scale++; } } int Engine::getScale() { return scale; } int Engine::getWidth() { return width; } int Engine::getHeight() { return height; } float Engine::getScaledWidth() { return (float) width / scale; } float Engine::getScaledHeight() { return (float) height / scale; } float Engine::getFieldOfView() { return fovY; } float Engine::getNearClip() { return nearClip; } float Engine::getFarClip() { return farClip; } void Engine::printError() { GLenum error = glGetError(); switch(error) { case GL_NO_ERROR: //cout << "> No error has been recorded." << endl; break; case GL_INVALID_ENUM: cout << "> An unacceptable value is specified for an enumerated argument." << endl; break; case GL_INVALID_VALUE: cout << "> A numeric argument is out of range." << endl; break; case GL_INVALID_OPERATION: cout << "> The specified operation is not allowed in the current state." << endl; break; case GL_INVALID_FRAMEBUFFER_OPERATION: cout << "> The framebuffer object is not complete." << endl; break; case GL_OUT_OF_MEMORY: cout << "> There is not enough memory left to execute the command." << endl; break; case GL_STACK_UNDERFLOW: cout << "> An attempt has been made to perform an operation that would cause an internal stack to underflow." << endl; break; case GL_STACK_OVERFLOW: cout << "> An attempt has been made to perform an operation that would cause an internal stack to overflow." << endl; break; default: cout << "> Unknown OpenGL error: " << error << endl; } } void Engine::onRenderTick(float lag) { // update projection matrix float tan = tanf((0.5f * fovY) * M_PI / 180.0f); float q = 1.0f / tan; float aspect = (float) width / height; projMatrix.set(0, 0, q / aspect); projMatrix.set(1, 1, q); projMatrix.set(2, 2, (nearClip + farClip) / (nearClip - farClip)); projMatrix.set(3, 2, -1.0f); projMatrix.set(2, 3, (2.0f * nearClip * farClip) / (nearClip - farClip)); projMatrix.set(3, 3, 0); // ------------------------------------------------------------------------- // shader stage 1 - world // ------------------------------------------------------------------------- stage = 1; worldShader.preRender(projMatrix.getValues()); // call render tick for further drawing client->render3DTick(lag); // ------------------------------------------------------------------------- // shader stage 2 - world ssao // ------------------------------------------------------------------------- stage = 2; ssaoShader.preRender(projMatrix.getValues()); // bind previously generated texture data buffers worldShader.bindPositionTexture(1); worldShader.bindNormalTexture(2); worldShader.bindColorTexture(3); worldShader.bindDepthTexture(4); ssaoShader.bindNoiseTexture(5); rectangle.draw(); // ------------------------------------------------------------------------- // shader stage 3 - world ssao blur // ------------------------------------------------------------------------- stage = 3; ssaoBlurShader.preRender(); ssaoShader.bindTexture(6); rectangle.draw(); // ------------------------------------------------------------------------- // shader stage 4 - world post // ------------------------------------------------------------------------- stage = 4; worldPostShader.preRender(); ssaoBlurShader.bindTexture(7); rectangle.draw(); // ------------------------------------------------------------------------- // shader stage 5 - 2D overlay // ------------------------------------------------------------------------- stage = 5; overlayShader.preRender(); worldPostShader.bindTexture(0); rectangle.draw(); overlayShader.setViewMatrix(); setMixMode(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); client->render2DTick(lag); glDisable(GL_BLEND); } void Engine::setViewMatrix(const float* data) { if(stage == 1) { worldShader.setViewMatrix(data); } } void Engine::setModelMatrix(const float* data) { if(stage == 1) { worldShader.setModelMatrix(data); } else if(stage == 5) { overlayShader.setModelMatrix(data); } } void Engine::setMouseTrapped(bool mode) { if(mode) { glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); } else { glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } } void Engine::setMixMode() { if(stage == 5) { overlayShader.setUseColor(true); overlayShader.setUseTexture(true); } } void Engine::setColorMode() { if(stage == 5) { overlayShader.setUseColor(true); overlayShader.setUseTexture(false); } } void Engine::setTextureMode() { if(stage == 5) { overlayShader.setUseColor(false); overlayShader.setUseTexture(true); } }