Improve performance of tweening entities between ticks

This commit is contained in:
Matt 2021-01-04 23:31:41 +02:00
parent 48c8e66d7b
commit c442ef6207
No known key found for this signature in database
GPG Key ID: 6D4C24A61C93E208
9 changed files with 109 additions and 98 deletions

View File

@ -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<float>(_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

View File

@ -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);

View File

@ -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

View File

@ -766,7 +766,7 @@ std::unique_ptr<GameActions::Result> Peep::Place(const TileCoordsXYZ& location,
ActionSpriteImageOffset = 0;
ActionSpriteType = PeepActionSpriteType::None;
PathCheckOptimisation = 0;
sprite_position_tween_reset();
EntityTweener::Get().Reset();
if (AssignedPeepType == PeepType::Guest)
{

View File

@ -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)

View File

@ -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<int32_t>(std::round(posB.x * alpha + posA.x * inv)),
static_cast<int32_t>(std::round(posB.y * alpha + posA.y * inv)),
static_cast<int32_t>(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<int32_t>(std::round(posB.x * alpha + posA.x * inv)),
static_cast<int32_t>(std::round(posB.y * alpha + posA.y * inv)),
static_cast<int32_t>(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);

View File

@ -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<SpriteBase*> _ents;
std::vector<CoordsXYZ> _prePos;
std::vector<CoordsXYZ> _postPos;
private:
void PopulateEntities(EntityListId id);
public:
static EntityTweener& Get();
void PreTick();
void PostTick();
void Tween(float alpha);
void Restore();
void Reset();
};
#endif

View File

@ -54,7 +54,7 @@ static std::unique_ptr<IContext> 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();

View File

@ -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();