From c442ef6207a906e203e5d64e89af7fb08d543a54 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 4 Jan 2021 23:31:41 +0200 Subject: [PATCH] Improve performance of tweening entities between ticks --- src/openrct2/Context.cpp | 14 +-- src/openrct2/ReplayManager.cpp | 2 +- src/openrct2/network/NetworkBase.cpp | 2 +- src/openrct2/peep/Peep.cpp | 2 +- src/openrct2/rct2/S6Importer.cpp | 4 +- src/openrct2/world/Sprite.cpp | 155 +++++++++++++-------------- src/openrct2/world/Sprite.h | 24 ++++- test/tests/PlayTests.cpp | 2 +- test/tests/S6ImportExportTests.cpp | 2 +- 9 files changed, 109 insertions(+), 98 deletions(-) diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index e67fabe32d..ba51f27cd5 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -624,7 +624,7 @@ namespace OpenRCT2 gFirstTimeSaving = true; game_fix_save_vars(); AutoCreateMapAnimations(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); gScreenAge = 0; gLastAutoSaveUpdate = AUTOSAVE_PAUSE; @@ -1003,10 +1003,12 @@ namespace OpenRCT2 { uint32_t currentTick = platform_get_ticks(); + auto& tweener = EntityTweener::Get(); + bool draw = !_isWindowMinimised && !gOpenRCT2Headless; if (_lastTick == 0) { - sprite_position_tween_reset(); + tweener.Reset(); _lastTick = currentTick; } @@ -1021,7 +1023,7 @@ namespace OpenRCT2 { // Get the original position of each sprite if (draw) - sprite_position_tween_store_a(); + tweener.PreTick(); Update(); @@ -1029,20 +1031,18 @@ namespace OpenRCT2 // Get the next position of each sprite if (draw) - sprite_position_tween_store_b(); + tweener.PostTick(); } if (draw) { const float alpha = std::min(static_cast(_accumulator) / GAME_UPDATE_TIME_MS, 1.0f); - sprite_position_tween_all(alpha); + tweener.Tween(alpha); _drawingEngine->BeginDraw(); _painter->Paint(*_drawingEngine); _drawingEngine->EndDraw(); - sprite_position_tween_restore(); - // Note: It's important to call UpdateWindows after restoring the sprite positions, not in between, // otherwise the window updates to positions of sprites could be reverted. // This can be observed when changing ride settings using the mouse wheel that removes all diff --git a/src/openrct2/ReplayManager.cpp b/src/openrct2/ReplayManager.cpp index 4b2c2a5d7b..f0450f4e4b 100644 --- a/src/openrct2/ReplayManager.cpp +++ b/src/openrct2/ReplayManager.cpp @@ -530,7 +530,7 @@ namespace OpenRCT2 importer->Import(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); // Load all map global variables. DataSerialiser parkParamsDs(false, data.parkParams); diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index 98af0414c6..5e4fc23f7b 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -2718,7 +2718,7 @@ bool NetworkBase::LoadMap(IStream* stream) objManager.LoadObjects(loadResult.RequiredObjects.data(), loadResult.RequiredObjects.size()); importer->Import(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); AutoCreateMapAnimations(); // Read checksum diff --git a/src/openrct2/peep/Peep.cpp b/src/openrct2/peep/Peep.cpp index 2f4d948392..329a5f61ba 100644 --- a/src/openrct2/peep/Peep.cpp +++ b/src/openrct2/peep/Peep.cpp @@ -766,7 +766,7 @@ std::unique_ptr Peep::Place(const TileCoordsXYZ& location, ActionSpriteImageOffset = 0; ActionSpriteType = PeepActionSpriteType::None; PathCheckOptimisation = 0; - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); if (AssignedPeepType == PeepType::Guest) { diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 1ab377d569..bacd7058c7 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -1709,7 +1709,7 @@ void load_from_sv6(const char* path) s6Importer->Import(); game_fix_save_vars(); AutoCreateMapAnimations(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); gScreenAge = 0; gLastAutoSaveUpdate = AUTOSAVE_PAUSE; } @@ -1749,7 +1749,7 @@ void load_from_sc6(const char* path) s6Importer->Import(); game_fix_save_vars(); AutoCreateMapAnimations(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); return; } catch (const ObjectLoadException& loadError) diff --git a/src/openrct2/world/Sprite.cpp b/src/openrct2/world/Sprite.cpp index 81b55f3eec..eafbe01331 100644 --- a/src/openrct2/world/Sprite.cpp +++ b/src/openrct2/world/Sprite.cpp @@ -46,9 +46,6 @@ const rct_string_id litterNames[12] = { STR_LITTER_VOMIT, STR_SHOP_ITEM_SINGULAR_EMPTY_JUICE_CUP, STR_SHOP_ITEM_SINGULAR_EMPTY_BOWL_BLUE }; -static CoordsXYZ _spritelocations1[MAX_SPRITES]; -static CoordsXYZ _spritelocations2[MAX_SPRITES]; - static size_t GetSpatialIndexOffset(int32_t x, int32_t y); static void move_sprite_to_list(SpriteBase* sprite, EntityListId newListIndex); @@ -928,106 +925,106 @@ uint16_t remove_floating_sprites() return removed; } -/** - * Determines whether it's worth tweening a sprite or not when frame smoothing is on. - */ -static bool sprite_should_tween(SpriteBase* sprite) +static bool IsValidEntity(SpriteBase* ent) { - switch (sprite->sprite_identifier) - { - case SpriteIdentifier::Peep: - case SpriteIdentifier::Vehicle: - return true; - case SpriteIdentifier::Misc: - case SpriteIdentifier::Litter: - case SpriteIdentifier::Null: - return false; - } - return false; + if (ent->sprite_identifier == SpriteIdentifier::Null) + return false; + if (ent->sprite_index == SPRITE_INDEX_NULL) + return false; + return true; } -static void store_sprite_locations(CoordsXYZ* sprite_locations) +void EntityTweener::PopulateEntities(EntityListId id) { - for (uint16_t i = 0; i < MAX_SPRITES; i++) + for (auto ent : EntityList(id)) { - // skip going through `get_sprite` to not get stalled on assert, - // this can get very expensive for busy parks with uncap FPS option on - const rct_sprite* sprite = &_spriteList[i]; - sprite_locations[i].x = sprite->generic.x; - sprite_locations[i].y = sprite->generic.y; - sprite_locations[i].z = sprite->generic.z; + _ents.push_back(&(*ent)); + _prePos.push_back({ ent->x, ent->y, ent->z }); } } -void sprite_position_tween_store_a() +void EntityTweener::PreTick() { - store_sprite_locations(_spritelocations1); + Restore(); + Reset(); + PopulateEntities(EntityListId::Peep); + PopulateEntities(EntityListId::Vehicle); + PopulateEntities(EntityListId::TrainHead); } -void sprite_position_tween_store_b() +void EntityTweener::PostTick() { - store_sprite_locations(_spritelocations2); + for (auto ent : _ents) + { + if (!IsValidEntity(ent)) + { + // Sprite was removed, add a dummy position to keep the index aligned. + _postPos.push_back({}); + } + else + { + _postPos.push_back({ ent->x, ent->y, ent->z }); + } + } } -void sprite_position_tween_all(float alpha) +void EntityTweener::Tween(float alpha) { const float inv = (1.0f - alpha); - - for (uint16_t i = 0; i < MAX_SPRITES; i++) + for (size_t i = 0; i < _ents.size(); ++i) { - auto* sprite = GetEntity(i); - if (sprite != nullptr && sprite_should_tween(sprite)) - { - auto posA = _spritelocations1[i]; - auto posB = _spritelocations2[i]; - if (posA == posB) - { - continue; - } - sprite_set_coordinates( - { static_cast(std::round(posB.x * alpha + posA.x * inv)), - static_cast(std::round(posB.y * alpha + posA.y * inv)), - static_cast(std::round(posB.z * alpha + posA.z * inv)) }, - sprite); - sprite->Invalidate(); - } - } -} - -/** - * Restore the real positions of the sprites so they aren't left at the mid-tween positions - */ -void sprite_position_tween_restore() -{ - for (uint16_t i = 0; i < MAX_SPRITES; i++) - { - auto* sprite = GetEntity(i); - if (sprite != nullptr && sprite_should_tween(sprite)) - { - sprite->Invalidate(); - - auto pos = _spritelocations2[i]; - sprite_set_coordinates(pos, sprite); - } - } -} - -void sprite_position_tween_reset() -{ - for (uint16_t i = 0; i < MAX_SPRITES; i++) - { - auto* sprite = GetEntity(i); - if (sprite == nullptr) + auto* ent = _ents[i]; + if (!IsValidEntity(ent)) { + // Sprite was removed, leave untouched. continue; } - _spritelocations1[i].x = _spritelocations2[i].x = sprite->x; - _spritelocations1[i].y = _spritelocations2[i].y = sprite->y; - _spritelocations1[i].z = _spritelocations2[i].z = sprite->z; + auto& posA = _prePos[i]; + auto& posB = _postPos[i]; + + if (posA == posB) + continue; + + sprite_set_coordinates( + { static_cast(std::round(posB.x * alpha + posA.x * inv)), + static_cast(std::round(posB.y * alpha + posA.y * inv)), + static_cast(std::round(posB.z * alpha + posA.z * inv)) }, + ent); + ent->Invalidate(); } } +void EntityTweener::Restore() +{ + for (size_t i = 0; i < _ents.size(); ++i) + { + auto* ent = _ents[i]; + if (!IsValidEntity(ent)) + { + // Sprite was removed, leave untouched. + continue; + } + + sprite_set_coordinates(_postPos[i], ent); + ent->Invalidate(); + } +} + +void EntityTweener::Reset() +{ + _ents.clear(); + _prePos.clear(); + _postPos.clear(); +} + +static EntityTweener tweener; + +EntityTweener& EntityTweener::Get() +{ + return tweener; +} + void sprite_set_flashing(SpriteBase* sprite, bool flashing) { assert(sprite->sprite_index < MAX_SPRITES); diff --git a/src/openrct2/world/Sprite.h b/src/openrct2/world/Sprite.h index ffa893e5f6..59ad77f633 100644 --- a/src/openrct2/world/Sprite.h +++ b/src/openrct2/world/Sprite.h @@ -246,11 +246,6 @@ uint16_t remove_floating_sprites(); void sprite_misc_explosion_cloud_create(const CoordsXYZ& cloudPos); void sprite_misc_explosion_flare_create(const CoordsXYZ& flarePos); uint16_t sprite_get_first_in_quadrant(const CoordsXY& spritePos); -void sprite_position_tween_store_a(); -void sprite_position_tween_store_b(); -void sprite_position_tween_all(float nudge); -void sprite_position_tween_restore(); -void sprite_position_tween_reset(); /////////////////////////////////////////////////////////////// // Balloon @@ -378,4 +373,23 @@ public: } }; +class EntityTweener +{ + std::vector _ents; + std::vector _prePos; + std::vector _postPos; + +private: + void PopulateEntities(EntityListId id); + +public: + static EntityTweener& Get(); + + void PreTick(); + void PostTick(); + void Tween(float alpha); + void Restore(); + void Reset(); +}; + #endif diff --git a/test/tests/PlayTests.cpp b/test/tests/PlayTests.cpp index c73dfc4526..7758f80920 100644 --- a/test/tests/PlayTests.cpp +++ b/test/tests/PlayTests.cpp @@ -54,7 +54,7 @@ static std::unique_ptr localStartGame(const std::string& parkPath) scenery_set_default_placement_configuration(); load_palette(); map_reorganise_elements(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); AutoCreateMapAnimations(); fix_invalid_vehicle_sprite_sizes(); diff --git a/test/tests/S6ImportExportTests.cpp b/test/tests/S6ImportExportTests.cpp index b64e6ed137..e2c2814d97 100644 --- a/test/tests/S6ImportExportTests.cpp +++ b/test/tests/S6ImportExportTests.cpp @@ -68,7 +68,7 @@ static void GameInit(bool retainSpatialIndices) scenery_set_default_placement_configuration(); load_palette(); map_reorganise_elements(); - sprite_position_tween_reset(); + EntityTweener::Get().Reset(); AutoCreateMapAnimations(); fix_invalid_vehicle_sprite_sizes();