From 6109d35bbd8f45a83f095b9017775c68ff61973f Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Mon, 12 Jul 2021 23:21:00 +0100 Subject: [PATCH] cleanup rendering code --- .gitignore | 1 + demo/imgui.ini | 5 +++ demo/main.cpp | 22 ++++++---- fggl/data/model.hpp | 14 ++++--- fggl/data/procedural.cpp | 41 +++++++++++++++---- fggl/data/procedural.hpp | 4 +- fggl/ecs/ecs.hpp | 23 ++++++++++- fggl/gfx/ogl.cpp | 6 +-- fggl/gfx/ogl.hpp | 6 +-- fggl/gfx/renderer.cpp | 86 +++++++++++++++++++++++++++++++++++----- fggl/gfx/renderer.hpp | 24 ++++++++++- fggl/gfx/shader.cpp | 6 +-- fggl/gfx/window.cpp | 4 +- fggl/gfx/window.hpp | 11 +++-- fggl/math/types.hpp | 9 ++++- tools/RenderDoc.cap | 27 +++++++++++++ 16 files changed, 236 insertions(+), 53 deletions(-) create mode 100644 demo/imgui.ini create mode 100644 tools/RenderDoc.cap diff --git a/.gitignore b/.gitignore index 33de309..cd44b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build/ cmake-build-debug/ +cmake-build-debug-coverage/ .idea/ diff --git a/demo/imgui.ini b/demo/imgui.ini new file mode 100644 index 0000000..4a5c201 --- /dev/null +++ b/demo/imgui.ini @@ -0,0 +1,5 @@ +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + diff --git a/demo/main.cpp b/demo/main.cpp index 0ce8dfd..cece584 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -6,6 +6,7 @@ #include <fggl/gfx/renderer.hpp> #include <fggl/gfx/shader.hpp> #include <fggl/data/procedural.hpp> +#include <fggl/ecs/ecs.hpp> #include <fggl/debug/debug.h> #include <fggl/data/storage.hpp> @@ -30,15 +31,20 @@ int main(int argc, char* argv[]) { config.name = "unlit"; config.vertex = "unlit_vert.glsl"; config.fragment = "unlit_frag.glsl"; - cache.load(config); + auto shader = cache.load(config); - // generate meshes - std::vector<fggl::data::Model> scene; + // create ECS + fggl::ecs::ECS ecs; + ecs.registerComponent<fggl::gfx::MeshToken>(); -/* fggl::data::Model model; - fggl::data::Mesh cube = fggl::data::make_cube(); - model.append(cube); - scene.push_back( model );*/ + // create an entity + auto entity = ecs.createEntity(); + + // in a supprise to no one it's a triangle + auto mesh = fggl::data::make_triangle(); + auto token = meshRenderer.upload(mesh); + token.pipeline = shader; + ecs.addComponent<fggl::gfx::MeshToken>(entity, token); while( !win.closeRequested() ) { ctx.pollEvents(); @@ -48,7 +54,7 @@ int main(int argc, char* argv[]) { // render step ogl.clear(); - meshRenderer.render(scene); + meshRenderer.render(win, ecs); debug.draw(); win.swap(); diff --git a/fggl/data/model.hpp b/fggl/data/model.hpp index 6e7a7a7..ed67945 100644 --- a/fggl/data/model.hpp +++ b/fggl/data/model.hpp @@ -24,7 +24,7 @@ namespace fggl::data { * be added to the indicies list. */ inline void push(Vertex vert) { - int idx = indexOf(vert); + auto idx = indexOf(vert); if ( idx == -1 ) pushVertex(vert); else @@ -53,10 +53,18 @@ namespace fggl::data { */ int indexOf(Vertex vert); + inline std::vector<Vertex>& vertexList() { + return m_verts; + } + inline std::size_t vertexCount() { return m_verts.size(); } + inline std::vector<uint32_t>& indexList() { + return m_index; + } + inline std::size_t indexCount() { return m_index.size(); } @@ -79,10 +87,6 @@ namespace fggl::data { m_meshes.push_back( mesh ); } - inline const std::vector<Mesh>& meshes() const { - return m_meshes; - } - private: std::vector<Mesh> m_meshes; }; diff --git a/fggl/data/procedural.cpp b/fggl/data/procedural.cpp index 8cf57d3..b762adc 100644 --- a/fggl/data/procedural.cpp +++ b/fggl/data/procedural.cpp @@ -4,7 +4,32 @@ using namespace fggl::data; -constexpr float verts[8 * 3] = { +fggl::data::Mesh fggl::data::make_triangle() { + constexpr fggl::math::vec3 pos[]{ + {-0.5f, -0.5f, 0.0f}, + {0.5f, -0.5f, 0.0f}, + {0.0f, 0.5f, 0.0f} + }; + + // add points + fggl::data::Mesh mesh; + for (auto po : pos) { + Vertex vert{}; + vert.posititon = po; + vert.colour = fggl::math::vec3(1.0f, 1.0f, 1.0f); + mesh.push(vert); + } + + // mesh + for (int i = 0; i < 3; ++i) { + mesh.pushIndex(i); + } + + return mesh; +} + +/* +constexpr float cubeVertex[8 * 3] = { 0, 0, 0, 0, 1, 0, 1, 1, 0, @@ -15,7 +40,7 @@ constexpr float verts[8 * 3] = { 0, 0, 0, }; -constexpr int quads[6 * 4] = { +constexpr int cubeIndexes[6 * 4] = { 7, 6, 5, 4, 0, 1, 2, 3, 6, 7, 3, 2, @@ -77,8 +102,8 @@ void computeNormals(Mesh& mesh) { Mesh fggl::data::make_cube() { - int nVerts = sizeof(verts) / sizeof(verts[0]) / 3; - int nQuads = sizeof(quads) / sizeof(quads[0]) / 4; + int nVerts = sizeof(cubeVertex) / sizeof(cubeVertex[0]) / 3; + int nQuads = sizeof(cubeIndexes) / sizeof(cubeIndexes[0]) / 4; std::vector< unsigned int > vertIndex; @@ -87,16 +112,16 @@ Mesh fggl::data::make_cube() { // stick the vertex data into the mesh for ( int i=0; i < nVerts; i++ ) { fggl::data::Vertex vert; - vert.posititon = fggl::math::vec3( verts[i], verts[i + 1], verts[i+2] ); + vert.posititon = fggl::math::vec3( cubeVertex[i], cubeVertex[i + 1], cubeVertex[i+2] ); - int idx = cube.pushVertex(vert); + auto idx = cube.pushVertex(vert); vertIndex.push_back(idx); } // triagulate the quads - quadsToTris(cube, vertIndex, quads, nQuads); + quadsToTris(cube, vertIndex, cubeIndexes, nQuads); computeNormals(cube); return cube; -} +}*/ diff --git a/fggl/data/procedural.hpp b/fggl/data/procedural.hpp index 2c9ddf7..32b0424 100644 --- a/fggl/data/procedural.hpp +++ b/fggl/data/procedural.hpp @@ -3,5 +3,7 @@ namespace fggl::data { - Mesh make_cube(); + //Mesh make_cube(); + + Mesh make_triangle(); } diff --git a/fggl/ecs/ecs.hpp b/fggl/ecs/ecs.hpp index 077cd12..e62b683 100644 --- a/fggl/ecs/ecs.hpp +++ b/fggl/ecs/ecs.hpp @@ -163,7 +163,7 @@ namespace fggl::ecs { void removeComponent(const entity_t& entityId); template<class C> - bool hasComponent(const entity_t& entityId) { + bool hasComponent(const entity_t& entityId) const { const component_type_t componentID = Component<C>::typeID(); const auto* arch = m_entityArchtypes.at(entityId).archetype; if ( arch == nullptr ) { @@ -192,8 +192,27 @@ namespace fggl::ecs { return nullptr; } + template<class C> + const C* getComponent(const entity_t& entityId) const { + assert( hasComponent<C>( entityId ) ); + const auto type = Component<C>::typeID(); + + const ComponentBase* const newComp = m_componentMap.at(type); + const auto record = m_entityArchtypes.at(entityId); + const auto* arch = record.archetype; + + // JWR: linear search... seems a little suspect, they're ordered after all + for ( std::size_t i=0; i < arch->type.size(); ++i ) { + if ( arch->type[i] == type ) { + return reinterpret_cast<C*>(& (arch->data[i][ record.index * newComp->size() ]) ); + } + } + + return nullptr; + } + template<class... Cs> - std::vector<entity_t> getEntityWith() { + std::vector<entity_t> getEntityWith() const { // construct the key archToken_t key; (key.push_back( Component<Cs>::typeID() ), ...); diff --git a/fggl/gfx/ogl.cpp b/fggl/gfx/ogl.cpp index f283956..8e84cca 100644 --- a/fggl/gfx/ogl.cpp +++ b/fggl/gfx/ogl.cpp @@ -4,7 +4,7 @@ using namespace fggl::gfx; -Graphics::Graphics(Window& window) : m_window(window) { +GlGraphics::GlGraphics(Window& window) : m_window(window) { window.activate(); GLenum err = glewInit(); if ( GLEW_OK != err ) { @@ -12,10 +12,10 @@ Graphics::Graphics(Window& window) : m_window(window) { } } -Graphics::~Graphics() { +GlGraphics::~GlGraphics() { } -void Graphics::clear() { +void GlGraphics::clear() { m_window.activate(); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); diff --git a/fggl/gfx/ogl.hpp b/fggl/gfx/ogl.hpp index 58c417e..0cc7138 100644 --- a/fggl/gfx/ogl.hpp +++ b/fggl/gfx/ogl.hpp @@ -12,10 +12,10 @@ */ namespace fggl::gfx { - class Graphics { + class GlGraphics { public: - Graphics(Window& window); - ~Graphics(); + GlGraphics(Window& window); + ~GlGraphics(); void clear(); diff --git a/fggl/gfx/renderer.cpp b/fggl/gfx/renderer.cpp index 96be4dc..9d90911 100644 --- a/fggl/gfx/renderer.cpp +++ b/fggl/gfx/renderer.cpp @@ -1,21 +1,85 @@ #include <fggl/gfx/renderer.hpp> #include <fggl/gfx/rendering.hpp> +#include <fggl/data/model.hpp> + +/** + * Future optimisations: + * recommended approach is to group stuff in to as few vao as possible - this will do one vao per mesh, aka bad. + * Add support for instanced rendering (particles) + * Look at packing vertex data in better ways (with profiling) + * Support shader specialisation (ie, dynamic/streamed data) + * Follow best recommendations for Vertex attributes (ie normals not using floats, bytes for colour) + * + * Future features: + * Add support for models with weights (for animations) + * OpenGL ES for the FOSS thinkpad users who can't run anything even remotely modern + */ + + using namespace fggl::gfx; -static void gl_render_mesh(GLuint pid, GLuint vao, GLuint idxc) { - glUseProgram(pid); - glBindVertexArray(vao); - glDrawElements(GL_TRIANGLES, idxc, GL_UNSIGNED_INT, (void*)0); +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(); glBindVertexArray(0); + + return token; } -void MeshRenderer::render(const std::vector<data::Model> &models) { - for ( auto& model : models ) { - for ( auto& mesh : model.meshes() ) { - //TODO gl reneder mesh - //gl_render_mesh(); - } - } +void MeshRenderer::render(const Window& window, const fggl::ecs::ECS& ecs) { + auto entities = ecs.getEntityWith<GlRenderToken>(); + + // nothing to render, pointless doing anything else + if ( entities.size() == 0) return; + + // make sure the correct rendering context is active + window.activate(); + + // TODO better performance if grouped by vao first + // TODO the nvidia performance presentation said I shouldn't use uniforms for large data + for ( auto& entity : entities ) { + const auto& mesh = ecs.getComponent<GlRenderToken>(entity); + + glBindVertexArray( mesh->vao ); + glDrawElements( GL_TRIANGLES, mesh->idxSize, GL_UNSIGNED_INT, reinterpret_cast<void*>(mesh->idxOffset) ); + } + + glBindVertexArray(0); + } diff --git a/fggl/gfx/renderer.hpp b/fggl/gfx/renderer.hpp index 69868bd..c8fcb60 100644 --- a/fggl/gfx/renderer.hpp +++ b/fggl/gfx/renderer.hpp @@ -4,14 +4,34 @@ #include <vector> #include <fggl/data/model.hpp> +#include <fggl/ecs/ecs.hpp> +#include <fggl/gfx/ogl.hpp> namespace fggl::gfx { - class MeshRenderer { + struct GlRenderToken { + GLuint vao; + GLuint buffs[2]; + GLuint idxOffset; + GLuint idxSize; + GLuint pipeline; + }; + + class GlMeshRenderer { public: - void render(const std::vector<data::Model>& models); + using token_t = GlRenderToken; + + token_t upload(fggl::data::Mesh& mesh); + + // are VAO safe across opengl contexts? :S + void render(const Window& window, const fggl::ecs::ECS& ecs); }; + // specialisation hooks + using Graphics = GlGraphics; + using MeshRenderer = GlMeshRenderer; + using MeshToken = GlRenderToken; + }; #endif diff --git a/fggl/gfx/shader.cpp b/fggl/gfx/shader.cpp index 3876f1f..7aa478c 100644 --- a/fggl/gfx/shader.cpp +++ b/fggl/gfx/shader.cpp @@ -129,7 +129,7 @@ GLuint ShaderCache::load(const ShaderConfig& config) { // get the error char infoLog[512]; - glGetProgramInfoLog(pid, 512, NULL, infoLog); + glGetProgramInfoLog(pid, 512, nullptr, infoLog); std::cerr << infoLog << std::endl; // cleanup @@ -177,7 +177,7 @@ bool ShaderCache::cacheLoad(GLuint pid, const BinaryCache* cache) { template<> bool fggl::data::fggl_deserialize(std::filesystem::path &data, fggl::gfx::BinaryCache *out) { auto f = std::fopen( data.c_str(), "r"); - if ( f == NULL ) { + if ( f == nullptr ) { std::cerr << "fp was null..." << std::endl; return false; } @@ -229,7 +229,7 @@ bool fggl::data::fggl_serialize(std::filesystem::path &data, const fggl::gfx::Bi // try and write auto f = std::fopen( data.c_str(), "w"); - if ( f == NULL ){ + if ( f == nullptr ){ return false; } diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp index 180444c..dd27fba 100644 --- a/fggl/gfx/window.cpp +++ b/fggl/gfx/window.cpp @@ -31,7 +31,7 @@ void Context::pollEvents() { } Window::Window() : m_window(nullptr), m_input(nullptr) { - m_window = glfwCreateWindow(1920, 1080, "main", NULL, NULL); + m_window = glfwCreateWindow(1920, 1080, "main", nullptr, nullptr); glfwSetWindowUserPointer(m_window, this); } @@ -42,7 +42,7 @@ Window::~Window() { } } -void Window::activate() { +void Window::activate() const { assert( m_window != nullptr ); glfwMakeContextCurrent(m_window); } diff --git a/fggl/gfx/window.hpp b/fggl/gfx/window.hpp index 66bf9e1..0afc6f1 100644 --- a/fggl/gfx/window.hpp +++ b/fggl/gfx/window.hpp @@ -45,10 +45,11 @@ namespace fggl::gfx { ~Window(); // window <-> opengl stuff - void activate(); + void activate() const; void swap(); // window manager stuff + [[nodiscard]] inline bool closeRequested() const { assert( m_window != nullptr ); return glfwWindowShouldClose(m_window); @@ -59,9 +60,10 @@ namespace fggl::gfx { glfwSetWindowTitle( m_window, title.c_str() ); } + [[nodiscard]] inline bool fullscreen() const { assert( m_window != nullptr ); - return glfwGetWindowMonitor( m_window ) != NULL; + return glfwGetWindowMonitor( m_window ) != nullptr; } inline void fullscreen(bool state) { @@ -74,16 +76,18 @@ namespace fggl::gfx { mode->width, mode->height, mode->refreshRate); } else { - glfwSetWindowMonitor( m_window, NULL, + glfwSetWindowMonitor( m_window, nullptr, 0, 0, 800, 600, 0); } } + [[nodiscard]] inline bool checkHint(WindowHint hint) const { return check_hint(hint); } + [[nodiscard]] inline bool checkHint(MutWindowHint hint) const { return check_hint(hint); } @@ -115,6 +119,7 @@ namespace fggl::gfx { glfwSetWindowAttrib( m_window, hint, state ); } + [[nodiscard]] inline bool check_hint(int hint) const { assert( m_window != nullptr ); return glfwGetWindowAttrib( m_window, hint) == GLFW_TRUE; diff --git a/fggl/math/types.hpp b/fggl/math/types.hpp index ae53de8..d3c5b6e 100644 --- a/fggl/math/types.hpp +++ b/fggl/math/types.hpp @@ -21,18 +21,21 @@ namespace fggl::math { struct Transform { - Transform() : m_local(1.0f), m_origin(0.0f), m_rotation() { + Transform() : m_local(1.0f), m_origin(0.0f), m_model(), m_rotation() { } // local reference vectors + [[nodiscard]] inline vec3 up() const { return vec4( UP, 1.0 ) * m_local; } + [[nodiscard]] inline vec3 forward() const { return vec4( FORWARD, 1.0 ) * m_local; } + [[nodiscard]] inline vec3 right() const { return vec4( RIGHT, 1.0 ) * m_local; } @@ -46,7 +49,8 @@ namespace fggl::math { m_origin = pos; update(); } - + + [[nodiscard]] inline vec3 origin() const { return m_origin; } @@ -56,6 +60,7 @@ namespace fggl::math { update(); } + [[nodiscard]] inline glm::vec3 euler() const { return glm::eulerAngles(m_rotation); } diff --git a/tools/RenderDoc.cap b/tools/RenderDoc.cap new file mode 100644 index 0000000..acbc557 --- /dev/null +++ b/tools/RenderDoc.cap @@ -0,0 +1,27 @@ +{ + "rdocCaptureSettings": 1, + "settings": { + "autoStart": false, + "commandLine": "", + "environment": [ + ], + "executable": "/home/webpigeon/Documents/gamedev/projects-cmake/build/demo/FgglDemo", + "inject": false, + "numQueuedFrames": 0, + "options": { + "allowFullscreen": true, + "allowVSync": true, + "apiValidation": false, + "captureAllCmdLists": false, + "captureCallstacks": false, + "captureCallstacksOnlyDraws": false, + "debugOutputMute": true, + "delayForDebugger": 0, + "hookIntoChildren": false, + "refAllResources": false, + "verifyBufferAccess": false + }, + "queuedFrameCap": 0, + "workingDir": "" + } +} -- GitLab