From 6d48267ec2b14a5311a1906b0d72316599179b31 Mon Sep 17 00:00:00 2001 From: Dynamitos Date: Wed, 12 Jan 2022 14:40:26 +0100 Subject: [PATCH] Basic mutability framework --- .vscode/launch.json | 2 +- CMakeLists.txt | 2 +- CMakeSettings.json | 2 +- res/textures/Logo.png | Bin 0 -> 105314 bytes src/Engine/Asset/Asset.cpp | 1 + src/Engine/Asset/Asset.h | 4 +- src/Engine/Asset/MeshAsset.cpp | 14 +- src/Engine/Asset/MeshAsset.h | 6 +- src/Engine/Asset/MeshLoader.cpp | 8 +- src/Engine/Asset/TextureAsset.h | 4 +- src/Engine/Containers/Array.h | 1201 +++++++++-------- src/Engine/Containers/Array.natvis | 13 + src/Engine/Containers/CMakeLists.txt | 1 + src/Engine/Containers/List.h | 85 +- src/Engine/Graphics/Graphics.h | 2 +- src/Engine/Graphics/GraphicsInitializer.h | 9 +- src/Engine/Graphics/GraphicsResources.cpp | 7 +- src/Engine/Graphics/GraphicsResources.h | 10 +- src/Engine/Graphics/Mesh.h | 5 - src/Engine/Graphics/MeshBatch.cpp | 1 + src/Engine/Graphics/MeshBatch.h | 6 +- src/Engine/Graphics/RenderPass/BasePass.cpp | 11 +- src/Engine/Graphics/RenderPass/BasePass.h | 4 +- .../Graphics/RenderPass/DepthPrepass.cpp | 12 +- src/Engine/Graphics/RenderPass/DepthPrepass.h | 4 +- .../Graphics/RenderPass/MeshProcessor.cpp | 5 +- .../Graphics/RenderPass/MeshProcessor.h | 2 +- .../Vulkan/NsightAftermathGpuCrashTracker.cpp | 4 +- .../Graphics/Vulkan/VulkanAllocator.cpp | 17 +- src/Engine/Graphics/Vulkan/VulkanBuffer.cpp | 618 ++++----- .../Graphics/Vulkan/VulkanCommandBuffer.cpp | 58 +- .../Graphics/Vulkan/VulkanCommandBuffer.h | 7 +- .../Graphics/Vulkan/VulkanDescriptorSets.cpp | 2 +- .../Graphics/Vulkan/VulkanDescriptorSets.h | 8 +- src/Engine/Graphics/Vulkan/VulkanGraphics.cpp | 57 +- src/Engine/Graphics/Vulkan/VulkanGraphics.h | 14 +- .../Graphics/Vulkan/VulkanInitializer.cpp | 2 + .../Graphics/Vulkan/VulkanPipelineCache.cpp | 2 +- src/Engine/Graphics/Vulkan/VulkanQueue.cpp | 2 +- src/Engine/Material/MaterialAsset.cpp | 2 +- src/Engine/MinimalEngine.h | 8 +- src/Engine/Scene/Actor/Actor.cpp | 30 +- src/Engine/Scene/Actor/Actor.h | 9 +- src/Engine/Scene/CMakeLists.txt | 1 + src/Engine/Scene/Components/CMakeLists.txt | 4 + .../Scene/Components/CameraComponent.cpp | 9 +- src/Engine/Scene/Components/Component.cpp | 60 +- src/Engine/Scene/Components/Component.h | 34 +- src/Engine/Scene/Components/MyComponent.cpp | 26 + src/Engine/Scene/Components/MyComponent.h | 20 + .../Scene/Components/MyOtherComponent.cpp | 15 + .../Scene/Components/MyOtherComponent.h | 15 + .../Scene/Components/PrimitiveComponent.cpp | 3 +- .../Scene/Components/PrimitiveComponent.h | 6 +- src/Engine/Scene/Scene.cpp | 28 +- src/Engine/Scene/Scene.h | 14 +- src/Engine/Scene/SceneUpdater.cpp | 14 +- src/Engine/Scene/Util.h | 149 ++ src/Engine/ThreadPool.cpp | 94 +- src/Engine/ThreadPool.h | 53 +- src/Engine/UI/Elements/Element.cpp | 2 + src/Engine/UI/RenderHierarchy.cpp | 8 +- src/Engine/Window/InspectorView.cpp | 7 +- src/Engine/Window/InspectorView.h | 4 +- src/Engine/Window/SceneView.cpp | 32 +- src/Engine/Window/SceneView.h | 4 +- src/Engine/Window/View.h | 4 +- src/Engine/Window/Window.cpp | 4 +- src/Engine/Window/WindowManager.cpp | 2 +- test/Engine/Containers/Array.cpp | 231 ++-- test/Engine/EngineTest.h | 3 + test/Engine/ThreadPool.cpp | 15 +- 72 files changed, 1781 insertions(+), 1341 deletions(-) create mode 100644 res/textures/Logo.png create mode 100644 src/Engine/Containers/Array.natvis create mode 100644 src/Engine/Scene/Components/MyComponent.cpp create mode 100644 src/Engine/Scene/Components/MyComponent.h create mode 100644 src/Engine/Scene/Components/MyOtherComponent.cpp create mode 100644 src/Engine/Scene/Components/MyOtherComponent.h create mode 100644 src/Engine/Scene/Util.h diff --git a/.vscode/launch.json b/.vscode/launch.json index edebfa5..ad270ca 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -77,7 +77,7 @@ "type": "cppvsdbg", "request": "launch", "program": "${workspaceRoot}/bin/Debug/Seele_unit_tests.exe", - "args": [], + "args": ["--detect_memory_leaks=15533"], "stopAtEntry": false, "console": "internalConsole", "cwd": "${workspaceRoot}/bin/Debug", diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a8921d..2cbbe02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ add_subdirectory(src/) if(MSVC) set(_CRT_SECURE_NO_WARNINGS) - target_compile_options(SeeleEngine PRIVATE /DEBUG:FASTLINK /Zi) + target_compile_options(SeeleEngine PRIVATE /Zi) else() target_compile_options(SeeleEngine PRIVATE -g -Wall -Wextra -Wno-delete-incomplete -pedantic -fcoroutines) endif() diff --git a/CMakeSettings.json b/CMakeSettings.json index 85715ee..78e54b1 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -5,7 +5,7 @@ "cmakeCommandArgs": "-DUSE_SUPERBUILD=OFF", "buildCommandArgs": "", "ctestCommandArgs": "", - "variables": [] + "inheritEnvironments": [] } ] } \ No newline at end of file diff --git a/res/textures/Logo.png b/res/textures/Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d12735798696686d64aac8a69d10149c21595285 GIT binary patch literal 105314 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 literal 0 HcmV?d00001 diff --git a/src/Engine/Asset/Asset.cpp b/src/Engine/Asset/Asset.cpp index 234aaa4..9ae4201 100644 --- a/src/Engine/Asset/Asset.cpp +++ b/src/Engine/Asset/Asset.cpp @@ -9,6 +9,7 @@ Asset::Asset() , parentDir("") , extension("") , status(Status::Uninitialized) + , byteSize(0) { } Asset::Asset(const std::filesystem::path& path) diff --git a/src/Engine/Asset/Asset.h b/src/Engine/Asset/Asset.h index b748f21..5fa86bb 100644 --- a/src/Engine/Asset/Asset.h +++ b/src/Engine/Asset/Asset.h @@ -28,12 +28,12 @@ public: std::string getExtension() const; inline Status getStatus() { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); return status; } inline void setStatus(Status status) { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); this->status = status; } protected: diff --git a/src/Engine/Asset/MeshAsset.cpp b/src/Engine/Asset/MeshAsset.cpp index 319a5ff..39e4567 100644 --- a/src/Engine/Asset/MeshAsset.cpp +++ b/src/Engine/Asset/MeshAsset.cpp @@ -25,24 +25,20 @@ MeshAsset::~MeshAsset() } void MeshAsset::save() { - boost::archive::text_oarchive archive(getWriteStream()); - archive << meshes; } void MeshAsset::load() { - boost::archive::text_iarchive archive(getReadStream()); - archive >> meshes; } void MeshAsset::addMesh(PMesh mesh) { - std::unique_lock lck(lock); - meshes.add(mesh); - referencedMaterials.add(mesh->referencedMaterial); + std::scoped_lock lck(lock); + meshes.push_back(mesh); + referencedMaterials.push_back(mesh->referencedMaterial); } -const Array MeshAsset::getMeshes() +const std::vector MeshAsset::getMeshes() { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); return meshes; } \ No newline at end of file diff --git a/src/Engine/Asset/MeshAsset.h b/src/Engine/Asset/MeshAsset.h index 3af4fcf..39e8033 100644 --- a/src/Engine/Asset/MeshAsset.h +++ b/src/Engine/Asset/MeshAsset.h @@ -15,11 +15,11 @@ public: virtual void save() override; virtual void load() override; void addMesh(PMesh mesh); - const Array getMeshes(); + const std::vector getMeshes(); //Workaround while no editor - Array referencedMaterials; + std::vector referencedMaterials; private: - Array meshes; + std::vector meshes; }; DEFINE_REF(MeshAsset) } // namespace Seele \ No newline at end of file diff --git a/src/Engine/Asset/MeshLoader.cpp b/src/Engine/Asset/MeshLoader.cpp index e97b2ae..4718027 100644 --- a/src/Engine/Asset/MeshLoader.cpp +++ b/src/Engine/Asset/MeshLoader.cpp @@ -122,7 +122,9 @@ VertexStreamComponent createVertexStream(uint32 size, aiVector3D* sourceData, Gf vbInfo.resourceData.data = (uint8 *)buffer.data(); vbInfo.resourceData.owner = Gfx::QueueType::DEDICATED_TRANSFER; vbInfo.resourceData.size = sizeof(Vector) * (uint32)buffer.size(); - return VertexStreamComponent(graphics->createVertexBuffer(vbInfo), 0, vbInfo.vertexSize, Gfx::SE_FORMAT_R32G32B32_SFLOAT); + Gfx::PVertexBuffer vertexBuffer = graphics->createVertexBuffer(vbInfo); + vertexBuffer->transferOwnership(Gfx::QueueType::GRAPHICS); + return VertexStreamComponent(vertexBuffer, 0, vbInfo.vertexSize, Gfx::SE_FORMAT_R32G32B32_SFLOAT); } VertexStreamComponent createVertexStream(uint32 size, aiVector2D* sourceData, Gfx::PGraphics graphics) { @@ -137,7 +139,9 @@ VertexStreamComponent createVertexStream(uint32 size, aiVector2D* sourceData, Gf vbInfo.resourceData.data = (uint8 *)buffer.data(); vbInfo.resourceData.owner = Gfx::QueueType::DEDICATED_TRANSFER; vbInfo.resourceData.size = sizeof(Vector2) * (uint32)buffer.size(); - return VertexStreamComponent(graphics->createVertexBuffer(vbInfo), 0, vbInfo.vertexSize, Gfx::SE_FORMAT_R32G32_SFLOAT); + Gfx::PVertexBuffer vertexBuffer = graphics->createVertexBuffer(vbInfo); + vertexBuffer->transferOwnership(Gfx::QueueType::GRAPHICS); + return VertexStreamComponent(vertexBuffer, 0, vbInfo.vertexSize, Gfx::SE_FORMAT_R32G32_SFLOAT); } void MeshLoader::loadGlobalMeshes(const aiScene* scene, Array& globalMeshes, const Array& materials, Gfx::PGraphics graphics) { diff --git a/src/Engine/Asset/TextureAsset.h b/src/Engine/Asset/TextureAsset.h index 7afd679..7e1a0b2 100644 --- a/src/Engine/Asset/TextureAsset.h +++ b/src/Engine/Asset/TextureAsset.h @@ -14,12 +14,12 @@ public: virtual void load() override; void setTexture(Gfx::PTexture texture) { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); this->texture = texture; } Gfx::PTexture getTexture() { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); return texture; } private: diff --git a/src/Engine/Containers/Array.h b/src/Engine/Containers/Array.h index c91c52d..1afc34d 100644 --- a/src/Engine/Containers/Array.h +++ b/src/Engine/Containers/Array.h @@ -12,667 +12,670 @@ namespace Seele { - template > - struct Array +template > +struct Array +{ +public: + template + class IteratorBase { public: - template - class IteratorBase - { - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = X; - using difference_type = std::ptrdiff_t; - using reference = X&; - using pointer = X*; - - IteratorBase(X *x = nullptr) - : p(x) - { - } - reference operator*() const - { - return *p; - } - pointer operator->() const - { - return p; - } - inline bool operator!=(const IteratorBase &other) const - { - return p != other.p; - } - inline bool operator==(const IteratorBase &other) const - { - return p == other.p; - } - inline int operator-(const IteratorBase &other) const - { - return (int)(p - other.p); - } - IteratorBase &operator++() - { - p++; - return *this; - } - IteratorBase &operator--() - { - p--; - return *this; - } - IteratorBase operator++(int) - { - IteratorBase tmp(*this); - ++*this; - return tmp; - } - IteratorBase operator--(int) - { - IteratorBase tmp(*this); - --*this; - return tmp; - } - - private: - X *p; - }; - - using value_type = T; - using allocator_type = Allocator; - using size_type = std::size_t; + using iterator_category = std::random_access_iterator_tag; + using value_type = X; using difference_type = std::ptrdiff_t; - using pointer = T*; - using const_pointer = const T*; - using reference = value_type&; - using const_reference = const value_type&; + using reference = X&; + using pointer = X*; - using Iterator = IteratorBase; - using ConstIterator = IteratorBase; - using iterator = Iterator; - using const_iterator = ConstIterator; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - - constexpr Array() noexcept(noexcept(Allocator())) - : arraySize(0) - , allocated(DEFAULT_ALLOC_SIZE) - , allocator(Allocator()) + IteratorBase(X *x = nullptr) + : p(x) { - _data = allocateArray(DEFAULT_ALLOC_SIZE); - assert(_data != nullptr); - markIteratorDirty(); + } + reference operator*() const + { + return *p; + } + pointer operator->() const + { + return p; + } + inline bool operator!=(const IteratorBase &other) const + { + return p != other.p; + } + inline bool operator==(const IteratorBase &other) const + { + return p == other.p; + } + inline int operator-(const IteratorBase &other) const + { + return (int)(p - other.p); + } + IteratorBase &operator++() + { + p++; + return *this; + } + IteratorBase &operator--() + { + p--; + return *this; + } + IteratorBase operator++(int) + { + IteratorBase tmp(*this); + ++*this; + return tmp; + } + IteratorBase operator--(int) + { + IteratorBase tmp(*this); + --*this; + return tmp; } - constexpr explicit Array(const allocator_type& alloc) - : arraySize(0) - , allocated(DEFAULT_ALLOC_SIZE) - , allocator(alloc) + private: + X *p; + }; + + using value_type = T; + using allocator_type = Allocator; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = value_type&; + using const_reference = const value_type&; + + using Iterator = IteratorBase; + using ConstIterator = IteratorBase; + using iterator = Iterator; + using const_iterator = ConstIterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr Array() noexcept(noexcept(Allocator())) + : arraySize(0) + , allocated(DEFAULT_ALLOC_SIZE) + , allocator(Allocator()) + { + _data = allocateArray(DEFAULT_ALLOC_SIZE); + assert(_data != nullptr); + markIteratorDirty(); + } + + constexpr explicit Array(const allocator_type& alloc) + : arraySize(0) + , allocated(DEFAULT_ALLOC_SIZE) + , allocator(alloc) + { + _data = allocateArray(DEFAULT_ALLOC_SIZE); + assert(_data != nullptr); + markIteratorDirty(); + } + constexpr Array(size_type size, const value_type& value, const allocator_type& alloc = allocator_type()) + : arraySize(size) + , allocated(size) + , allocator(alloc) + { + _data = allocateArray(size); + assert(_data != nullptr); + for (size_type i = 0; i < size; ++i) { - _data = allocateArray(DEFAULT_ALLOC_SIZE); - assert(_data != nullptr); - markIteratorDirty(); + _data[i] = value; } - constexpr Array(size_type size, const value_type& value, const allocator_type& alloc = allocator_type()) - : arraySize(size) - , allocated(size) - , allocator(alloc) + markIteratorDirty(); + } + constexpr explicit Array(size_type size, const allocator_type& alloc = allocator_type()) + : arraySize(size) + , allocated(size) + , allocator(alloc) + { + _data = allocateArray(size); + assert(_data != nullptr); + for (size_type i = 0; i < size; ++i) { - _data = allocateArray(size); - for (size_type i = 0; i < size; ++i) + std::allocator_traits::construct(allocator, &_data[i]); + } + markIteratorDirty(); + } + constexpr Array(std::initializer_list init, const allocator_type& alloc = allocator_type()) + : arraySize(init.size()) + , allocated(init.size()) + , allocator(alloc) + { + _data = allocateArray(init.size()); + assert(_data != nullptr); + markIteratorDirty(); + std::uninitialized_copy(init.begin(), init.end(), begin()); + } + Array(const Array &other) + : arraySize(other.arraySize) + , allocated(other.allocated) + , allocator(std::allocator_traits::select_on_container_copy_construction(other.allocator)) + { + _data = allocateArray(other.allocated); + assert(_data != nullptr); + markIteratorDirty(); + std::uninitialized_copy(other.begin(), other.end(), begin()); + } + Array(Array &&other) noexcept + : arraySize(std::move(other.arraySize)) + , allocated(std::move(other.allocated)) + , allocator(std::move(other.allocator)) + { + _data = other._data; + other._data = nullptr; + other.allocated = 0; + other.arraySize = 0; + markIteratorDirty(); + } + Array &operator=(const Array &other) noexcept + { + if (this != &other) + { + if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value ) { - _data[i] = value; + if (!std::allocator_traits::is_always_equal::value + && allocator != other.allocator) + { + clear(); + } + allocator = other.allocator; } - markIteratorDirty(); - } - constexpr explicit Array(size_type size, const allocator_type& alloc = allocator_type()) - : arraySize(size) - , allocated(size) - , allocator(alloc) - { - _data = allocateArray(size); - for (size_type i = 0; i < size; ++i) + if (other.arraySize > allocated) { - std::allocator_traits::construct(allocator, &_data[i]); + clear(); } - markIteratorDirty(); - } - constexpr Array(std::initializer_list init, const allocator_type& alloc = allocator_type()) - : arraySize(init.size()) - , allocated(init.size()) - , allocator(alloc) - { - _data = allocateArray(init.size()); - markIteratorDirty(); - std::uninitialized_copy(init.begin(), init.end(), begin()); - } - Array(const Array &other) - : arraySize(other.arraySize) - , allocated(other.allocated) - , allocator(std::allocator_traits::select_on_container_copy_construction(other.allocator)) - { - _data = allocateArray(other.allocated); + if(_data == nullptr) + { + _data = allocateArray(other.allocated); + allocated = other.allocated; + } + arraySize = other.arraySize; markIteratorDirty(); std::uninitialized_copy(other.begin(), other.end(), begin()); } - Array(Array &&other) noexcept - : arraySize(std::move(other.arraySize)) - , allocated(std::move(other.allocated)) - , allocator(std::move(other.allocator)) + return *this; + } + Array &operator=(Array &&other) noexcept + { + if (this != &other) { + if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) + { + allocator = std::move(other.allocator); + } + if (_data != nullptr) + { + clear(); + } + allocated = std::move(other.allocated); + arraySize = std::move(other.arraySize); _data = other._data; other._data = nullptr; - other.allocated = 0; - other.arraySize = 0; markIteratorDirty(); } - Array &operator=(const Array &other) noexcept + return *this; + } + ~Array() + { + clear(); + } + + constexpr bool operator==(const Array &other) + { + return _data == other._data; + } + + constexpr bool operator!=(const Array &other) + { + return !(*this == other); + } + + constexpr iterator find(const value_type &item) + { + for (uint32 i = 0; i < arraySize; ++i) { - if (this != &other) + if (_data[i] == item) { - if constexpr (std::allocator_traits::propagate_on_container_copy_assignment::value ) - { - if (!std::allocator_traits::is_always_equal::value - && allocator != other.allocator) - { - deallocateArray(_data, allocated); - _data = nullptr; - } - allocator = other.allocator; - } - if(_data == nullptr || other.arraySize > allocated) - { - if(_data != nullptr) - { - deallocateArray(_data, allocated); - } - _data = allocateArray(other.allocated); - allocated = other.allocated; - } - arraySize = other.arraySize; - markIteratorDirty(); - std::uninitialized_copy(other.begin(), other.end(), begin()); + return iterator(&_data[i]); } - return *this; } - Array &operator=(Array &&other) noexcept + return endIt; + } + constexpr iterator find(value_type&& item) + { + for (uint32 i = 0; i < arraySize; ++i) { - if (this != &other) + if (_data[i] == item) { - if constexpr (std::allocator_traits::propagate_on_container_move_assignment::value) - { - if (!std::allocator_traits::is_always_equal::value - && allocator != other.allocator) - { - deallocateArray(_data, allocated); - _data = nullptr; - } - allocator = std::move(other.allocator); - } - if (_data != nullptr) - { - deallocateArray(_data, allocated); - _data = nullptr; - } - allocated = std::move(other.allocated); - arraySize = std::move(other.arraySize); - _data = other._data; - other._data = nullptr; - markIteratorDirty(); + return iterator(&_data[i]); } - return *this; } - ~Array() + return endIt; + } + constexpr allocator_type get_allocator() const + { + return allocator; + } + constexpr iterator begin() const + { + return beginIt; + } + constexpr iterator end() const + { + return endIt; + } + constexpr const_iterator cbegin() const + { + return beginIt; + } + constexpr const_iterator cend() const + { + return endIt; + } + constexpr reference add(const value_type &item = value_type()) + { + return addInternal(item); + } + constexpr reference add(value_type&& item) + { + return addInternal(std::forward(item)); + } + constexpr void addAll(Array other) + { + for(auto value : other) { - clear(); + addInternal(value); + } + } + constexpr reference addUnique(const value_type &item = value_type()) + { + iterator it; + if((it = std::move(find(item))) != endIt) + { + return *it; + } + return addInternal(item); + } + template + constexpr reference emplace(args... arguments) + { + if (arraySize == allocated) + { + size_type newSize = arraySize + 1; + allocated = calculateGrowth(newSize); + T *tempArray = allocateArray(allocated); + assert(tempArray != nullptr); + + std::uninitialized_move(begin(), end(), Iterator(tempArray)); + deallocateArray(_data, arraySize); + _data = tempArray; + } + std::allocator_traits::construct(allocator, &_data[arraySize++], arguments...); + markIteratorDirty(); + return _data[arraySize - 1]; + } + constexpr void remove(iterator it, bool keepOrder = true) + { + remove(it - beginIt, keepOrder); + } + constexpr void remove(int index, bool keepOrder = true) + { + if (keepOrder) + { + for(uint32 i = index; i < arraySize-1; ++i) + { + _data[i] = std::move(_data[i+1]); + } + } + else + { + _data[index] = std::move(_data[arraySize - 1]); + } + arraySize--; + markIteratorDirty(); + } + constexpr void resize(size_type newSize) + { + resizeInternal(newSize, std::move(T())); + } + constexpr void resize(size_type newSize, const value_type& value) + { + resizeInternal(newSize, value); + } + constexpr void clear() + { + if(_data == nullptr) + { + return; + } + for(size_type i = 0; i < arraySize; ++i) + { + std::allocator_traits::destroy(allocator, &_data[i]); + } + deallocateArray(_data, allocated); + _data = nullptr; + arraySize = 0; + allocated = 0; + markIteratorDirty(); + } + inline size_type indexOf(iterator iterator) + { + return iterator - beginIt; + } + inline size_type indexOf(const_iterator iterator) const + { + return iterator.p - beginIt.p; + } + inline size_type indexOf(T& t) + { + return indexOf(find(t)); + } + inline size_type indexOf(const T& t) const + { + return indexOf(find(t)); + } + inline size_type size() const + { + return arraySize; + } + inline size_type empty() const + { + return arraySize == 0; + } + inline size_type capacity() const + { + return allocated; + } + inline pointer data() const + { + return _data; + } + inline reference front() const + { + assert(arraySize > 0); + return _data[0]; + } + inline reference back() const + { + assert(arraySize > 0); + return _data[arraySize - 1]; + } + void pop() + { + std::allocator_traits::destroy(allocator, &_data[--arraySize]); + markIteratorDirty(); + } + constexpr inline reference operator[](size_type index) + { + assert(index < arraySize); + return _data[index]; + } + constexpr inline const reference operator[](size_type index) const + { + assert(index < arraySize); + return _data[index]; + } +private: + size_type calculateGrowth(size_type newSize) const + { + const size_type oldCapacity = capacity(); + + if (oldCapacity > SIZE_MAX - oldCapacity) + { + return newSize; // geometric growth would overflow } - constexpr bool operator==(const Array &other) + const size_type geometric = oldCapacity + oldCapacity; + + if (geometric < newSize) { - return _data == other._data; + return newSize; // geometric growth would be insufficient } - constexpr bool operator!=(const Array &other) + return geometric; // geometric growth is sufficient + } + void markIteratorDirty() + { + beginIt = Iterator(_data); + endIt = Iterator(_data + arraySize); + } + T* allocateArray(size_type size) + { + T* result = allocator.allocate(size); + assert(result != nullptr); + return result; + } + void deallocateArray(T* ptr, size_type size) + { + allocator.deallocate(ptr, size); + } + template + T& addInternal(Type&& t) + { + if (arraySize == allocated) { - return !(*this == other); - } - - constexpr iterator find(const value_type &item) - { - for (uint32 i = 0; i < arraySize; ++i) + size_type newSize = arraySize + 1; + allocated = calculateGrowth(newSize); + T *tempArray = allocateArray(allocated); + for (size_type i = 0; i < arraySize; ++i) { - if (_data[i] == item) + std::allocator_traits::construct(allocator, &tempArray[i], std::forward(_data[i])); + } + deallocateArray(_data, arraySize); + _data = tempArray; + } + std::allocator_traits::construct(allocator, &_data[arraySize++], std::forward(t)); + markIteratorDirty(); + return _data[arraySize - 1]; + } + template + void resizeInternal(size_type newSize, Type&& value) + { + if (newSize <= allocated) + { + // The array is already big enough + if(newSize < arraySize) + { + // But since we are sizing down we destruct some of them + for(size_type i = newSize; i < arraySize; ++i) { - return iterator(&_data[i]); - } - } - return endIt; - } - constexpr iterator find(value_type&& item) - { - for (uint32 i = 0; i < arraySize; ++i) - { - if (_data[i] == item) - { - return iterator(&_data[i]); - } - } - return endIt; - } - constexpr allocator_type get_allocator() const - { - return allocator; - } - constexpr iterator begin() const - { - return beginIt; - } - constexpr iterator end() const - { - return endIt; - } - constexpr const_iterator cbegin() const - { - return beginIt; - } - constexpr const_iterator cend() const - { - return endIt; - } - constexpr reference add(const value_type &item = value_type()) - { - return addInternal(item); - } - constexpr reference add(value_type&& item) - { - return addInternal(std::forward(item)); - } - constexpr reference addUnique(const value_type &item = value_type()) - { - iterator it; - if((it = std::move(find(item))) != endIt) - { - return *it; - } - return addInternal(item); - } - template - constexpr reference emplace(args... arguments) - { - if (arraySize == allocated) - { - size_type newSize = arraySize + 1; - allocated = calculateGrowth(newSize); - T *tempArray = allocateArray(allocated); - assert(tempArray != nullptr); - - std::uninitialized_move(begin(), end(), Iterator(tempArray)); - deallocateArray(_data, arraySize); - _data = tempArray; - } - std::allocator_traits::construct(allocator, &_data[arraySize++], arguments...); - markIteratorDirty(); - return _data[arraySize - 1]; - } - constexpr void remove(iterator it, bool keepOrder = true) - { - remove(it - beginIt, keepOrder); - } - constexpr void remove(int index, bool keepOrder = true) - { - if (keepOrder) - { - for(uint32 i = index; i < arraySize-1; ++i) - { - _data[i] = std::move(_data[i+1]); + std::allocator_traits::destroy(allocator, &_data[i]); } } else { - _data[index] = std::move(_data[arraySize - 1]); - } - arraySize--; - markIteratorDirty(); - } - constexpr void resize(size_type newSize) - { - resizeInternal(newSize, std::move(T())); - } - constexpr void resize(size_type newSize, const value_type& value) - { - resizeInternal(newSize, value); - } - constexpr void clear() - { - if(_data == nullptr) - { - return; - } - for(size_type i = 0; i < arraySize; ++i) - { - _data[i].~T(); - } - deallocateArray(_data, allocated); - _data = nullptr; - arraySize = 0; - allocated = 0; - markIteratorDirty(); - } - inline size_type indexOf(iterator iterator) - { - return iterator - beginIt; - } - inline size_type indexOf(const_iterator iterator) const - { - return iterator.p - beginIt.p; - } - inline size_type indexOf(T& t) - { - return indexOf(find(t)); - } - inline size_type indexOf(const T& t) const - { - return indexOf(find(t)); - } - inline size_type size() const - { - return arraySize; - } - inline size_type empty() const - { - return arraySize == 0; - } - inline size_type capacity() const - { - return allocated; - } - inline pointer data() const - { - return _data; - } - inline reference front() const - { - assert(arraySize > 0); - return _data[0]; - } - inline reference back() const - { - assert(arraySize > 0); - return _data[arraySize - 1]; - } - void pop() - { - _data[--arraySize].~T(); - markIteratorDirty(); - } - constexpr inline reference operator[](size_type index) - { - assert(index < arraySize); - return _data[index]; - } - constexpr inline const reference operator[](size_type index) const - { - assert(index < arraySize); - return _data[index]; - } - private: - size_type calculateGrowth(size_type newSize) const - { - const size_type oldCapacity = capacity(); - - if (oldCapacity > SIZE_MAX - oldCapacity / 2) - { - return newSize; // geometric growth would overflow - } - - const size_type geometric = oldCapacity + oldCapacity / 2; - - if (geometric < newSize) - { - return newSize; // geometric growth would be insufficient - } - - return geometric; // geometric growth is sufficient - } - void markIteratorDirty() - { - beginIt = Iterator(_data); - endIt = Iterator(_data + arraySize); - } - T* allocateArray(size_type size) - { - T* result = allocator.allocate(size); - assert(result != nullptr); - return result; - } - void deallocateArray(T* ptr, size_type size) - { - allocator.deallocate(ptr, size); - } - template - T& addInternal(Type&& t) - { - if (arraySize == allocated) - { - size_type newSize = arraySize + 1; - allocated = calculateGrowth(newSize); - T *tempArray = allocateArray(allocated); - for (size_type i = 0; i < arraySize; ++i) - { - std::allocator_traits::construct(allocator, &tempArray[i], std::forward(_data[i])); - } - deallocateArray(_data, arraySize); - _data = tempArray; - } - std::allocator_traits::construct(allocator, &_data[arraySize++], std::forward(t)); - markIteratorDirty(); - return _data[arraySize - 1]; - } - template - void resizeInternal(size_type newSize, Type&& value) - { - if (newSize <= allocated) - { - // The array is already big enough - if(newSize < arraySize) - { - // But since we are sizing down we destruct some of them - for(size_type i = newSize; i < arraySize; ++i) - { - std::allocator_traits::destroy(allocator, &_data[i]); - } - } - else - { - // Or construct the new elements by default - for(size_type i = arraySize; i < newSize; ++i) - { - std::allocator_traits::construct(allocator, &_data[i], std::move(value)); - } - } - arraySize = newSize; - } - else - { - // The array is not big enough, so we make a new one - T *newData = allocateArray(newSize); - - // And move the current elements into that one - for(size_type i = 0; i < arraySize; ++i) - { - newData[i] = std::forward(_data[i]); - } - // As well as default initialize the others + // Or construct the new elements by default for(size_type i = arraySize; i < newSize; ++i) { - std::allocator_traits::construct(allocator, &newData[i], std::move(value)); + std::allocator_traits::construct(allocator, &_data[i], std::move(value)); } - deallocateArray(_data, allocated); - arraySize = newSize; - allocated = newSize; - _data = newData; } - markIteratorDirty(); + arraySize = newSize; } - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int) + else { - ar & arraySize; - resize(arraySize); + // The array is not big enough, so we make a new one + T *newData = allocateArray(newSize); + + // And move the current elements into that one for(size_type i = 0; i < arraySize; ++i) - ar & _data[i]; - markIteratorDirty(); + { + newData[i] = std::forward(_data[i]); + } + // As well as default initialize the others + for(size_type i = arraySize; i < newSize; ++i) + { + std::allocator_traits::construct(allocator, &newData[i], std::move(value)); + } + deallocateArray(_data, allocated); + arraySize = newSize; + allocated = newSize; + _data = newData; } - size_type arraySize = 0; - size_type allocated = 0; - Iterator beginIt; - Iterator endIt; - T *_data = nullptr; - allocator_type allocator; - }; - - template - struct StaticArray + markIteratorDirty(); + } + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int) { - public: - template - class IteratorBase - { - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = X; - using difference_type = std::ptrdiff_t; - using reference = X&; - using pointer = X*; + ar & arraySize; + resize(arraySize); + for(size_type i = 0; i < arraySize; ++i) + ar & _data[i]; + markIteratorDirty(); + } + size_type arraySize = 0; + size_type allocated = 0; + Iterator beginIt; + Iterator endIt; + T *_data = nullptr; + allocator_type allocator; +}; - IteratorBase(X *x = nullptr) - : p(x) - { - } - reference operator*() const - { - return *p; - } - pointer operator->() const - { - return p; - } - inline bool operator!=(const IteratorBase &other) - { - return p != other.p; - } - inline bool operator==(const IteratorBase &other) - { - return p == other.p; - } - IteratorBase &operator++() - { - p++; - return *this; - } - IteratorBase operator++(int) - { - IteratorBase tmp(*this); - ++*this; - return tmp; - } - IteratorBase &operator--() - { - p--; - return *this; - } - IteratorBase operator--(int) - { - IteratorBase tmp(*this); - --*this; - return tmp; - } - - - private: - X *p; - }; - using value_type = T; - using size_type = size_t; +template +struct StaticArray +{ +public: + template + class IteratorBase + { + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = X; using difference_type = std::ptrdiff_t; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; + using reference = X&; + using pointer = X*; - using iterator = IteratorBase; - using const_iterator = IteratorBase; - - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - - StaticArray() + IteratorBase(X *x = nullptr) + : p(x) { - beginIt = iterator(_data); - endIt = iterator(_data + N); } - StaticArray(T value) + reference operator*() const { - for (int i = 0; i < N; ++i) - { - _data[i] = value; - } - beginIt = iterator(_data); - endIt = iterator(_data + N); + return *p; } - ~StaticArray() + pointer operator->() const { + return p; + } + inline bool operator!=(const IteratorBase &other) + { + return p != other.p; + } + inline bool operator==(const IteratorBase &other) + { + return p == other.p; + } + IteratorBase &operator++() + { + p++; + return *this; + } + IteratorBase operator++(int) + { + IteratorBase tmp(*this); + ++*this; + return tmp; + } + IteratorBase &operator--() + { + p--; + return *this; + } + IteratorBase operator--(int) + { + IteratorBase tmp(*this); + --*this; + return tmp; } - inline size_type size() const - { - return N; - } - inline pointer data() - { - return _data; - } - inline const_pointer data() const - { - return _data; - } - constexpr reference operator[](size_type index) noexcept - { - assert(index < N); - return _data[index]; - } - constexpr const_reference operator[](size_type index) const noexcept - { - assert(index < N); - return _data[index]; - } - iterator begin() - { - return beginIt; - } - iterator end() - { - return endIt; - } - const_iterator begin() const - { - return beginIt; - } - const_iterator end() const - { - return beginIt; - } + private: - T _data[N]; - iterator beginIt; - iterator endIt; - friend class boost::serialization::access; - template - void serialize(Archive& ar, const unsigned int version) - { - ar & version; - ar & N; - ar & _data; - } + X *p; }; + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + + using iterator = IteratorBase; + using const_iterator = IteratorBase; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + StaticArray() + { + beginIt = iterator(_data); + endIt = iterator(_data + N); + } + StaticArray(T value) + { + for (int i = 0; i < N; ++i) + { + _data[i] = value; + } + beginIt = iterator(_data); + endIt = iterator(_data + N); + } + ~StaticArray() + { + } + + inline size_type size() const + { + return N; + } + inline pointer data() + { + return _data; + } + inline const_pointer data() const + { + return _data; + } + constexpr reference operator[](size_type index) noexcept + { + assert(index < N); + return _data[index]; + } + constexpr const_reference operator[](size_type index) const noexcept + { + assert(index < N); + return _data[index]; + } + iterator begin() + { + return beginIt; + } + iterator end() + { + return endIt; + } + const_iterator begin() const + { + return beginIt; + } + const_iterator end() const + { + return beginIt; + } +private: + T _data[N]; + iterator beginIt; + iterator endIt; + friend class boost::serialization::access; + template + void serialize(Archive& ar, const unsigned int version) + { + ar & version; + ar & N; + ar & _data; + } +}; } // namespace Seele \ No newline at end of file diff --git a/src/Engine/Containers/Array.natvis b/src/Engine/Containers/Array.natvis new file mode 100644 index 0000000..d2620ed --- /dev/null +++ b/src/Engine/Containers/Array.natvis @@ -0,0 +1,13 @@ + + + + {{ size={arraySize} }} + + arraySize + allocated + + arraySize + _data + + + diff --git a/src/Engine/Containers/CMakeLists.txt b/src/Engine/Containers/CMakeLists.txt index 93a7932..24e2d04 100644 --- a/src/Engine/Containers/CMakeLists.txt +++ b/src/Engine/Containers/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(SeeleEngine PRIVATE + Array.natvis Array.h Map.h List.h) \ No newline at end of file diff --git a/src/Engine/Containers/List.h b/src/Engine/Containers/List.h index a9ca027..3b1a3e7 100644 --- a/src/Engine/Containers/List.h +++ b/src/Engine/Containers/List.h @@ -152,15 +152,7 @@ public: { if(this != &other) { - if(root != nullptr) - { - delete root; - } - if(tail != nullptr) - { - delete tail; - } - _size = 0; + clear(); for(const auto& it : other) { add(it); @@ -173,10 +165,7 @@ public: { if(this != &other) { - if(root != nullptr) - { - clear(); - } + clear(); root = other.root; tail = other.tail; beginIt = other.beginIt; @@ -205,6 +194,7 @@ public: for (Node *tmp = root; tmp != tail;) { tmp = tmp->next; + destroyNode(tmp->prev); deallocateNode(tmp->prev); } deallocateNode(tail); @@ -216,43 +206,16 @@ public: //Insert at the end iterator add(const T &value) { - if (root == nullptr) - { - root = allocateNode(); - tail = root; - } - initializeNode(tail, value); - Node *newTail = allocateNode(); - newTail->prev = tail; - newTail->next = nullptr; - tail->next = newTail; - iterator insertedElement(tail); - tail = newTail; - markIteratorDirty(); - _size++; - return insertedElement; + return addInternal(value); } iterator add(T&& value) { - if (root == nullptr) - { - root = allocateNode(); - tail = root; - } - initializeNode(tail, std::move(value)); - Node *newTail = allocateNode(); - newTail->prev = tail; - newTail->next = nullptr; - tail->next = newTail; - Iterator insertedElement(tail); - tail = newTail; - markIteratorDirty(); - _size++; - return insertedElement; + return addInternal(std::move(value)); } // takes all elements from other and move-inserts them into // this, clearing other in the process - void moveElements(List& other){ + void moveElements(List& other) + { tail->prev->next = other.root; other.root->prev = tail->prev; _size += other._size; @@ -309,12 +272,13 @@ public: } if(next == nullptr) { - root = prev; + tail = prev; } else { next->prev = prev; } + destroyNode(pos.node); deallocateNode(pos.node); markIteratorDirty(); return Iterator(next); @@ -408,10 +372,33 @@ private: node->next, std::forward(data)); } + void destroyNode(Node* node) + { + std::allocator_traits::destroy(allocator, node); + } void deallocateNode(Node* node) { allocator.deallocate(node, 1); } + template + iterator addInternal(ValueType&& value) + { + if (root == nullptr) + { + root = allocateNode(); + tail = root; + } + initializeNode(tail, std::forward(value)); + Node* newTail = allocateNode(); + newTail->prev = tail; + newTail->next = nullptr; + tail->next = newTail; + Iterator insertedElement(tail); + tail = newTail; + markIteratorDirty(); + _size++; + return insertedElement; + } void markIteratorDirty() { beginIt = Iterator(root); @@ -421,10 +408,10 @@ private: } Node *root; Node *tail; - Iterator beginIt; - Iterator endIt; - ConstIterator cbeginIt; - ConstIterator cendIt; + iterator beginIt; + iterator endIt; + const_iterator cbeginIt; + const_iterator cendIt; size_type _size; NodeAllocator allocator; }; diff --git a/src/Engine/Graphics/Graphics.h b/src/Engine/Graphics/Graphics.h index 18d1e9f..af73c17 100644 --- a/src/Engine/Graphics/Graphics.h +++ b/src/Engine/Graphics/Graphics.h @@ -56,7 +56,7 @@ public: virtual PSamplerState createSamplerState(const SamplerCreateInfo& createInfo) = 0; virtual PDescriptorLayout createDescriptorLayout(const std::string& name = "") = 0; - virtual PPipelineLayout createPipelineLayout() = 0; + virtual PPipelineLayout createPipelineLayout(PPipelineLayout baseLayout = nullptr) = 0; virtual void copyTexture(Gfx::PTexture srcTexture, Gfx::PTexture dstTexture) = 0; diff --git a/src/Engine/Graphics/GraphicsInitializer.h b/src/Engine/Graphics/GraphicsInitializer.h index 750d1ee..1c3c1f3 100644 --- a/src/Engine/Graphics/GraphicsInitializer.h +++ b/src/Engine/Graphics/GraphicsInitializer.h @@ -22,20 +22,13 @@ struct GraphicsInitializer GraphicsInitializer() : applicationName("SeeleEngine") , engineName("SeeleEngine") + , windowLayoutFile(nullptr) , layers{"VK_LAYER_KHRONOS_validation", "VK_LAYER_LUNARG_monitor"} , instanceExtensions{} , deviceExtensions{"VK_KHR_swapchain"} , windowHandle(nullptr) { } - GraphicsInitializer(const GraphicsInitializer &other) - : applicationName(other.applicationName) - , engineName(other.engineName) - , layers(other.layers) - , instanceExtensions(other.instanceExtensions) - , deviceExtensions(other.deviceExtensions) - { - } }; struct WindowCreateInfo { diff --git a/src/Engine/Graphics/GraphicsResources.cpp b/src/Engine/Graphics/GraphicsResources.cpp index 90b1d57..8fa32c3 100644 --- a/src/Engine/Graphics/GraphicsResources.cpp +++ b/src/Engine/Graphics/GraphicsResources.cpp @@ -57,6 +57,7 @@ ShaderCollection& ShaderMap::createShaders( VertexInputType* vertexInput, bool /*bPositionOnly*/) { + std::scoped_lock lock(shadersLock); ShaderCollection& collection = shaders.add(); //collection.vertexDeclaration = bPositionOnly ? vertexInput->getPositionDeclaration() : vertexInput->getDeclaration(); @@ -107,7 +108,7 @@ void DescriptorLayout::addDescriptorBinding(uint32 bindingIndex, SeDescriptorTyp PDescriptorSet DescriptorLayout::allocateDescriptorSet() { - std::unique_lock lock(allocatorLock); + std::scoped_lock lock(allocatorLock); PDescriptorSet result; allocator->allocateDescriptorSet(result); return result; @@ -115,7 +116,7 @@ PDescriptorSet DescriptorLayout::allocateDescriptorSet() void DescriptorLayout::reset() { - std::unique_lock lock(allocatorLock); + std::scoped_lock lock(allocatorLock); allocator->reset(); } @@ -270,7 +271,7 @@ static Map vertexDeclarationCache; PVertexDeclaration VertexDeclaration::createDeclaration(PGraphics graphics, const Array& elementList) { - std::unique_lock lock(vertexDeclarationLock); + std::scoped_lock lock(vertexDeclarationLock); boost::crc_32_type result; result.process_bytes(&elementList, sizeof(VertexElement) * elementList.size()); uint32 key = result.checksum(); diff --git a/src/Engine/Graphics/GraphicsResources.h b/src/Engine/Graphics/GraphicsResources.h index 1dcd4d7..ba5c4a9 100644 --- a/src/Engine/Graphics/GraphicsResources.h +++ b/src/Engine/Graphics/GraphicsResources.h @@ -144,6 +144,7 @@ public: VertexInputType* vertexInput, bool bPositionOnly); private: + std::mutex shadersLock; Array shaders; }; DEFINE_REF(ShaderMap) @@ -242,7 +243,14 @@ DEFINE_REF(DescriptorLayout) class PipelineLayout { public: - PipelineLayout() {} + PipelineLayout(PPipelineLayout baseLayout) + { + if(baseLayout != nullptr) + { + descriptorSetLayouts = baseLayout->descriptorSetLayouts; + pushConstants = baseLayout->pushConstants; + } + } virtual ~PipelineLayout() {} virtual void create() = 0; virtual void reset() = 0; diff --git a/src/Engine/Graphics/Mesh.h b/src/Engine/Graphics/Mesh.h index e7ad89c..00d70ba 100644 --- a/src/Engine/Graphics/Mesh.h +++ b/src/Engine/Graphics/Mesh.h @@ -15,11 +15,6 @@ public: PVertexShaderInput vertexInput; PMaterialAsset referencedMaterial; private: - friend class boost::serialization::access; - template - void serialize(Archive&, const unsigned int) - { - } }; DEFINE_REF(Mesh) } // namespace Seele \ No newline at end of file diff --git a/src/Engine/Graphics/MeshBatch.cpp b/src/Engine/Graphics/MeshBatch.cpp index 5c5091b..745dfa0 100644 --- a/src/Engine/Graphics/MeshBatch.cpp +++ b/src/Engine/Graphics/MeshBatch.cpp @@ -24,5 +24,6 @@ MeshBatch::MeshBatch() , topology(Gfx::SE_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) , vertexInput(nullptr) , material(nullptr) + , elements() { } \ No newline at end of file diff --git a/src/Engine/Graphics/MeshBatch.h b/src/Engine/Graphics/MeshBatch.h index b9ffc8a..656e04e 100644 --- a/src/Engine/Graphics/MeshBatch.h +++ b/src/Engine/Graphics/MeshBatch.h @@ -34,7 +34,7 @@ public: }; struct MeshBatch { - Array elements; + std::vector elements; uint8 useReverseCulling : 1; uint8 isBackfaceCullingDisabled : 1; @@ -68,6 +68,10 @@ struct MeshBatch } MeshBatch(); + MeshBatch(const MeshBatch& other) = default; + MeshBatch(MeshBatch&& other) = default; + MeshBatch& operator=(const MeshBatch& other) = default; + MeshBatch& operator=(MeshBatch&& other) = default; }; DECLARE_REF(PrimitiveComponent) struct StaticMeshBatch : public MeshBatch diff --git a/src/Engine/Graphics/RenderPass/BasePass.cpp b/src/Engine/Graphics/RenderPass/BasePass.cpp index 3062b64..5fbd58c 100644 --- a/src/Engine/Graphics/RenderPass/BasePass.cpp +++ b/src/Engine/Graphics/RenderPass/BasePass.cpp @@ -20,11 +20,11 @@ BasePassMeshProcessor::~BasePassMeshProcessor() { } -void BasePassMeshProcessor::processMeshBatch( +Job BasePassMeshProcessor::processMeshBatch( const MeshBatch& batch, // const PPrimitiveComponent primitiveComponent, const Gfx::PRenderPass& renderPass, - Gfx::PPipelineLayout pipelineLayout, + Gfx::PPipelineLayout baseLayout, Gfx::PDescriptorLayout primitiveLayout, Array descriptorSets, int32 /*staticMeshId*/) @@ -40,6 +40,7 @@ void BasePassMeshProcessor::processMeshBatch( Gfx::PRenderCommand renderCommand = graphics->createRenderCommand(); renderCommand->setViewport(target); + Gfx::PPipelineLayout pipelineLayout = graphics->createPipelineLayout(baseLayout); pipelineLayout->addDescriptorLayout(BasePass::INDEX_MATERIAL, material->getDescriptorLayout()); pipelineLayout->create(); Gfx::PDescriptorSet materialSet = material->createDescriptorSet(); @@ -63,9 +64,9 @@ void BasePassMeshProcessor::processMeshBatch( collection->fragmentShader, false); } - std::unique_lock lock(commandLock); + std::scoped_lock lock(commandLock); renderCommands.add(renderCommand); - //co_return; + co_return; } BasePass::BasePass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor source) @@ -157,7 +158,7 @@ MainJob BasePass::render() for (auto &&meshBatch : passData.staticDrawList) { //jobs.add( - processor->processMeshBatch(meshBatch, renderPass, basePassLayout, primitiveLayout, descriptorSets); + co_await processor->processMeshBatch(meshBatch, renderPass, basePassLayout, primitiveLayout, descriptorSets); } //co_await Job::all(jobs); graphics->executeCommands(processor->getRenderCommands()); diff --git a/src/Engine/Graphics/RenderPass/BasePass.h b/src/Engine/Graphics/RenderPass/BasePass.h index 7822500..bbcc9a4 100644 --- a/src/Engine/Graphics/RenderPass/BasePass.h +++ b/src/Engine/Graphics/RenderPass/BasePass.h @@ -11,7 +11,7 @@ public: BasePassMeshProcessor(Gfx::PViewport viewport, Gfx::PGraphics graphics, uint8 translucentBasePass); virtual ~BasePassMeshProcessor(); - virtual void processMeshBatch( + virtual Job processMeshBatch( const MeshBatch& batch, // const PPrimitiveComponent primitiveComponent, const Gfx::PRenderPass& renderPass, @@ -30,7 +30,7 @@ DECLARE_REF(CameraActor) DECLARE_REF(CameraComponent) struct BasePassData { - Array staticDrawList; + std::vector staticDrawList; }; class BasePass : public RenderPass { diff --git a/src/Engine/Graphics/RenderPass/DepthPrepass.cpp b/src/Engine/Graphics/RenderPass/DepthPrepass.cpp index e9fd5df..a4bc161 100644 --- a/src/Engine/Graphics/RenderPass/DepthPrepass.cpp +++ b/src/Engine/Graphics/RenderPass/DepthPrepass.cpp @@ -18,11 +18,11 @@ DepthPrepassMeshProcessor::~DepthPrepassMeshProcessor() { } -void DepthPrepassMeshProcessor::processMeshBatch( +Job DepthPrepassMeshProcessor::processMeshBatch( const MeshBatch& batch, // const PPrimitiveComponent primitiveComponent, const Gfx::PRenderPass& renderPass, - Gfx::PPipelineLayout pipelineLayout, + Gfx::PPipelineLayout baseLayout, Gfx::PDescriptorLayout primitiveLayout, Array descriptorSets, int32 /*staticMeshId*/) @@ -38,6 +38,7 @@ void DepthPrepassMeshProcessor::processMeshBatch( Gfx::PRenderCommand renderCommand = graphics->createRenderCommand(); renderCommand->setViewport(target); + Gfx::PPipelineLayout pipelineLayout = graphics->createPipelineLayout(baseLayout); pipelineLayout->addDescriptorLayout(DepthPrepass::INDEX_MATERIAL, material->getDescriptorLayout()); pipelineLayout->create(); Gfx::PDescriptorSet materialSet = material->createDescriptorSet(); @@ -61,10 +62,10 @@ void DepthPrepassMeshProcessor::processMeshBatch( collection->fragmentShader, true); } - std::unique_lock lock(commandLock); + std::scoped_lock lock(commandLock); renderCommands.add(renderCommand); //std::cout << "Finished depth job" << std::endl; - //co_return; + co_return; } DepthPrepass::DepthPrepass(Gfx::PGraphics graphics, Gfx::PViewport viewport, PCameraActor source) @@ -128,10 +129,9 @@ MainJob DepthPrepass::render() for (auto &&meshBatch : passData.staticDrawList) { //jobs.add( - processor->processMeshBatch(meshBatch, renderPass, depthPrepassLayout, primitiveLayout, descriptorSets); + co_await processor->processMeshBatch(meshBatch, renderPass, depthPrepassLayout, primitiveLayout, descriptorSets); } //co_await Job::all(jobs); - //std::cout << "Finished waiting depth jobs " << std::endl; graphics->executeCommands(processor->getRenderCommands()); graphics->endRenderPass(); co_return; diff --git a/src/Engine/Graphics/RenderPass/DepthPrepass.h b/src/Engine/Graphics/RenderPass/DepthPrepass.h index 55d91e4..d87ab35 100644 --- a/src/Engine/Graphics/RenderPass/DepthPrepass.h +++ b/src/Engine/Graphics/RenderPass/DepthPrepass.h @@ -11,7 +11,7 @@ public: DepthPrepassMeshProcessor(Gfx::PViewport viewport, Gfx::PGraphics graphics); virtual ~DepthPrepassMeshProcessor(); - virtual void processMeshBatch( + virtual Job processMeshBatch( const MeshBatch& batch, const Gfx::PRenderPass& renderPass, Gfx::PPipelineLayout pipelineLayout, @@ -27,7 +27,7 @@ DECLARE_REF(CameraActor) DECLARE_REF(CameraComponent) struct DepthPrepassData { - Array staticDrawList; + std::vector staticDrawList; }; class DepthPrepass : public RenderPass { diff --git a/src/Engine/Graphics/RenderPass/MeshProcessor.cpp b/src/Engine/Graphics/RenderPass/MeshProcessor.cpp index d3540a0..910de4d 100644 --- a/src/Engine/Graphics/RenderPass/MeshProcessor.cpp +++ b/src/Engine/Graphics/RenderPass/MeshProcessor.cpp @@ -1,6 +1,7 @@ #include "MeshProcessor.h" #include "Graphics/Graphics.h" #include "Graphics/VertexShaderInput.h" +#include "Graphics/Vulkan/VulkanGraphicsResources.h" using namespace Seele; @@ -32,8 +33,6 @@ void MeshProcessor::buildMeshDrawCommand( GraphicsPipelineCreateInfo pipelineInitializer; pipelineInitializer.topology = meshBatch.topology; - Gfx::PVertexDeclaration vertexDecl = positionOnly ? vertexInput->getPositionDeclaration() : vertexInput->getDeclaration(); - pipelineInitializer.vertexDeclaration = vertexDecl; pipelineInitializer.vertexShader = vertexShader; pipelineInitializer.controlShader = controlShader; pipelineInitializer.evalShader = evaluationShader; @@ -46,10 +45,12 @@ void MeshProcessor::buildMeshDrawCommand( if(positionOnly) { vertexInput->getPositionOnlyStream(vertexStreams); + pipelineInitializer.vertexDeclaration = vertexInput->getPositionDeclaration(); } else { vertexInput->getStreams(vertexStreams); + pipelineInitializer.vertexDeclaration = vertexInput->getDeclaration(); } Gfx::PGraphicsPipeline pipeline = graphics->createGraphicsPipeline(pipelineInitializer); drawCommand->bindPipeline(pipeline); diff --git a/src/Engine/Graphics/RenderPass/MeshProcessor.h b/src/Engine/Graphics/RenderPass/MeshProcessor.h index a24663c..24010ec 100644 --- a/src/Engine/Graphics/RenderPass/MeshProcessor.h +++ b/src/Engine/Graphics/RenderPass/MeshProcessor.h @@ -17,7 +17,7 @@ public: protected: PScene scene; Gfx::PGraphics graphics; - virtual void processMeshBatch( + virtual Job processMeshBatch( const MeshBatch& batch, // const PPrimitiveComponent primitiveComponent, const Gfx::PRenderPass& renderPass, diff --git a/src/Engine/Graphics/Vulkan/NsightAftermathGpuCrashTracker.cpp b/src/Engine/Graphics/Vulkan/NsightAftermathGpuCrashTracker.cpp index 5f44808..b19c44d 100644 --- a/src/Engine/Graphics/Vulkan/NsightAftermathGpuCrashTracker.cpp +++ b/src/Engine/Graphics/Vulkan/NsightAftermathGpuCrashTracker.cpp @@ -77,7 +77,7 @@ void GpuCrashTracker::Initialize() void GpuCrashTracker::OnCrashDump(const void* pGpuCrashDump, const uint32_t gpuCrashDumpSize) { // Make sure only one thread at a time... - std::unique_lock lock(m_mutex); + std::scoped_lock lock(m_mutex); // Write to file for later in-depth analysis with Nsight Graphics. WriteGpuCrashDumpToFile(pGpuCrashDump, gpuCrashDumpSize); @@ -87,7 +87,7 @@ void GpuCrashTracker::OnCrashDump(const void* pGpuCrashDump, const uint32_t gpuC void GpuCrashTracker::OnShaderDebugInfo(const void* pShaderDebugInfo, const uint32_t shaderDebugInfoSize) { // Make sure only one thread at a time... - std::unique_lock lock(m_mutex); + std::scoped_lock lock(m_mutex); // Get shader debug information identifier GFSDK_Aftermath_ShaderDebugInfoIdentifier identifier = {}; diff --git a/src/Engine/Graphics/Vulkan/VulkanAllocator.cpp b/src/Engine/Graphics/Vulkan/VulkanAllocator.cpp index 472830e..fbcef8c 100644 --- a/src/Engine/Graphics/Vulkan/VulkanAllocator.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanAllocator.cpp @@ -74,7 +74,7 @@ Allocation::~Allocation() PSubAllocation Allocation::getSuballocation(VkDeviceSize requestedSize, VkDeviceSize alignment) { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); if (isDedicated) { if (activeAllocations.empty() && requestedSize == bytesAllocated) @@ -134,7 +134,7 @@ void Allocation::markFree(SubAllocation *allocation) PSubAllocation freeRangeToDelete; { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); //Join lower bound for (auto freeRange : freeRanges) { @@ -204,7 +204,7 @@ Allocator::Allocator(PGraphics graphics) Allocator::~Allocator() { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); for (auto heap : heaps) { for (auto alloc : heap.allocations) @@ -219,7 +219,7 @@ Allocator::~Allocator() PSubAllocation Allocator::allocate(const VkMemoryRequirements2 &memRequirements2, VkMemoryPropertyFlags properties, VkMemoryDedicatedAllocateInfo *dedicatedInfo) { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); const VkMemoryRequirements &requirements = memRequirements2.memoryRequirements; uint8 memoryTypeIndex; VK_CHECK(findMemoryType(requirements.memoryTypeBits, properties, &memoryTypeIndex)); @@ -252,7 +252,7 @@ PSubAllocation Allocator::allocate(const VkMemoryRequirements2 &memRequirements2 void Allocator::free(Allocation *allocation) { - std::unique_lock lck(lock); + std::scoped_lock lck(lock); for (auto heap : heaps) { for (uint32 i = 0; i < heap.allocations.size(); ++i) @@ -307,7 +307,7 @@ void StagingManager::clearPending() PStagingBuffer StagingManager::allocateStagingBuffer(uint32 size, VkBufferUsageFlags usage, bool bCPURead) { - std::unique_lock l(lock); + std::scoped_lock l(lock); for (auto it = freeBuffers.begin(); it != freeBuffers.end(); ++it) { auto freeBuffer = *it; @@ -323,6 +323,9 @@ PStagingBuffer StagingManager::allocateStagingBuffer(uint32 size, VkBufferUsageF PStagingBuffer stagingBuffer = new StagingBuffer(); VkBufferCreateInfo stagingBufferCreateInfo = init::BufferCreateInfo(usage, size); stagingBufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + uint32 queueIndex = graphics->getFamilyMapping().getQueueTypeFamilyIndex(Gfx::QueueType::DEDICATED_TRANSFER); + stagingBufferCreateInfo.queueFamilyIndexCount = 1; + stagingBufferCreateInfo.pQueueFamilyIndices = &queueIndex; VkDevice vulkanDevice = graphics->getDevice(); VK_CHECK(vkCreateBuffer(vulkanDevice, &stagingBufferCreateInfo, nullptr, &stagingBuffer->buffer)); @@ -355,7 +358,7 @@ PStagingBuffer StagingManager::allocateStagingBuffer(uint32 size, VkBufferUsageF void StagingManager::releaseStagingBuffer(PStagingBuffer buffer) { - std::unique_lock l(lock); + std::scoped_lock l(lock); freeBuffers.add(buffer); activeBuffers.remove(activeBuffers.find(buffer.getHandle())); } \ No newline at end of file diff --git a/src/Engine/Graphics/Vulkan/VulkanBuffer.cpp b/src/Engine/Graphics/Vulkan/VulkanBuffer.cpp index 87ecc94..62c44c8 100644 --- a/src/Engine/Graphics/Vulkan/VulkanBuffer.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanBuffer.cpp @@ -10,277 +10,277 @@ using namespace Seele::Vulkan; struct PendingBuffer { - PStagingBuffer stagingBuffer; - Gfx::QueueType prevQueue; - bool bWriteOnly; + PStagingBuffer stagingBuffer; + Gfx::QueueType prevQueue; + bool bWriteOnly; }; static Map pendingBuffers; ShaderBuffer::ShaderBuffer(PGraphics graphics, uint32 size, VkBufferUsageFlags usage, Gfx::QueueType& queueType, bool bDynamic) - : graphics(graphics) - , currentBuffer(0) - , size(size) - , owner(queueType) + : graphics(graphics) + , currentBuffer(0) + , size(size) + , owner(queueType) { - if(bDynamic) - { - numBuffers = Gfx::numFramesBuffered; - } - else - { - numBuffers = 1; - } - usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; - usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; - VkBufferCreateInfo info = - init::BufferCreateInfo( - usage, - size); - info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - uint32 queueFamilyIndex = graphics->getFamilyMapping().getQueueTypeFamilyIndex(queueType); - info.pQueueFamilyIndices = &queueFamilyIndex; - info.queueFamilyIndexCount = 0; - VkBufferMemoryRequirementsInfo2 bufferReqInfo; - bufferReqInfo.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2; - bufferReqInfo.pNext = nullptr; - VkMemoryDedicatedRequirements dedicatedRequirements; - dedicatedRequirements.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS; - dedicatedRequirements.pNext = nullptr; - VkMemoryRequirements2 memRequirements; - memRequirements.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; - memRequirements.pNext = &dedicatedRequirements; - for (uint32 i = 0; i < numBuffers; ++i) - { - VK_CHECK(vkCreateBuffer(graphics->getDevice(), &info, nullptr, &buffers[i].buffer)); - bufferReqInfo.buffer = buffers[i].buffer; - vkGetBufferMemoryRequirements2(graphics->getDevice(), &bufferReqInfo, &memRequirements); - auto temp = graphics->getAllocator()->allocate(memRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, buffers[i].buffer); - buffers[i].allocation = temp; - vkBindBufferMemory(graphics->getDevice(), buffers[i].buffer, buffers[i].allocation->getHandle(), buffers[i].allocation->getOffset()); - } + if(bDynamic) + { + numBuffers = Gfx::numFramesBuffered; + } + else + { + numBuffers = 1; + } + usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + VkBufferCreateInfo info = + init::BufferCreateInfo( + usage, + size); + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + uint32 queueFamilyIndex = graphics->getFamilyMapping().getQueueTypeFamilyIndex(queueType); + info.pQueueFamilyIndices = &queueFamilyIndex; + info.queueFamilyIndexCount = 1; + VkBufferMemoryRequirementsInfo2 bufferReqInfo; + bufferReqInfo.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2; + bufferReqInfo.pNext = nullptr; + VkMemoryDedicatedRequirements dedicatedRequirements; + dedicatedRequirements.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS; + dedicatedRequirements.pNext = nullptr; + VkMemoryRequirements2 memRequirements; + memRequirements.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; + memRequirements.pNext = &dedicatedRequirements; + for (uint32 i = 0; i < numBuffers; ++i) + { + VK_CHECK(vkCreateBuffer(graphics->getDevice(), &info, nullptr, &buffers[i].buffer)); + bufferReqInfo.buffer = buffers[i].buffer; + vkGetBufferMemoryRequirements2(graphics->getDevice(), &bufferReqInfo, &memRequirements); + auto temp = graphics->getAllocator()->allocate(memRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, buffers[i].buffer); + buffers[i].allocation = temp; + vkBindBufferMemory(graphics->getDevice(), buffers[i].buffer, buffers[i].allocation->getHandle(), buffers[i].allocation->getOffset()); + } } ShaderBuffer::~ShaderBuffer() { - PCmdBuffer cmdBuffer = graphics->getQueueCommands(owner)->getCommands(); - VkDevice device = graphics->getDevice(); - auto deletionLambda = [cmdBuffer, device](VkBuffer buffer) -> Job - { - //co_await cmdBuffer->asyncWait(); - //vkDestroyBuffer(device, buffer, nullptr); + PCmdBuffer cmdBuffer = graphics->getQueueCommands(owner)->getCommands(); + VkDevice device = graphics->getDevice(); + auto deletionLambda = [cmdBuffer, device](VkBuffer buffer) -> Job + { + //co_await cmdBuffer->asyncWait(); + //vkDestroyBuffer(device, buffer, nullptr); co_return; }; - for (uint32 i = 0; i < numBuffers; ++i) - { - deletionLambda(buffers[i].buffer); - buffers[i].allocation = nullptr; - } - graphics = nullptr; + for (uint32 i = 0; i < numBuffers; ++i) + { + deletionLambda(buffers[i].buffer); + buffers[i].allocation = nullptr; + } + graphics = nullptr; } VkDeviceSize ShaderBuffer::getOffset() const { - return buffers[currentBuffer].allocation->getOffset(); + return buffers[currentBuffer].allocation->getOffset(); } void ShaderBuffer::executeOwnershipBarrier(Gfx::QueueType newOwner) { - VkBufferMemoryBarrier barrier = - init::BufferMemoryBarrier(); - PCommandBufferManager sourceManager = graphics->getQueueCommands(owner); - PCommandBufferManager dstManager = nullptr; - VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; - VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; - Gfx::QueueFamilyMapping mapping = graphics->getFamilyMapping(); - barrier.srcQueueFamilyIndex = mapping.getQueueTypeFamilyIndex(owner); - barrier.dstQueueFamilyIndex = mapping.getQueueTypeFamilyIndex(newOwner); - assert(barrier.srcQueueFamilyIndex != barrier.dstQueueFamilyIndex); - if (owner == Gfx::QueueType::TRANSFER || owner == Gfx::QueueType::DEDICATED_TRANSFER) - { - barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; - srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - } - else if (owner == Gfx::QueueType::COMPUTE) - { - barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - srcStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - } - else if (owner == Gfx::QueueType::GRAPHICS) - { - barrier.srcAccessMask = getSourceAccessMask(); - srcStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; - } - if (newOwner == Gfx::QueueType::TRANSFER) - { - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - dstManager = graphics->getTransferCommands(); - } - else if (newOwner == Gfx::QueueType::DEDICATED_TRANSFER) - { - barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; - dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; - dstManager = graphics->getDedicatedTransferCommands(); - } - else if (newOwner == Gfx::QueueType::COMPUTE) - { - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - dstManager = graphics->getComputeCommands(); - } - else if (newOwner == Gfx::QueueType::GRAPHICS) - { - barrier.dstAccessMask = getDestAccessMask(); - dstStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; - dstManager = graphics->getGraphicsCommands(); - } - VkCommandBuffer srcCommand = sourceManager->getCommands()->getHandle(); - VkCommandBuffer dstCommand = dstManager->getCommands()->getHandle(); - VkBufferMemoryBarrier dynamicBarriers[Gfx::numFramesBuffered]; - barrier.offset = 0; - barrier.size = size; - for (uint32 i = 0; i < numBuffers; ++i) - { - dynamicBarriers[i] = barrier; - dynamicBarriers[i].buffer = buffers[i].buffer; - } - vkCmdPipelineBarrier(srcCommand, srcStage, srcStage, 0, 0, nullptr, numBuffers, dynamicBarriers, 0, nullptr); - vkCmdPipelineBarrier(dstCommand, dstStage, dstStage, 0, 0, nullptr, numBuffers, dynamicBarriers, 0, nullptr); - sourceManager->submitCommands(); + VkBufferMemoryBarrier barrier = + init::BufferMemoryBarrier(); + PCommandBufferManager sourceManager = graphics->getQueueCommands(owner); + PCommandBufferManager dstManager = nullptr; + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + Gfx::QueueFamilyMapping mapping = graphics->getFamilyMapping(); + barrier.srcQueueFamilyIndex = mapping.getQueueTypeFamilyIndex(owner); + barrier.dstQueueFamilyIndex = mapping.getQueueTypeFamilyIndex(newOwner); + assert(barrier.srcQueueFamilyIndex != barrier.dstQueueFamilyIndex); + if (owner == Gfx::QueueType::TRANSFER || owner == Gfx::QueueType::DEDICATED_TRANSFER) + { + barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } + else if (owner == Gfx::QueueType::COMPUTE) + { + barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + srcStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + } + else if (owner == Gfx::QueueType::GRAPHICS) + { + barrier.srcAccessMask = getSourceAccessMask(); + srcStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + } + if (newOwner == Gfx::QueueType::TRANSFER) + { + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstManager = graphics->getTransferCommands(); + } + else if (newOwner == Gfx::QueueType::DEDICATED_TRANSFER) + { + barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + dstManager = graphics->getDedicatedTransferCommands(); + } + else if (newOwner == Gfx::QueueType::COMPUTE) + { + barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + dstStage = VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + dstManager = graphics->getComputeCommands(); + } + else if (newOwner == Gfx::QueueType::GRAPHICS) + { + barrier.dstAccessMask = getDestAccessMask(); + dstStage = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + dstManager = graphics->getGraphicsCommands(); + } + VkCommandBuffer srcCommand = sourceManager->getCommands()->getHandle(); + VkCommandBuffer dstCommand = dstManager->getCommands()->getHandle(); + VkBufferMemoryBarrier dynamicBarriers[Gfx::numFramesBuffered]; + barrier.offset = 0; + barrier.size = size; + for (uint32 i = 0; i < numBuffers; ++i) + { + dynamicBarriers[i] = barrier; + dynamicBarriers[i].buffer = buffers[i].buffer; + } + vkCmdPipelineBarrier(srcCommand, srcStage, srcStage, 0, 0, nullptr, numBuffers, dynamicBarriers, 0, nullptr); + vkCmdPipelineBarrier(dstCommand, dstStage, dstStage, 0, 0, nullptr, numBuffers, dynamicBarriers, 0, nullptr); + sourceManager->submitCommands(); } void ShaderBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineStageFlags srcStage, VkAccessFlags dstAccess, VkPipelineStageFlags dstStage) { - PCmdBuffer commandBuffer = graphics->getQueueCommands(owner)->getCommands(); - VkBufferMemoryBarrier barrier = init::BufferMemoryBarrier(); - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstAccessMask = dstAccess; - barrier.srcAccessMask = srcAccess; - barrier.offset = 0; - barrier.size = size; - VkBufferMemoryBarrier dynamicBarriers[Gfx::numFramesBuffered]; - for(uint32 i = 0; i < numBuffers; ++i) - { - dynamicBarriers[i] = barrier; - dynamicBarriers[i].buffer = buffers[i].buffer; - } - vkCmdPipelineBarrier(commandBuffer->getHandle(), srcStage, dstStage, 0, 0, nullptr, numBuffers, dynamicBarriers, 0, nullptr); + PCmdBuffer commandBuffer = graphics->getQueueCommands(owner)->getCommands(); + VkBufferMemoryBarrier barrier = init::BufferMemoryBarrier(); + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstAccessMask = dstAccess; + barrier.srcAccessMask = srcAccess; + barrier.offset = 0; + barrier.size = size; + VkBufferMemoryBarrier dynamicBarriers[Gfx::numFramesBuffered]; + for(uint32 i = 0; i < numBuffers; ++i) + { + dynamicBarriers[i] = barrier; + dynamicBarriers[i].buffer = buffers[i].buffer; + } + vkCmdPipelineBarrier(commandBuffer->getHandle(), srcStage, dstStage, 0, 0, nullptr, numBuffers, dynamicBarriers, 0, nullptr); } void *ShaderBuffer::lock(bool bWriteOnly) { - void *data = nullptr; + void *data = nullptr; - /*if (bVolatile) - { - if (lockMode == RLM_ReadOnly) - { - assert(0); - } - else - { - throw new std::logic_error("TODO implement volatile buffers"); - //device->getRHIDevice()->getTemp - } - }*/ - //assert(bStatic || bDynamic || bUAV); + /*if (bVolatile) + { + if (lockMode == RLM_ReadOnly) + { + assert(0); + } + else + { + throw new std::logic_error("TODO implement volatile buffers"); + //device->getRHIDevice()->getTemp + } + }*/ + //assert(bStatic || bDynamic || bUAV); - PendingBuffer pending; - pending.bWriteOnly = bWriteOnly; - pending.prevQueue = owner; - if (bWriteOnly) - { - //requestOwnershipTransfer(Gfx::QueueType::DEDICATED_TRANSFER); - PStagingBuffer stagingBuffer = graphics->getStagingManager()->allocateStagingBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); - data = stagingBuffer->getMappedPointer(); - pending.stagingBuffer = stagingBuffer; - } - else - { - PCmdBuffer current = graphics->getQueueCommands(owner)->getCommands(); - graphics->getQueueCommands(owner)->submitCommands(); - current->waitForCommand(); + PendingBuffer pending; + pending.bWriteOnly = bWriteOnly; + pending.prevQueue = owner; + if (bWriteOnly) + { + //requestOwnershipTransfer(Gfx::QueueType::DEDICATED_TRANSFER); + PStagingBuffer stagingBuffer = graphics->getStagingManager()->allocateStagingBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + data = stagingBuffer->getMappedPointer(); + pending.stagingBuffer = stagingBuffer; + } + else + { + PCmdBuffer current = graphics->getQueueCommands(owner)->getCommands(); + graphics->getQueueCommands(owner)->submitCommands(); + current->waitForCommand(); - requestOwnershipTransfer(Gfx::QueueType::DEDICATED_TRANSFER); - VkCommandBuffer handle = graphics->getQueueCommands(owner)->getCommands()->getHandle(); + requestOwnershipTransfer(Gfx::QueueType::DEDICATED_TRANSFER); + VkCommandBuffer handle = graphics->getQueueCommands(owner)->getCommands()->getHandle(); - VkBufferMemoryBarrier barrier = - init::BufferMemoryBarrier(); - barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; - barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; - barrier.buffer = buffers[currentBuffer].buffer; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.offset = 0; - barrier.size = size; - vkCmdPipelineBarrier(handle, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &barrier, 0, nullptr); + VkBufferMemoryBarrier barrier = + init::BufferMemoryBarrier(); + barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + barrier.buffer = buffers[currentBuffer].buffer; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.offset = 0; + barrier.size = size; + vkCmdPipelineBarrier(handle, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &barrier, 0, nullptr); - PStagingBuffer stagingBuffer = graphics->getStagingManager()->allocateStagingBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, true); + PStagingBuffer stagingBuffer = graphics->getStagingManager()->allocateStagingBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, true); - VkBufferCopy regions; - regions.size = size; - regions.srcOffset = 0; - regions.dstOffset = 0; + VkBufferCopy regions; + regions.size = size; + regions.srcOffset = 0; + regions.dstOffset = 0; - vkCmdCopyBuffer(handle, buffers[currentBuffer].buffer, stagingBuffer->getHandle(), 1, ®ions); + vkCmdCopyBuffer(handle, buffers[currentBuffer].buffer, stagingBuffer->getHandle(), 1, ®ions); - graphics->getQueueCommands(owner)->submitCommands(); - vkQueueWaitIdle(graphics->getQueueCommands(owner)->getQueue()->getHandle()); + graphics->getQueueCommands(owner)->submitCommands(); + vkQueueWaitIdle(graphics->getQueueCommands(owner)->getQueue()->getHandle()); - stagingBuffer->flushMappedMemory(); - pending.stagingBuffer = stagingBuffer; + stagingBuffer->flushMappedMemory(); + pending.stagingBuffer = stagingBuffer; - data = stagingBuffer->getMappedPointer(); - } - pendingBuffers[this] = pending; + data = stagingBuffer->getMappedPointer(); + } + pendingBuffers[this] = pending; - assert(data); - return data; + assert(data); + return data; } void ShaderBuffer::unlock() { - auto found = pendingBuffers.find(this); - if (found != pendingBuffers.end()) - { - PendingBuffer pending = found->value; - pending.stagingBuffer->flushMappedMemory(); - pendingBuffers.erase(this); - if (pending.bWriteOnly) - { - PStagingBuffer stagingBuffer = pending.stagingBuffer; - PCmdBuffer cmdBuffer = graphics->getQueueCommands(owner)->getCommands(); - VkCommandBuffer cmdHandle = cmdBuffer->getHandle(); + auto found = pendingBuffers.find(this); + if (found != pendingBuffers.end()) + { + PendingBuffer pending = found->value; + pending.stagingBuffer->flushMappedMemory(); + pendingBuffers.erase(this); + if (pending.bWriteOnly) + { + PStagingBuffer stagingBuffer = pending.stagingBuffer; + PCmdBuffer cmdBuffer = graphics->getQueueCommands(owner)->getCommands(); + VkCommandBuffer cmdHandle = cmdBuffer->getHandle(); - VkBufferCopy region; - std::memset(®ion, 0, sizeof(VkBufferCopy)); - region.size = size; - vkCmdCopyBuffer(cmdHandle, stagingBuffer->getHandle(), buffers[currentBuffer].buffer, 1, ®ion); - graphics->getQueueCommands(owner)->submitCommands(); - } - //requestOwnershipTransfer(pending.prevQueue); - graphics->getStagingManager()->releaseStagingBuffer(pending.stagingBuffer); - } + VkBufferCopy region; + std::memset(®ion, 0, sizeof(VkBufferCopy)); + region.size = size; + vkCmdCopyBuffer(cmdHandle, stagingBuffer->getHandle(), buffers[currentBuffer].buffer, 1, ®ion); + graphics->getQueueCommands(owner)->submitCommands(); + } + //requestOwnershipTransfer(pending.prevQueue); + graphics->getStagingManager()->releaseStagingBuffer(pending.stagingBuffer); + } } UniformBuffer::UniformBuffer(PGraphics graphics, const UniformBufferCreateInfo &createInfo) - : Gfx::UniformBuffer(graphics->getFamilyMapping(), createInfo.resourceData) - , Vulkan::ShaderBuffer(graphics, createInfo.resourceData.size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, currentOwner, createInfo.bDynamic) - , dedicatedStagingBuffer(nullptr) + : Gfx::UniformBuffer(graphics->getFamilyMapping(), createInfo.resourceData) + , Vulkan::ShaderBuffer(graphics, createInfo.resourceData.size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, currentOwner, createInfo.bDynamic) + , dedicatedStagingBuffer(nullptr) { - if(createInfo.bDynamic) - { - dedicatedStagingBuffer = graphics->getStagingManager()->allocateStagingBuffer(createInfo.resourceData.size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); - } - if (createInfo.resourceData.data != nullptr) - { - void *data = lock(); - std::memcpy(data, createInfo.resourceData.data, createInfo.resourceData.size); - unlock(); - } + if(createInfo.bDynamic) + { + dedicatedStagingBuffer = graphics->getStagingManager()->allocateStagingBuffer(createInfo.resourceData.size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + } + if (createInfo.resourceData.data != nullptr) + { + void *data = lock(); + std::memcpy(data, createInfo.resourceData.data, createInfo.resourceData.size); + unlock(); + } } UniformBuffer::~UniformBuffer() @@ -290,52 +290,52 @@ UniformBuffer::~UniformBuffer() bool UniformBuffer::updateContents(const BulkResourceData &resourceData) { if(!Gfx::UniformBuffer::updateContents(resourceData)) - { - // no update was performed, skip - return false; - } + { + // no update was performed, skip + return false; + } void* data = lock(); std::memcpy(data, resourceData.data, resourceData.size); unlock(); - return true; + return true; } void* UniformBuffer::lock(bool bWriteOnly) { - if(dedicatedStagingBuffer != nullptr) - { - return dedicatedStagingBuffer->getMappedPointer(); - } - return ShaderBuffer::lock(bWriteOnly); + if(dedicatedStagingBuffer != nullptr) + { + return dedicatedStagingBuffer->getMappedPointer(); + } + return ShaderBuffer::lock(bWriteOnly); } void UniformBuffer::unlock() { - if(dedicatedStagingBuffer != nullptr) - { - dedicatedStagingBuffer->flushMappedMemory(); - PCmdBuffer cmdBuffer = graphics->getQueueCommands(currentOwner)->getCommands(); - VkCommandBuffer cmdHandle = cmdBuffer->getHandle(); + if(dedicatedStagingBuffer != nullptr) + { + dedicatedStagingBuffer->flushMappedMemory(); + PCmdBuffer cmdBuffer = graphics->getQueueCommands(currentOwner)->getCommands(); + VkCommandBuffer cmdHandle = cmdBuffer->getHandle(); - VkBufferCopy region; - std::memset(®ion, 0, sizeof(VkBufferCopy)); - region.size = ShaderBuffer::size; - vkCmdCopyBuffer(cmdHandle, dedicatedStagingBuffer->getHandle(), buffers[currentBuffer].buffer, 1, ®ion); - graphics->getQueueCommands(currentOwner)->submitCommands(); - } - else - { - ShaderBuffer::unlock(); - } + VkBufferCopy region; + std::memset(®ion, 0, sizeof(VkBufferCopy)); + region.size = ShaderBuffer::size; + vkCmdCopyBuffer(cmdHandle, dedicatedStagingBuffer->getHandle(), buffers[currentBuffer].buffer, 1, ®ion); + graphics->getQueueCommands(currentOwner)->submitCommands(); + } + else + { + ShaderBuffer::unlock(); + } } void UniformBuffer::requestOwnershipTransfer(Gfx::QueueType newOwner) { - Gfx::QueueOwnedResource::transferOwnership(newOwner); + Gfx::QueueOwnedResource::transferOwnership(newOwner); } void UniformBuffer::executeOwnershipBarrier(Gfx::QueueType newOwner) { - Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); + Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); } void UniformBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineStageFlags srcStage, @@ -346,24 +346,24 @@ void UniformBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineSt VkAccessFlags UniformBuffer::getSourceAccessMask() { - return VK_ACCESS_MEMORY_WRITE_BIT; + return VK_ACCESS_MEMORY_WRITE_BIT; } VkAccessFlags UniformBuffer::getDestAccessMask() { - return VK_ACCESS_UNIFORM_READ_BIT; + return VK_ACCESS_UNIFORM_READ_BIT; } StructuredBuffer::StructuredBuffer(PGraphics graphics, const StructuredBufferCreateInfo &resourceData) - : Gfx::StructuredBuffer(graphics->getFamilyMapping(), resourceData.resourceData) - , Vulkan::ShaderBuffer(graphics, resourceData.resourceData.size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, currentOwner, resourceData.bDynamic) + : Gfx::StructuredBuffer(graphics->getFamilyMapping(), resourceData.resourceData) + , Vulkan::ShaderBuffer(graphics, resourceData.resourceData.size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, currentOwner, resourceData.bDynamic) { - if (resourceData.resourceData.data != nullptr) - { - void *data = lock(); - std::memcpy(data, resourceData.resourceData.data, resourceData.resourceData.size); - unlock(); - } + if (resourceData.resourceData.data != nullptr) + { + void *data = lock(); + std::memcpy(data, resourceData.resourceData.data, resourceData.resourceData.size); + unlock(); + } } StructuredBuffer::~StructuredBuffer() @@ -373,49 +373,49 @@ StructuredBuffer::~StructuredBuffer() bool StructuredBuffer::updateContents(const BulkResourceData &resourceData) { Gfx::StructuredBuffer::updateContents(resourceData); - //We always want to update, as the contents could be different on the GPU + //We always want to update, as the contents could be different on the GPU void* data = lock(); std::memcpy(data, resourceData.data, resourceData.size); unlock(); - return true; + return true; } void* StructuredBuffer::lock(bool bWriteOnly) { - if(dedicatedStagingBuffer != nullptr) - { - return dedicatedStagingBuffer->getMappedPointer(); - } - return ShaderBuffer::lock(bWriteOnly); + if(dedicatedStagingBuffer != nullptr) + { + return dedicatedStagingBuffer->getMappedPointer(); + } + return ShaderBuffer::lock(bWriteOnly); } void StructuredBuffer::unlock() { - if(dedicatedStagingBuffer != nullptr) - { - dedicatedStagingBuffer->flushMappedMemory(); - PCmdBuffer cmdBuffer = graphics->getQueueCommands(currentOwner)->getCommands(); - VkCommandBuffer cmdHandle = cmdBuffer->getHandle(); + if(dedicatedStagingBuffer != nullptr) + { + dedicatedStagingBuffer->flushMappedMemory(); + PCmdBuffer cmdBuffer = graphics->getQueueCommands(currentOwner)->getCommands(); + VkCommandBuffer cmdHandle = cmdBuffer->getHandle(); - VkBufferCopy region; - std::memset(®ion, 0, sizeof(VkBufferCopy)); - region.size = ShaderBuffer::size; - vkCmdCopyBuffer(cmdHandle, dedicatedStagingBuffer->getHandle(), buffers[currentBuffer].buffer, 1, ®ion); - graphics->getQueueCommands(currentOwner)->submitCommands(); - } - else - { - ShaderBuffer::unlock(); - } + VkBufferCopy region; + std::memset(®ion, 0, sizeof(VkBufferCopy)); + region.size = ShaderBuffer::size; + vkCmdCopyBuffer(cmdHandle, dedicatedStagingBuffer->getHandle(), buffers[currentBuffer].buffer, 1, ®ion); + graphics->getQueueCommands(currentOwner)->submitCommands(); + } + else + { + ShaderBuffer::unlock(); + } } void StructuredBuffer::requestOwnershipTransfer(Gfx::QueueType newOwner) { - Gfx::QueueOwnedResource::transferOwnership(newOwner); + Gfx::QueueOwnedResource::transferOwnership(newOwner); } void StructuredBuffer::executeOwnershipBarrier(Gfx::QueueType newOwner) { - Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); + Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); } void StructuredBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineStageFlags srcStage, @@ -426,24 +426,24 @@ void StructuredBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelin VkAccessFlags StructuredBuffer::getSourceAccessMask() { - return VK_ACCESS_MEMORY_WRITE_BIT; + return VK_ACCESS_MEMORY_WRITE_BIT; } VkAccessFlags StructuredBuffer::getDestAccessMask() { - return VK_ACCESS_MEMORY_READ_BIT; + return VK_ACCESS_MEMORY_READ_BIT; } VertexBuffer::VertexBuffer(PGraphics graphics, const VertexBufferCreateInfo &resourceData) - : Gfx::VertexBuffer(graphics->getFamilyMapping(), resourceData.numVertices, resourceData.vertexSize, resourceData.resourceData.owner) - , Vulkan::ShaderBuffer(graphics, resourceData.resourceData.size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, currentOwner) + : Gfx::VertexBuffer(graphics->getFamilyMapping(), resourceData.numVertices, resourceData.vertexSize, resourceData.resourceData.owner) + , Vulkan::ShaderBuffer(graphics, resourceData.resourceData.size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, currentOwner) { - if (resourceData.resourceData.data != nullptr) - { - void *data = lock(); - std::memcpy(data, resourceData.resourceData.data, resourceData.resourceData.size); - unlock(); - } + if (resourceData.resourceData.data != nullptr) + { + void *data = lock(); + std::memcpy(data, resourceData.resourceData.data, resourceData.resourceData.size); + unlock(); + } } VertexBuffer::~VertexBuffer() @@ -452,12 +452,12 @@ VertexBuffer::~VertexBuffer() void VertexBuffer::requestOwnershipTransfer(Gfx::QueueType newOwner) { - Gfx::QueueOwnedResource::transferOwnership(newOwner); + Gfx::QueueOwnedResource::transferOwnership(newOwner); } void VertexBuffer::executeOwnershipBarrier(Gfx::QueueType newOwner) { - Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); + Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); } void VertexBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineStageFlags srcStage, @@ -468,24 +468,24 @@ void VertexBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineSta VkAccessFlags VertexBuffer::getSourceAccessMask() { - return VK_ACCESS_MEMORY_WRITE_BIT; + return VK_ACCESS_MEMORY_WRITE_BIT; } VkAccessFlags VertexBuffer::getDestAccessMask() { - return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; + return VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; } IndexBuffer::IndexBuffer(PGraphics graphics, const IndexBufferCreateInfo &resourceData) - : Gfx::IndexBuffer(graphics->getFamilyMapping(), resourceData.resourceData.size, resourceData.indexType, resourceData.resourceData.owner) - , Vulkan::ShaderBuffer(graphics, resourceData.resourceData.size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, currentOwner) + : Gfx::IndexBuffer(graphics->getFamilyMapping(), resourceData.resourceData.size, resourceData.indexType, resourceData.resourceData.owner) + , Vulkan::ShaderBuffer(graphics, resourceData.resourceData.size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, currentOwner) { - if (resourceData.resourceData.data != nullptr) - { - void *data = lock(); - std::memcpy(data, resourceData.resourceData.data, resourceData.resourceData.size); - unlock(); - } + if (resourceData.resourceData.data != nullptr) + { + void *data = lock(); + std::memcpy(data, resourceData.resourceData.data, resourceData.resourceData.size); + unlock(); + } } IndexBuffer::~IndexBuffer() @@ -494,12 +494,12 @@ IndexBuffer::~IndexBuffer() void IndexBuffer::requestOwnershipTransfer(Gfx::QueueType newOwner) { - Gfx::QueueOwnedResource::transferOwnership(newOwner); + Gfx::QueueOwnedResource::transferOwnership(newOwner); } void IndexBuffer::executeOwnershipBarrier(Gfx::QueueType newOwner) { - Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); + Vulkan::ShaderBuffer::executeOwnershipBarrier(newOwner); } void IndexBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineStageFlags srcStage, @@ -510,10 +510,10 @@ void IndexBuffer::executePipelineBarrier(VkAccessFlags srcAccess, VkPipelineStag VkAccessFlags IndexBuffer::getSourceAccessMask() { - return VK_ACCESS_MEMORY_WRITE_BIT; + return VK_ACCESS_MEMORY_WRITE_BIT; } VkAccessFlags IndexBuffer::getDestAccessMask() { - return VK_ACCESS_INDEX_READ_BIT; + return VK_ACCESS_INDEX_READ_BIT; } \ No newline at end of file diff --git a/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.cpp b/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.cpp index b28da6f..9761db2 100644 --- a/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.cpp @@ -16,16 +16,13 @@ using namespace Seele::Vulkan; CmdBuffer::CmdBuffer(PGraphics graphics, VkCommandPool cmdPool, PCommandBufferManager manager) : graphics(graphics) , manager(manager) - , renderPass(nullptr) - , framebuffer(nullptr) - , subpassIndex(0) , owner(cmdPool) { VkCommandBufferAllocateInfo allocInfo = init::CommandBufferAllocateInfo(cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); VK_CHECK(vkAllocateCommandBuffers(graphics->getDevice(), &allocInfo, &handle)) fence = new Fence(graphics); @@ -34,10 +31,8 @@ CmdBuffer::CmdBuffer(PGraphics graphics, VkCommandPool cmdPool, PCommandBufferMa CmdBuffer::~CmdBuffer() { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); vkFreeCommandBuffers(graphics->getDevice(), owner, 1, &handle); - renderPass = nullptr; - framebuffer = nullptr; waitSemaphores.clear(); } @@ -46,24 +41,22 @@ void CmdBuffer::begin() VkCommandBufferBeginInfo beginInfo = init::CommandBufferBeginInfo(); beginInfo.pInheritanceInfo = nullptr; - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); VK_CHECK(vkBeginCommandBuffer(handle, &beginInfo)); state = State::InsideBegin; } void CmdBuffer::end() { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); VK_CHECK(vkEndCommandBuffer(handle)); state = State::Ended; } -void CmdBuffer::beginRenderPass(PRenderPass newRenderPass, PFramebuffer newFramebuffer) +void CmdBuffer::beginRenderPass(PRenderPass renderPass, PFramebuffer framebuffer) { assert(state == State::InsideBegin); - std::unique_lock lock(handleLock); - renderPass = newRenderPass; - framebuffer = newFramebuffer; + std::scoped_lock lock(handleLock); VkRenderPassBeginInfo beginInfo = init::RenderPassBeginInfo(); @@ -74,15 +67,13 @@ void CmdBuffer::beginRenderPass(PRenderPass newRenderPass, PFramebuffer newFrame beginInfo.framebuffer = framebuffer->getHandle(); vkCmdBeginRenderPass(handle, &beginInfo, renderPass->getSubpassContents()); state = State::RenderPassActive; - //std::cout << "Beginning renderPass" << std::endl; } void CmdBuffer::endRenderPass() { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); vkCmdEndRenderPass(handle); state = State::InsideBegin; - //std::cout << "Ending renderPass" << std::endl; } void CmdBuffer::executeCommands(const Array& commands) @@ -90,9 +81,10 @@ void CmdBuffer::executeCommands(const Array& commands) assert(state == State::RenderPassActive); if(commands.size() == 0) { + std::cout << "No commands provided" << std::endl; return; } - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); Array cmdBuffers(commands.size()); for (uint32 i = 0; i < commands.size(); ++i) { @@ -111,7 +103,7 @@ void CmdBuffer::executeCommands(const Array& commands) void CmdBuffer::executeCommands(const Array& commands) { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); if(commands.size() == 0) { return; @@ -134,14 +126,14 @@ void CmdBuffer::executeCommands(const Array& commands) void CmdBuffer::addWaitSemaphore(VkPipelineStageFlags flags, PSemaphore semaphore) { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); waitSemaphores.add(semaphore); waitFlags.add(flags); } void CmdBuffer::refreshFence() { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); if (state == State::Submitted) { if (fence->isSignaled()) @@ -174,7 +166,7 @@ void CmdBuffer::refreshFence() void CmdBuffer::waitForCommand(uint32 timeout) { - std::unique_lock lock(handleLock); + std::scoped_lock lock(handleLock); fence->wait(timeout); refreshFence(); } @@ -212,7 +204,7 @@ RenderCommand::~RenderCommand() vkFreeCommandBuffers(graphics->getDevice(), owner, 1, &handle); } -void RenderCommand::begin(PCmdBuffer parent) +void RenderCommand::begin(PRenderPass renderPass, PFramebuffer framebuffer) { threadId = std::this_thread::get_id(); ready = false; @@ -220,9 +212,9 @@ void RenderCommand::begin(PCmdBuffer parent) init::CommandBufferBeginInfo(); VkCommandBufferInheritanceInfo inheritanceInfo = init::CommandBufferInheritanceInfo(); - inheritanceInfo.framebuffer = parent->framebuffer->getHandle(); - inheritanceInfo.renderPass = parent->renderPass->getHandle(); - inheritanceInfo.subpass = parent->subpassIndex; + inheritanceInfo.framebuffer = framebuffer->getHandle(); + inheritanceInfo.renderPass = renderPass->getHandle(); + inheritanceInfo.subpass = 0; beginInfo.pInheritanceInfo = &inheritanceInfo; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT; VK_CHECK(vkBeginCommandBuffer(handle, &beginInfo)); @@ -230,13 +222,11 @@ void RenderCommand::begin(PCmdBuffer parent) void RenderCommand::end() { - assert(threadId == std::this_thread::get_id()); VK_CHECK(vkEndCommandBuffer(handle)); } void RenderCommand::reset() { - assert(threadId == std::this_thread::get_id()); vkResetCommandBuffer(handle, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); boundDescriptors.clear(); ready = true; @@ -420,7 +410,7 @@ CommandBufferManager::CommandBufferManager(PGraphics graphics, PQueue queue) activeCmdBuffer = new CmdBuffer(graphics, commandPool, this); activeCmdBuffer->begin(); - std::unique_lock lock(allocatedBufferLock); + std::scoped_lock lock(allocatedBufferLock); allocatedBuffers.add(activeCmdBuffer); } @@ -436,29 +426,29 @@ PCmdBuffer CommandBufferManager::getCommands() return activeCmdBuffer; } -PRenderCommand CommandBufferManager::createRenderCommand(const std::string& name) +PRenderCommand CommandBufferManager::createRenderCommand(PRenderPass renderPass, PFramebuffer framebuffer, const std::string& name) { - std::unique_lock lck(allocatedRenderLock); + std::scoped_lock lck(allocatedRenderLock); for (uint32 i = 0; i < allocatedRenderCommands.size(); ++i) { PRenderCommand cmdBuffer = allocatedRenderCommands[i]; if (cmdBuffer->isReady()) { cmdBuffer->name = name; - cmdBuffer->begin(activeCmdBuffer); + cmdBuffer->begin(renderPass, framebuffer); return cmdBuffer; } } PRenderCommand result = new RenderCommand(graphics, commandPool); result->name = name; - result->begin(activeCmdBuffer); + result->begin(renderPass, framebuffer); allocatedRenderCommands.add(result); return result; } PComputeCommand CommandBufferManager::createComputeCommand(const std::string& name) { - std::unique_lock lck(allocatedComputeLock); + std::scoped_lock lck(allocatedComputeLock); for (uint32 i = 0; i < allocatedComputeCommands.size(); ++i) { PComputeCommand cmdBuffer = allocatedComputeCommands[i]; @@ -495,7 +485,7 @@ void CommandBufferManager::submitCommands(PSemaphore signalSemaphore) queue->submitCommandBuffer(activeCmdBuffer); } } - std::unique_lock lock(allocatedBufferLock); + std::scoped_lock lock(allocatedBufferLock); for (uint32 i = 0; i < allocatedBuffers.size(); ++i) { PCmdBuffer cmdBuffer = allocatedBuffers[i]; diff --git a/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.h b/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.h index dad4831..59aee44 100644 --- a/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.h +++ b/src/Engine/Graphics/Vulkan/VulkanCommandBuffer.h @@ -47,10 +47,7 @@ public: private: PGraphics graphics; PCommandBufferManager manager; - PRenderPass renderPass; - PFramebuffer framebuffer; PFence fence; - uint32 subpassIndex; State state; VkViewport currentViewport; VkRect2D currentScissor; @@ -79,7 +76,7 @@ public: { return handle; } - void begin(PCmdBuffer parent); + void begin(PRenderPass renderPass, PFramebuffer framebuffer); void end(); void reset(); virtual bool isReady() override; @@ -145,7 +142,7 @@ public: return queue; } PCmdBuffer getCommands(); - PRenderCommand createRenderCommand(const std::string& name); + PRenderCommand createRenderCommand(PRenderPass renderPass, PFramebuffer framebuffer, const std::string& name); PComputeCommand createComputeCommand(const std::string& name); VkCommandPool getPoolHandle() const { diff --git a/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.cpp b/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.cpp index 7d2b5fb..db06c23 100644 --- a/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.cpp @@ -54,7 +54,7 @@ PipelineLayout::~PipelineLayout() { if (layoutHandle != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(graphics->getDevice(), layoutHandle, nullptr); + //vkDestroyPipelineLayout(graphics->getDevice(), layoutHandle, nullptr); } } diff --git a/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.h b/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.h index a13b489..3d997c8 100644 --- a/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.h +++ b/src/Engine/Graphics/Vulkan/VulkanDescriptorSets.h @@ -29,12 +29,12 @@ DEFINE_REF(DescriptorLayout) class PipelineLayout : public Gfx::PipelineLayout { public: - PipelineLayout(PGraphics graphics) - : graphics(graphics) + PipelineLayout(PGraphics graphics, Gfx::PPipelineLayout baseLayout) + : Gfx::PipelineLayout(baseLayout) + , graphics(graphics) , layoutHash(0) , layoutHandle(VK_NULL_HANDLE) - { - } + {} virtual ~PipelineLayout(); virtual void create(); virtual void reset(); diff --git a/src/Engine/Graphics/Vulkan/VulkanGraphics.cpp b/src/Engine/Graphics/Vulkan/VulkanGraphics.cpp index 2a8d98d..9096b7e 100644 --- a/src/Engine/Graphics/Vulkan/VulkanGraphics.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanGraphics.cpp @@ -14,6 +14,11 @@ using namespace Seele::Vulkan; +thread_local PCommandBufferManager Seele::Vulkan::Graphics::graphicsCommands = nullptr; +thread_local PCommandBufferManager Seele::Vulkan::Graphics::computeCommands = nullptr; +thread_local PCommandBufferManager Seele::Vulkan::Graphics::transferCommands = nullptr; +thread_local PCommandBufferManager Seele::Vulkan::Graphics::dedicatedTransferCommands = nullptr; + Graphics::Graphics() : instance(VK_NULL_HANDLE) , handle(VK_NULL_HANDLE) @@ -52,7 +57,7 @@ Gfx::PWindow Graphics::createWindow(const WindowCreateInfo &createInfo) Gfx::PViewport Graphics::createViewport(Gfx::PWindow owner, const ViewportCreateInfo &viewportInfo) { PViewport result = new Viewport(this, owner, viewportInfo); - std::unique_lock lock(viewportLock); + std::scoped_lock lock(viewportLock); viewports.add(result); return result; } @@ -67,7 +72,7 @@ void Graphics::beginRenderPass(Gfx::PRenderPass renderPass) uint32 framebufferHash = rp->getFramebufferHash(); PFramebuffer framebuffer; { - std::unique_lock lock(allocatedFrameBufferLock); + std::scoped_lock lock(allocatedFrameBufferLock); auto found = allocatedFramebuffers.find(framebufferHash); if (found == allocatedFramebuffers.end()) { @@ -80,12 +85,18 @@ void Graphics::beginRenderPass(Gfx::PRenderPass renderPass) } } getGraphicsCommands()->getCommands()->beginRenderPass(rp, framebuffer); + std::scoped_lock lock(renderPassLock); + activeRenderPass = rp; + activeFramebuffer = framebuffer; } void Graphics::endRenderPass() { getGraphicsCommands()->getCommands()->endRenderPass(); getGraphicsCommands()->submitCommands(); + std::scoped_lock lock(renderPassLock); + activeRenderPass = nullptr; + activeFramebuffer = nullptr; } void Graphics::executeCommands(const Array& commands) @@ -128,7 +139,7 @@ Gfx::PIndexBuffer Graphics::createIndexBuffer(const IndexBufferCreateInfo &bulkD } Gfx::PRenderCommand Graphics::createRenderCommand(const std::string& name) { - PRenderCommand cmdBuffer = getGraphicsCommands()->createRenderCommand(name); + PRenderCommand cmdBuffer = getGraphicsCommands()->createRenderCommand(activeRenderPass, activeFramebuffer, name); return cmdBuffer; } @@ -206,9 +217,9 @@ Gfx::PDescriptorLayout Graphics::createDescriptorLayout(const std::string& name) PDescriptorLayout layout = new DescriptorLayout(this, name); return layout; } -Gfx::PPipelineLayout Graphics::createPipelineLayout() +Gfx::PPipelineLayout Graphics::createPipelineLayout(Gfx::PPipelineLayout baseLayout) { - PPipelineLayout layout = new PipelineLayout(this); + PPipelineLayout layout = new PipelineLayout(this, baseLayout); return layout; } @@ -295,49 +306,37 @@ PCommandBufferManager Graphics::getQueueCommands(Gfx::QueueType queueType) throw new std::logic_error("invalid queue type"); } } -std::mutex graphicsCommandLock; PCommandBufferManager Graphics::getGraphicsCommands() { - std::unique_lock lock(graphicsCommandLock); - auto id = std::this_thread::get_id(); - if(graphicsCommands.find(id) == graphicsCommands.end()) + if(graphicsCommands == nullptr) { - graphicsCommands[id] = new CommandBufferManager(this, graphicsQueue); + graphicsCommands = new CommandBufferManager(this, graphicsQueue); } - return graphicsCommands[id]; + return graphicsCommands; } -std::mutex computeCommandLock; PCommandBufferManager Graphics::getComputeCommands() { - std::unique_lock lock(computeCommandLock); - auto id = std::this_thread::get_id(); - if(computeCommands.find(id) == computeCommands.end()) + if(computeCommands == nullptr) { - computeCommands[id] = new CommandBufferManager(this, computeQueue); + computeCommands = new CommandBufferManager(this, computeQueue); } - return computeCommands[id]; + return computeCommands; } -std::mutex transferCommandLock; PCommandBufferManager Graphics::getTransferCommands() { - std::unique_lock lock(transferCommandLock); - auto id = std::this_thread::get_id(); - if(transferCommands.find(id) == transferCommands.end()) + if(transferCommands == nullptr) { - transferCommands[id] = new CommandBufferManager(this, transferQueue); + transferCommands = new CommandBufferManager(this, transferQueue); } - return transferCommands[id]; + return transferCommands; } -std::mutex dedicatedCommandLock; PCommandBufferManager Graphics::getDedicatedTransferCommands() { - std::unique_lock lock(dedicatedCommandLock); - auto id = std::this_thread::get_id(); - if(dedicatedTransferCommands.find(id) == dedicatedTransferCommands.end()) + if(dedicatedTransferCommands == dedicatedTransferCommands) { - dedicatedTransferCommands[id] = new CommandBufferManager(this, dedicatedTransferQueue); + dedicatedTransferCommands = new CommandBufferManager(this, dedicatedTransferQueue); } - return dedicatedTransferCommands[id]; + return dedicatedTransferCommands; } PAllocator Graphics::getAllocator() { diff --git a/src/Engine/Graphics/Vulkan/VulkanGraphics.h b/src/Engine/Graphics/Vulkan/VulkanGraphics.h index 4dcd5f1..5a57c34 100644 --- a/src/Engine/Graphics/Vulkan/VulkanGraphics.h +++ b/src/Engine/Graphics/Vulkan/VulkanGraphics.h @@ -11,6 +11,7 @@ DECLARE_REF(Allocator) DECLARE_REF(StagingManager) DECLARE_REF(CommandBufferManager) DECLARE_REF(Queue) +DECLARE_REF(RenderPass) DECLARE_REF(Framebuffer) DECLARE_REF(RenderCommand) DECLARE_REF(PipelineCache) @@ -63,7 +64,7 @@ public: virtual Gfx::PSamplerState createSamplerState(const SamplerCreateInfo& createInfo) override; virtual Gfx::PDescriptorLayout createDescriptorLayout(const std::string& name = "") override; - virtual Gfx::PPipelineLayout createPipelineLayout() override; + virtual Gfx::PPipelineLayout createPipelineLayout(Gfx::PPipelineLayout baseLayout = nullptr) override; virtual void copyTexture(Gfx::PTexture srcTexture, Gfx::PTexture dstTexture) override; protected: @@ -82,10 +83,13 @@ protected: PQueue transferQueue; PQueue dedicatedTransferQueue; PPipelineCache pipelineCache; - Map graphicsCommands; - Map computeCommands; - Map transferCommands; - Map dedicatedTransferCommands; + std::mutex renderPassLock; + PRenderPass activeRenderPass; + PFramebuffer activeFramebuffer; + thread_local static PCommandBufferManager graphicsCommands; + thread_local static PCommandBufferManager computeCommands; + thread_local static PCommandBufferManager transferCommands; + thread_local static PCommandBufferManager dedicatedTransferCommands; VkPhysicalDeviceProperties props; VkPhysicalDeviceFeatures features; VkDebugReportCallbackEXT callback; diff --git a/src/Engine/Graphics/Vulkan/VulkanInitializer.cpp b/src/Engine/Graphics/Vulkan/VulkanInitializer.cpp index 30f00f6..fb2b9b1 100644 --- a/src/Engine/Graphics/Vulkan/VulkanInitializer.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanInitializer.cpp @@ -296,6 +296,8 @@ VkBufferMemoryBarrier init::BufferMemoryBarrier() VkBufferMemoryBarrier bufferMemoryBarrier = {}; bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufferMemoryBarrier.pNext = NULL; + bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; return bufferMemoryBarrier; } diff --git a/src/Engine/Graphics/Vulkan/VulkanPipelineCache.cpp b/src/Engine/Graphics/Vulkan/VulkanPipelineCache.cpp index 47b64dc..de59b04 100644 --- a/src/Engine/Graphics/Vulkan/VulkanPipelineCache.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanPipelineCache.cpp @@ -323,7 +323,7 @@ PGraphicsPipeline PipelineCache::createPipeline(const GraphicsPipelineCreateInfo uint32 hash = crc.checksum(); VkPipeline pipelineHandle; - std::unique_lock lock(createdPipelinesLock); + std::scoped_lock lock(createdPipelinesLock); auto foundPipeline = createdPipelines.find(hash); if (foundPipeline != createdPipelines.end()) { diff --git a/src/Engine/Graphics/Vulkan/VulkanQueue.cpp b/src/Engine/Graphics/Vulkan/VulkanQueue.cpp index 350fbbe..4dae95e 100644 --- a/src/Engine/Graphics/Vulkan/VulkanQueue.cpp +++ b/src/Engine/Graphics/Vulkan/VulkanQueue.cpp @@ -22,7 +22,7 @@ Queue::~Queue() void Queue::submitCommandBuffer(PCmdBuffer cmdBuffer, uint32 numSignalSemaphores, VkSemaphore *signalSemaphores) { - std::unique_lock lck(queueLock); + std::scoped_lock lck(queueLock); assert(cmdBuffer->state == CmdBuffer::State::Ended); PFence fence = cmdBuffer->fence; diff --git a/src/Engine/Material/MaterialAsset.cpp b/src/Engine/Material/MaterialAsset.cpp index 78bb2e7..103c7af 100644 --- a/src/Engine/Material/MaterialAsset.cpp +++ b/src/Engine/Material/MaterialAsset.cpp @@ -185,6 +185,6 @@ const Gfx::ShaderCollection* MaterialAsset::getShaders(Gfx::RenderPassType rende Gfx::ShaderCollection& MaterialAsset::createShaders(Gfx::PGraphics graphics, Gfx::RenderPassType renderPass, VertexInputType* vertexInput) { - std::unique_lock lock(shaderMapLock); + std::scoped_lock lock(shaderMapLock); return shaderMap.createShaders(graphics, renderPass, this, vertexInput, false); } diff --git a/src/Engine/MinimalEngine.h b/src/Engine/MinimalEngine.h index 21fdaa9..4848c94 100644 --- a/src/Engine/MinimalEngine.h +++ b/src/Engine/MinimalEngine.h @@ -49,7 +49,7 @@ public: ~RefObject() { { - std::unique_lock lock(registeredObjectsLock); + std::scoped_lock lock(registeredObjectsLock); registeredObjects.erase(handle); } // #pragma warning( disable: 4150) @@ -114,7 +114,7 @@ public: } RefPtr(T *ptr, Deleter deleter = Deleter()) { - std::unique_lock l(registeredObjectsLock); + std::scoped_lock l(registeredObjectsLock); auto registeredObj = registeredObjects.find(ptr); // get here for thread safetly auto registeredEnd = registeredObjects.end(); @@ -249,6 +249,10 @@ public: { return object->getHandle(); } + RefPtr clone() + { + return RefPtr(new T(*getHandle())); + } private: RefObject *object; diff --git a/src/Engine/Scene/Actor/Actor.cpp b/src/Engine/Scene/Actor/Actor.cpp index dcc7dba..0dc19e8 100644 --- a/src/Engine/Scene/Actor/Actor.cpp +++ b/src/Engine/Scene/Actor/Actor.cpp @@ -12,8 +12,36 @@ Actor::~Actor() { } -void Actor::tick(float) + +void Actor::launchStart() { + rootComponent->launchStart(); + for(auto child : children) + { + child->launchStart(); + } + start(); +} +Array Actor::launchTick(float deltaTime) const +{ + Array result = rootComponent->launchTick(deltaTime); + for(auto child : children) + { + result.addAll(child->launchTick(deltaTime)); + } + result.add(tick(deltaTime)); + return result; +} + +Array Actor::launchUpdate() +{ + Array result = rootComponent->launchUpdate(); + for(auto child : children) + { + result.addAll(child->launchUpdate()); + } + result.add(update()); + return result; } void Actor::notifySceneAttach(PScene scene) { diff --git a/src/Engine/Scene/Actor/Actor.h b/src/Engine/Scene/Actor/Actor.h index 5080b8a..424ee1e 100644 --- a/src/Engine/Scene/Actor/Actor.h +++ b/src/Engine/Scene/Actor/Actor.h @@ -1,6 +1,7 @@ #pragma once #include "MinimalEngine.h" #include "Math/Transform.h" +#include "ThreadPool.h" namespace Seele { @@ -12,8 +13,12 @@ class Actor public: Actor(); virtual ~Actor(); - - void tick(float deltaTime); + virtual void launchStart(); + virtual void start() {} + Array launchTick(float deltaTime) const; + virtual Job tick(float deltaTime) const { co_return; } + Array launchUpdate(); + virtual Job update() { co_return; } void notifySceneAttach(PScene scene); PScene getScene(); diff --git a/src/Engine/Scene/CMakeLists.txt b/src/Engine/Scene/CMakeLists.txt index a592c30..1d102c6 100644 --- a/src/Engine/Scene/CMakeLists.txt +++ b/src/Engine/Scene/CMakeLists.txt @@ -1,5 +1,6 @@ target_sources(SeeleEngine PRIVATE + Util.h Scene.cpp Scene.h) diff --git a/src/Engine/Scene/Components/CMakeLists.txt b/src/Engine/Scene/Components/CMakeLists.txt index fb05eb3..15bd041 100644 --- a/src/Engine/Scene/Components/CMakeLists.txt +++ b/src/Engine/Scene/Components/CMakeLists.txt @@ -4,6 +4,10 @@ target_sources(SeeleEngine CameraComponent.cpp Component.h Component.cpp + MyComponent.h + MyComponent.cpp + MyOtherComponent.h + MyOtherComponent.cpp PrimitiveComponent.cpp PrimitiveComponent.h PrimitiveUniformBufferLayout.h) \ No newline at end of file diff --git a/src/Engine/Scene/Components/CameraComponent.cpp b/src/Engine/Scene/Components/CameraComponent.cpp index c6fad56..e876562 100644 --- a/src/Engine/Scene/Components/CameraComponent.cpp +++ b/src/Engine/Scene/Components/CameraComponent.cpp @@ -5,11 +5,16 @@ using namespace Seele; -CameraComponent::CameraComponent() - : bNeedsViewBuild(true) +CameraComponent::CameraComponent() + : aspectRatio(0) + , fieldOfView(0) + , bNeedsViewBuild(true) , bNeedsProjectionBuild(true) , originPoint(0, 0, 0) , cameraPosition(0, 0, 50) + , eye(Vector()) + , projectionMatrix(Matrix4()) + , viewMatrix(Matrix4()) { distance = 50; rotationX = 0; diff --git a/src/Engine/Scene/Components/Component.cpp b/src/Engine/Scene/Components/Component.cpp index b0be1b8..b97785f 100644 --- a/src/Engine/Scene/Components/Component.cpp +++ b/src/Engine/Scene/Components/Component.cpp @@ -13,9 +13,38 @@ Component::Component() Component::~Component() { } -void Component::tick(float) + +void Component::launchStart() { + for(auto child : children) + { + child->launchStart(); + } + start(); } + +Array Component::launchTick(float deltaTime) const +{ + Array result; + for(auto child : children) + { + result.addAll(child->launchTick(deltaTime)); + } + result.add(tick(deltaTime)); + return result; +} + +Array Component::launchUpdate() +{ + Array result; + for(auto child : children) + { + result.addAll(child->launchUpdate()); + } + result.add(update()); + return result; +} + PComponent Component::getParent() { return parent; @@ -32,6 +61,25 @@ PActor Component::getOwner() } return nullptr; } +const Array& Component::getChildComponents() +{ + return children; +} + +void Component::setParent(PComponent newParent) +{ + parent = newParent; +} + +void Component::setOwner(PActor newOwner) +{ + owner = newOwner; +} + +void Component::addChildComponent(PComponent component) +{ + children.add(component); +} void Component::notifySceneAttach(PScene scene) { @@ -118,16 +166,6 @@ Transform Component::getTransform() const return transform; } -void Component::setParent(PComponent newParent) -{ - parent = newParent; -} - -void Component::setOwner(PActor newOwner) -{ - owner = newOwner; -} - void Component::internalSetTransform(Vector newLocation, Quaternion newRotation) { if (parent != nullptr) diff --git a/src/Engine/Scene/Components/Component.h b/src/Engine/Scene/Components/Component.h index b1062ad..95dae38 100644 --- a/src/Engine/Scene/Components/Component.h +++ b/src/Engine/Scene/Components/Component.h @@ -1,6 +1,8 @@ #pragma once #include "MinimalEngine.h" #include "Math/Transform.h" +#include "Scene/Util.h" +#include "ThreadPool.h" namespace Seele { @@ -12,11 +14,18 @@ class Component public: Component(); virtual ~Component(); - void tick(float deltaTime); + void launchStart(); + virtual void start() {}; + Array launchTick(float deltaTime) const; + virtual Job tick(float deltaTime) const { co_return; } + Array launchUpdate(); + virtual Job update() { co_return; } PComponent getParent(); PActor getOwner(); + const Array& getChildComponents(); void setParent(PComponent parent); void setOwner(PActor owner); + void addChildComponent(PComponent component); virtual void notifySceneAttach(PScene scene); void setWorldLocation(Vector location); @@ -35,7 +44,30 @@ public: Transform getTransform() const; + template + RefPtr getComponent() + { + return tryFindComponent(); + } private: + template + RefPtr tryFindComponent() + { + for(auto child : children) + { + RefPtr result = child.cast(); + if(result != nullptr) + { + return result; + } + result = parent->tryFindComponent(); + if(result != nullptr) + { + return result; + } + } + return nullptr; + } void internalSetTransform(Vector newLocation, Quaternion newRotation); void propagateTransformUpdate(); void updateComponentTransform(Quaternion relativeRotationQuat); diff --git a/src/Engine/Scene/Components/MyComponent.cpp b/src/Engine/Scene/Components/MyComponent.cpp new file mode 100644 index 0000000..9d75e23 --- /dev/null +++ b/src/Engine/Scene/Components/MyComponent.cpp @@ -0,0 +1,26 @@ +#include "MyComponent.h" + +using namespace Seele; + +MyComponent::MyComponent() +{ +} + +void MyComponent::start() +{ + otherComp = getComponent(); + otherComp.update(); +} + +Job MyComponent::tick(float deltatime) const +{ + writable++; + otherComp->data = *writable; + co_return; +} + +Job MyComponent::update() +{ + writable.update(); + co_return; +} diff --git a/src/Engine/Scene/Components/MyComponent.h b/src/Engine/Scene/Components/MyComponent.h new file mode 100644 index 0000000..0e5fa0c --- /dev/null +++ b/src/Engine/Scene/Components/MyComponent.h @@ -0,0 +1,20 @@ +#pragma once +#include "Component.h" +#include "MyOtherComponent.h" + +namespace Seele +{ +class MyComponent : public Component +{ +public: + MyComponent(); + virtual void start(); + virtual Job tick(float deltatime) const; + virtual Job update(); +private: + Writable otherComp; + Writable writable = Writable(0); + uint32 notWritable = 10; +}; +DECLARE_REF(MyComponent); +} // namespace Seele diff --git a/src/Engine/Scene/Components/MyOtherComponent.cpp b/src/Engine/Scene/Components/MyOtherComponent.cpp new file mode 100644 index 0000000..e930585 --- /dev/null +++ b/src/Engine/Scene/Components/MyOtherComponent.cpp @@ -0,0 +1,15 @@ +#include "MyOtherComponent.h" + +using namespace Seele; + +Job MyOtherComponent::tick(float deltaTime) const +{ + std::cout << *data << std::endl; + co_return; +} + +Job MyOtherComponent::update() +{ + data.update(); + co_return; +} diff --git a/src/Engine/Scene/Components/MyOtherComponent.h b/src/Engine/Scene/Components/MyOtherComponent.h new file mode 100644 index 0000000..51793ad --- /dev/null +++ b/src/Engine/Scene/Components/MyOtherComponent.h @@ -0,0 +1,15 @@ +#pragma once +#include "Component.h" + +namespace Seele +{ +class MyOtherComponent : public Component +{ +public: + virtual Job tick(float deltaTime) const; + virtual Job update(); + Writable data = Writable(0); +private: +}; +DEFINE_REF(MyOtherComponent); +} // namespace Seele diff --git a/src/Engine/Scene/Components/PrimitiveComponent.cpp b/src/Engine/Scene/Components/PrimitiveComponent.cpp index 3d11d30..aefba4d 100644 --- a/src/Engine/Scene/Components/PrimitiveComponent.cpp +++ b/src/Engine/Scene/Components/PrimitiveComponent.cpp @@ -26,7 +26,7 @@ PrimitiveComponent::PrimitiveComponent(PMeshAsset asset) batch.useReverseCulling = false; batch.useWireframe = false; batch.vertexInput = assetMeshes[i]->vertexInput; - auto& batchElement = batch.elements.add(); + MeshBatchElement batchElement; batchElement.baseVertexIndex = 0; batchElement.firstIndex = 0; batchElement.indexBuffer = assetMeshes[i]->indexBuffer; @@ -35,6 +35,7 @@ PrimitiveComponent::PrimitiveComponent(PMeshAsset asset) batchElement.isInstanced = false; batchElement.numInstances = 1; batchElement.numPrimitives = assetMeshes[i]->indexBuffer->getNumIndices() / 3; //TODO: hardcoded + batch.elements.push_back(batchElement); } } diff --git a/src/Engine/Scene/Components/PrimitiveComponent.h b/src/Engine/Scene/Components/PrimitiveComponent.h index 9a42ef2..1e17932 100644 --- a/src/Engine/Scene/Components/PrimitiveComponent.h +++ b/src/Engine/Scene/Components/PrimitiveComponent.h @@ -16,14 +16,14 @@ public: ~PrimitiveComponent(); virtual void notifySceneAttach(PScene scene) override; Matrix4 getRenderMatrix(); - const Array& getStaticMeshes() + std::vector& getStaticMeshes() { return staticMeshes; } private: - Array materials; + std::vector materials; Gfx::PUniformBuffer uniformBuffer; - Array staticMeshes; + std::vector staticMeshes; friend class Scene; }; DEFINE_REF(PrimitiveComponent) diff --git a/src/Engine/Scene/Scene.cpp b/src/Engine/Scene/Scene.cpp index be75c53..ce5778b 100644 --- a/src/Engine/Scene/Scene.cpp +++ b/src/Engine/Scene/Scene.cpp @@ -33,19 +33,39 @@ Scene::~Scene() { } -void Scene::tick(double) +void Scene::start() { + for(auto actor : rootActors) + { + actor->launchStart(); + } +} + +Job Scene::beginUpdate(double deltaTime) +{ + for(auto actor : rootActors) + { + co_await Job::all(actor->launchTick(static_cast(deltaTime))); + } +} + +Job Scene::commitUpdate() +{ + for(auto actor : rootActors) + { + co_await Job::all(actor->launchUpdate()); + } } void Scene::addActor(PActor actor) { - rootActors.add(actor); + rootActors.push_back(actor); actor->notifySceneAttach(this); } void Scene::addPrimitiveComponent(PPrimitiveComponent comp) { - primitives.add(comp); + primitives.push_back(comp); for(auto& batch : comp->getStaticMeshes()) { PrimitiveUniformBuffer data; @@ -62,6 +82,6 @@ void Scene::addPrimitiveComponent(PPrimitiveComponent comp) { element.uniformBuffer = uniformBuffer; } - staticMeshes.add(batch); + staticMeshes.push_back(batch); } } diff --git a/src/Engine/Scene/Scene.h b/src/Engine/Scene/Scene.h index d023eca..cdd491e 100644 --- a/src/Engine/Scene/Scene.h +++ b/src/Engine/Scene/Scene.h @@ -38,17 +38,19 @@ class Scene public: Scene(Gfx::PGraphics graphics); ~Scene(); - void tick(double deltaTime); + void start(); + Job beginUpdate(double deltaTime); + Job commitUpdate(); void addActor(PActor actor); void addPrimitiveComponent(PPrimitiveComponent comp); - const Array& getPrimitives() const { return primitives; } - const Array& getStaticMeshes() const { return staticMeshes; } + const std::vector& getPrimitives() const { return primitives; } + const std::vector& getStaticMeshes() const { return staticMeshes; } const LightEnv& getLightBuffer() const { return lightEnv; } private: - Array staticMeshes; - Array rootActors; - Array primitives; + std::vector staticMeshes; + std::vector rootActors; + std::vector primitives; LightEnv lightEnv; Gfx::PGraphics graphics; }; diff --git a/src/Engine/Scene/SceneUpdater.cpp b/src/Engine/Scene/SceneUpdater.cpp index a5f0879..98991ba 100644 --- a/src/Engine/Scene/SceneUpdater.cpp +++ b/src/Engine/Scene/SceneUpdater.cpp @@ -26,25 +26,25 @@ SceneUpdater::~SceneUpdater() void SceneUpdater::registerComponentUpdate(PComponent component) { - std::unique_lock lck(pendingUpdatesLock); + std::scoped_lock lck(pendingUpdatesLock); updatesRan.add([component](float delta) mutable { component->tick(delta); }); } void SceneUpdater::registerActorUpdate(PActor actor) { - std::unique_lock lck(pendingUpdatesLock); + std::scoped_lock lck(pendingUpdatesLock); updatesRan.add([actor](float delta) mutable { actor->tick(delta); }); } void SceneUpdater::runUpdates(float delta) { - std::unique_lock pendingLock(pendingUpdatesLock); + std::scoped_lock pendingLock(pendingUpdatesLock); frameDelta = delta; pendingUpdates = std::move(updatesRan); updatesRan = List>(); - std::unique_lock lck(frameFinishedLock); + std::scoped_lock lck(frameFinishedLock); pendingLock.unlock(); pendingUpdatesSem.release(pendingUpdates.size()); frameFinishedCV.wait(lck); @@ -57,18 +57,18 @@ void SceneUpdater::work() pendingUpdatesSem.acquire(); std::function function; { - std::unique_lock lck(pendingUpdatesLock); + std::scoped_lock lck(pendingUpdatesLock); function = std::move(pendingUpdates.front()); pendingUpdates.popFront(); if(pendingUpdates.empty()) { - std::unique_lock lck(frameFinishedLock); + std::scoped_lock lck(frameFinishedLock); frameFinishedCV.notify_all(); } } function(frameDelta); { - std::unique_lock lck(pendingUpdatesLock); + std::scoped_lock lck(pendingUpdatesLock); updatesRan.add(std::move(function)); } } diff --git a/src/Engine/Scene/Util.h b/src/Engine/Scene/Util.h new file mode 100644 index 0000000..3c8b191 --- /dev/null +++ b/src/Engine/Scene/Util.h @@ -0,0 +1,149 @@ +#pragma once +#include "MinimalEngine.h" + +namespace Seele +{ +template +class Writable +{ +public: + Writable() + {} + explicit Writable(T initialData) + : data(initialData) + , toBeWritten(initialData) + {} + Writable(const Writable& other) = delete; + Writable(Writable&& other) = default; + ~Writable() = default; + Writable& operator=(const Writable& other) = delete; + Writable& operator=(Writable&& other) = default; + const Writable& operator=(const T& other) const + { + deferWrite(other); + return *this; + } + const Writable& operator=(T&& other) const + { + deferWrite(std::move(other)); + return *this; + } + template + const Writable& operator+(Other&& other) const + { + deferWrite(data + std::forward(other)); + return *this; + } + template + const Writable& operator-(Other&& other) const + { + deferWrite(data - std::forward(other)); + return *this; + } + template + const Writable& operator*(Other&& other) const + { + deferWrite(data * std::forward(other)); + return *this; + } + template + const Writable& operator/(Other&& other) const + { + deferWrite(data / std::forward(other)); + return *this; + } + template + const Writable& operator%(Other&& other) const + { + deferWrite(data % std::forward(other)); + return *this; + } + template + const Writable& operator^(Other&& other) const + { + deferWrite(data ^ std::forward(other)); + return *this; + } + template + const Writable& operator&(Other&& other) const + { + deferWrite(data & std::forward(other)); + return *this; + } + template + const Writable& operator|(Other&& other) const + { + deferWrite(data | std::forward(other)); + return *this; + } + template + bool operator==(Type other) const + { + return data == other.data; + } + template + bool operator<=>(Type other) const + { + return data <=> other.data; + } + const Writable& operator++() const + { + deferWrite(data+1); + return *this; + } + const Writable& operator--() const + { + deferWrite(data-1); + return *this; + } + Writable&& operator++(int) const + { + Writable tmp(data); + ++*this; + return std::move(tmp); + } + Writable&& operator--(int) const + { + Writable tmp(data); + --*this; + return std::move(tmp); + } + const T& operator*() const + { + return data; + } + const T& get() const + { + return data; + } + const T& operator->() const + { + return data; + } + void update() + { + // lock should not be necessary, but lets keep it for now + std::scoped_lock lock(dataLock); + data = toBeWritten; + dirty = false; + } +private: + template + void deferWrite(Type&& newValue) const + { + std::scoped_lock lock(dataLock); + assert(!dirty); + toBeWritten = std::forward(newValue); + dirty = true; + } + mutable bool dirty = false; + mutable std::mutex dataLock; + mutable T toBeWritten; + T data; +}; +template +concept writable = requires(A&& a) +{ + a.update(); +}; +} // namespace Seele diff --git a/src/Engine/ThreadPool.cpp b/src/Engine/ThreadPool.cpp index 44e77e1..9638f20 100644 --- a/src/Engine/ThreadPool.cpp +++ b/src/Engine/ThreadPool.cpp @@ -6,7 +6,7 @@ using namespace Seele; std::atomic_uint64_t Seele::globalCounter; Event::Event() - : flag(new std::atomic_bool()) + : flag(std::make_shared()) { } @@ -16,7 +16,8 @@ Event::Event(nullptr_t) } Event::Event(const std::string &name) - : name(name), flag(new std::atomic_bool()) + : name(name) + , flag(std::make_shared()) { } @@ -48,75 +49,80 @@ ThreadPool::ThreadPool(uint32 threadCount) ThreadPool::~ThreadPool() { running.store(false); + { + std::scoped_lock lock(jobQueueLock); + jobQueueCV.notify_all(); + } + { + std::scoped_lock lock(mainJobLock); + mainJobCV.notify_all(); + } for (auto &thread : workers) { thread.join(); } + workers.clear(); + waitingJobs.clear(); + waitingMainJobs.clear(); } void ThreadPool::enqueueWaiting(Event &event, Promise* job) { assert(!job->done()); - if(event == nullptr) - { - std::unique_lock lock(jobQueueLock); - //std::cout << "Queueing job " << job->finishedEvent.name << std::endl; - jobQueue.add(job); - jobQueueCV.notify_one(); - } - else - { - std::unique_lock lock(waitingLock); - //std::cout << "Job " << job->finishedEvent.name << " waiting on event " << event.name << std::endl; - waitingJobs[event].add(job); - } + std::scoped_lock lock(waitingLock); + //std::cout << "Job " << job->finishedEvent.name << " waiting on event " << event.name << std::endl; + waitingJobs[event].push_back(job); } void ThreadPool::enqueueWaiting(Event &event, MainPromise* job) { assert(!job->done()); - if(event == nullptr || event) - { - std::unique_lock lock(mainJobLock); - //std::cout << "Queueing job " << job->finishedEvent.name << std::endl; - mainJobs.add(job); - mainJobCV.notify_one(); - } - else - { - std::unique_lock lock(waitingMainLock); - //std::cout << job->finishedEvent.name << " waiting on event " << event.name << std::endl; - waitingMainJobs[event].add(job); - } + std::scoped_lock lock(waitingMainLock); + //std::cout << job->finishedEvent.name << " waiting on event " << event.name << std::endl; + waitingMainJobs[event].push_back(job); +} +void ThreadPool::scheduleJob(Promise* job) +{ + assert(!job->done()); + std::scoped_lock lock(jobQueueLock); + //std::cout << "Queueing job " << job->finishedEvent.name << std::endl; + jobQueue.push_back(job); + jobQueueCV.notify_one(); +} +void ThreadPool::scheduleJob(MainPromise* job) +{ + assert(!job->done()); + std::scoped_lock lock(mainJobLock); + //std::cout << "Queueing job " << job->finishedEvent.name << std::endl; + mainJobs.push_back(job); + mainJobCV.notify_one(); } void ThreadPool::notify(Event &event) { //std::cout << "Event " << event.name << " raised" << std::endl; { - std::unique_lock lock(jobQueueLock); - std::unique_lock lock2(waitingLock); - List &jobs = waitingJobs[event]; + std::scoped_lock lock(jobQueueLock, waitingLock); + std::list jobs = std::move(waitingJobs[event]); + waitingJobs.erase(event); for (auto &job : jobs) { //assert(job.id != -1ull); //std::cout << "Waking up " << job->finishedEvent.name << std::endl; job->state = Promise::State::SCHEDULED; - jobQueue.add(job); + jobQueue.push_back(job); jobQueueCV.notify_one(); } - waitingJobs.erase(event); } { - std::unique_lock lock(mainJobLock); - std::unique_lock lock2(waitingMainLock); - List &jobs = waitingMainJobs[event]; + std::scoped_lock lock(mainJobLock, waitingMainLock); + std::list jobs = std::move(waitingMainJobs[event]); + waitingMainJobs.erase(event); for (auto &job : jobs) { //assert(job.id != -1ull); //std::cout << "Waking up main " << job->finishedEvent.name << std::endl; job->state = MainPromise::State::SCHEDULED; - mainJobs.add(job); + mainJobs.push_back(job); mainJobCV.notify_one(); } - waitingMainJobs.erase(event); } } void ThreadPool::threadLoop(const bool mainThread) @@ -134,7 +140,8 @@ void ThreadPool::threadLoop(const bool mainThread) } if (!mainJobs.empty()) { - job = mainJobs.retrieve(); + job = mainJobs.front(); + mainJobs.pop_front(); } else { @@ -144,7 +151,7 @@ void ThreadPool::threadLoop(const bool mainThread) job->resume(); if (job->done()) { - job->raise(); + job->finalize(); } } else @@ -158,7 +165,8 @@ void ThreadPool::threadLoop(const bool mainThread) } if (!jobQueue.empty()) { - job = jobQueue.retrieve(); + job = jobQueue.front(); + jobQueue.pop_front(); } else { @@ -167,9 +175,9 @@ void ThreadPool::threadLoop(const bool mainThread) } //std::cout << "Starting job " << job.id << std::endl; job->resume(); - if (job->done()) + if(job->done()) { - job->raise(); + job->finalize(); } } } diff --git a/src/Engine/ThreadPool.h b/src/Engine/ThreadPool.h index acc1751..2a908a2 100644 --- a/src/Engine/ThreadPool.h +++ b/src/Engine/ThreadPool.h @@ -16,6 +16,7 @@ public: Event(); Event(nullptr_t); Event(const std::string& name); + ~Event() = default; auto operator<=>(const Event& other) const { return flag <=> other.flag; @@ -41,7 +42,7 @@ public: constexpr void await_resume() {} private: std::string name; - RefPtr flag; + std::shared_ptr flag; friend class ThreadPool; }; @@ -65,6 +66,13 @@ struct JobPromiseBase id = globalCounter++; finishedEvent = Event(std::format("Job {}", id)); } + ~JobPromiseBase() + { + if (handle) + { + handle.destroy(); + } + } JobBase get_return_object() noexcept; inline auto initial_suspend() noexcept; @@ -78,7 +86,6 @@ struct JobPromiseBase void resume() { - std::unique_lock lock(promiseLock); if(!handle || handle.done() || executing()) { return; @@ -88,10 +95,9 @@ struct JobPromiseBase } void setContinuation(JobPromiseBase* cont) { - std::unique_lock lock(promiseLock); - std::unique_lock lock2(cont->promiseLock); + std::scoped_lock lock(promiseLock, cont->promiseLock); continuation = cont->handle; - cont->precondition = finishedEvent; + cont->state = State::SCHEDULED; } bool done() { @@ -113,15 +119,15 @@ struct JobPromiseBase { return state == State::READY; } - void raise() + void finalize() { - std::unique_lock lock(promiseLock); + std::scoped_lock lock(promiseLock); state = State::DONE; finishedEvent.raise(); } void reset() { - std::unique_lock lock(promiseLock); + std::scoped_lock lock(promiseLock); finishedEvent.reset(); } void enqueue(Event& event) @@ -136,13 +142,12 @@ struct JobPromiseBase } void schedule() { - std::unique_lock lock(promiseLock); + std::scoped_lock lock(promiseLock); if(!handle || done() || !ready()) { return; } - state = (precondition == nullptr) ? State::SCHEDULED : State::WAITING; - getGlobalThreadPool().enqueueWaiting(precondition, this); + getGlobalThreadPool().scheduleJob(this); } std::mutex promiseLock; @@ -150,7 +155,6 @@ struct JobPromiseBase std::coroutine_handle<> continuation = std::noop_coroutine(); uint64 id; Event finishedEvent; - Event precondition = nullptr; State state = State::READY; }; @@ -193,13 +197,9 @@ public: { return promise->done(); } - void raise() + void finalize() { - promise->raise(); - } - void reset() - { - promise->reset(); + promise->finalize(); } Event operator co_await() { @@ -245,30 +245,32 @@ using Promise = JobPromiseBase; class ThreadPool { public: - ThreadPool(uint32 threadCount = std::thread::hardware_concurrency()); + ThreadPool(uint32 threadCount = 1); virtual ~ThreadPool(); - // Adds a job to the waiting queue for event, or directly to the job queue if event is nullptr + // Adds a job to the waiting queue for event void enqueueWaiting(Event& event, Promise* job); - // Adds a job to the waiting queue for event, or directly to the job queue if event is nullptr + // Adds a job to the waiting queue for event void enqueueWaiting(Event& event, MainPromise* job); + void scheduleJob(Promise* job); + void scheduleJob(MainPromise* job); void notify(Event& event); void threadLoop(const bool isMainThread); private: std::atomic_bool running; std::vector workers; - List mainJobs; + std::list mainJobs; std::mutex mainJobLock; std::condition_variable mainJobCV; - List jobQueue; + std::list jobQueue; std::mutex jobQueueLock; std::condition_variable jobQueueCV; - std::map> waitingMainJobs; + std::map> waitingMainJobs; std::mutex waitingMainLock; - std::map> waitingJobs; + std::map> waitingJobs; std::mutex waitingLock; }; @@ -283,6 +285,7 @@ inline auto JobPromiseBase::initial_suspend() noexcept { return std::suspend_always{}; } + template inline auto JobPromiseBase::final_suspend() const noexcept { diff --git a/src/Engine/UI/Elements/Element.cpp b/src/Engine/UI/Elements/Element.cpp index f56625e..cc0bc2b 100644 --- a/src/Engine/UI/Elements/Element.cpp +++ b/src/Engine/UI/Elements/Element.cpp @@ -5,6 +5,8 @@ using namespace Seele; using namespace Seele::UI; Element::Element() + : dirty(false) + , enabled(false) { } diff --git a/src/Engine/UI/RenderHierarchy.cpp b/src/Engine/UI/RenderHierarchy.cpp index ad6e7d5..a94ac56 100644 --- a/src/Engine/UI/RenderHierarchy.cpp +++ b/src/Engine/UI/RenderHierarchy.cpp @@ -31,7 +31,7 @@ RenderHierarchy::~RenderHierarchy() void RenderHierarchy::addElement(PElement addedElement) { - std::unique_lock lock(updateLock); + std::scoped_lock lock(updateLock); updates.add(new AddElementRenderHierarchyUpdate{ addedElement.getHandle(), addedElement->getParent().getHandle() @@ -40,7 +40,7 @@ void RenderHierarchy::addElement(PElement addedElement) void RenderHierarchy::removeElement(PElement elementToRemove) { - std::unique_lock lock(updateLock); + std::scoped_lock lock(updateLock); updates.add(new RemoveElementRenderHierarchyUpdate{ elementToRemove.getHandle(), }); @@ -48,7 +48,7 @@ void RenderHierarchy::removeElement(PElement elementToRemove) void RenderHierarchy::moveElement(PElement elementToMove, PElement newParent) { - std::unique_lock lock(updateLock); + std::scoped_lock lock(updateLock); updates.add(new AddElementRenderHierarchyUpdate{ elementToMove.getHandle(), newParent.getHandle() @@ -62,7 +62,7 @@ void RenderHierarchy::updateHierarchy() { List localUpdates; { // make a local copy of the updates so we dont hold the lock for too long - std::unique_lock lock(updateLock); + std::scoped_lock lock(updateLock); localUpdates = updates; updates.clear(); } diff --git a/src/Engine/Window/InspectorView.cpp b/src/Engine/Window/InspectorView.cpp index 83b1c9c..4f954bb 100644 --- a/src/Engine/Window/InspectorView.cpp +++ b/src/Engine/Window/InspectorView.cpp @@ -19,13 +19,14 @@ InspectorView::~InspectorView() { } -void InspectorView::beginUpdate() +Job InspectorView::beginUpdate() { + co_return; } -void InspectorView::update() +Job InspectorView::update() { - + co_return; } void InspectorView::commitUpdate() diff --git a/src/Engine/Window/InspectorView.h b/src/Engine/Window/InspectorView.h index dd7227a..db62bf5 100644 --- a/src/Engine/Window/InspectorView.h +++ b/src/Engine/Window/InspectorView.h @@ -13,8 +13,8 @@ public: InspectorView(Gfx::PGraphics graphics, PWindow window, const ViewportCreateInfo &createInfo); virtual ~InspectorView(); - virtual void beginUpdate() override; - virtual void update() override; + virtual Job beginUpdate() override; + virtual Job update() override; virtual void commitUpdate() override; virtual void prepareRender() override; diff --git a/src/Engine/Window/SceneView.cpp b/src/Engine/Window/SceneView.cpp index f16135a..a04d8e6 100644 --- a/src/Engine/Window/SceneView.cpp +++ b/src/Engine/Window/SceneView.cpp @@ -6,6 +6,8 @@ #include "Asset/AssetRegistry.h" #include "Scene/Actor/CameraActor.h" #include "Scene/Components/CameraComponent.h" +#include "Scene/Components/MyComponent.h" +#include "Scene/Components/MyOtherComponent.h" using namespace Seele; @@ -28,18 +30,23 @@ Seele::SceneView::SceneView(Gfx::PGraphics graphics, PWindow owner, const Viewpo AssetRegistry::importFile("C:\\Users\\Dynamitos\\TestSeeleProject\\Assets\\Ely\\Ely.fbx"); AssetRegistry::importFile("C:\\Users\\Dynamitos\\TestSeeleProject\\Assets\\Cube\\cube.obj"); AssetRegistry::importFile("C:\\Users\\Dynamitos\\TestSeeleProject\\Assets\\Plane\\plane.fbx"); - srand(time(NULL)); - for(uint32 i = 0; i < 100; ++i) - { - PPrimitiveComponent ayaka = new PrimitiveComponent(AssetRegistry::findMesh("Ayaka")); - ayaka->addWorldTranslation(Vector(((float)rand() / RAND_MAX) * 100, 0, ((float)rand()/RAND_MAX) * 100)); - ayaka->setWorldScale(Vector(10, 10, 10)); - scene->addPrimitiveComponent(ayaka); - } + + PPrimitiveComponent ayaka = new PrimitiveComponent(AssetRegistry::findMesh("Ayaka")); + ayaka->addWorldTranslation(Vector(0, 0, 0)); + ayaka->setWorldScale(Vector(10, 10, 10)); + scene->addPrimitiveComponent(ayaka); PPrimitiveComponent plane = new PrimitiveComponent(AssetRegistry::findMesh("plane")); plane->setWorldScale(Vector(100, 100, 100)); scene->addPrimitiveComponent(plane); + + PMyComponent myComp = new MyComponent(); + PMyOtherComponent myOtherComp = new MyOtherComponent(); + PActor actor = new Actor(); + actor->setRootComponent(myComp); + myComp->addChildComponent(myOtherComp); + scene->addActor(actor); + scene->start(); PRenderGraphResources resources = new RenderGraphResources(); depthPrepass.setResources(resources); @@ -59,13 +66,16 @@ Seele::SceneView::~SceneView() { } -void SceneView::beginUpdate() +Job SceneView::beginUpdate() { - scene->tick(Gfx::currentFrameDelta); + co_await scene->beginUpdate(Gfx::currentFrameDelta); + co_return; } -void SceneView::update() +Job SceneView::update() { + co_await scene->commitUpdate(); + co_return; } void SceneView::commitUpdate() diff --git a/src/Engine/Window/SceneView.h b/src/Engine/Window/SceneView.h index b6b0ef7..015552d 100644 --- a/src/Engine/Window/SceneView.h +++ b/src/Engine/Window/SceneView.h @@ -13,8 +13,8 @@ class SceneView : public View public: SceneView(Gfx::PGraphics graphics, PWindow owner, const ViewportCreateInfo &createInfo); ~SceneView(); - virtual void beginUpdate() override; - virtual void update() override; + virtual Job beginUpdate() override; + virtual Job update() override; virtual void commitUpdate() override; virtual void prepareRender() override; diff --git a/src/Engine/Window/View.h b/src/Engine/Window/View.h index f9a0b2b..79eb0ad 100644 --- a/src/Engine/Window/View.h +++ b/src/Engine/Window/View.h @@ -13,8 +13,8 @@ public: virtual ~View(); // These are called from the view thread, and handle updating game data - virtual void beginUpdate() = 0; - virtual void update() = 0; + virtual Job beginUpdate() = 0; + virtual Job update() = 0; // End frame is called with a lock, so it is safe to write to shared memory virtual void commitUpdate() = 0; diff --git a/src/Engine/Window/Window.cpp b/src/Engine/Window/Window.cpp index 6672715..e2c9574 100644 --- a/src/Engine/Window/Window.cpp +++ b/src/Engine/Window/Window.cpp @@ -32,7 +32,7 @@ MainJob Window::render() co_await windowView->updateFinished; windowView->updateFinished.reset(); { - std::unique_lock lock(windowView->workerMutex); + std::scoped_lock lock(windowView->workerMutex); windowView->view->prepareRender(); } windowView->view->render(); @@ -78,7 +78,7 @@ Job Window::viewWorker(size_t viewIndex) windowView->view->beginUpdate(); windowView->view->update(); { - std::unique_lock lock(windowView->workerMutex); + std::scoped_lock lock(windowView->workerMutex); windowView->view->commitUpdate(); } //std::cout << "Update completed" << std::endl; diff --git a/src/Engine/Window/WindowManager.cpp b/src/Engine/Window/WindowManager.cpp index 283e098..57447e8 100644 --- a/src/Engine/Window/WindowManager.cpp +++ b/src/Engine/Window/WindowManager.cpp @@ -41,7 +41,7 @@ void WindowManager::notifyWindowClosed(PWindow window) windows.remove(windows.find(window)); if(windows.empty()) { - std::unique_lock lock(windowsLock); + std::scoped_lock lock(windowsLock); windowsCV.notify_all(); } } diff --git a/test/Engine/Containers/Array.cpp b/test/Engine/Containers/Array.cpp index e56bdd9..9a8b7e7 100644 --- a/test/Engine/Containers/Array.cpp +++ b/test/Engine/Containers/Array.cpp @@ -12,131 +12,170 @@ BOOST_AUTO_TEST_SUITE(Array_Suite) BOOST_AUTO_TEST_CASE(empty_constructur) { - Array array; - BOOST_CHECK_EQUAL(array.size(), 0); + Array array; + BOOST_CHECK_EQUAL(array.size(), 0); } BOOST_AUTO_TEST_CASE(initialial_size) { - Array array(3); - BOOST_CHECK_EQUAL(array.size(), 3); + Array array(3); + BOOST_CHECK_EQUAL(array.size(), 3); } BOOST_AUTO_TEST_CASE(resize) { - Array array; - array.add(2); - array.add(3); - BOOST_CHECK_EQUAL(array.size(), 2); - array.resize(5); - BOOST_CHECK_EQUAL(array.size(), 5); + Array array; + array.add(2); + array.add(3); + BOOST_CHECK_EQUAL(array.size(), 2); + array.resize(5); + BOOST_CHECK_EQUAL(array.size(), 5); } BOOST_AUTO_TEST_CASE(clear) { - Array array; - array.add(3); - array.add(2); - array.add(6); - BOOST_CHECK_EQUAL(array.size(), 3); - array.clear(); - BOOST_CHECK_EQUAL(array.size(), 0); + Array array; + array.add(3); + array.add(2); + array.add(6); + BOOST_CHECK_EQUAL(array.size(), 3); + array.clear(); + BOOST_CHECK_EQUAL(array.size(), 0); + array.add(2); } BOOST_AUTO_TEST_CASE(remove_keeporder) { - Array array; - array.add(1); - array.add(2); - array.add(3); - array.add(4); - array.add(5); - array.remove(1); - BOOST_CHECK_EQUAL(array[1], 3); - BOOST_CHECK_EQUAL(array[2], 4); - BOOST_CHECK_EQUAL(array[3], 5); - BOOST_CHECK_EQUAL(array.size(), 4); + Array array; + array.add(1); + array.add(2); + array.add(3); + array.add(4); + array.add(5); + array.remove(1); + BOOST_CHECK_EQUAL(array[1], 3); + BOOST_CHECK_EQUAL(array[2], 4); + BOOST_CHECK_EQUAL(array[3], 5); + BOOST_CHECK_EQUAL(array.size(), 4); } BOOST_AUTO_TEST_CASE(remove_swap) { - Array array; - array.add(1); - array.add(2); - array.add(3); - array.add(4); - array.add(5); - array.remove(1, false); - BOOST_CHECK_EQUAL(array[1], 5); - BOOST_CHECK_EQUAL(array[2], 3); - BOOST_CHECK_EQUAL(array[3], 4); - BOOST_CHECK_EQUAL(array.size(), 4); + Array array; + array.add(1); + array.add(2); + array.add(3); + array.add(4); + array.add(5); + array.remove(1, false); + BOOST_CHECK_EQUAL(array[1], 5); + BOOST_CHECK_EQUAL(array[2], 3); + BOOST_CHECK_EQUAL(array[3], 4); + BOOST_CHECK_EQUAL(array.size(), 4); } BOOST_AUTO_TEST_CASE(remove_iterator) { - Array array; - array.add(1); - array.add(2); - array.add(3); - array.add(4); - array.add(5); - array.remove(array.find(1), false); - BOOST_CHECK_EQUAL(array[0], 5); - BOOST_CHECK_EQUAL(array[1], 2); - BOOST_CHECK_EQUAL(array[2], 3); - BOOST_CHECK_EQUAL(array[3], 4); - BOOST_CHECK_EQUAL(array.size(), 4); + Array array; + array.add(1); + array.add(2); + array.add(3); + array.add(4); + array.add(5); + array.remove(array.find(1), false); + BOOST_CHECK_EQUAL(array[0], 5); + BOOST_CHECK_EQUAL(array[1], 2); + BOOST_CHECK_EQUAL(array[2], 3); + BOOST_CHECK_EQUAL(array[3], 4); + BOOST_CHECK_EQUAL(array.size(), 4); } BOOST_AUTO_TEST_CASE(remove_iterator_keep_order) { - Array array; - array.add(1); - array.add(2); - array.add(3); - array.add(4); - array.add(5); - array.remove(array.find(1)); - BOOST_CHECK_EQUAL(array[0], 2); - BOOST_CHECK_EQUAL(array[1], 3); - BOOST_CHECK_EQUAL(array[2], 4); - BOOST_CHECK_EQUAL(array[3], 5); - BOOST_CHECK_EQUAL(array.size(), 4); + Array array; + array.add(1); + array.add(2); + array.add(3); + array.add(4); + array.add(5); + array.remove(array.find(1)); + BOOST_CHECK_EQUAL(array[0], 2); + BOOST_CHECK_EQUAL(array[1], 3); + BOOST_CHECK_EQUAL(array[2], 4); + BOOST_CHECK_EQUAL(array[3], 5); + BOOST_CHECK_EQUAL(array.size(), 4); } BOOST_AUTO_TEST_CASE(random_access) { - Array array; - array.add(4); - array.add(5); - array.add(6); - BOOST_CHECK_EQUAL(array[2], 6); - BOOST_CHECK_EQUAL(array[0], 4); + Array array; + array.add(4); + array.add(5); + array.add(6); + BOOST_CHECK_EQUAL(array[2], 6); + BOOST_CHECK_EQUAL(array[0], 4); +} + +BOOST_AUTO_TEST_CASE(copy) +{ + Array array; + array.add(0); + array.add(1); + array.add(2); + array.add(3); + Array copy = array; + BOOST_CHECK_EQUAL_COLLECTIONS(array.begin(), array.end(), copy.begin(), copy.end()); + Array copy2(copy); + BOOST_CHECK_EQUAL_COLLECTIONS(array.begin(), array.end(), copy2.begin(), copy2.end()); +} + +class BaseElement +{ + uint64 test; +}; + +class BaseContainer +{ + Array elements; +}; + +class DerivedContainer : public BaseContainer +{ + uint64 test; +}; + +BOOST_AUTO_TEST_CASE(virtual_classes) +{ + Array array(64); + for(uint32 i = 0; i < 100; ++i) + { + Array copy = array; + BOOST_CHECK_EQUAL(array.size(), copy.size()); + } } class TestStruct { public: - uint32 data; - ~TestStruct() - { - data = 1; - } + uint32 data; + ~TestStruct() + { + data = 1; + } }; DECLARE_REF(TestStruct); BOOST_AUTO_TEST_CASE(refptr_interaction) { - uint32* dataPtr; - { - PTestStruct test = new TestStruct(); - test->data = 32123; - dataPtr = &test->data; - { - Array arr(1); - arr[0] = test; - } - BOOST_CHECK_EQUAL(test->data, 32123); - } + uint32* dataPtr; + { + PTestStruct test = new TestStruct(); + test->data = 32123; + dataPtr = &test->data; + { + Array arr(1); + arr[0] = test; + } + BOOST_CHECK_EQUAL(test->data, 32123); + } } BOOST_AUTO_TEST_SUITE_END() @@ -146,25 +185,25 @@ BOOST_AUTO_TEST_SUITE(StaticArray_Suite) BOOST_AUTO_TEST_CASE(empty_constructur) { - StaticArray array; - BOOST_CHECK_EQUAL(array.size(), 2); + StaticArray array; + BOOST_CHECK_EQUAL(array.size(), 2); } BOOST_AUTO_TEST_CASE(initialial_size) { - StaticArray array(3); - BOOST_CHECK_EQUAL(array.size(), 3); - BOOST_CHECK_EQUAL(array[1], 3); + StaticArray array(3); + BOOST_CHECK_EQUAL(array.size(), 3); + BOOST_CHECK_EQUAL(array[1], 3); } BOOST_AUTO_TEST_CASE(random_access) { - StaticArray array; - array[0] = 4; - array[1] = 5; - array[2] = 6; - BOOST_CHECK_EQUAL(array[0], 4); - BOOST_CHECK_EQUAL(array[2], 6); + StaticArray array; + array[0] = 4; + array[1] = 5; + array[2] = 6; + BOOST_CHECK_EQUAL(array[0], 4); + BOOST_CHECK_EQUAL(array[2], 6); } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/test/Engine/EngineTest.h b/test/Engine/EngineTest.h index a927a0a..e525c5c 100644 --- a/test/Engine/EngineTest.h +++ b/test/Engine/EngineTest.h @@ -1,4 +1,6 @@ #pragma once +#include +#include "ThreadPool.h" namespace Seele { @@ -6,6 +8,7 @@ namespace Seele { GlobalFixture() { + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); } ~GlobalFixture() { diff --git a/test/Engine/ThreadPool.cpp b/test/Engine/ThreadPool.cpp index a19da35..40b13eb 100644 --- a/test/Engine/ThreadPool.cpp +++ b/test/Engine/ThreadPool.cpp @@ -23,13 +23,18 @@ Job basicThenThird() co_return; } -BOOST_AUTO_TEST_CASE(basic_then) +Job basicThenBase() { - basicThenFirst() - .then(basicThenSecond()) - .then(basicThenThird()); + co_await basicThenFirst(); + co_await basicThenSecond(); + co_await basicThenThird(); } +BOOST_AUTO_TEST_CASE(basic_then) +{ + basicThenBase(); +} +/* uint64 basicAllState1 = 0; uint64 basicAllState2 = 0; Job basicAllFirst() @@ -69,6 +74,6 @@ BOOST_AUTO_TEST_CASE(basic_callable) BOOST_REQUIRE_EQUAL(basicCallable, 10); co_return; }); -} +}*/ BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file