diff --git a/demo/main.cpp b/demo/main.cpp index 60d60a9a8a9ac68cc6cd7f2c30f6890deee5527a..820e84875d8787f9be4dd44637e8adc2a313afd3 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -366,15 +366,8 @@ int main(int argc, const char* argv[]) { // FIXME: janky API(s) auto& locator = fggl::util::ServiceLocator::instance(); - - auto inputs = std::make_shared<fggl::input::Input>(); - locator.supply<fggl::input::Input>(inputs); - - auto storage = std::make_shared<fggl::data::Storage>(); - locator.supply<fggl::data::Storage>(storage); - - std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images; - auto atlas = fggl::gfx::ImageAtlas<char>::pack(images); + auto inputs = locator.supply<fggl::input::Input>(std::make_shared<fggl::input::Input>()); + auto storage = locator.supply<fggl::data::Storage>(std::make_shared<fggl::data::Storage>()); // Would be nice to not take args like this, it messes with lifetimes auto& windowing = app.use<fggl::gfx::ecsGlfwModule>(inputs); @@ -385,62 +378,16 @@ int main(int argc, const char* argv[]) { window->fullscreen( true ); app.setWindow( std::move(window) ); - // and now our states - auto menu = app.add_state<fggl::scenes::BasicMenu>("menu"); + // atlas testing + std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images; + auto *atlas = fggl::gfx::ImageAtlas<char>::pack(images); + + // and now our states + auto *menu = app.add_state<fggl::scenes::BasicMenu>("menu"); menu->add("start", [&app]() { app.change_state("game"); }); // game state app.add_state<GameScene>("game"); - - // debug layer - //std::shared_ptr<fggl::debug::DebugUI> debug = std::make_shared<fggl::debug::DebugUI>(window); - //locator.supply<fggl::debug::DebugUI>(debug); - //debug->addWindow("gamepad", gamepadDebug); - //debug->addWindow("imgui-demo", ImGui::ShowDemoWindow); - //debug->addWindow("imgui-about", ImGui::ShowAboutWindow); - //debug->addWindow("imgui-help", [](bool* val) { ImGui::ShowUserGuide(); } ); - //debug->visible(true); - - // Scene management - //auto scenes = std::make_shared<fggl::scenes::SceneManager>(); - //locator.supply<fggl::scenes::SceneManager>(scenes); - //scenes->create("main_menu", std::make_shared<MenuScene>(inputs)); - //scenes->create("game", std::make_shared<GameScene>(ecs, inputs)); - //scenes->activate("main_menu"); - - /* - // Main game/event loop - fggl::util::Timer time{}; - time.frequency( glfwGetTimerFrequency() ); - time.setup( glfwGetTimerValue() ); - - while( !window->closeRequested() ) { - // - // Setup setup - // - time.tick( glfwGetTimerValue() ); - inputs->frame( time.delta() ); - - glfwModule->context.pollEvents(); - debug->frameStart(); - - // - // update step - // - scenes->update(); - - // render the scene - window->activate(); - glModule->ogl.clear(); - - // allow the scene to do stuff, then actually render - scenes->render(); - - debug->draw(); - window->swap(); - } - */ - return app.run(argc, argv); } diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index 5cefcc84e32cfb878cf60a226addec327e622612..244f3bf49760ca97e5cb1579e377fc9c6ca215cf 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -8,7 +8,6 @@ endif() target_sources(${PROJECT_NAME} PRIVATE - fggl.cpp app.cpp ecs/ecs.cpp data/model.cpp diff --git a/fggl/app.cpp b/fggl/app.cpp index 8f3e3d65a17f8ac02c3a28d4dc287a98d1e6f1a6..c74cea5a3c356ee3f41eb52f6689eb76acf32561 100644 --- a/fggl/app.cpp +++ b/fggl/app.cpp @@ -18,6 +18,7 @@ namespace fggl { m_running(true), m_types(std::make_unique<ecs3::TypeRegistry>()), m_modules(std::make_unique<ecs3::ModuleManager>(*m_types)), + m_window(nullptr), m_states() {} int App::run(int argc, const char** argv) { diff --git a/fggl/fggl.cpp b/fggl/fggl.cpp deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/fggl/fggl.hpp b/fggl/fggl.hpp deleted file mode 100644 index d734444d30d0c714bd53870118c74efe5c638cdd..0000000000000000000000000000000000000000 --- a/fggl/fggl.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef FGGL_H -#define FGGL_H - -#include <fggl/gfx/display.hpp> - -#endif diff --git a/fggl/gfx/input.cpp b/fggl/gfx/input.cpp index 8be1ecb74105ce13eb86a714d53b6db0ba5c66ba..ef7329fa0498e9936370e0bed47252ca425444b4 100644 --- a/fggl/gfx/input.cpp +++ b/fggl/gfx/input.cpp @@ -23,8 +23,8 @@ void Input::clear() { for ( auto& pad : m_pad_curr ) { pad.present = false; pad.buttons.reset(); - for (int i=0; i<6; i++){ - pad.axes[i] = 0.0f; + for (float & axe : pad.axes){ + axe = 0.0F; } } m_pad_last = m_pad_curr; diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index 4a0f5708a453bc52404340ceabb2905e32a2abb2..b4d18e8a3c37a0a10dbdc7d28e546de1a975613a 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -12,6 +12,88 @@ #include <glm/gtc/type_ptr.hpp> #include <memory> +extern "C" { + +/** + * Convert an OpenGL source enum to a string. + * + * list of sources taken from table 20.1, GL Spec 4.5 + * @param source + * @return string representing the source + */ +constexpr auto fggl_ogl_source(GLenum source) -> const char * { + switch (source) { + case GL_DEBUG_SOURCE_API: return "GL"; + case GL_DEBUG_SOURCE_SHADER_COMPILER: return "GLSL compiler"; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: return "Windowing System"; + case GL_DEBUG_SOURCE_THIRD_PARTY: return "Third Party"; + case GL_DEBUG_SOURCE_APPLICATION: return "Application"; + default: + case GL_DEBUG_SOURCE_OTHER: return "Other"; + } + assert(false); + return "unknown"; +} + +/** + * Convert an OpenGL type enum to a string. + * + * list of sources taken from table 20.2, GL Spec 4.5 + * @param type + * @return string representing the type + */ +constexpr auto static fggl_ogl_type(GLenum type) -> const char * { + switch (type) { + case GL_DEBUG_TYPE_ERROR: return "Error"; + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "Deprecated Behaviour"; + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "Undefined Behaviour"; + break; + case GL_DEBUG_TYPE_PERFORMANCE: return "Performance"; + break; + case GL_DEBUG_TYPE_PORTABILITY: return "Portability"; + break; + case GL_DEBUG_TYPE_MARKER: return "Marker"; + break; + case GL_DEBUG_TYPE_PUSH_GROUP: return "Push Group"; + break; + case GL_DEBUG_TYPE_POP_GROUP: return "Pop Group"; + break; + default: + case GL_DEBUG_TYPE_OTHER: return "Other"; + break; + } + assert(false); + return "unknown"; +} + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "bugprone-easily-swappable-parameters" +void static fggl_ogl_to_spdlog(GLenum source, GLenum type, unsigned int msgId, GLenum severity, GLsizei /*length*/, + const char *message, const void * /*userParam*/) { + std::string fmt = "[GL] {}, {}: [{}]: {}"; + + const auto *const sourceStr = fggl_ogl_source(source); + const auto *const typeStr = fggl_ogl_type(type); + + // table 20.3, GL spec 4.5 + switch (severity) { + case GL_DEBUG_SEVERITY_HIGH: spdlog::error(fmt, sourceStr, typeStr, msgId, message); + break; + default: + case GL_DEBUG_SEVERITY_MEDIUM: spdlog::warn(fmt, sourceStr, typeStr, msgId, message); + break; + case GL_DEBUG_SEVERITY_LOW: spdlog::info(fmt, sourceStr, typeStr, msgId, message); + break; + case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, msgId, message); + break; + } +} +#pragma clang diagnostic pop + +} + /** * Future optimisations: * recommended approach is to group stuff in to as few vao as possible - this @@ -28,245 +110,271 @@ */ namespace fggl::gfx { -using data::Mesh2D; -using data::Vertex2D; - -GlRenderToken setupVertex2D() { - GlRenderToken token{}; - glGenVertexArrays(1, &token.vao); - glBindVertexArray(token.vao); - - glGenBuffers(2, token.buffs); - - glBindBuffer(GL_ARRAY_BUFFER, token.buffs[0]); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), - reinterpret_cast<void*>(offsetof(Vertex2D, position))); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), - reinterpret_cast<void*>(offsetof(Vertex2D, colour))); - - glBindVertexArray(0); - return token; -} - -OpenGL4Backend::OpenGL4Backend(const Window& owner) : fggl::gfx::Graphics() { - // initialise GLEW, or fail - GLenum err = glewInit(); - if (GLEW_OK != err) { - throw std::runtime_error("couldn't init glew"); - } - - // setup the viewport based on the window's framebuffer - auto fbSize = owner.frameSize(); - glViewport(0, 0, fbSize.x, fbSize.y); - - // setup the shader cache - auto& locator = util::ServiceLocator::instance(); - auto storage = locator.get<data::Storage>(); - m_cache = std::make_unique<ShaderCache>(storage); - - // setup 2D rendering system - m_token2D = setupVertex2D(); - ShaderConfig shader2DConfig = ShaderFromName("shader2D"); - m_cache->load(shader2DConfig); -}; - -void OpenGL4Backend::clear() { - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -} - -static void generateMesh(const gfx::Paint& paint, Mesh2D& mesh) { - for (auto& cmd : paint.cmds()) { - auto path = cmd.path; - - std::vector<data::Vertex2D> verts; - math::vec3 colour{1.0f, 1.0f, 1.0f}; - auto idx = 0; - auto colourIdx = 0; - - for (auto& type : path.m_types) { - if (type == PathType::PATH) { - verts.push_back({.position = path.m_points[idx++], .colour = colour}); - } else if (type == PathType::MOVE) { - // polygon finished - if (verts.size() < 3) { - // empty, point, or line - } else if (verts.size() == 3) { - // triangle - } else { - // polygon - math::fanTriangulation(verts, mesh); - } - - verts.clear(); - verts.push_back({.position = path.m_points[idx++], .colour = colour}); - } else if (type == PathType::COLOUR) { - colour = path.m_colours[colourIdx++]; - } else { - // unsupported type - } - } - - if (!verts.empty()) { - math::fanTriangulation(verts, mesh); - } - } -} - -void OpenGL4Backend::draw2D(const gfx::Paint& paint) { - // generate the mesh from a paint command list - data::Mesh2D mesh; - generateMesh(paint, mesh); - - // render the resulting mesh - auto shader2D = m_cache->get("shader2D"); - - glUseProgram(shader2D); - - auto projMat = glm::ortho(0.0f, 1920.0f, 0.0f, 1080.f); - glUniformMatrix4fv(glGetUniformLocation(shader2D, "projection"), 1, GL_FALSE, - glm::value_ptr(projMat)); - - glBindVertexArray(m_token2D.vao); - - glBindBuffer(GL_ARRAY_BUFFER, m_token2D.buffs[0]); - glBufferData(GL_ARRAY_BUFFER, mesh.vertexList.size() * sizeof(Vertex2D), - mesh.vertexList.data(), GL_DYNAMIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_token2D.buffs[1]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, - mesh.indexList.size() * sizeof(uint32_t), mesh.indexList.data(), - GL_DYNAMIC_DRAW); - - glDrawElements(GL_TRIANGLES, mesh.indexList.size(), GL_UNSIGNED_INT, - (GLvoid*)0); - - glBindVertexArray(0); - glUseProgram(0); - // glDisable(GL_PRIMITIVE_RESTART); -} - -template <typename T> -static GLuint createArrayBuffer(std::vector<T>& vertexData) { - GLuint buffId; - glGenBuffers(1, &buffId); - glBindBuffer(GL_ARRAY_BUFFER, buffId); - glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(T), - vertexData.data(), GL_STATIC_DRAW); - return buffId; -} - -static GLuint createIndexBuffer(std::vector<uint32_t>& indexData) { - GLuint buffId; - glGenBuffers(1, &buffId); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffId); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.size() * sizeof(uint32_t), - indexData.data(), GL_STATIC_DRAW); - return buffId; -} - -GlRenderToken MeshRenderer::upload(fggl::data::Mesh& mesh) { - GlRenderToken token{}; - glGenVertexArrays(1, &token.vao); - glBindVertexArray(token.vao); - - token.buffs[0] = createArrayBuffer<fggl::data::Vertex>(mesh.vertexList()); - glEnableVertexAttribArray(0); - glVertexAttribPointer( - 0, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex), - reinterpret_cast<void*>(offsetof(fggl::data::Vertex, posititon))); - glEnableVertexAttribArray(1); - glVertexAttribPointer( - 1, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex), - reinterpret_cast<void*>(offsetof(fggl::data::Vertex, normal))); - - token.buffs[1] = createIndexBuffer(mesh.indexList()); - token.idxOffset = 0; - token.idxSize = mesh.indexCount(); - token.restartVertex = mesh.restartVertex; - glBindVertexArray(0); - - return token; -} + using data::Mesh2D; + using data::Vertex2D; + + GlRenderToken setupVertex2D() { + GlRenderToken token{}; + glGenVertexArrays(1, &token.vao); + glBindVertexArray(token.vao); + + glGenBuffers(2, token.buffs); + + glBindBuffer(GL_ARRAY_BUFFER, token.buffs[0]); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), + reinterpret_cast<void *>(offsetof(Vertex2D, position))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), + reinterpret_cast<void *>(offsetof(Vertex2D, colour))); + + glBindVertexArray(0); + return token; + } + + OpenGL4Backend::OpenGL4Backend(const Window &owner) : fggl::gfx::Graphics() { + // initialise GLEW, or fail + GLenum err = glewInit(); + if (GLEW_OK != err) { + throw std::runtime_error("couldn't init glew"); + } + + // OpenGL debug Support + GLint flags = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &flags); + if ( (flags & GL_CONTEXT_FLAG_DEBUG_BIT) == GL_CONTEXT_FLAG_DEBUG_BIT) { // NOLINT(hicpp-signed-bitwise) + spdlog::info("enabling OpenGL debug output"); + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + glDebugMessageCallback(fggl_ogl_to_spdlog, nullptr); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); + } + + // setup the viewport based on the window's framebuffer + auto fbSize = owner.frameSize(); + glViewport(0, 0, fbSize.x, fbSize.y); + + // setup the shader cache + auto &locator = util::ServiceLocator::instance(); + auto storage = locator.get<data::Storage>(); + m_cache = std::make_unique<ShaderCache>(storage); + + // setup 2D rendering system + m_token2D = setupVertex2D(); + ShaderConfig shader2DConfig = ShaderFromName("shader2D"); + m_cache->load(shader2DConfig); + + }; + + void OpenGL4Backend::clear() { + glClearColor(0.0F, 0.0F, 0.0F, 0.0F); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + inline static void add_mesh_triangle(data::Mesh2D& mesh, const std::vector<data::Vertex2D>& verts) { + assert( verts.size() == 3); + for( const auto& vert : verts ) { + auto idx = mesh.add_vertex(vert); + mesh.add_index(idx); + } + } + + static void generateMesh(const gfx::Paint &paint, Mesh2D &mesh) { + for (const auto &cmd : paint.cmds()) { + auto path = cmd.path; + + std::vector<data::Vertex2D> verts; + math::vec3 colour{1.0F, 1.0F, 1.0F}; + auto idx = 0; + auto colourIdx = 0; + + for (auto &type : path.m_types) { + if (type == PathType::PATH) { + verts.push_back({.position = path.m_points[idx++], .colour = colour}); + } else if (type == PathType::MOVE) { + // polygon finished + if (verts.size() < 3) { + // empty, point, or line + // TODO deal with whatever I'm meant to do with this... + } else if (verts.size() == 3) { + // triangle + add_mesh_triangle(mesh, verts); + } else { + // polygon + math::fanTriangulation(verts, mesh); + } + + verts.clear(); + verts.push_back({.position = path.m_points[idx++], .colour = colour}); + } else if (type == PathType::COLOUR) { + colour = path.m_colours[colourIdx++]; + } else { + // unsupported type + } + } + + if (!verts.empty()) { + math::fanTriangulation(verts, mesh); + } + } + } + + void OpenGL4Backend::draw2D(const gfx::Paint &paint) { + // generate the mesh from a paint command list + data::Mesh2D mesh; + generateMesh(paint, mesh); + + // render the resulting mesh + auto shader2D = m_cache->get("shader2D"); + + glUseProgram(shader2D); + + auto projMat = glm::ortho(0.0f, 1920.0f, 0.0f, 1080.f); + glUniformMatrix4fv(glGetUniformLocation(shader2D, "projection"), 1, GL_FALSE, + glm::value_ptr(projMat)); + + glBindVertexArray(m_token2D.vao); + + glBindBuffer(GL_ARRAY_BUFFER, m_token2D.buffs[0]); + glBufferData(GL_ARRAY_BUFFER, mesh.vertexList.size() * sizeof(Vertex2D), + mesh.vertexList.data(), GL_DYNAMIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_token2D.buffs[1]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, + mesh.indexList.size() * sizeof(uint32_t), mesh.indexList.data(), + GL_DYNAMIC_DRAW); + + glDrawElements(GL_TRIANGLES, mesh.indexList.size(), GL_UNSIGNED_INT, + (GLvoid *) 0); + + glBindVertexArray(0); + glUseProgram(0); + // glDisable(GL_PRIMITIVE_RESTART); + } + + void OpenGL4Backend::resize(int width, int height) { + glViewport(0, 0, width, height); + } + + template<typename T> + static GLuint createArrayBuffer(std::vector<T> &vertexData) { + GLuint buffId; + glGenBuffers(1, &buffId); + glBindBuffer(GL_ARRAY_BUFFER, buffId); + glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(T), + vertexData.data(), GL_STATIC_DRAW); + return buffId; + } + + static GLuint createIndexBuffer(std::vector<uint32_t> &indexData) { + GLuint buffId; + glGenBuffers(1, &buffId); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffId); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.size() * sizeof(uint32_t), + indexData.data(), GL_STATIC_DRAW); + return buffId; + } + + GlRenderToken MeshRenderer::upload(fggl::data::Mesh &mesh) { + GlRenderToken token{}; + glGenVertexArrays(1, &token.vao); + glBindVertexArray(token.vao); + + token.buffs[0] = createArrayBuffer<fggl::data::Vertex>(mesh.vertexList()); + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex), + reinterpret_cast<void *>(offsetof(fggl::data::Vertex, posititon))); + glEnableVertexAttribArray(1); + glVertexAttribPointer( + 1, 3, GL_FLOAT, GL_FALSE, sizeof(fggl::data::Vertex), + reinterpret_cast<void *>(offsetof(fggl::data::Vertex, normal))); + + token.buffs[1] = createIndexBuffer(mesh.indexList()); + token.idxOffset = 0; + token.idxSize = mesh.indexCount(); + token.restartVertex = mesh.restartVertex; + glBindVertexArray(0); + + return token; + } // TODO(webpigeon): this shouldn't be hard-coded -constexpr glm::vec3 DEFAULT_LIGHTPOS = glm::vec3(20.0F, 20.0F, 15.0F); - -void MeshRenderer::render(fggl::ecs3::World& ecs, ecs3::entity_t camera, - float dt) { - if (camera == ecs::NULL_ENTITY) { - spdlog::warn("tried to render a scene, but no camera exists!"); - return; - } - - auto entities = ecs.findMatching<GlRenderToken>(); - if (entities.empty()) { - spdlog::warn("asked to render, but no entities are renderable"); - return; - } - - total += dt; - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - glEnable(GL_DEPTH_TEST); - - // camera logic - auto* const camTransform = ecs.get<math::Transform>(camera); - auto* const camComp = ecs.get<gfx::Camera>(camera); - glm::mat4 proj = glm::perspective(camComp->fov, camComp->aspectRatio, - camComp->nearPlane, camComp->farPlane); - glm::mat4 view = - glm::lookAt(camTransform->origin(), camComp->target, camTransform->up()); - - // lighting - glm::vec3 lightPos = DEFAULT_LIGHTPOS; - - // TODO(webpigeon): better performance if grouped by vao first - // TODO(webpigeon): the nvidia performance presentation said I shouldn't use - // uniforms for large data - for (auto& entity : entities) { - const auto& transform = ecs.get<fggl::math::Transform>(entity); - const auto& mesh = ecs.get<GlRenderToken>(entity); - - glm::mat4 model = transform->model(); - // model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f), - //glm::vec3(0.0f,1.0f,0.0f)); - - auto shader = mesh->pipeline; - glUseProgram(shader); - - glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE, - glm::value_ptr(model)); - glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE, - glm::value_ptr(view)); - glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE, - glm::value_ptr(proj)); - - // lighting - GLint lightID = glGetUniformLocation(shader, "lightPos"); - if (lightID != -1) { - glUniform3fv(lightID, 1, glm::value_ptr(lightPos)); - } - - glBindVertexArray(mesh->vao); - - if (mesh->renderType == GlRenderType::triangle_strip) { - glEnable(GL_PRIMITIVE_RESTART); - glPrimitiveRestartIndex(mesh->restartVertex); - } - - glDrawElements(mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT, - reinterpret_cast<void*>(mesh->idxOffset)); - if (mesh->renderType == GlRenderType::triangle_strip) { - glDisable(GL_PRIMITIVE_RESTART); - } - } - - glBindVertexArray(0); -} + constexpr glm::vec3 DEFAULT_LIGHTPOS = glm::vec3(20.0F, 20.0F, 15.0F); + + void MeshRenderer::render(fggl::ecs3::World &ecs, ecs3::entity_t camera, + float dt) { + if (camera == ecs::NULL_ENTITY) { + spdlog::warn("tried to render a scene, but no camera exists!"); + return; + } + + auto entities = ecs.findMatching<GlRenderToken>(); + if (entities.empty()) { + spdlog::warn("asked to render, but no entities are renderable"); + return; + } + + total += dt; + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + glEnable(GL_DEPTH_TEST); + + // camera logic + auto *const camTransform = ecs.get<math::Transform>(camera); + auto *const camComp = ecs.get<gfx::Camera>(camera); + glm::mat4 proj = glm::perspective(camComp->fov, camComp->aspectRatio, + camComp->nearPlane, camComp->farPlane); + glm::mat4 view = + glm::lookAt(camTransform->origin(), camComp->target, camTransform->up()); + + // lighting + glm::vec3 lightPos = DEFAULT_LIGHTPOS; + + // TODO(webpigeon): better performance if grouped by vao first + // TODO(webpigeon): the nvidia performance presentation said I shouldn't use + // uniforms for large data + for (auto &entity : entities) { + const auto &transform = ecs.get<fggl::math::Transform>(entity); + const auto &mesh = ecs.get<GlRenderToken>(entity); + + glm::mat4 model = transform->model(); + // model = glm::rotate(model, glm::radians(total/2048.0f * 360.0f), + //glm::vec3(0.0f,1.0f,0.0f)); + + auto shader = mesh->pipeline; + glUseProgram(shader); + + glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE, + glm::value_ptr(model)); + glUniformMatrix4fv(glGetUniformLocation(shader, "view"), 1, GL_FALSE, + glm::value_ptr(view)); + glUniformMatrix4fv(glGetUniformLocation(shader, "projection"), 1, GL_FALSE, + glm::value_ptr(proj)); + + // lighting + GLint lightID = glGetUniformLocation(shader, "lightPos"); + if (lightID != -1) { + glUniform3fv(lightID, 1, glm::value_ptr(lightPos)); + } + + glBindVertexArray(mesh->vao); + + if (mesh->renderType == GlRenderType::triangle_strip) { + glEnable(GL_PRIMITIVE_RESTART); + glPrimitiveRestartIndex(mesh->restartVertex); + } + + glDrawElements(mesh->renderType, mesh->idxSize, GL_UNSIGNED_INT, + reinterpret_cast<void *>(mesh->idxOffset)); + if (mesh->renderType == GlRenderType::triangle_strip) { + glDisable(GL_PRIMITIVE_RESTART); + } + } + + glBindVertexArray(0); + } } // namespace fggl::gfx diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp index a3cd4113041ba35d7f82981a29f33b91375a65ec..0b973be2d7c6484b4dfe61688974c47ff6b9eab0 100644 --- a/fggl/gfx/window.cpp +++ b/fggl/gfx/window.cpp @@ -1,3 +1,4 @@ +#include <utility> #include <fggl/gfx/window.hpp> #include <fggl/gfx/window_input.hpp> @@ -10,68 +11,19 @@ using namespace fggl::gfx; -void APIENTRY glDebugOutput(GLenum source, - GLenum type, - unsigned int id, - GLenum severity, - GLsizei length, - const char *message, - const void *userParam) -{ - // ignore non-significant error/warning codes - if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; - - std::string fmt = "[GL] {}, {}: [{}]: {}"; - std::string sourceStr = "unknown"; - std::string typeStr = "unknown"; - - switch (source) - { - case GL_DEBUG_SOURCE_API: sourceStr = "API"; break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: sourceStr = "Window System"; break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: sourceStr = "Shader Compiler"; break; - case GL_DEBUG_SOURCE_THIRD_PARTY: sourceStr = "Third Party"; break; - case GL_DEBUG_SOURCE_APPLICATION: sourceStr = "Application"; break; - case GL_DEBUG_SOURCE_OTHER: sourceStr = "Other"; break; - } - - switch (type) - { - case GL_DEBUG_TYPE_ERROR: typeStr = "Error"; break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "Deprecated Behaviour"; break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeStr = "Undefined Behaviour"; break; - case GL_DEBUG_TYPE_PORTABILITY: typeStr = "Portability"; break; - case GL_DEBUG_TYPE_PERFORMANCE: typeStr = "Performance"; break; - case GL_DEBUG_TYPE_MARKER: typeStr = "Marker"; break; - case GL_DEBUG_TYPE_PUSH_GROUP: typeStr = "Push Group"; break; - case GL_DEBUG_TYPE_POP_GROUP: typeStr = "Pop Group"; break; - case GL_DEBUG_TYPE_OTHER: typeStr = "Other"; break; - } - - switch (severity) - { - case GL_DEBUG_SEVERITY_HIGH: spdlog::error(fmt, sourceStr, typeStr, id, message); break; - case GL_DEBUG_SEVERITY_MEDIUM: spdlog::warn(fmt, sourceStr, typeStr, id, message); break; - case GL_DEBUG_SEVERITY_LOW: spdlog::info(fmt, sourceStr, typeStr, id, message); break; - case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, id, message); break; - } -} static void glfw_error(int code, const char* description) { spdlog::warn("[GLFW error] {}: {}", code, description); } static void framebuffer_resize(GLFWwindow* window, int width, int height) { - glfwMakeContextCurrent( window ); - glViewport(0, 0, width, height); - - auto fgglWindow = reinterpret_cast<GlfwWindow*>(glfwGetWindowUserPointer( window )); + auto *fgglWindow = static_cast<GlfwWindow*>(glfwGetWindowUserPointer( window )); fgglWindow->framesize( width, height ); } static void fggl_input_cursor(GLFWwindow* window, double x, double y) { auto& input = GlfwInputManager::instance(); - auto fgglWin = (GlfwWindow*)glfwGetWindowUserPointer(window); + auto *fgglWin = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window)); #ifndef FGGL_INPUT_SCREEN_COORDS // convert to nice ranges... @@ -190,45 +142,36 @@ void GlfwContext::pollEvents() { fggl_joystick_poll(); } -GlfwWindow::GlfwWindow() : m_window(nullptr), m_framesize() { +GlfwWindow::GlfwWindow(std::shared_ptr<GlfwContext> context) : m_context(std::move(context)), m_window(nullptr), m_framesize() { spdlog::debug("[glfw] creating window"); + + // FIXME - this ties the graphics API before window creation glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); + glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); m_window = glfwCreateWindow(1920, 1080, "main", nullptr, nullptr); if ( m_window == nullptr ) { return; } - activate(); - auto result = glewInit(); - spdlog::debug("[glfw] glew result: {}", result == GLEW_OK); - - m_framesize = glm::vec2(1920, 1080); glfwSetWindowUserPointer(m_window, this); - glfwSetFramebufferSizeCallback( m_window, framebuffer_resize ); - // input functions + // setup callback wrappers (will invoke the methods via the user ptr above) + glfwSetFramebufferSizeCallback( m_window, framebuffer_resize ); glfwSetScrollCallback(m_window, fggl_input_scroll); glfwSetCursorPosCallback(m_window, fggl_input_cursor); glfwSetMouseButtonCallback(m_window, fggl_input_mouse_btn); glfwSetKeyCallback(m_window, fggl_input_keyboard); - int flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); - if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) { - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(glDebugOutput, "DEBUG MODE"); - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); - } - spdlog::debug("[glfw] window creation complete"); } GlfwWindow::~GlfwWindow() { if ( m_window != nullptr ) { + // prevent dangling pointers + glfwSetWindowUserPointer(m_window, nullptr); glfwDestroyWindow(m_window); m_window = nullptr; } diff --git a/include/fggl/app.hpp b/include/fggl/app.hpp index 6b9732a368003e2213b676645785a12627754fb8..14fb9582c079d3ddc81222e2291277de4a853af7 100644 --- a/include/fggl/app.hpp +++ b/include/fggl/app.hpp @@ -55,6 +55,7 @@ namespace fggl { * @param owner a non-owned reference to the owner of the state. */ explicit AppState(App &owner) : m_owner(owner) {} + virtual ~AppState() = default; /** * Update the underlying model of this state. @@ -92,9 +93,12 @@ namespace fggl { // class is non copy-able App(const App &app) = delete; - App &operator=(App other) = delete; + App(const App &&app) = delete; - inline void setWindow(std::unique_ptr<gfx::Window> &&window) { + App &operator=(const App& other) = delete; + App &operator=(App&& other) = delete; + + inline void setWindow(std::unique_ptr<gfx::Window>&& window) { m_window = std::move(window); } diff --git a/include/fggl/gfx/compat.hpp b/include/fggl/gfx/compat.hpp index 32b766d8d70ab2b0cf87de7090ebedff70bcacff..0dfdb9d20f4f0f7ef7a8eb648b32e775f91de9d5 100644 --- a/include/fggl/gfx/compat.hpp +++ b/include/fggl/gfx/compat.hpp @@ -22,21 +22,21 @@ namespace fggl::gfx { // fake module support - allows us to still RAII // struct ecsGlfwModule : ecs3::Module { - GlfwContext context; + std::shared_ptr<GlfwContext> context; inline explicit - ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context(std::move(inputs)) { + ecsGlfwModule(std::shared_ptr<fggl::input::Input> inputs) : context( std::make_shared<GlfwContext>(std::move(inputs))) { } inline std::unique_ptr<GlfwWindow> createWindow(const std::string &title) { - auto window = std::make_unique<GlfwWindow>(); + std::unique_ptr<GlfwWindow> window = std::make_unique<GlfwWindow>(context); window->title(title); - return window; + return std::move(window); } void onUpdate() override { - context.pollEvents(); + context->pollEvents(); } [[nodiscard]] diff --git a/include/fggl/gfx/ogl/renderer.hpp b/include/fggl/gfx/ogl/renderer.hpp index b4fbb92ab040b0fae29975a9a00d28ed3c75044b..c88e9f36ee2eeacb3d3d7a8da42e21ba01696c0f 100644 --- a/include/fggl/gfx/ogl/renderer.hpp +++ b/include/fggl/gfx/ogl/renderer.hpp @@ -45,6 +45,7 @@ namespace fggl::gfx { ~OpenGL4Backend() override = default; void clear() override; + void resize(int width, int height) override; void draw2D(const Paint &paint) override; diff --git a/include/fggl/gfx/window.hpp b/include/fggl/gfx/window.hpp index 3b24e29f88f45a0f45b2c0841dd79f5cf114ee90..36327b72a374b0191ea082150f01abbab2c31c19 100644 --- a/include/fggl/gfx/window.hpp +++ b/include/fggl/gfx/window.hpp @@ -13,12 +13,17 @@ namespace fggl::gfx { + class GlfwWindow; + class GlfwContext { public: explicit GlfwContext(std::shared_ptr<fggl::input::Input> input); ~GlfwContext(); void pollEvents(); + + private: + std::vector<std::unique_ptr<GlfwWindow>> m_windows; }; enum MutWindowHint { @@ -44,8 +49,9 @@ namespace fggl::gfx { class GlfwWindow : public Window { public: - GlfwWindow(); + GlfwWindow(std::shared_ptr<GlfwContext> context); ~GlfwWindow(); + GlfwWindow(Window &) = delete; GlfwWindow(Window &&) = delete; @@ -71,6 +77,9 @@ namespace fggl::gfx { inline void framesize(int width, int height) { m_framesize = math::vec2(width, height); + if ( m_graphics != nullptr ) { + m_graphics->resize( width, height ); + } } // window manager stuff @@ -136,6 +145,7 @@ namespace fggl::gfx { } private: + std::shared_ptr<GlfwContext> m_context; GLFWwindow *m_window; math::vec2 m_framesize; diff --git a/include/fggl/gfx/windowing.hpp b/include/fggl/gfx/windowing.hpp index 33e9f07ce9ae127c1550922798cfa93b1d53db5c..a307896d9dafc7e2db6650f53d2d4fb23e6a8e0e 100644 --- a/include/fggl/gfx/windowing.hpp +++ b/include/fggl/gfx/windowing.hpp @@ -11,6 +11,7 @@ namespace fggl::gfx { public: virtual ~Graphics() = default; virtual void clear() = 0; + virtual void resize(int width, int height) = 0; virtual void draw2D(const Paint &paint) = 0; }; diff --git a/include/fggl/math/triangulation.hpp b/include/fggl/math/triangulation.hpp index fa7a20f699856f5d1a8a34f90941a997949c618b..11578c88cccddc80d94b1f8e80a1a92a0cbd0396 100644 --- a/include/fggl/math/triangulation.hpp +++ b/include/fggl/math/triangulation.hpp @@ -119,33 +119,14 @@ namespace fggl::math { /** * Fast Triangulation for convex polygons. */ - void fanTriangulation(const Polygon &polygon, data::Mesh2D &mesh) { - assert(polygon.size() >= 3); - - // add the first two points to the mesh - auto firstIdx = mesh.add_vertex(pointToVertex(polygon[0])); - auto prevIdx = mesh.add_vertex(pointToVertex(polygon[1])); - - // deal with the indicies - const auto nTris = polygon.size() - 2; - for (auto i = 0; i < nTris; i++) { - mesh.add_index(firstIdx); - mesh.add_index(prevIdx); - - auto currIdx = mesh.add_vertex(pointToVertex(polygon[i + 2])); - mesh.add_index(currIdx); - prevIdx = currIdx; - } - } - - void fanTriangulation(const PolygonVertex polygon, data::Mesh2D &mesh) { + void fanTriangulation(const PolygonVertex& polygon, data::Mesh2D &mesh) { assert(polygon.size() >= 3); // add the first two points to the mesh auto firstIdx = mesh.add_vertex(polygon[0]); auto prevIdx = mesh.add_vertex(polygon[1]); - // deal with the indicies + // deal with the indices const auto nTris = polygon.size() - 2; for (auto i = 0; i < nTris; i++) { mesh.add_index(firstIdx); diff --git a/include/fggl/util/ownership.hpp b/include/fggl/util/ownership.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b11056da0df293e9cdb490c167cabe0f4d0dd890 --- /dev/null +++ b/include/fggl/util/ownership.hpp @@ -0,0 +1,14 @@ +// +// Created by webpigeon on 17/04/22. +// + +#ifndef FGGL_UTIL_OWNERSHIP_HPP +#define FGGL_UTIL_OWNERSHIP_HPP + +#include <memory> + +namespace fggl::util { + +} // namespace fggl::util + +#endif //FGGL_UTIL_OWNERSHIP_HPP diff --git a/include/fggl/util/service.h b/include/fggl/util/service.h index d7f60442648c46aeb54bac360ad8e65e1b9f1e88..d80b3bd38be6728f416d0cad88a6a78a472813f8 100644 --- a/include/fggl/util/service.h +++ b/include/fggl/util/service.h @@ -29,9 +29,10 @@ namespace fggl::util { } template<typename T> - void supply(std::shared_ptr<T> ptr) { + std::shared_ptr<T> supply(std::shared_ptr<T> ptr) { auto info = std::type_index(typeid(T)); m_services[info] = ptr; + return ptr; } template<typename T>