From 1caf47e45e0dffcf56818c62d3ea6f6237eb94da Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 28 Apr 2021 20:33:51 +0100 Subject: [PATCH] Use unrolled linked list for paint entries --- src/openrct2/paint/Paint.cpp | 118 +++++++++++++++++++++++++++++++++ src/openrct2/paint/Paint.h | 66 ++++++++++++------ src/openrct2/paint/Painter.cpp | 5 +- src/openrct2/paint/Painter.h | 2 +- 4 files changed, 167 insertions(+), 24 deletions(-) diff --git a/src/openrct2/paint/Paint.cpp b/src/openrct2/paint/Paint.cpp index f51955f23b..b000725ad9 100644 --- a/src/openrct2/paint/Paint.cpp +++ b/src/openrct2/paint/Paint.cpp @@ -968,3 +968,121 @@ void PaintDrawMoneyStructs(rct_drawpixelinfo* dpi, paint_string_struct* ps) FontSpriteBase::MEDIUM); } while ((ps = ps->next) != nullptr); } + +PaintEntryPool::Chain::Chain(PaintEntryPool* pool) + : Pool(pool) +{ +} + +PaintEntryPool::Chain::Chain(Chain&& chain) +{ + *this = std::move(chain); +} + +PaintEntryPool::Chain::~Chain() +{ + Clear(); +} + +PaintEntryPool::Chain& PaintEntryPool::Chain::operator=(Chain&& chain) noexcept +{ + Pool = chain.Pool; + Head = chain.Head; + Current = chain.Current; + chain.Pool = nullptr; + chain.Head = nullptr; + chain.Current = nullptr; + return *this; +} + +paint_entry* PaintEntryPool::Chain::Allocate() +{ + if (Pool == nullptr) + { + return nullptr; + } + + if (Current == nullptr) + { + assert(Head == nullptr); + Head = Pool->AllocateNode(); + if (Head == nullptr) + { + // Unable to allocate any more nodes + return nullptr; + } + Current = Head; + } + else if (Current->Count >= NodeSize) + { + // We need another node + Current->Next = Pool->AllocateNode(); + if (Current->Next == nullptr) + { + // Unable to allocate any more nodes + return nullptr; + } + Current = Current->Next; + } + + assert(Current->Count < NodeSize); + return &Current->PaintStructs[Current->Count++]; +} + +void PaintEntryPool::Chain::Clear() +{ + if (Pool != nullptr) + { + Pool->FreeNodes(Head); + Head = nullptr; + Current = nullptr; + } + assert(Head == nullptr); + assert(Current == nullptr); +} + +PaintEntryPool::~PaintEntryPool() +{ + for (auto node : _available) + { + delete node; + } + _available.clear(); +} + +PaintEntryPool::Node* PaintEntryPool::AllocateNode() +{ + std::lock_guard lock(_mutex); + + PaintEntryPool::Node* result; + if (_available.size() > 0) + { + result = _available.back(); + _available.pop_back(); + } + else + { + result = new (std::nothrow) PaintEntryPool::Node(); + } + return result; +} + +PaintEntryPool::Chain PaintEntryPool::Create() +{ + return PaintEntryPool::Chain(this); +} + +void PaintEntryPool::FreeNodes(PaintEntryPool::Node* head) +{ + std::lock_guard lock(_mutex); + + auto node = head; + while (node != nullptr) + { + auto next = node->Next; + node->Next = nullptr; + node->Count = 0; + _available.push_back(node); + node = next; + } +} diff --git a/src/openrct2/paint/Paint.h b/src/openrct2/paint/Paint.h index 47ba653f95..5360209c4b 100644 --- a/src/openrct2/paint/Paint.h +++ b/src/openrct2/paint/Paint.h @@ -138,33 +138,59 @@ struct tunnel_entry #define MAX_PAINT_QUADRANTS 512 #define TUNNEL_MAX_COUNT 65 -struct PaintStructPool +/** + * A pool of paint_entry instances that can be rented out. + * The internal implementation uses an unrolled linked list so that each + * paint session can quickly allocate a new paint entry until it requires + * another node / block of paint entries. Only the node allocation needs to + * be thread safe. + */ +class PaintEntryPool { - FixedVector PaintStructs; + static constexpr size_t NodeSize = 512; + +public: + struct Node + { + Node* Next{}; + size_t Count{}; + paint_entry PaintStructs[NodeSize]{}; + }; + + struct Chain + { + PaintEntryPool* Pool{}; + Node* Head{}; + Node* Current{}; + + Chain() = default; + Chain(PaintEntryPool* pool); + Chain(Chain&& chain); + ~Chain(); + + Chain& operator=(Chain&& chain) noexcept; + + paint_entry* Allocate(); + void Clear(); + }; + +private: + std::vector _available; std::mutex _mutex; - paint_entry* Allocate() - { - std::lock_guard guard(_mutex); + Node* AllocateNode(); - if (PaintStructs.size() < PaintStructs.capacity()) - { - return &PaintStructs.emplace_back(); - } - return nullptr; - } +public: + ~PaintEntryPool(); - void Clear() - { - PaintStructs.clear(); - } + Chain Create(); + void FreeNodes(Node* head); }; struct paint_session { rct_drawpixelinfo DPI; - // FixedVector PaintStructs; - PaintStructPool* SharedPaintStructPool; + PaintEntryPool::Chain PaintEntryChain; paint_struct* Quadrants[MAX_PAINT_QUADRANTS]; paint_struct* LastPS; paint_string_struct* PSStringHead; @@ -197,7 +223,7 @@ struct paint_session paint_struct* AllocateNormalPaintEntry() noexcept { - auto* entry = SharedPaintStructPool->Allocate(); + auto* entry = PaintEntryChain.Allocate(); if (entry != nullptr) { LastPS = &entry->basic; @@ -208,7 +234,7 @@ struct paint_session attached_paint_struct* AllocateAttachedPaintEntry() noexcept { - auto* entry = SharedPaintStructPool->Allocate(); + auto* entry = PaintEntryChain.Allocate(); if (entry != nullptr) { LastAttachedPS = &entry->attached; @@ -219,7 +245,7 @@ struct paint_session paint_string_struct* AllocateStringPaintEntry() noexcept { - auto* entry = SharedPaintStructPool->Allocate(); + auto* entry = PaintEntryChain.Allocate(); if (entry != nullptr) { auto* string = &entry->string; diff --git a/src/openrct2/paint/Painter.cpp b/src/openrct2/paint/Painter.cpp index 9364b01160..d39ea3186f 100644 --- a/src/openrct2/paint/Painter.cpp +++ b/src/openrct2/paint/Painter.cpp @@ -37,8 +37,6 @@ Painter::Painter(const std::shared_ptr& uiContext) void Painter::Paint(IDrawingEngine& de) { - _paintStructPool.Clear(); - auto dpi = de.GetDrawingPixelInfo(); if (gIntroState != IntroState::None) { @@ -153,7 +151,7 @@ paint_session* Painter::CreateSession(rct_drawpixelinfo* dpi, uint32_t viewFlags session->ViewFlags = viewFlags; session->QuadrantBackIndex = std::numeric_limits::max(); session->QuadrantFrontIndex = 0; - session->SharedPaintStructPool = &_paintStructPool; + session->PaintEntryChain = _paintStructPool.Create(); std::fill(std::begin(session->Quadrants), std::end(session->Quadrants), nullptr); session->LastPS = nullptr; @@ -169,5 +167,6 @@ paint_session* Painter::CreateSession(rct_drawpixelinfo* dpi, uint32_t viewFlags void Painter::ReleaseSession(paint_session* session) { + session->PaintEntryChain.Clear(); _freePaintSessions.push_back(session); } diff --git a/src/openrct2/paint/Painter.h b/src/openrct2/paint/Painter.h index 42528d5d0a..d776f6f557 100644 --- a/src/openrct2/paint/Painter.h +++ b/src/openrct2/paint/Painter.h @@ -38,7 +38,7 @@ namespace OpenRCT2 std::shared_ptr const _uiContext; std::vector> _paintSessionPool; std::vector _freePaintSessions; - PaintStructPool _paintStructPool; + PaintEntryPool _paintStructPool; time_t _lastSecond = 0; int32_t _currentFPS = 0; int32_t _frames = 0;