diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ab3a103..74f831eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,13 +21,14 @@ TombEngine releases are located in this repository (alongside with Tomb Editor): * Fixed custom shatter sounds with custom sound IDs not playing correctly. ### New Features -* Added diary module. +* Added multithreading and an option for it to flow system settings. * Added ability to use keys and puzzle items underwater. - You must update your Lara object: https://github.com/TombEngine/Resources/raw/main/Wad2%20Objects/Lara/TEN_Lara.wad2 * Added a particle based waterfall emitter object and associated sprite slots. - You must use this version: https://github.com/TombEngine/Resources/raw/refs/heads/main/Wad2%20Objects/Interactables/TEN_Waterfall_Emitter.wad2 ### Lua API changes +* Added diary module. * Added Effects.EmitAirBubble() function to spawn air bubbles. * Added additional arguments for Sprite object slot and starting rotation value for EmitParticle function. * Added various Translate() methods to Vec2 and Vec3 script objects. diff --git a/Documentation/doc/2 classes/Flow.Settings.html b/Documentation/doc/2 classes/Flow.Settings.html index 229e91651..fefca5dfe 100644 --- a/Documentation/doc/2 classes/Flow.Settings.html +++ b/Documentation/doc/2 classes/Flow.Settings.html @@ -305,6 +305,10 @@ How should the application respond to script errors? + multiThreaded + Use multithreading in certain calculations. + + fastReload Can the game utilize the fast reload feature? @@ -1253,6 +1257,29 @@ + +
+ + multiThreaded +
+
+ Use multithreading in certain calculations.
+ When set to true, some performance-critical calculations will be performed in parallel, which can give + a significant performance boost. Don't disable unless you have problems with launching or using TombEngine. + + + + + + + + +
diff --git a/TombEngine/Game/Setup.cpp b/TombEngine/Game/Setup.cpp index 84bd677d6..733989cb6 100644 --- a/TombEngine/Game/Setup.cpp +++ b/TombEngine/Game/Setup.cpp @@ -78,38 +78,38 @@ ObjectInfo& ObjectHandler::GetFirstAvailableObject() void StaticHandler::Initialize() { - _lookupTable.resize(0); - _lookupTable.reserve(_defaultLUTSize); + _lut.resize(0); + _lut.reserve(LUT_SIZE); _statics.resize(0); } int StaticHandler::GetIndex(int staticID) { - if (staticID < 0 || staticID >= _lookupTable.size()) + if (staticID < 0 || staticID >= _lut.size()) { - TENLog("Attempt to get nonexistent static mesh ID slot index (" + std::to_string(staticID) + ")", LogLevel::Warning); - return _lookupTable.front(); + TENLog("Attempted to get index of missing static object " + std::to_string(staticID) + ".", LogLevel::Warning); + return _lut.front(); } - return _lookupTable[staticID]; + return _lut[staticID]; } StaticInfo& StaticHandler::operator [](int staticID) { if (staticID < 0) { - TENLog("Attempt to access illegal static mesh ID slot info", LogLevel::Warning); + TENLog("Attempted to access missing static object " + std::to_string(staticID) + ".", LogLevel::Warning); return _statics.front(); } - if (staticID >= _lookupTable.size()) - _lookupTable.resize(staticID + 1, NO_VALUE); + if (staticID >= _lut.size()) + _lut.resize(staticID + 1, NO_VALUE); - if (_lookupTable[staticID] != NO_VALUE) - return _statics[_lookupTable[staticID]]; + if (_lut[staticID] != NO_VALUE) + return _statics[_lut[staticID]]; _statics.emplace_back(); - _lookupTable[staticID] = (int)_statics.size() - 1; + _lut[staticID] = (int)_statics.size() - 1; return _statics.back(); } diff --git a/TombEngine/Game/Setup.h b/TombEngine/Game/Setup.h index 2ba3a4796..0cee4c098 100644 --- a/TombEngine/Game/Setup.h +++ b/TombEngine/Game/Setup.h @@ -135,10 +135,10 @@ struct StaticInfo class StaticHandler { private: - static const int _defaultLUTSize = 256; + static constexpr auto LUT_SIZE = 256; std::vector _statics = {}; - std::vector _lookupTable = {}; + std::vector _lut = {}; public: void Initialize(); @@ -147,6 +147,7 @@ public: StaticInfo& operator [](int staticID); // Iterators + auto begin() { return _statics.begin(); } // Non-const begin auto end() { return _statics.end(); } // Non-const end auto begin() const { return _statics.cbegin(); } // Const begin diff --git a/TombEngine/Renderer/Renderer.h b/TombEngine/Renderer/Renderer.h index 68f5fc9a5..db0f7e115 100644 --- a/TombEngine/Renderer/Renderer.h +++ b/TombEngine/Renderer/Renderer.h @@ -509,7 +509,7 @@ namespace TEN::Renderer const Vector4& color0, const Vector4& color1, const Vector4& color2, const Vector4& color3, BlendMode blendMode, RenderView& view, SpriteRenderType renderType = SpriteRenderType::Default); - Matrix GetWorldMatrixForSprite(RendererSpriteToDraw* spr, RenderView& view); + Matrix GetWorldMatrixForSprite(const RendererSpriteToDraw& sprite, RenderView& view); RendererObject& GetRendererObject(GAME_OBJECT_ID id); RendererMesh* GetMesh(int meshIndex); Texture2D CreateDefaultNormalTexture(); diff --git a/TombEngine/Renderer/RendererDraw.cpp b/TombEngine/Renderer/RendererDraw.cpp index ab1606014..49dbeb089 100644 --- a/TombEngine/Renderer/RendererDraw.cpp +++ b/TombEngine/Renderer/RendererDraw.cpp @@ -2925,7 +2925,7 @@ namespace TEN::Renderer rDrawSprite.Width = STAR_SIZE * star.Scale; rDrawSprite.Height = STAR_SIZE * star.Scale; - _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); + _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(rDrawSprite, renderView); _stInstancedSpriteBuffer.Sprites[i].Color = Vector4( star.Color.x, star.Color.y, @@ -2988,7 +2988,7 @@ namespace TEN::Renderer rDrawSprite.Height = 192; rDrawSprite.ConstrainAxis = meteor.Direction; - _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); + _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(rDrawSprite, renderView); _stInstancedSpriteBuffer.Sprites[i].Color = Vector4( meteor.Color.x, meteor.Color.y, @@ -3092,7 +3092,7 @@ namespace TEN::Renderer rDrawSprite.Height = SUN_SIZE; rDrawSprite.color = renderView.LensFlaresToDraw[0].Color; - _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); + _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(rDrawSprite, renderView); _stInstancedSpriteBuffer.Sprites[0].Color = renderView.LensFlaresToDraw[0].Color; _stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1; _stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = 0; @@ -3442,7 +3442,7 @@ namespace TEN::Renderer uv2 = spr->Sprite->UV[2]; uv3 = spr->Sprite->UV[3]; - auto world = GetWorldMatrixForSprite(currentObject->Sprite, view); + auto world = GetWorldMatrixForSprite(*currentObject->Sprite, view); Vertex v0; v0.Position = Vector3::Transform(p0t, world); diff --git a/TombEngine/Renderer/RendererDrawEffect.cpp b/TombEngine/Renderer/RendererDrawEffect.cpp index 14076756c..988e0b186 100644 --- a/TombEngine/Renderer/RendererDrawEffect.cpp +++ b/TombEngine/Renderer/RendererDrawEffect.cpp @@ -1291,14 +1291,14 @@ namespace TEN::Renderer } } - Matrix Renderer::GetWorldMatrixForSprite(RendererSpriteToDraw* sprite, RenderView& view) + Matrix Renderer::GetWorldMatrixForSprite(const RendererSpriteToDraw& sprite, RenderView& view) { auto spriteMatrix = Matrix::Identity; - auto scaleMatrix = Matrix::CreateScale(sprite->Width * sprite->Scale, sprite->Height * sprite->Scale, sprite->Scale); + auto scaleMatrix = Matrix::CreateScale(sprite.Width * sprite.Scale, sprite.Height * sprite.Scale, sprite.Scale); - auto spritePos = sprite->pos; + auto spritePos = sprite.pos; - if (sprite->Type == SpriteType::ThreeD) + if (sprite.Type == SpriteType::ThreeD) { ReflectMatrixOptionally(spriteMatrix); } @@ -1307,23 +1307,23 @@ namespace TEN::Renderer ReflectVectorOptionally(spritePos); } - switch (sprite->Type) + switch (sprite.Type) { case SpriteType::Billboard: { auto cameraUp = Vector3(view.Camera.View._12, view.Camera.View._22, view.Camera.View._32); - spriteMatrix = scaleMatrix * Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateBillboard(spritePos, Camera.pos.ToVector3(), cameraUp); + spriteMatrix = scaleMatrix * Matrix::CreateRotationZ(sprite.Rotation) * Matrix::CreateBillboard(spritePos, Camera.pos.ToVector3(), cameraUp); } break; case SpriteType::CustomBillboard: { - auto rotMatrix = Matrix::CreateRotationY(sprite->Rotation); + auto rotMatrix = Matrix::CreateRotationY(sprite.Rotation); auto quadForward = Vector3(0.0f, 0.0f, 1.0f); spriteMatrix = scaleMatrix * Matrix::CreateConstrainedBillboard( spritePos, Camera.pos.ToVector3(), - sprite->ConstrainAxis, + sprite.ConstrainAxis, nullptr, &quadForward); } @@ -1332,7 +1332,7 @@ namespace TEN::Renderer case SpriteType::LookAtBillboard: { auto translationMatrix = Matrix::CreateTranslation(spritePos); - auto rotMatrix = Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateLookAt(Vector3::Zero, sprite->LookAtAxis, Vector3::UnitZ); + auto rotMatrix = Matrix::CreateRotationZ(sprite.Rotation) * Matrix::CreateLookAt(Vector3::Zero, sprite.LookAtAxis, Vector3::UnitZ); spriteMatrix = scaleMatrix * rotMatrix * translationMatrix; } break; diff --git a/TombEngine/Renderer/RendererSprites.cpp b/TombEngine/Renderer/RendererSprites.cpp index 22e841990..03a0c67bd 100644 --- a/TombEngine/Renderer/RendererSprites.cpp +++ b/TombEngine/Renderer/RendererSprites.cpp @@ -1,14 +1,16 @@ #include "framework.h" #include "Renderer/Structures/RendererSprite.h" + #include "Renderer/Structures/RendererSpriteBucket.h" #include "Renderer/Renderer.h" +#include "Specific/Parallel.h" + +using namespace TEN::Renderer::Structures; namespace TEN::Renderer { - using namespace TEN::Renderer::Structures; - void Renderer::AddSpriteBillboard(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, float scale, - Vector2 size, BlendMode blendMode, bool isSoftParticle, RenderView& view, SpriteRenderType renderType) + Vector2 size, BlendMode blendMode, bool isSoftParticle, RenderView& view, SpriteRenderType renderType) { if (scale <= 0.0f) scale = 1.0f; @@ -38,8 +40,8 @@ namespace TEN::Renderer } void Renderer::AddSpriteBillboardConstrained(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, - float scale, Vector2 size, BlendMode blendMode, const Vector3& constrainAxis, - bool isSoftParticle, RenderView& view, SpriteRenderType renderType) + float scale, Vector2 size, BlendMode blendMode, const Vector3& constrainAxis, + bool isSoftParticle, RenderView& view, SpriteRenderType renderType) { if (scale <= 0.0f) scale = 1.0f; @@ -261,16 +263,16 @@ namespace TEN::Renderer return; // Draw instanced sprites. - bool wasGPUSet = false; - for (auto& spriteBucket : _spriteBuckets) + bool wasGpuSet = false; + for (const auto& spriteBucket : _spriteBuckets) { - if (spriteBucket.SpritesToDraw.size() == 0 || !spriteBucket.IsBillboard) + if (spriteBucket.SpritesToDraw.empty() || !spriteBucket.IsBillboard) continue; if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0)) continue; - if (!wasGPUSet) + if (!wasGpuSet) { _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); @@ -286,42 +288,45 @@ namespace TEN::Renderer unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); - wasGPUSet = true; + wasGpuSet = true; } - // Prepare constant buffer for instanced sprites. - for (int i = 0; i < spriteBucket.SpritesToDraw.size(); i++) + // Define sprite preparation logic. + auto prepareSprites = [&](int start, int end) { - auto& rDrawSprite = spriteBucket.SpritesToDraw[i]; + for (int i = start; i < end; i++) + { + const auto& spriteToDraw = spriteBucket.SpritesToDraw[i]; - _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, view); - _stInstancedSpriteBuffer.Sprites[i].Color = rDrawSprite.color; - _stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1; - _stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = rDrawSprite.SoftParticle ? 1 : 0; + _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(spriteToDraw, view); + _stInstancedSpriteBuffer.Sprites[i].Color = spriteToDraw.color; + _stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1.0f; + _stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = spriteToDraw.SoftParticle ? 1.0f : 0.0f; - // NOTE: Strange packing due to particular HLSL 16 byte alignment requirements. - _stInstancedSpriteBuffer.Sprites[i].UV[0].x = rDrawSprite.Sprite->UV[0].x; - _stInstancedSpriteBuffer.Sprites[i].UV[0].y = rDrawSprite.Sprite->UV[1].x; - _stInstancedSpriteBuffer.Sprites[i].UV[0].z = rDrawSprite.Sprite->UV[2].x; - _stInstancedSpriteBuffer.Sprites[i].UV[0].w = rDrawSprite.Sprite->UV[3].x; - _stInstancedSpriteBuffer.Sprites[i].UV[1].x = rDrawSprite.Sprite->UV[0].y; - _stInstancedSpriteBuffer.Sprites[i].UV[1].y = rDrawSprite.Sprite->UV[1].y; - _stInstancedSpriteBuffer.Sprites[i].UV[1].z = rDrawSprite.Sprite->UV[2].y; - _stInstancedSpriteBuffer.Sprites[i].UV[1].w = rDrawSprite.Sprite->UV[3].y; - } + // NOTE: Strange packing due to particular HLSL 16 byte alignment requirements. + _stInstancedSpriteBuffer.Sprites[i].UV[0].x = spriteToDraw.Sprite->UV[0].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].y = spriteToDraw.Sprite->UV[1].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].z = spriteToDraw.Sprite->UV[2].x; + _stInstancedSpriteBuffer.Sprites[i].UV[0].w = spriteToDraw.Sprite->UV[3].x; + _stInstancedSpriteBuffer.Sprites[i].UV[1].x = spriteToDraw.Sprite->UV[0].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].y = spriteToDraw.Sprite->UV[1].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].z = spriteToDraw.Sprite->UV[2].y; + _stInstancedSpriteBuffer.Sprites[i].UV[1].w = spriteToDraw.Sprite->UV[3].y; + } + }; + g_Parallel.AddTasks((int)spriteBucket.SpritesToDraw.size(), prepareSprites).wait(); BindTexture(TextureRegister::ColorMap, spriteBucket.Sprite->Texture, SamplerStateRegister::LinearClamp); - _cbInstancedSpriteBuffer.UpdateData(_stInstancedSpriteBuffer, _context.Get()); // Draw sprites with instancing. - DrawInstancedTriangles(4, (unsigned int)spriteBucket.SpritesToDraw.size(), 0); + DrawInstancedTriangles(4, (int)spriteBucket.SpritesToDraw.size(), 0); _numInstancedSpritesDrawCalls++; } // Draw 3D non-instanced sprites. - wasGPUSet = false; + wasGpuSet = false; for (auto& spriteBucket : _spriteBuckets) { @@ -331,7 +336,7 @@ namespace TEN::Renderer if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0)) continue; - if (!wasGPUSet) + if (!wasGpuSet) { _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); @@ -342,10 +347,10 @@ namespace TEN::Renderer _shaders.Bind(Shader::Sprites); - wasGPUSet = true; + wasGpuSet = true; } - _stSprite.IsSoftParticle = spriteBucket.IsSoftParticle ? 1 : 0; + _stSprite.IsSoftParticle = spriteBucket.IsSoftParticle ? 1.0f : 0.0f; _stSprite.RenderType = (int)spriteBucket.RenderType; _cbSprite.UpdateData(_stSprite, _context.Get()); @@ -414,11 +419,11 @@ namespace TEN::Renderer _shaders.Bind(Shader::InstancedSprites); // Set up vertex buffer and parameters. - UINT stride = sizeof(Vertex); - UINT offset = 0; + unsigned int stride = sizeof(Vertex); + unsigned int offset = 0; _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); - _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(object->Sprite, view); + _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(*object->Sprite, view); _stInstancedSpriteBuffer.Sprites[0].Color = object->Sprite->color; _stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1; _stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = object->Sprite->SoftParticle ? 1 : 0; diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp index 6c5092676..ba2cf5e71 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp +++ b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.cpp @@ -328,6 +328,12 @@ namespace TEN::Scripting // @tfield Flow.ErrorMode errorMode error mode to use. */ "errorMode", &SystemSettings::ErrorMode, + /// Use multithreading in certain calculations.
+ // When set to `true`, some performance-critical calculations will be performed in parallel, which can give + // a significant performance boost. Don't disable unless you have problems with launching or using TombEngine. + // @tfield bool multithreaded determines whether to use multithreading or not. */ + "multithreaded", &SystemSettings::Multithreaded, + /// Can the game utilize the fast reload feature?
// When set to `true`, the game will attempt to perform fast savegame reloading if current level is the same as // the level loaded from the savegame. It will not work if the level timestamp or checksum has changed diff --git a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h index 9ab2ac617..7b2d962ad 100644 --- a/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h +++ b/TombEngine/Scripting/Internal/TEN/Flow/Settings/Settings.h @@ -78,8 +78,9 @@ namespace TEN::Scripting struct SystemSettings { - ErrorMode ErrorMode = ErrorMode::Warn; - bool FastReload = true; + ErrorMode ErrorMode = ErrorMode::Warn; + bool FastReload = true; + bool Multithreaded = true; static void Register(sol::table& parent); }; diff --git a/TombEngine/Specific/Parallel.cpp b/TombEngine/Specific/Parallel.cpp new file mode 100644 index 000000000..ac7524cc0 --- /dev/null +++ b/TombEngine/Specific/Parallel.cpp @@ -0,0 +1,173 @@ +#include "framework.h" +#include "Specific/Parallel.h" + +#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h" + +namespace TEN::Utils +{ + ParallelTaskManager& g_Parallel = ParallelTaskManager::Get(); + + ParallelTaskManager::ParallelTaskManager() + { + _deinitialize = false; + } + + ParallelTaskManager::~ParallelTaskManager() + { + // LOCK: Restrict shutdown flag access. + { + auto taskLock = std::lock_guard(_taskMutex); + + _deinitialize = true; + } + + // Notify all threads they should stop. + _taskCond.notify_all(); + + // Join all threads. + for (auto& thread : _threads) + { + if (!thread.joinable()) + continue; + + try + { + thread.join(); + } + catch (const std::exception& ex) + { + TENLog("Error joining thread: " + std::string(ex.what()), LogLevel::Error); + } + } + } + + ParallelTaskManager& ParallelTaskManager::Get() + { + static auto instance = ParallelTaskManager(); + return instance; + } + + unsigned int ParallelTaskManager::GetThreadCount() const + { + return (unsigned int)_threads.size(); + } + + unsigned int ParallelTaskManager::GetCoreCount() const + { + return std::max(std::thread::hardware_concurrency(), 1u); + } + + void ParallelTaskManager::Initialize() + { + // Reserve threads. + unsigned int threadCount = g_GameFlow->GetSettings()->System.Multithreaded ? (GetCoreCount() * 2) : 1; + _threads.reserve(threadCount); + + // Create threads. + for (int i = 0; i < threadCount; i++) + _threads.push_back(std::thread(&ParallelTaskManager::Worker, this)); + } + + std::future ParallelTaskManager::AddTask(const ParallelTask& task) + { + return AddTasks(ParallelTaskGroup{ task }); + } + + std::future ParallelTaskManager::AddTasks(const ParallelTaskGroup& tasks) + { + // HEAP ALLOC: Create counter and promise. + auto counter = std::make_shared>(); + auto promise = std::make_shared>(); + + counter->store((int)tasks.size(), std::memory_order_release); + + // Add group tasks. + for (const auto& task : tasks) + AddTask(task, counter, promise); + + // Notify available threads to handle tasks. + _taskCond.notify_all(); + + // Return future to wait on task group completion if needed. + return promise->get_future(); + } + + std::future ParallelTaskManager::AddTasks(int elementCount, const std::function& splitTask) + { + // TODO: Make this a configuration option? + constexpr auto SERIAL_UNIT_COUNT_MAX = 32; + + auto tasks = ParallelTaskGroup{}; + + // Process in parallel. + if (g_GameFlow->GetSettings()->System.Multithreaded && + elementCount > SERIAL_UNIT_COUNT_MAX) + { + int threadCount = GetCoreCount(); + int chunkSize = ((elementCount + threadCount) - 1) / threadCount; + + // Collect group tasks. + tasks.reserve(threadCount); + for (int i = 0; i < threadCount; i++) + { + int start = i * chunkSize; + int end = std::min(start + chunkSize, elementCount); + tasks.push_back([&splitTask, start, end]() { splitTask(start, end); }); + } + } + // Process linearly. + else + { + tasks.push_back([&splitTask, elementCount]() { splitTask(0, elementCount); }); + } + + // Add task group and return future to wait on completion if needed. + return AddTasks(tasks); + } + + void ParallelTaskManager::Worker() + { + while (true) + { + auto task = ParallelTask(); + + // LOCK: Restrict task queue access. + { + auto taskLock = std::unique_lock(_taskMutex); + _taskCond.wait(taskLock, [this] { return (_deinitialize || !_tasks.empty()); }); + + // Shutting down and no pending tasks; return early. + if (_deinitialize && _tasks.empty()) + return; + + // Get task. + task = _tasks.front(); + _tasks.pop(); + } + + // Execute task. + if (task) + task(); + } + } + + void ParallelTaskManager::AddTask(const ParallelTask& task, std::shared_ptr> counter, std::shared_ptr> promise) + { + // Increment counter for task group. + counter->fetch_add(1, std::memory_order_relaxed); + + // Add task with promise and counter handling. + _tasks.push([this, task, counter, promise]() { HandleTask(task, *counter, *promise); }); + } + + void ParallelTaskManager::HandleTask(const ParallelTask& task, std::atomic& counter, std::promise& promise) + { + // Execute task. + if (task) + task(); + + // Check for task group completion. + if (counter.fetch_sub(1, std::memory_order_acq_rel) == 1) + promise.set_value(); + } +} diff --git a/TombEngine/Specific/Parallel.h b/TombEngine/Specific/Parallel.h new file mode 100644 index 000000000..1115c21da --- /dev/null +++ b/TombEngine/Specific/Parallel.h @@ -0,0 +1,54 @@ +#pragma once + +namespace TEN::Utils +{ + using ParallelTask = std::function; + using ParallelTaskGroup = std::vector; + + class ParallelTaskManager + { + private: + // Fields + + std::vector _threads = {}; + std::queue _tasks = {}; + std::mutex _taskMutex = {}; + std::condition_variable _taskCond = {}; + bool _deinitialize = false; + + // Constructors, destructors + + ParallelTaskManager(); + ParallelTaskManager(const ParallelTaskManager& manager) = delete; + ~ParallelTaskManager(); + + public: + // Getters + + static ParallelTaskManager& Get(); + + unsigned int GetThreadCount() const; + unsigned int GetCoreCount() const; + + // Utilities + + void Initialize(); + + std::future AddTask(const ParallelTask& task); + std::future AddTasks(const ParallelTaskGroup& tasks); + std::future AddTasks(int elementCount, const std::function& splitTask); + + private: + // Helpers + + void Worker(); + void AddTask(const ParallelTask& task, std::shared_ptr> counter, std::shared_ptr> promise); + void HandleTask(const ParallelTask& task, std::atomic& counter, std::promise& promise); + + // Operators + + ParallelTaskManager& operator =(const ParallelTaskManager& manager) = delete; + }; + + extern ParallelTaskManager& g_Parallel; +} diff --git a/TombEngine/Specific/winmain.cpp b/TombEngine/Specific/winmain.cpp index 789fa8eb0..bd7b2cefa 100644 --- a/TombEngine/Specific/winmain.cpp +++ b/TombEngine/Specific/winmain.cpp @@ -14,6 +14,7 @@ #include "Sound/sound.h" #include "Specific/level.h" #include "Specific/configuration.h" +#include "Specific/Parallel.h" #include "Specific/trutils.h" #include "Scripting/Internal/LanguageScript.h" #include "Scripting/Include/ScriptInterfaceState.h" @@ -592,6 +593,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine DoTheGame = true; + g_Parallel.Initialize(); ThreadEnded = false; ThreadHandle = BeginThread(GameMain, ThreadID); diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index 00bbc1afa..1587af359 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -860,6 +860,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + @@ -1290,6 +1291,7 @@ xcopy /Y "$(SolutionDir)Libs\zlib\x64\*.dll" "$(TargetDir)" + diff --git a/TombEngine/framework.h b/TombEngine/framework.h index 8b57fc862..eac52766c 100644 --- a/TombEngine/framework.h +++ b/TombEngine/framework.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include using namespace DirectX;