Merge pull request #14561 from IntelOrca/increase-paint-structs

Dynamically allocate paint structs and remove 4000 per column limit
This commit is contained in:
Ted John 2021-05-08 23:36:40 +01:00 committed by GitHub
commit 14845b61e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 343 additions and 93 deletions

View File

@ -34,47 +34,50 @@
# include <iterator>
# include <vector>
static void fixup_pointers(paint_session* s, size_t paint_session_entries, size_t paint_struct_entries, size_t quadrant_entries)
static void fixup_pointers(std::vector<RecordedPaintSession>& s)
{
for (size_t i = 0; i < paint_session_entries; i++)
for (size_t i = 0; i < s.size(); i++)
{
for (size_t j = 0; j < paint_struct_entries; j++)
auto& entries = s[i].Entries;
auto& quadrants = s[i].Session.Quadrants;
for (size_t j = 0; j < entries.size(); j++)
{
if (s[i].PaintStructs[j].basic.next_quadrant_ps == reinterpret_cast<paint_struct*>(paint_struct_entries))
if (entries[j].basic.next_quadrant_ps == reinterpret_cast<paint_struct*>(-1))
{
s[i].PaintStructs[j].basic.next_quadrant_ps = nullptr;
entries[j].basic.next_quadrant_ps = nullptr;
}
else
{
auto nextQuadrantPs = reinterpret_cast<uintptr_t>(s[i].PaintStructs[j].basic.next_quadrant_ps);
s[i].PaintStructs[j].basic.next_quadrant_ps = &s[i].PaintStructs[nextQuadrantPs].basic;
auto nextQuadrantPs = reinterpret_cast<size_t>(entries[j].basic.next_quadrant_ps) / sizeof(paint_entry);
entries[j].basic.next_quadrant_ps = &s[i].Entries[nextQuadrantPs].basic;
}
}
for (size_t j = 0; j < quadrant_entries; j++)
for (size_t j = 0; j < std::size(quadrants); j++)
{
if (s[i].Quadrants[j] == reinterpret_cast<paint_struct*>(quadrant_entries))
if (quadrants[j] == reinterpret_cast<paint_struct*>(-1))
{
s[i].Quadrants[j] = nullptr;
quadrants[j] = nullptr;
}
else
{
s[i].Quadrants[j] = &s[i].PaintStructs[reinterpret_cast<size_t>(s[i].Quadrants[j])].basic;
auto ps = reinterpret_cast<size_t>(quadrants[j]) / sizeof(paint_entry);
quadrants[j] = &entries[ps].basic;
}
}
}
}
static std::vector<paint_session> extract_paint_session(const std::string parkFileName)
static std::vector<RecordedPaintSession> extract_paint_session(std::string_view parkFileName)
{
core_init();
gOpenRCT2Headless = true;
auto context = OpenRCT2::CreateContext();
std::vector<paint_session> sessions;
std::vector<RecordedPaintSession> sessions;
log_info("Starting...");
if (context->Initialise())
{
drawing_engine_init();
if (!context->LoadParkFromFile(parkFileName))
if (!context->LoadParkFromFile(std::string(parkFileName)))
{
log_error("Failed to load park!");
return {};
@ -133,21 +136,21 @@ static std::vector<paint_session> extract_paint_session(const std::string parkFi
}
// This function is based on benchgfx_render_screenshots
static void BM_paint_session_arrange(benchmark::State& state, const std::vector<paint_session> inputSessions)
static void BM_paint_session_arrange(benchmark::State& state, const std::vector<RecordedPaintSession> inputSessions)
{
std::vector<paint_session> sessions = inputSessions;
auto sessions = inputSessions;
// Fixing up the pointers continuously is wasteful. Fix it up once for `sessions` and store a copy.
// Keep in mind we need bit-exact copy, as the lists use pointers.
// Once sorted, just restore the copy with the original fixed-up version.
paint_session* local_s = new paint_session[std::size(sessions)];
fixup_pointers(&sessions[0], std::size(sessions), std::size(local_s->PaintStructs), std::size(local_s->Quadrants));
RecordedPaintSession* local_s = new RecordedPaintSession[std::size(sessions)];
fixup_pointers(sessions);
std::copy_n(sessions.cbegin(), std::size(sessions), local_s);
for (auto _ : state)
{
state.PauseTiming();
std::copy_n(local_s, std::size(sessions), sessions.begin());
state.ResumeTiming();
PaintSessionArrange(&sessions[0]);
PaintSessionArrange(&sessions[0].Session);
benchmark::DoNotOptimize(sessions);
}
state.SetItemsProcessed(state.iterations() * std::size(sessions));
@ -158,14 +161,14 @@ static int cmdline_for_bench_sprite_sort(int argc, const char** argv)
{
{
// Register some basic "baseline" benchmark
std::vector<paint_session> sessions(1);
for (auto& ps : sessions[0].PaintStructs)
std::vector<RecordedPaintSession> sessions(1);
for (auto& ps : sessions[0].Entries)
{
ps.basic.next_quadrant_ps = reinterpret_cast<paint_struct*>((std::size(sessions[0].PaintStructs)));
ps.basic.next_quadrant_ps = reinterpret_cast<paint_struct*>(-1);
}
for (auto& quad : sessions[0].Quadrants)
for (auto& quad : sessions[0].Session.Quadrants)
{
quad = reinterpret_cast<paint_struct*>((std::size(sessions[0].Quadrants)));
quad = reinterpret_cast<paint_struct*>(-1);
}
benchmark::RegisterBenchmark("baseline", BM_paint_session_arrange, sessions);
}
@ -183,7 +186,7 @@ static int cmdline_for_bench_sprite_sort(int argc, const char** argv)
if (Platform::FileExists(argv[i]))
{
// Register benchmark for sv6 if valid
std::vector<paint_session> sessions = extract_paint_session(argv[i]);
std::vector<RecordedPaintSession> sessions = extract_paint_session(argv[i]);
if (!sessions.empty())
benchmark::RegisterBenchmark(argv[i], BM_paint_session_arrange, sessions);
}

View File

@ -35,6 +35,7 @@
#include <algorithm>
#include <cstring>
#include <list>
#include <unordered_map>
using namespace OpenRCT2;
@ -805,7 +806,7 @@ void viewport_update_smart_vehicle_follow(rct_window* window)
*/
void viewport_render(
rct_drawpixelinfo* dpi, const rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom,
std::vector<paint_session>* sessions)
std::vector<RecordedPaintSession>* sessions)
{
if (right <= viewport->pos.x)
return;
@ -846,30 +847,68 @@ void viewport_render(
#endif
}
static void record_session(const paint_session* session, std::vector<paint_session>* recorded_sessions, size_t record_index)
static void record_session(
const paint_session* session, std::vector<RecordedPaintSession>* recorded_sessions, size_t record_index)
{
// Perform a deep copy of the paint session, use relative offsets.
// This is done to extract the session for benchmark.
// Place the copied session at provided record_index, so the caller can decide which columns/paint sessions to copy;
// there is no column information embedded in the session itself.
(*recorded_sessions)[record_index] = (*session);
paint_session* session_copy = &recorded_sessions->at(record_index);
auto& recordedSession = recorded_sessions->at(record_index);
recordedSession.Session = *session;
recordedSession.Entries.resize(session->PaintEntryChain.GetCount());
// Mind the offset needs to be calculated against the original `session`, not `session_copy`
for (auto& ps : session_copy->PaintStructs)
std::unordered_map<paint_struct*, paint_struct*> entryRemap;
// Copy all entries
auto paintIndex = 0;
auto chain = session->PaintEntryChain.Head;
while (chain != nullptr)
{
ps.basic.next_quadrant_ps = reinterpret_cast<paint_struct*>(
ps.basic.next_quadrant_ps ? int(ps.basic.next_quadrant_ps - &session->PaintStructs[0].basic)
: std::size(session->PaintStructs));
for (size_t i = 0; i < chain->Count; i++)
{
auto& src = chain->PaintStructs[i];
auto& dst = recordedSession.Entries[paintIndex++];
dst = src;
entryRemap[&src.basic] = reinterpret_cast<paint_struct*>(i * sizeof(paint_entry));
}
chain = chain->Next;
}
for (auto& quad : session_copy->Quadrants)
entryRemap[nullptr] = reinterpret_cast<paint_struct*>(-1);
// Remap all entries
for (auto& ps : recordedSession.Entries)
{
quad = reinterpret_cast<paint_struct*>(
quad ? int(quad - &session->PaintStructs[0].basic) : std::size(session->Quadrants));
auto& ptr = ps.basic.next_quadrant_ps;
auto it = entryRemap.find(ptr);
if (it == entryRemap.end())
{
assert(false);
ptr = nullptr;
}
else
{
ptr = it->second;
}
}
for (auto& ptr : recordedSession.Session.Quadrants)
{
auto it = entryRemap.find(ptr);
if (it == entryRemap.end())
{
assert(false);
ptr = nullptr;
}
else
{
ptr = it->second;
}
}
}
static void viewport_fill_column(paint_session* session, std::vector<paint_session>* recorded_sessions, size_t record_index)
static void viewport_fill_column(
paint_session* session, std::vector<RecordedPaintSession>* recorded_sessions, size_t record_index)
{
PaintSessionGenerate(session);
if (recorded_sessions != nullptr)
@ -922,7 +961,7 @@ static void viewport_paint_column(paint_session* session)
*/
void viewport_paint(
const rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom,
std::vector<paint_session>* recorded_sessions)
std::vector<RecordedPaintSession>* recorded_sessions)
{
uint32_t viewFlags = viewport->flags;
uint16_t width = right - left;

View File

@ -18,6 +18,7 @@
#include <vector>
struct paint_session;
struct RecordedPaintSession;
struct paint_struct;
struct rct_drawpixelinfo;
struct Peep;
@ -116,10 +117,10 @@ void viewport_update_smart_staff_follow(rct_window* window, Peep* peep);
void viewport_update_smart_vehicle_follow(rct_window* window);
void viewport_render(
rct_drawpixelinfo* dpi, const rct_viewport* viewport, int32_t left, int32_t top, int32_t right, int32_t bottom,
std::vector<paint_session>* sessions = nullptr);
std::vector<RecordedPaintSession>* sessions = nullptr);
void viewport_paint(
const rct_viewport* viewport, rct_drawpixelinfo* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom,
std::vector<paint_session>* sessions = nullptr);
std::vector<RecordedPaintSession>* sessions = nullptr);
CoordsXYZ viewport_adjust_for_map_height(const ScreenCoordsXY& startCoords);

View File

@ -139,9 +139,6 @@ static paint_struct* CreateNormalPaintStruct(
paint_session* session, const uint32_t image_id, const CoordsXYZ& offset, const CoordsXYZ& boundBoxSize,
const CoordsXYZ& boundBoxOffset)
{
if (session->NoPaintStructsAvailable())
return nullptr;
auto* const g1 = gfx_get_g1_element(image_id & 0x7FFFF);
if (g1 == nullptr)
{
@ -162,7 +159,12 @@ static paint_struct* CreateNormalPaintStruct(
const auto rotBoundBoxOffset = CoordsXYZ{ boundBoxOffset.Rotate(swappedRotation), boundBoxOffset.z };
const auto rotBoundBoxSize = RotateBoundBoxSize(boundBoxSize, session->CurrentRotation);
paint_struct* ps = session->AllocateNormalPaintEntry();
auto* ps = session->AllocateNormalPaintEntry();
if (ps == nullptr)
{
return nullptr;
}
ps->image_id = image_id;
ps->x = imagePos.x;
ps->y = imagePos.y;
@ -383,7 +385,7 @@ static paint_struct* PaintArrangeStructsHelperRotation(paint_struct* ps_next, ui
}
}
template<int TRotation> static void PaintSessionArrange(paint_session* session, bool)
template<int TRotation> static void PaintSessionArrange(PaintSessionCore* session, bool)
{
paint_struct* psHead = &session->PaintHead;
@ -423,7 +425,7 @@ template<int TRotation> static void PaintSessionArrange(paint_session* session,
*
* rct2: 0x00688217
*/
void PaintSessionArrange(paint_session* session)
void PaintSessionArrange(PaintSessionCore* session)
{
switch (session->CurrentRotation)
{
@ -847,18 +849,18 @@ paint_struct* PaintAddImageAsChild(
*/
bool PaintAttachToPreviousAttach(paint_session* session, uint32_t image_id, int16_t x, int16_t y)
{
if (session->NoPaintStructsAvailable())
{
return false;
}
attached_paint_struct* previousAttachedPS = session->LastAttachedPS;
auto* previousAttachedPS = session->LastAttachedPS;
if (previousAttachedPS == nullptr)
{
return PaintAttachToPreviousPS(session, image_id, x, y);
}
attached_paint_struct* ps = session->AllocateAttachedPaintEntry();
auto* ps = session->AllocateAttachedPaintEntry();
if (ps == nullptr)
{
return false;
}
ps->image_id = image_id;
ps->x = x;
ps->y = y;
@ -880,18 +882,18 @@ bool PaintAttachToPreviousAttach(paint_session* session, uint32_t image_id, int1
*/
bool PaintAttachToPreviousPS(paint_session* session, uint32_t image_id, int16_t x, int16_t y)
{
if (session->NoPaintStructsAvailable())
{
return false;
}
paint_struct* masterPs = session->LastPS;
auto* masterPs = session->LastPS;
if (masterPs == nullptr)
{
return false;
}
attached_paint_struct* ps = session->AllocateAttachedPaintEntry();
auto* ps = session->AllocateAttachedPaintEntry();
if (ps == nullptr)
{
return false;
}
ps->image_id = image_id;
ps->x = x;
ps->y = y;
@ -918,7 +920,8 @@ void PaintFloatingMoneyEffect(
paint_session* session, money32 amount, rct_string_id string_id, int16_t y, int16_t z, int8_t y_offsets[], int16_t offset_x,
uint32_t rotation)
{
if (session->NoPaintStructsAvailable())
auto* ps = session->AllocateStringPaintEntry();
if (ps == nullptr)
{
return;
}
@ -930,7 +933,6 @@ void PaintFloatingMoneyEffect(
};
const auto coord = translate_3d_to_2d_with_z(rotation, position);
paint_string_struct* ps = session->AllocateStringPaintEntry();
ps->string_id = string_id;
ps->next = nullptr;
ps->args[0] = amount;
@ -966,3 +968,133 @@ 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);
}
size_t PaintEntryPool::Chain::GetCount() const
{
size_t count = 0;
auto current = Head;
while (current != nullptr)
{
count += current->Count;
current = current->Next;
}
return count;
}
PaintEntryPool::~PaintEntryPool()
{
for (auto node : _available)
{
delete node;
}
_available.clear();
}
PaintEntryPool::Node* PaintEntryPool::AllocateNode()
{
std::lock_guard<std::mutex> 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<std::mutex> lock(_mutex);
auto node = head;
while (node != nullptr)
{
auto next = node->Next;
node->Next = nullptr;
node->Count = 0;
_available.push_back(node);
node = next;
}
}

View File

@ -15,6 +15,9 @@
#include "../interface/Colour.h"
#include "../world/Location.hpp"
#include <mutex>
#include <thread>
struct TileElement;
enum class ViewportInteractionItem : uint8_t;
@ -135,10 +138,58 @@ struct tunnel_entry
#define MAX_PAINT_QUADRANTS 512
#define TUNNEL_MAX_COUNT 65
struct paint_session
/**
* 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
{
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();
size_t GetCount() const;
};
private:
std::vector<Node*> _available;
std::mutex _mutex;
Node* AllocateNode();
public:
~PaintEntryPool();
Chain Create();
void FreeNodes(Node* head);
};
struct PaintSessionCore
{
rct_drawpixelinfo DPI;
FixedVector<paint_entry, 4000> PaintStructs;
paint_struct* Quadrants[MAX_PAINT_QUADRANTS];
paint_struct* LastPS;
paint_string_struct* PSStringHead;
@ -168,38 +219,60 @@ struct paint_session
uint8_t Unk141E9DB;
uint16_t WaterHeight;
uint32_t TrackColours[4];
};
constexpr bool NoPaintStructsAvailable() noexcept
{
return PaintStructs.size() >= PaintStructs.capacity();
}
struct paint_session : public PaintSessionCore
{
rct_drawpixelinfo DPI;
PaintEntryPool::Chain PaintEntryChain;
constexpr paint_struct* AllocateNormalPaintEntry() noexcept
paint_struct* AllocateNormalPaintEntry() noexcept
{
LastPS = &PaintStructs.emplace_back().basic;
return LastPS;
}
constexpr attached_paint_struct* AllocateAttachedPaintEntry() noexcept
{
LastAttachedPS = &PaintStructs.emplace_back().attached;
return LastAttachedPS;
}
constexpr paint_string_struct* AllocateStringPaintEntry() noexcept
{
auto* string = &PaintStructs.emplace_back().string;
if (LastPSString == nullptr)
auto* entry = PaintEntryChain.Allocate();
if (entry != nullptr)
{
PSStringHead = string;
LastPS = &entry->basic;
return LastPS;
}
else
{
LastPSString->next = string;
}
LastPSString = string;
return LastPSString;
return nullptr;
}
attached_paint_struct* AllocateAttachedPaintEntry() noexcept
{
auto* entry = PaintEntryChain.Allocate();
if (entry != nullptr)
{
LastAttachedPS = &entry->attached;
return LastAttachedPS;
}
return nullptr;
}
paint_string_struct* AllocateStringPaintEntry() noexcept
{
auto* entry = PaintEntryChain.Allocate();
if (entry != nullptr)
{
auto* string = &entry->string;
if (LastPSString == nullptr)
{
PSStringHead = string;
}
else
{
LastPSString->next = string;
}
LastPSString = string;
return LastPSString;
}
return nullptr;
}
};
struct RecordedPaintSession
{
PaintSessionCore Session;
std::vector<paint_entry> Entries;
};
extern paint_session gPaintSession;
@ -260,7 +333,7 @@ void PaintFloatingMoneyEffect(
paint_session* PaintSessionAlloc(rct_drawpixelinfo* dpi, uint32_t viewFlags);
void PaintSessionFree(paint_session* session);
void PaintSessionGenerate(paint_session* session);
void PaintSessionArrange(paint_session* session);
void PaintSessionArrange(PaintSessionCore* session);
void PaintDrawStructs(paint_session* session);
void PaintDrawMoneyStructs(rct_drawpixelinfo* dpi, paint_string_struct* ps);

View File

@ -151,7 +151,7 @@ paint_session* Painter::CreateSession(rct_drawpixelinfo* dpi, uint32_t viewFlags
session->ViewFlags = viewFlags;
session->QuadrantBackIndex = std::numeric_limits<uint32_t>::max();
session->QuadrantFrontIndex = 0;
session->PaintStructs.clear();
session->PaintEntryChain = _paintStructPool.Create();
std::fill(std::begin(session->Quadrants), std::end(session->Quadrants), nullptr);
session->LastPS = nullptr;
@ -167,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);
}

View File

@ -38,6 +38,7 @@ namespace OpenRCT2
std::shared_ptr<Ui::IUiContext> const _uiContext;
std::vector<std::unique_ptr<paint_session>> _paintSessionPool;
std::vector<paint_session*> _freePaintSessions;
PaintEntryPool _paintStructPool;
time_t _lastSecond = 0;
int32_t _currentFPS = 0;
int32_t _frames = 0;