diff --git a/CMakeLists.txt b/CMakeLists.txt index 976bdc8..0425d66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ if (CMAKE_GENERATOR STREQUAL "MinGW Makefiles") set(CMAKE_C_COMPILER ${MINGW_PATH}/bin/gcc.exe) set(CMAKE_CXX_COMPILER ${MINGW_PATH}/bin/g++.exe) endif() +message(STATUS "using C_Compiler ${CMAKE_C_COMPILER}") +message(STATUS "using CXX_Compiler ${CMAKE_CXX_COMPILER}") project(${NAME} VERSION 0.23.0) diff --git a/README.md b/README.md index b3f250e..a38ab82 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ A video tutorial series introducing computer graphics for [VulkanĀ®](https://www ``` - To Build + ``` cd littleVulkanEngine ./unixBuild.sh @@ -135,7 +136,7 @@ In this tutorial we add specular lighting to our simple fragment shader. #### [27 - Alpha Blending and Transparency](https://github.com/blurrypiano/littleVulkanEngine/tree/tut27) -In this tutorial we add a limited blending capability to our point light system, allowing them to be rendered with a nicer appearance. +In this tutorial we add a limited blending capability to our point light system, allowing them to be rendered with a nicer appearance. ([Video](https://youtu.be/uZqxj6tLDY4)) diff --git a/src/ecs/lve_ecs.hpp b/src/ecs/lve_ecs.hpp new file mode 100644 index 0000000..4a266ed --- /dev/null +++ b/src/ecs/lve_ecs.hpp @@ -0,0 +1,734 @@ +#pragma once + +#include "lve_utils.hpp" + +// std +#include +#include +#include +#include // For std::ptrdiff_t +#include +#include // For std::forward_iterator_tag +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace lve { + +using EntId = unsigned int; +using EntQueryId = int; +using ComponentType = int; // // how to cast any type? + +constexpr EntId NullEntId = 0; +constexpr EntQueryId NullQueryId = 0; + +class EntManager; +class EntQuery; +class EntQueryResult; +class EntQuery__internal { + public: + const size_t hashValue; + + bool operator==(const EntQuery__internal &other) const { + return hashValue == other.hashValue && allOf == other.allOf && anyOf == other.anyOf && + noneOf == other.noneOf; + } + + private: + EntQuery__internal( + size_t hash, + const std::unordered_set allOfTypes, + const std::unordered_set anyOfTypes, + const std::unordered_set noneOfTypes) + : hashValue{hash}, allOf{allOfTypes}, anyOf{anyOfTypes}, noneOf{noneOfTypes} {} + + const std::unordered_set allOf; + const std::unordered_set anyOf; + const std::unordered_set noneOf; + + friend class EntManager; + friend class EntQuery; +}; +} // namespace lve + +namespace std { +template <> +struct hash { + size_t operator()(lve::EntQuery__internal const &query) const { return query.hashValue; } +}; +} // namespace std + +namespace lve { + +template +class PackedSet { + public: + void add(K key) { + assert(!contains(key) && "PackedSet::Add failed: key already in index"); + + indexFromKey[key] = keyFromIndex.size(); + keyFromIndex.push_back(key); + } + + // returns the position of key that was removed and replaced by the last element + size_t removeAndPack(K key) { + assert(contains(key) && "PackedSet::Remove failed: key not in index"); + + size_t availableIndex = indexFromKey[key]; + + // move last element into newly available space + if (availableIndex != keyFromIndex.size() - 1) { + size_t lastElementK = keyFromIndex.back(); + keyFromIndex[availableIndex] = lastElementK; + indexFromKey[lastElementK] = availableIndex; + } + + // then remove last element + keyFromIndex.pop_back(); + indexFromKey.erase(key); + + return availableIndex; + } + + // returns the position of the key that was removed and replaced by the last element + size_t tryRemoveAndPack(K key) { + if (!contains(key)) return -1; + return removeAndPack(key); + } + + void entDestroyed(K entId) { + if (contains(entId)) { + removeAndPack(entId); + } + } + + bool contains(K key) const { return indexFromKey.find(key) != indexFromKey.end(); } + + const std::vector &getKeys() const { return keyFromIndex; } + + size_t getIndexFromEnt(K key) const { return indexFromKey.at(key); } + + private: + std::vector keyFromIndex{}; + std::unordered_map indexFromKey{}; +}; + +// TODO iterating isn't very fast right now, since memory access will typically be pretty random +// except when iterationg over components directly. Performance testing for iterating over an +// EntQueryResult vs direct components is about 10x slower +template +class PackedMap { + public: + V &get(K key) { + assert(keySet.contains(key) && "Get Component called with invalid ent ID"); + return values[keySet.getIndexFromEnt(key)]; + } + + void add(K key) { + assert(!keySet.contains(key) && "Component already added to entity"); + keySet.add(key); + values.push_back(V{}); + } + + void remove(K key) { + assert(keySet.contains(key) && "Cannot remove non-existant component from entity"); + + size_t availableIndex = keySet.removeAndPack(key); + + // move last element into newly available space if not already last position + if (availableIndex != values.size() - 1) { + values[availableIndex] = values.back(); + } + + // then remove last element + values.pop_back(); + } + + bool contains(K key) const { return keySet.contains(key); } + + const std::vector &getKeys() const { return keySet.getKeys(); } + + const PackedSet &getKeySet() const { return keySet; } + + std::vector &getValues() { return values; } + + private: + std::vector values{}; + PackedSet keySet{}; +}; + +class EntManager; +class Ent { + public: + EntId getId() const { return id; }; + template + T &get(); + + private: + Ent(EntId objId, EntManager &manager) : id{objId}, entManager{manager} {} + + // store ComponentType -> T* const (pointer cannot change, but object it points to can + // be modified) + EntId id; + EntManager &entManager; + + friend class EntManager; + friend class EntQueryResult; +}; + +template +class Iterate; + +// a query is an index, as its results reference +// needs to stick around and be valid so long as it exists +class EntQueryResult { + public: + const EntQueryId id; // can do assignment if we don't make this const + + ~EntQueryResult(); + EntQueryResult(const EntQueryResult &); + EntQueryResult(EntQueryResult &&) noexcept; + EntQueryResult &operator=(const EntQueryResult &) = delete; + EntQueryResult &operator=(EntQueryResult &&) = delete; + + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = Ent; + using pointer = Ent *; // or also value_type* + using reference = Ent &; // or also value_type& + + Iterator(std::vector::const_iterator iterator, EntManager &manager, bool isValid = true) + : it{iterator}, entManager{manager}, ent{isValid ? *iterator : NullEntId, manager} {} + + reference operator*() { return ent; } + pointer operator->() { return &ent; } + + // Prefix increment + Iterator &operator++() { + it++; + ent.id = *it; + return *this; + } + + // Postfix increment + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const Iterator &a, const Iterator &b) { return a.it == b.it; }; + friend bool operator!=(const Iterator &a, const Iterator &b) { return a.it != b.it; }; + + private: + Ent ent; // <- store pointer to an ent + EntManager &entManager; + std::vector::const_iterator it; + }; + + template + Iterate iterate(); + + // needs a signature? + // allOf, anyOf, noneOf, etc. + // register/deregister on construction desctruction + EntQueryResult( + EntManager &manager, const std::vector &entIds, EntQueryId queryId = NullQueryId); + + const std::vector &ids() { return results; } + + Iterator begin() { return Iterator{results.begin(), entManager, results.size() > 0}; } + Iterator end() { return Iterator{results.end(), entManager, false}; } + size_t size() const { return results.size(); } + + private: + const std::vector &results; + EntManager &entManager; + + friend class EntManager; +}; + +class EntManager { + public: + EntManager() = default; + ~EntManager() = default; + EntManager(const EntManager &) = delete; + EntManager &operator=(const EntManager &) = delete; + EntManager(EntManager &&) = delete; + EntManager &operator=(EntManager &&) = delete; + + EntId createEnt() { + auto entId = nextEntId; + nextEntId += 1; + allEntIds.add(entId); + return entId; + } + + // would require either virtual function + template + const std::vector &getAllIds() { + PackedMap &componentMap = getComponentMap(); + return componentMap.getKeys(); + } + + template + EntQueryResult getKeys() { + const auto &results = getAllIds(); + return EntQueryResult{*this, results}; + } + + template + EntQueryResult allOf(); + + template + typename std::enable_if::type allOf(); + + template + EntQueryResult anyOf(); + + template + typename std::enable_if::type anyOf(); + + template + EntQueryResult noneOf(); + + void retain(EntQueryId queryId) { + if (queryId == NullQueryId) return; + if (queryRefCount.find(queryId) != queryRefCount.end()) { + ++queryRefCount.at(queryId); + } else { + queryRefCount[queryId] = 1; + } + } + + void release(EntQueryId queryId) { + if (queryId == NullQueryId) return; + if (queryRefCount.find(queryId) == queryRefCount.end()) { + return; + } + auto &refCount = queryRefCount.at(queryId); + + // decrement ref count and erase if count = 0 + if (--refCount == 0) { + queryRefCount.erase(queryId); + queryIndexes.erase(queryId); + // TODO not very efficient (especially if many open indexes) + for (auto &kv : queryIdFromQuery) { + if (kv.second == queryId) { + queryIdFromQuery.erase(kv.first); + break; + } + } + } + } + + // TODO rename EntId to ent + // rename Ent To EntComponentAccessor? + + template + typename std::enable_if>::type add(EntId entId) { + updateTrackedQueries(entId); + return std::make_tuple(); + } + + template + std::tuple add(EntId entId) { + PackedMap &componentMap = getComponentMap(); + componentMap.add(entId); + std::tuple component = std::tie(componentMap.get(entId)); + std::tuple otherComponents = add(entId); + return std::tuple_cat(component, otherComponents); + } + + template + typename std::enable_if::type remove(EntId entId) { + updateTrackedQueries(entId); + } + + template + void remove(EntId entId) { + PackedMap &componentMap = getComponentMap(); + componentMap.remove(entId); + remove(entId); + } + + template + T &get(EntId entId) { + PackedMap &componentMap = getComponentMap(); + return componentMap.get(entId); + } + + template + typename std::enable_if>::type multiget(EntId entId) { + return std::make_tuple(); + } + + template + std::tuple multiget(EntId entId) { + PackedMap &componentMap = getComponentMap(); + std::tuple component = std::tie(componentMap.get(entId)); + std::tuple otherComponents = multiget(entId); + return std::tuple_cat(component, otherComponents); + } + + template + ComponentType getComponentType() { + const auto &typeIndex = std::type_index(typeid(T)); + + // register this type of component if first time being observed + if (componentTypeFromTypeIndex.find(typeIndex) == componentTypeFromTypeIndex.end()) { + componentTypeFromTypeIndex[typeIndex] = nextComponentType; + componentMaps.emplace(nextComponentType, PackedMap{}); + PackedMap &componentMap = + std::any_cast &>(componentMaps[nextComponentType]); + componentSets.emplace(nextComponentType, componentMap.getKeySet()); + return nextComponentType++; + } + + return componentTypeFromTypeIndex.at(typeIndex); + } + + template + typename std::enable_if::type insertComponentTypes( + std::unordered_set &set) {} + + template + void insertComponentTypes(std::unordered_set &set) { + auto componentType = getComponentType(); + set.insert(componentType); + insertComponentTypes(set); + } + + template + PackedMap &getComponentMap() { + const auto componentType = getComponentType(); + return std::any_cast &>(componentMaps[componentType]); + } + + template + typename std::enable_if &...>>::type + getComponentMaps() { + return std::make_tuple(); + } + + template + std::tuple &, PackedMap &...> getComponentMaps() { + PackedMap &componentMap = getComponentMap(); + auto otherMaps = getComponentMaps(); + return std::tuple_cat(std::tie(componentMap), otherMaps); + } + + EntQuery query(); + + EntQueryResult getAllEnts() { return EntQueryResult{*this, allEntIds.getKeys()}; } + + private: + EntId nextEntId = NullEntId + 1; + ComponentType nextComponentType = 0; + EntQueryId nextQueryId = NullQueryId + 1; + + const std::vector emptyQueryResult{}; + PackedSet allEntIds{}; + + // track existing queries + std::unordered_map queryIdFromQuery{}; + std::unordered_map queryRefCount{}; + std::unordered_map> queryIndexes{}; + + // tracking component types and registered components + std::unordered_map componentTypeFromTypeIndex{}; + std::unordered_map componentMaps{}; + std::unordered_map &> componentSets{}; + + void updateTrackedQueries(EntId entId) { + for (auto &kv : queryIdFromQuery) { + auto &query = kv.first; + EntQueryId queryId = kv.second; + auto &querySet = queryIndexes[queryId]; + bool wasMatch = querySet.contains(entId); + bool isMatch = matches(query, entId); + if (wasMatch && !isMatch) { + querySet.removeAndPack(entId); + } else if (!wasMatch && isMatch) { + querySet.add(entId); + } + } + } + + bool matches(const EntQuery__internal &query, const EntId entId) { + for (const auto componentType : query.allOf) { + auto &index = componentSets.at(componentType); + if (!index.contains(entId)) { + return false; + } + } + for (const auto componentType : query.noneOf) { + auto &index = componentSets.at(componentType); + if (index.contains(entId)) { + return false; + } + } + + if (query.anyOf.size() == 0) return true; + + for (const auto componentType : query.anyOf) { + auto &index = componentSets.at(componentType); + if (index.contains(entId)) { + return true; + } + } + return false; + } + + // slow process, should be pre-down when possible + EntQueryId getQueryId(const EntQuery__internal &query) { + if (query.hashValue == 0) { + return NullQueryId; + } + + // build index for query if one does not already exist + if (queryIdFromQuery.find(query) == queryIdFromQuery.end()) { + const EntQueryId queryId = nextQueryId; + nextQueryId += 1; + + queryIdFromQuery[query] = queryId; + queryIndexes[queryId] = PackedSet{}; + PackedSet &index = queryIndexes[queryId]; + + // build index + for (const auto entId : allEntIds.getKeys()) { + if (matches(query, entId)) { + index.add(entId); + } + } + } + + return queryIdFromQuery[query]; + } + + EntQueryResult getQueryResult(const EntQuery__internal &query) { + auto queryId = getQueryId(query); + + // return null query result for null query + if (queryId == 0) { + return EntQueryResult(*this, emptyQueryResult, 0); + } + + auto &index = queryIndexes[queryId]; + return EntQueryResult(*this, index.getKeys(), queryId); + } + + friend class EntQuery; +}; + +class EntQuery { + public: + template + EntQuery &allOf() { + allOfTypes.clear(); + entManager.insertComponentTypes(allOfTypes); + return *this; + } + + template + EntQuery &anyOf() { + anyOfTypes.clear(); + entManager.insertComponentTypes(anyOfTypes); + return *this; + } + + template + EntQuery &noneOf() { + noneOfTypes.clear(); + entManager.insertComponentTypes(noneOfTypes); + return *this; + } + + EntQueryResult result() { + EntQuery__internal queryInternal = build(); + return entManager.getQueryResult(queryInternal); + } + + private: + EntManager &entManager; + std::unordered_set allOfTypes; + std::unordered_set anyOfTypes; + std::unordered_set noneOfTypes; + + EntQuery(EntManager &manager) : entManager{manager} {} + + EntQuery__internal build() { + // calculate hashcode + size_t hashValue = 0; + for (const auto componentType : allOfTypes) lve::hashCombine(hashValue, componentType); + for (const auto componentType : anyOfTypes) lve::hashCombine(hashValue, componentType); + for (const auto componentType : noneOfTypes) lve::hashCombine(hashValue, componentType); + return EntQuery__internal{hashValue, allOfTypes, anyOfTypes, noneOfTypes}; + } + + friend class EntManager; +}; + +class ApplyMaps { + public: + template + static std::tuple get(EntId entId, PackedMap &componentMap) { + return std::tie(componentMap.get(entId)); + } + + template + static std::tuple get( + EntId entId, PackedMap &componentMap, PackedMap &...otherMaps) { + std::tuple component = std::tie(componentMap.get(entId)); + std::tuple otherComponents = ApplyMaps::get(entId, otherMaps...); + return std::tuple_cat(component, otherComponents); + } +}; + +template +class Iterate { + public: + struct Iterator { + using packed_map_list = std::tuple &...>; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::tuple; + using pointer = value_type *; // or also value_type* + using reference = value_type &; // or also value_type& + + Iterator(std::vector::const_iterator iterator, EntManager &manager) + : it{iterator}, entManager{manager}, componentMaps{manager.getComponentMaps()} {} + + reference operator*() { + lazyUpdateValue(); + return tuple.value(); + } + pointer operator->() { + lazyUpdateValue(); + return &(tuple.value()); + } + + // Prefix increment + Iterator &operator++() { + it++; + tuple.reset(); + return *this; + } + + // Postfix increment + Iterator operator++(int) { + Iterator tmp = *this; + ++(*this); + return tmp; + } + + friend bool operator==(const Iterator &a, const Iterator &b) { return a.it == b.it; }; + friend bool operator!=(const Iterator &a, const Iterator &b) { return a.it != b.it; }; + + private: + std::optional tuple; + std::vector::const_iterator it; + packed_map_list componentMaps; + EntManager &entManager; + + void lazyUpdateValue() { + if (!tuple.has_value()) { + auto args = std::tuple_cat(std::make_tuple(*it), componentMaps); + // Use a lambda to explicitly call ApplyMaps::get with template arguments. + auto components = std::apply( + [&](auto &&...args) -> decltype(auto) { + return ApplyMaps::get(std::forward(args)...); + }, + args); + tuple = std::tuple_cat(components, std::make_tuple(*it)); + } + } + }; + + Iterator begin() { return Iterator{results.begin(), entManager}; } + Iterator end() { return Iterator{results.end(), entManager}; } + size_t size() const { return results.size(); } + + private: + Iterate(EntManager &manager, const std::vector &entIds) + : entManager{manager}, results{entIds} {} + + const std::vector &results; + EntManager &entManager; + + friend class EntQueryResult; +}; + +template +T &Ent::get() { + return entManager.get(id); +} + +template +EntQueryResult EntManager::allOf() { + const PackedMap &componentMap = getComponentMap(); + return EntQueryResult(*this, componentMap.getKeys()); +} + +template +typename std::enable_if::type EntManager::allOf() { + EntQuery__internal query = EntQuery(*this).allOf().build(); + return getQueryResult(query); +} + +template +EntQueryResult EntManager::anyOf() { + const PackedMap &componentMap = getComponentMap(); + return EntQueryResult(*this, componentMap.getKeys()); +} + +template +typename std::enable_if::type EntManager::anyOf() { + EntQuery__internal query = EntQuery(*this).anyOf().build(); + return getQueryResult(query); +} + +template +EntQueryResult EntManager::noneOf() { + EntQuery__internal query = EntQuery(*this).noneOf().build(); + return getQueryResult(query); +} + +inline EntQuery EntManager::query() { return EntQuery{*this}; } + +inline EntQueryResult::EntQueryResult( + EntManager &manager, const std::vector &entIds, EntQueryId queryId) + : entManager{manager}, results{entIds}, id{queryId} { + entManager.retain(id); +} +inline EntQueryResult::~EntQueryResult() { entManager.release(id); } + +// copy constructor +inline EntQueryResult::EntQueryResult(const EntQueryResult &q) + : entManager{q.entManager}, results{q.results}, id{q.id} { + entManager.retain(id); +} + +// move assignment (deleted) +// copy assignment (deleted) + +// move constructor +inline EntQueryResult::EntQueryResult(EntQueryResult &&q) noexcept + : entManager{q.entManager}, results{q.results}, id{q.id} { + entManager.retain(id); +} + +template +inline Iterate EntQueryResult::iterate() { + return Iterate{entManager, results}; +} + +} // namespace lve diff --git a/src/first_app.cpp b/src/first_app.cpp index 957d4ea..aa27244 100644 --- a/src/first_app.cpp +++ b/src/first_app.cpp @@ -3,6 +3,7 @@ #include "keyboard_movement_controller.hpp" #include "lve_buffer.hpp" #include "lve_camera.hpp" +#include "lve_ubo.hpp" #include "systems/point_light_system.hpp" #include "systems/simple_render_system.hpp" @@ -26,23 +27,25 @@ FirstApp::FirstApp() { .setMaxSets(LveSwapChain::MAX_FRAMES_IN_FLIGHT) .addPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, LveSwapChain::MAX_FRAMES_IN_FLIGHT) .build(); + + // build frame descriptor pools + framePools.resize(LveSwapChain::MAX_FRAMES_IN_FLIGHT); + auto framePoolBuilder = LveDescriptorPool::Builder(lveDevice) + .setMaxSets(1000) + .addPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000) + .setPoolFlags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT) + .addPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000); + for (int i = 0; i < framePools.size(); i++) { + framePools[i] = framePoolBuilder.build(); + } + loadGameObjects(); } FirstApp::~FirstApp() {} void FirstApp::run() { - std::vector> uboBuffers(LveSwapChain::MAX_FRAMES_IN_FLIGHT); - for (int i = 0; i < uboBuffers.size(); i++) { - uboBuffers[i] = std::make_unique( - lveDevice, - sizeof(GlobalUbo), - 1, - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); - uboBuffers[i]->map(); - } - + LveUbo globalUbo{lveDevice, 1, false, false}; auto globalSetLayout = LveDescriptorSetLayout::Builder(lveDevice) .addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_ALL_GRAPHICS) @@ -50,7 +53,7 @@ void FirstApp::run() { std::vector globalDescriptorSets(LveSwapChain::MAX_FRAMES_IN_FLIGHT); for (int i = 0; i < globalDescriptorSets.size(); i++) { - auto bufferInfo = uboBuffers[i]->descriptorInfo(); + auto bufferInfo = globalUbo.bufferInfoForRegion(i); LveDescriptorWriter(*globalSetLayout, *globalPool) .writeBuffer(0, &bufferInfo) .build(globalDescriptorSets[i]); @@ -58,6 +61,7 @@ void FirstApp::run() { SimpleRenderSystem simpleRenderSystem{ lveDevice, + ecs, lveRenderer.getSwapChainRenderPass(), globalSetLayout->getDescriptorSetLayout()}; PointLightSystem pointLightSystem{ @@ -66,8 +70,10 @@ void FirstApp::run() { globalSetLayout->getDescriptorSetLayout()}; LveCamera camera{}; - auto viewerObject = LveGameObject::createGameObject(); - viewerObject.transform.translation.z = -2.5f; + // create a Transform component to store the viewer location + TransformComponent viewerTransform{}; + viewerTransform.translation.z = -2.5f; + KeyboardMovementController cameraController{}; auto currentTime = std::chrono::high_resolution_clock::now(); @@ -79,30 +85,32 @@ void FirstApp::run() { std::chrono::duration(newTime - currentTime).count(); currentTime = newTime; - cameraController.moveInPlaneXZ(lveWindow.getGLFWwindow(), frameTime, viewerObject); - camera.setViewYXZ(viewerObject.transform.translation, viewerObject.transform.rotation); + cameraController.moveInPlaneXZ(lveWindow.getGLFWwindow(), frameTime, viewerTransform); + camera.setViewYXZ(viewerTransform.translation, viewerTransform.rotation); float aspect = lveRenderer.getAspectRatio(); camera.setPerspectiveProjection(glm::radians(50.f), aspect, 0.1f, 100.f); if (auto commandBuffer = lveRenderer.beginFrame()) { int frameIndex = lveRenderer.getFrameIndex(); + framePools[frameIndex]->resetPool(); FrameInfo frameInfo{ frameIndex, frameTime, commandBuffer, camera, globalDescriptorSets[frameIndex], - gameObjects}; + *framePools[frameIndex], + ecs}; // update - GlobalUbo ubo{}; + GlobalUbo& ubo = globalUbo.get(frameIndex); ubo.projection = camera.getProjection(); ubo.view = camera.getView(); ubo.inverseView = camera.getInverseView(); pointLightSystem.update(frameInfo, ubo); - uboBuffers[frameIndex]->writeToBuffer(&ubo); - uboBuffers[frameIndex]->flush(); + // globalUbo.write(ubo, frameIndex); + globalUbo.flushRegion(frameIndex); // render lveRenderer.beginSwapChainRenderPass(commandBuffer); @@ -122,25 +130,27 @@ void FirstApp::run() { void FirstApp::loadGameObjects() { std::shared_ptr lveModel = LveModel::createModelFromFile(lveDevice, "models/flat_vase.obj"); - auto flatVase = LveGameObject::createGameObject(); - flatVase.model = lveModel; - flatVase.transform.translation = {-.5f, .5f, 0.f}; - flatVase.transform.scale = {3.f, 1.5f, 3.f}; - gameObjects.emplace(flatVase.getId(), std::move(flatVase)); + + auto flatVase = ecs.createEnt(); + auto [flatVaseTransform, flatVaseModel] = ecs.add(flatVase); + flatVaseTransform.translation = {-.5f, .5f, 0.f}; + flatVaseTransform.scale = {3.f, 1.5f, 3.f}; + flatVaseModel.model = lveModel; lveModel = LveModel::createModelFromFile(lveDevice, "models/smooth_vase.obj"); - auto smoothVase = LveGameObject::createGameObject(); - smoothVase.model = lveModel; - smoothVase.transform.translation = {.5f, .5f, 0.f}; - smoothVase.transform.scale = {3.f, 1.5f, 3.f}; - gameObjects.emplace(smoothVase.getId(), std::move(smoothVase)); + auto smoothVase = ecs.createEnt(); + auto [smoothVaseTransform, smoothVaseModel] = + ecs.add(smoothVase); + smoothVaseTransform.translation = {.5f, .5f, 0.f}; + smoothVaseTransform.scale = {3.f, 1.5f, 3.f}; + smoothVaseModel.model = lveModel; lveModel = LveModel::createModelFromFile(lveDevice, "models/quad.obj"); - auto floor = LveGameObject::createGameObject(); - floor.model = lveModel; - floor.transform.translation = {0.f, .5f, 0.f}; - floor.transform.scale = {3.f, 1.f, 3.f}; - gameObjects.emplace(floor.getId(), std::move(floor)); + auto floor = ecs.createEnt(); + auto [floorTransform, floorModel] = ecs.add(floor); + floorTransform.translation = {0.f, .5f, 0.f}; + floorTransform.scale = {3.f, 1.f, 3.f}; + floorModel.model = lveModel; std::vector lightColors{ {1.f, .1f, .1f}, @@ -152,14 +162,15 @@ void FirstApp::loadGameObjects() { }; for (int i = 0; i < lightColors.size(); i++) { - auto pointLight = LveGameObject::makePointLight(0.2f); - pointLight.color = lightColors[i]; + auto pointLight = makePointLight(ecs, 0.2f); + auto& pointLightColor = ecs.get(pointLight); + auto& pointLightTransform = ecs.get(pointLight); + pointLightColor.color = lightColors[i]; auto rotateLight = glm::rotate( glm::mat4(1.f), (i * glm::two_pi()) / lightColors.size(), {0.f, -1.f, 0.f}); - pointLight.transform.translation = glm::vec3(rotateLight * glm::vec4(-1.f, -1.f, -1.f, 1.f)); - gameObjects.emplace(pointLight.getId(), std::move(pointLight)); + pointLightTransform.translation = glm::vec3(rotateLight * glm::vec4(-1.f, -1.f, -1.f, 1.f)); } } diff --git a/src/first_app.hpp b/src/first_app.hpp index d17f5b8..27f491f 100644 --- a/src/first_app.hpp +++ b/src/first_app.hpp @@ -1,5 +1,6 @@ #pragma once +#include "ecs/lve_ecs.hpp" #include "lve_descriptors.hpp" #include "lve_device.hpp" #include "lve_game_object.hpp" @@ -33,6 +34,7 @@ class FirstApp { // note: order of declarations matters std::unique_ptr globalPool{}; - LveGameObject::Map gameObjects; + std::vector> framePools; + EntManager ecs{}; }; } // namespace lve diff --git a/src/keyboard_movement_controller.cpp b/src/keyboard_movement_controller.cpp index bd1f05a..10ed290 100644 --- a/src/keyboard_movement_controller.cpp +++ b/src/keyboard_movement_controller.cpp @@ -6,7 +6,7 @@ namespace lve { void KeyboardMovementController::moveInPlaneXZ( - GLFWwindow* window, float dt, LveGameObject& gameObject) { + GLFWwindow* window, float dt, TransformComponent& viewerTransform) { glm::vec3 rotate{0}; if (glfwGetKey(window, keys.lookRight) == GLFW_PRESS) rotate.y += 1.f; if (glfwGetKey(window, keys.lookLeft) == GLFW_PRESS) rotate.y -= 1.f; @@ -14,14 +14,14 @@ void KeyboardMovementController::moveInPlaneXZ( if (glfwGetKey(window, keys.lookDown) == GLFW_PRESS) rotate.x -= 1.f; if (glm::dot(rotate, rotate) > std::numeric_limits::epsilon()) { - gameObject.transform.rotation += lookSpeed * dt * glm::normalize(rotate); + viewerTransform.rotation += lookSpeed * dt * glm::normalize(rotate); } // limit pitch values between about +/- 85ish degrees - gameObject.transform.rotation.x = glm::clamp(gameObject.transform.rotation.x, -1.5f, 1.5f); - gameObject.transform.rotation.y = glm::mod(gameObject.transform.rotation.y, glm::two_pi()); + viewerTransform.rotation.x = glm::clamp(viewerTransform.rotation.x, -1.5f, 1.5f); + viewerTransform.rotation.y = glm::mod(viewerTransform.rotation.y, glm::two_pi()); - float yaw = gameObject.transform.rotation.y; + float yaw = viewerTransform.rotation.y; const glm::vec3 forwardDir{sin(yaw), 0.f, cos(yaw)}; const glm::vec3 rightDir{forwardDir.z, 0.f, -forwardDir.x}; const glm::vec3 upDir{0.f, -1.f, 0.f}; @@ -35,7 +35,7 @@ void KeyboardMovementController::moveInPlaneXZ( if (glfwGetKey(window, keys.moveDown) == GLFW_PRESS) moveDir -= upDir; if (glm::dot(moveDir, moveDir) > std::numeric_limits::epsilon()) { - gameObject.transform.translation += moveSpeed * dt * glm::normalize(moveDir); + viewerTransform.translation += moveSpeed * dt * glm::normalize(moveDir); } } } // namespace lve \ No newline at end of file diff --git a/src/keyboard_movement_controller.hpp b/src/keyboard_movement_controller.hpp index fa5b74f..f2ca3ca 100644 --- a/src/keyboard_movement_controller.hpp +++ b/src/keyboard_movement_controller.hpp @@ -19,7 +19,7 @@ class KeyboardMovementController { int lookDown = GLFW_KEY_DOWN; }; - void moveInPlaneXZ(GLFWwindow* window, float dt, LveGameObject& gameObject); + void moveInPlaneXZ(GLFWwindow* window, float dt, TransformComponent& viewerTransform); KeyMappings keys{}; float moveSpeed{3.f}; diff --git a/src/lve_buffer.cpp b/src/lve_buffer.cpp index 2c0b1b6..f797633 100644 --- a/src/lve_buffer.cpp +++ b/src/lve_buffer.cpp @@ -13,36 +13,15 @@ namespace lve { -/** - * Returns the minimum instance size required to be compatible with devices minOffsetAlignment - * - * @param instanceSize The size of an instance - * @param minOffsetAlignment The minimum required alignment, in bytes, for the offset member (eg - * minUniformBufferOffsetAlignment) - * - * @return VkResult of the buffer mapping call - */ -VkDeviceSize LveBuffer::getAlignment(VkDeviceSize instanceSize, VkDeviceSize minOffsetAlignment) { - if (minOffsetAlignment > 0) { - return (instanceSize + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1); - } - return instanceSize; -} - LveBuffer::LveBuffer( LveDevice &device, - VkDeviceSize instanceSize, - uint32_t instanceCount, + VkDeviceSize bufferSize, VkBufferUsageFlags usageFlags, - VkMemoryPropertyFlags memoryPropertyFlags, - VkDeviceSize minOffsetAlignment) + VkMemoryPropertyFlags memoryPropertyFlags) : lveDevice{device}, - instanceSize{instanceSize}, - instanceCount{instanceCount}, + bufferSize{bufferSize}, usageFlags{usageFlags}, memoryPropertyFlags{memoryPropertyFlags} { - alignmentSize = getAlignment(instanceSize, minOffsetAlignment); - bufferSize = alignmentSize * instanceCount; device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer, memory); } @@ -154,48 +133,4 @@ VkDescriptorBufferInfo LveBuffer::descriptorInfo(VkDeviceSize size, VkDeviceSize size, }; } - -/** - * Copies "instanceSize" bytes of data to the mapped buffer at an offset of index * alignmentSize - * - * @param data Pointer to the data to copy - * @param index Used in offset calculation - * - */ -void LveBuffer::writeToIndex(void *data, int index) { - writeToBuffer(data, instanceSize, index * alignmentSize); -} - -/** - * Flush the memory range at index * alignmentSize of the buffer to make it visible to the device - * - * @param index Used in offset calculation - * - */ -VkResult LveBuffer::flushIndex(int index) { return flush(alignmentSize, index * alignmentSize); } - -/** - * Create a buffer info descriptor - * - * @param index Specifies the region given by index * alignmentSize - * - * @return VkDescriptorBufferInfo for instance at index - */ -VkDescriptorBufferInfo LveBuffer::descriptorInfoForIndex(int index) { - return descriptorInfo(alignmentSize, index * alignmentSize); -} - -/** - * Invalidate a memory range of the buffer to make it visible to the host - * - * @note Only required for non-coherent memory - * - * @param index Specifies the region to invalidate: index * alignmentSize - * - * @return VkResult of the invalidate call - */ -VkResult LveBuffer::invalidateIndex(int index) { - return invalidate(alignmentSize, index * alignmentSize); -} - } // namespace lve diff --git a/src/lve_buffer.hpp b/src/lve_buffer.hpp index a4021af..50b718d 100644 --- a/src/lve_buffer.hpp +++ b/src/lve_buffer.hpp @@ -8,11 +8,9 @@ class LveBuffer { public: LveBuffer( LveDevice& device, - VkDeviceSize instanceSize, - uint32_t instanceCount, + VkDeviceSize bufferSize, VkBufferUsageFlags usageFlags, - VkMemoryPropertyFlags memoryPropertyFlags, - VkDeviceSize minOffsetAlignment = 1); + VkMemoryPropertyFlags memoryPropertyFlags); ~LveBuffer(); LveBuffer(const LveBuffer&) = delete; @@ -26,32 +24,19 @@ class LveBuffer { VkDescriptorBufferInfo descriptorInfo(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0); VkResult invalidate(VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0); - void writeToIndex(void* data, int index); - VkResult flushIndex(int index); - VkDescriptorBufferInfo descriptorInfoForIndex(int index); - VkResult invalidateIndex(int index); - VkBuffer getBuffer() const { return buffer; } void* getMappedMemory() const { return mapped; } - uint32_t getInstanceCount() const { return instanceCount; } - VkDeviceSize getInstanceSize() const { return instanceSize; } - VkDeviceSize getAlignmentSize() const { return instanceSize; } VkBufferUsageFlags getUsageFlags() const { return usageFlags; } VkMemoryPropertyFlags getMemoryPropertyFlags() const { return memoryPropertyFlags; } VkDeviceSize getBufferSize() const { return bufferSize; } private: - static VkDeviceSize getAlignment(VkDeviceSize instanceSize, VkDeviceSize minOffsetAlignment); - LveDevice& lveDevice; void* mapped = nullptr; VkBuffer buffer = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; VkDeviceSize bufferSize; - uint32_t instanceCount; - VkDeviceSize instanceSize; - VkDeviceSize alignmentSize; VkBufferUsageFlags usageFlags; VkMemoryPropertyFlags memoryPropertyFlags; }; diff --git a/src/lve_frame_info.hpp b/src/lve_frame_info.hpp index 3a89be2..b88f6c9 100644 --- a/src/lve_frame_info.hpp +++ b/src/lve_frame_info.hpp @@ -1,6 +1,7 @@ #pragma once #include "lve_camera.hpp" +#include "lve_descriptors.hpp" #include "lve_game_object.hpp" // lib @@ -30,6 +31,7 @@ struct FrameInfo { VkCommandBuffer commandBuffer; LveCamera &camera; VkDescriptorSet globalDescriptorSet; - LveGameObject::Map &gameObjects; + LveDescriptorPool &frameDescriptorPool; // pool of descriptors that is cleared each frame + EntManager &ecs; }; } // namespace lve diff --git a/src/lve_game_object.cpp b/src/lve_game_object.cpp index 7beb8fa..2b62a49 100644 --- a/src/lve_game_object.cpp +++ b/src/lve_game_object.cpp @@ -59,13 +59,4 @@ glm::mat3 TransformComponent::normalMatrix() { }; } -LveGameObject LveGameObject::makePointLight(float intensity, float radius, glm::vec3 color) { - LveGameObject gameObj = LveGameObject::createGameObject(); - gameObj.color = color; - gameObj.transform.scale.x = radius; - gameObj.pointLight = std::make_unique(); - gameObj.pointLight->lightIntensity = intensity; - return gameObj; -} - -} // namespace lve \ No newline at end of file +} // namespace lve diff --git a/src/lve_game_object.hpp b/src/lve_game_object.hpp index 657252b..4fc3dbb 100644 --- a/src/lve_game_object.hpp +++ b/src/lve_game_object.hpp @@ -1,5 +1,6 @@ #pragma once +#include "ecs/lve_ecs.hpp" #include "lve_model.hpp" // libs @@ -28,36 +29,31 @@ struct PointLightComponent { float lightIntensity = 1.0f; }; -class LveGameObject { - public: - using id_t = unsigned int; - using Map = std::unordered_map; - - static LveGameObject createGameObject() { - static id_t currentId = 0; - return LveGameObject{currentId++}; - } - - static LveGameObject makePointLight( - float intensity = 10.f, float radius = 0.1f, glm::vec3 color = glm::vec3(1.f)); - - LveGameObject(const LveGameObject &) = delete; - LveGameObject &operator=(const LveGameObject &) = delete; - LveGameObject(LveGameObject &&) = default; - LveGameObject &operator=(LveGameObject &&) = default; - - id_t getId() { return id; } - +struct ColorComponent { glm::vec3 color{}; - TransformComponent transform{}; +}; - // Optional pointer components +struct ModelComponent { std::shared_ptr model{}; - std::unique_ptr pointLight = nullptr; +}; - private: - LveGameObject(id_t objId) : id{objId} {} +inline EntId makePointLight( + EntManager& ecs, + float intensity = 10.f, + float radius = 0.1f, + glm::vec3 color = glm::vec3(1.f)) { + EntId entId = ecs.createEnt(); + // TODO simplify -> unpack component references + ecs.add(entId); + + auto& transformComp = ecs.get(entId); + auto& colorComp = ecs.get(entId); + auto& pointLightComp = ecs.get(entId); + + colorComp.color = color; + transformComp.scale.x = radius; + pointLightComp.lightIntensity = intensity; + return entId; +} - id_t id; -}; } // namespace lve diff --git a/src/lve_model.cpp b/src/lve_model.cpp index e69a916..a033a8b 100644 --- a/src/lve_model.cpp +++ b/src/lve_model.cpp @@ -52,8 +52,7 @@ void LveModel::createVertexBuffers(const std::vector &vertices) { LveBuffer stagingBuffer{ lveDevice, - vertexSize, - vertexCount, + vertexSize * vertexCount, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, }; @@ -63,8 +62,7 @@ void LveModel::createVertexBuffers(const std::vector &vertices) { vertexBuffer = std::make_unique( lveDevice, - vertexSize, - vertexCount, + vertexSize * vertexCount, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); @@ -84,8 +82,7 @@ void LveModel::createIndexBuffers(const std::vector &indices) { LveBuffer stagingBuffer{ lveDevice, - indexSize, - indexCount, + indexSize * indexCount, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, }; @@ -95,8 +92,7 @@ void LveModel::createIndexBuffers(const std::vector &indices) { indexBuffer = std::make_unique( lveDevice, - indexSize, - indexCount, + indexSize * indexCount, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); diff --git a/src/lve_ubo.hpp b/src/lve_ubo.hpp new file mode 100644 index 0000000..1d8309a --- /dev/null +++ b/src/lve_ubo.hpp @@ -0,0 +1,181 @@ +#pragma once + +#include "lve_buffer.hpp" +#include "lve_device.hpp" +#include "lve_swap_chain.hpp" +#include "lve_utils.hpp" + +// std +#include +#include +#include +#include +#include + +namespace lve { + +template +class LveUbo { + public: + LveUbo( + LveDevice &device, + int instancesPerRegion, + bool flushablePerElement = true, + bool descriptorInfoPerElement = true, + int numRegions = LveSwapChain::MAX_FRAMES_IN_FLIGHT) + : lveDevice{device}, + instancesPerRegion{instancesPerRegion}, + instanceSize{sizeof(T)}, + numRegions{numRegions}, + flushablePerElement{flushablePerElement}, + descriptorInfoPerElement{descriptorInfoPerElement} { + calculateAlignmentAndRegionSize(); + + buffer = std::make_unique( + device, + regionSize * numRegions, + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + buffer->map(); + printInfo(); + } + + void printInfo() { + const auto nonCoherentAtomSize = lveDevice.properties.limits.nonCoherentAtomSize; + const auto minUniformBufferOffsetAlignment = + lveDevice.properties.limits.minUniformBufferOffsetAlignment; + std::cout << "InstanceSize: " << sizeof(T) << std::endl; + std::cout << "AlignmentPerInstance: " << alignmentPerInstance << std::endl; + std::cout << "instancesPerRegion: " << instancesPerRegion << std::endl; + std::cout << "numRegions: " << numRegions << std::endl; + std::cout << "regionSize: " << regionSize << std::endl; + std::cout << "nonCoherentAtomSize: " << nonCoherentAtomSize << std::endl; + std::cout << "minUniformBufferOffsetAlignment: " << minUniformBufferOffsetAlignment + << std::endl; + } + + // TODO constructor for non-dynamic buffer + // takes all data at initialization, stores in device only memory + + LveUbo(const LveUbo &) = delete; + LveUbo &operator=(const LveUbo &) = delete; + LveUbo(LveUbo &&) = delete; + LveUbo &operator=(LveUbo &&) = delete; + + // TODO add iterators over region? + + T &get(int frameIndex, int elementIndex = 0) { + assert(frameIndex < numRegions && "Trying to write to region outside ubo range"); + assert(elementIndex < instancesPerRegion && "Trying to write to instance outside ubo range"); + + char *mapped = static_cast(buffer->getMappedMemory()); + assert(mapped != nullptr && "Cannot get element if buffer is not mapped"); + + int offset = frameIndex * regionSize + elementIndex * alignmentPerInstance; + return (*static_cast(reinterpret_cast(mapped + offset))); + } + + void write(T &item, int frameIndex, int elementIndex = 0) { + assert(frameIndex < numRegions && "Trying to write to region outside ubo range"); + assert(elementIndex < instancesPerRegion && "Trying to write to instance outside ubo range"); + buffer->writeToBuffer( + (void *)&item, + instanceSize, + frameIndex * regionSize + elementIndex * alignmentPerInstance); + } + + void flushRegion(int frameIndex) { + assert(frameIndex < numRegions && "Trying to flush to region outside ubo range"); + buffer->flush(regionSize, regionSize * frameIndex); + } + + void flushRange(int frameIndex, int elementStart, int elementEnd) { + assert(frameIndex < numRegions && "Trying to flush element in region outside ubo range"); + assert(elementStart < instancesPerRegion && "Trying to flush element outside ubo range"); + assert(elementEnd < instancesPerRegion && "Trying to flush element outside ubo range"); + assert(elementStart < elementEnd && "Must have start < end to flush range"); + assert( + flushablePerElement && + "Cannot call flushRange if not initialized with flushablePerElement=true"); + buffer->flush( + alignmentPerInstance * (elementEnd - elementStart), + frameIndex * regionSize + alignmentPerInstance * elementStart); + } + + void flushElement(int frameIndex, int elementIndex) { + assert(frameIndex < numRegions && "Trying to flush element in region outside ubo range"); + assert(elementIndex < instancesPerRegion && "Trying to flush element outside ubo range"); + assert( + flushablePerElement && + "Cannot call flushElement if not initialized with flushablePerElement=true"); + buffer->flush( + alignmentPerInstance, + frameIndex * regionSize + alignmentPerInstance * elementIndex); + } + + void invalidate(int frameIndex); + void invalidate(int frameIndex, int elementIndex); + + VkDescriptorBufferInfo bufferInfoForRegion(int frameIndex) const { + assert(frameIndex < numRegions && "Trying to get descriptorInfo for region outside ubo range"); + return buffer->descriptorInfo(regionSize, regionSize * frameIndex); + } + + VkDescriptorBufferInfo bufferInfoForElement(int frameIndex, int elementIndex) const { + assert(frameIndex < numRegions && "Trying to flush element in region outside ubo range"); + assert(elementIndex < instancesPerRegion && "Trying to flush element outside ubo range"); + assert( + descriptorInfoPerElement && + "Cannot call descriptorInfoForElement if not initialized with " + "descriptorInfoPerElement=true"); + return buffer->descriptorInfo( + alignmentPerInstance, + frameIndex * regionSize + elementIndex * alignmentPerInstance); + } + + // can make thos stack allocated potentially + std::unique_ptr buffer; + + private: + LveDevice &lveDevice; + int numRegions; + int regionSize; + int instancesPerRegion; + int alignmentPerInstance; + int instanceSize; + bool flushablePerElement; + bool descriptorInfoPerElement; + + VkDeviceSize getAlignment(VkDeviceSize instanceSize, VkDeviceSize minOffsetAlignment) { + if (minOffsetAlignment > 0) { + return (instanceSize + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1); + } + return instanceSize; + } + + void calculateAlignmentAndRegionSize() { + const auto nonCoherentAtomSize = lveDevice.properties.limits.nonCoherentAtomSize; + const auto minUniformBufferOffsetAlignment = + lveDevice.properties.limits.minUniformBufferOffsetAlignment; + + // calculate alignmentPerInstance + if (flushablePerElement && descriptorInfoPerElement) { + alignmentPerInstance = getAlignment( + instanceSize, + std::lcm(nonCoherentAtomSize, minUniformBufferOffsetAlignment)); + } else if (flushablePerElement && !descriptorInfoPerElement) { + alignmentPerInstance = getAlignment(instanceSize, nonCoherentAtomSize); + } else if (!flushablePerElement && descriptorInfoPerElement) { + alignmentPerInstance = getAlignment(instanceSize, minUniformBufferOffsetAlignment); + } else { + alignmentPerInstance = instanceSize; + } + + // regions must always be flushable and provide buffer info + regionSize = instancesPerRegion * alignmentPerInstance; + regionSize = + getAlignment(regionSize, std::lcm(nonCoherentAtomSize, minUniformBufferOffsetAlignment)); + } +}; + +} // namespace lve \ No newline at end of file diff --git a/src/systems/point_light_system.cpp b/src/systems/point_light_system.cpp index d0dc8ee..c65e5d4 100644 --- a/src/systems/point_light_system.cpp +++ b/src/systems/point_light_system.cpp @@ -71,18 +71,22 @@ void PointLightSystem::createPipeline(VkRenderPass renderPass) { void PointLightSystem::update(FrameInfo& frameInfo, GlobalUbo& ubo) { auto rotateLight = glm::rotate(glm::mat4(1.f), 0.5f * frameInfo.frameTime, {0.f, -1.f, 0.f}); int lightIndex = 0; - for (auto& kv : frameInfo.gameObjects) { - auto& obj = kv.second; - if (obj.pointLight == nullptr) continue; - + // TODO store pointLightQueryResult + auto pointLights = frameInfo.ecs.allOf(); + for (auto& light : pointLights) { assert(lightIndex < MAX_LIGHTS && "Point lights exceed maximum specified"); + auto& lightTransform = light.get(); + // update light position - obj.transform.translation = glm::vec3(rotateLight * glm::vec4(obj.transform.translation, 1.f)); + lightTransform.translation = + glm::vec3(rotateLight * glm::vec4(lightTransform.translation, 1.f)); // copy light to ubo - ubo.pointLights[lightIndex].position = glm::vec4(obj.transform.translation, 1.f); - ubo.pointLights[lightIndex].color = glm::vec4(obj.color, obj.pointLight->lightIntensity); + ubo.pointLights[lightIndex].position = glm::vec4(lightTransform.translation, 1.f); + ubo.pointLights[lightIndex].color = glm::vec4( + light.get().color, + light.get().lightIntensity); lightIndex += 1; } @@ -91,15 +95,15 @@ void PointLightSystem::update(FrameInfo& frameInfo, GlobalUbo& ubo) { void PointLightSystem::render(FrameInfo& frameInfo) { // sort lights - std::map sorted; - for (auto& kv : frameInfo.gameObjects) { - auto& obj = kv.second; - if (obj.pointLight == nullptr) continue; + std::map sorted; + auto pointLights = frameInfo.ecs.allOf(); + for (auto lightId : pointLights.ids()) { + auto& lightTransform = frameInfo.ecs.get(lightId); // calculate distance - auto offset = frameInfo.camera.getPosition() - obj.transform.translation; + auto offset = frameInfo.camera.getPosition() - lightTransform.translation; float disSquared = glm::dot(offset, offset); - sorted[disSquared] = obj.getId(); + sorted[disSquared] = lightId; } lvePipeline->bind(frameInfo.commandBuffer); @@ -117,12 +121,15 @@ void PointLightSystem::render(FrameInfo& frameInfo) { // iterate through sorted lights in reverse order for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) { // use game obj id to find light object - auto& obj = frameInfo.gameObjects.at(it->second); + auto lightId = it->second; + auto& lightTransform = frameInfo.ecs.get(lightId); + auto& lightColor = frameInfo.ecs.get(lightId); + auto& lightComp = frameInfo.ecs.get(lightId); PointLightPushConstants push{}; - push.position = glm::vec4(obj.transform.translation, 1.f); - push.color = glm::vec4(obj.color, obj.pointLight->lightIntensity); - push.radius = obj.transform.scale.x; + push.position = glm::vec4(lightTransform.translation, 1.f); + push.color = glm::vec4(lightColor.color, lightComp.lightIntensity); + push.radius = lightTransform.scale.x; vkCmdPushConstants( frameInfo.commandBuffer, diff --git a/src/systems/simple_render_system.cpp b/src/systems/simple_render_system.cpp index 6c3e45b..6c0f20c 100644 --- a/src/systems/simple_render_system.cpp +++ b/src/systems/simple_render_system.cpp @@ -19,8 +19,11 @@ struct SimplePushConstantData { }; SimpleRenderSystem::SimpleRenderSystem( - LveDevice& device, VkRenderPass renderPass, VkDescriptorSetLayout globalSetLayout) - : lveDevice{device} { + LveDevice& device, + EntManager& ecs, + VkRenderPass renderPass, + VkDescriptorSetLayout globalSetLayout) + : lveDevice{device}, ents{ecs.allOf()} { createPipelineLayout(globalSetLayout); createPipeline(renderPass); } @@ -35,7 +38,16 @@ void SimpleRenderSystem::createPipelineLayout(VkDescriptorSetLayout globalSetLay pushConstantRange.offset = 0; pushConstantRange.size = sizeof(SimplePushConstantData); - std::vector descriptorSetLayouts{globalSetLayout}; + renderSystemLayout = LveDescriptorSetLayout::Builder(lveDevice) + .addBinding( + 0, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT) + .build(); + + std::vector descriptorSetLayouts{ + globalSetLayout, + renderSystemLayout->getDescriptorSetLayout()}; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; @@ -76,12 +88,45 @@ void SimpleRenderSystem::renderGameObjects(FrameInfo& frameInfo) { 0, nullptr); - for (auto& kv : frameInfo.gameObjects) { - auto& obj = kv.second; - if (obj.model == nullptr) continue; + // transform ubo system could create a T iterator wrapper + // update transformUbo for each game object + // LveUboRegion + + // vulkan memory allocator + // avoid buffer allocation limit + // easier to grow + + int index = 0; + for (auto [transform, entId] : ents.iterate()) { + TransformUboData& transformData = transformUbo.get(frameInfo.frameIndex, index); + transformData.modelMatrix = transform.mat4(); + transformData.normalMatrix = transform.normalMatrix(); + index += 1; + } + transformUbo.flushRegion(frameInfo.frameIndex); + + // render each game object + index = 0; + for (auto [transform, model, entId] : ents.iterate()) { + auto bufferInfo = transformUbo.bufferInfoForElement(frameInfo.frameIndex, index); + VkDescriptorSet transformDescriptorSet; + LveDescriptorWriter(*renderSystemLayout, frameInfo.frameDescriptorPool) + .writeBuffer(0, &bufferInfo) + .build(transformDescriptorSet); + + vkCmdBindDescriptorSets( + frameInfo.commandBuffer, + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipelineLayout, + 1, // starting set (0 is the globalDescriptorSet, 1 is the set specific to this system) + 1, // binding 1 more set + &transformDescriptorSet, + 0, + nullptr); + SimplePushConstantData push{}; - push.modelMatrix = obj.transform.mat4(); - push.normalMatrix = obj.transform.normalMatrix(); + push.modelMatrix = transform.mat4(); + push.normalMatrix = transform.normalMatrix(); vkCmdPushConstants( frameInfo.commandBuffer, @@ -90,8 +135,10 @@ void SimpleRenderSystem::renderGameObjects(FrameInfo& frameInfo) { 0, sizeof(SimplePushConstantData), &push); - obj.model->bind(frameInfo.commandBuffer); - obj.model->draw(frameInfo.commandBuffer); + model.model->bind(frameInfo.commandBuffer); + model.model->draw(frameInfo.commandBuffer); + + index += 1; } } diff --git a/src/systems/simple_render_system.hpp b/src/systems/simple_render_system.hpp index 41aa7be..c45d6c3 100644 --- a/src/systems/simple_render_system.hpp +++ b/src/systems/simple_render_system.hpp @@ -1,20 +1,32 @@ #pragma once +#include "ecs/lve_ecs.hpp" #include "lve_camera.hpp" +#include "lve_descriptors.hpp" #include "lve_device.hpp" #include "lve_frame_info.hpp" #include "lve_game_object.hpp" #include "lve_pipeline.hpp" +#include "lve_ubo.hpp" // std #include #include namespace lve { + +struct TransformUboData { + glm::mat4 modelMatrix{1.f}; + glm::mat4 normalMatrix{1.f}; +}; + class SimpleRenderSystem { public: SimpleRenderSystem( - LveDevice &device, VkRenderPass renderPass, VkDescriptorSetLayout globalSetLayout); + LveDevice &device, + EntManager &ecs, + VkRenderPass renderPass, + VkDescriptorSetLayout globalSetLayout); ~SimpleRenderSystem(); SimpleRenderSystem(const SimpleRenderSystem &) = delete; @@ -27,8 +39,13 @@ class SimpleRenderSystem { void createPipeline(VkRenderPass renderPass); LveDevice &lveDevice; + LveUbo transformUbo{lveDevice, 1000, false, true}; std::unique_ptr lvePipeline; VkPipelineLayout pipelineLayout; + + std::unique_ptr renderSystemLayout; + + EntQueryResult ents; }; } // namespace lve