Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the rules of how game objects are updated after sprite's update/deletion #2117

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 28 additions & 34 deletions Common/ac/spritecache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@

using namespace AGS::Common;

// Internal SpriteCache's flags, not serialized in game data
//
// Tells that the sprite is found in the game resources.
#define SPRCACHEFLAG_ISASSET 0x01
// Tells that the sprite is assigned externally and cannot be autodisposed.
#define SPRCACHEFLAG_EXTERNAL 0x02
// Tells that the sprite index was remapped to the placeholder (sprite 0).
#define SPRCACHEFLAG_REMAP0 0x04
// Tells that the asset sprite failed to load
#define SPRCACHEFLAG_ERROR 0x04
// Locked sprites are ones that should not be freed when out of cache space.
#define SPRCACHEFLAG_LOCKED 0x08

Expand All @@ -49,11 +51,9 @@ SpriteCache::SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &cal
_callbacks.InitSprite = (callbacks.InitSprite) ? callbacks.InitSprite : DummyInitSprite;
_callbacks.PostInitSprite = (callbacks.PostInitSprite) ? callbacks.PostInitSprite : DummyPostInitSprite;
_callbacks.PrewriteSprite = (callbacks.PrewriteSprite) ? callbacks.PrewriteSprite : DummyPrewriteSprite;
}

SpriteCache::~SpriteCache()
{
Reset();
// Generate a placeholder sprite: 1x1 transparent bitmap
_placeholder.reset(BitmapHelper::CreateTransparentBitmap(1, 1));
}

size_t SpriteCache::GetSpriteSlotCount() const
Expand Down Expand Up @@ -115,7 +115,7 @@ void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset)
ResourceCache::Dispose(index); // make sure it's free
if (as_asset)
_spriteData[index].Flags = SPRCACHEFLAG_ISASSET;
RemapSpriteToSprite0(index);
RemapSpriteToPlaceholder(index);
}

Bitmap *SpriteCache::RemoveSprite(sprkey_t index)
Expand Down Expand Up @@ -175,17 +175,17 @@ sprkey_t SpriteCache::GetFreeIndex()

bool SpriteCache::SpriteData::IsAssetSprite() const
{
return (Flags & SPRCACHEFLAG_ISASSET) != 0; // found in game resources
return (Flags & SPRCACHEFLAG_ISASSET) != 0;
}

bool SpriteCache::SpriteData::IsRemapped() const
bool SpriteCache::SpriteData::IsError() const
{
return (Flags & SPRCACHEFLAG_REMAP0) != 0; // was remapped to placeholder (sprite 0)
return (Flags & SPRCACHEFLAG_ERROR) != 0;
}

bool SpriteCache::SpriteData::IsExternalSprite() const
{
return (Flags & SPRCACHEFLAG_EXTERNAL) != 0; // assigned externally
return (Flags & SPRCACHEFLAG_EXTERNAL) != 0;
}

bool SpriteCache::SpriteData::IsLocked() const
Expand All @@ -195,7 +195,7 @@ bool SpriteCache::SpriteData::IsLocked() const

bool SpriteCache::DoesSpriteExist(sprkey_t index) const
{
return index >= 0 && (size_t)index < _spriteData.size() && // in the valid range
return (index >= 0 && (size_t)index < _spriteData.size()) && // in the valid range
_spriteData[index].IsValid(); // has assigned sprite
}

Expand All @@ -208,19 +208,21 @@ Bitmap *SpriteCache::operator [] (sprkey_t index)
{
// invalid sprite slot
assert(index >= 0); // out of positive range indexes are valid to fail
if (index < 0 || (size_t)index >= _spriteData.size())
return nullptr;
if (!DoesSpriteExist(index) || _spriteData[index].IsError())
return _placeholder.get();

// Resolve potentially remapped sprites
index = GetDataIndex(index);
// Try get image from cache
auto &image = ResourceCache::Get(index);
if (image)
return image.get();
// If no ready image, but has an asset, then try loading one
if (_spriteData[index].IsAssetSprite())
return LoadSprite(index);
return nullptr;
{
auto *bitmap = LoadSprite(index);
if (bitmap)
return bitmap;
}
return _placeholder.get();
}

void SpriteCache::DisposeAllCached()
Expand All @@ -245,12 +247,6 @@ void SpriteCache::Precache(sprkey_t index)
SprCacheLog("Precached %d", index);
}

sprkey_t SpriteCache::GetDataIndex(sprkey_t index)
{
assert((index >= 0) && ((size_t)index < _spriteData.size()));
return (_spriteData[index].Flags & SPRCACHEFLAG_REMAP0) == 0 ? index : 0;
}

size_t SpriteCache::CalcSize(const std::unique_ptr<Bitmap> &item)
{
assert(item);
Expand All @@ -269,9 +265,9 @@ Bitmap *SpriteCache::LoadSprite(sprkey_t index)
if (!image)
{
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
"LoadSprite: failed to load sprite %d:\n%s\n - remapping to sprite 0.", index,
"LoadSprite: failed to load sprite %d:\n%s\n - remapping to placeholder.", index,
err ? "Sprite does not exist." : err->FullMessage().GetCStr());
RemapSpriteToSprite0(index);
RemapSpriteToPlaceholder(index);
return nullptr;
}

Expand All @@ -280,8 +276,8 @@ Bitmap *SpriteCache::LoadSprite(sprkey_t index)
if (!image)
{
Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn,
"LoadSprite: failed to initialize sprite %d, remapping to sprite 0.", index);
RemapSpriteToSprite0(index);
"LoadSprite: failed to initialize sprite %d, remapping to placeholder.", index);
RemapSpriteToPlaceholder(index);
return nullptr;
}

Expand All @@ -303,14 +299,12 @@ Bitmap *SpriteCache::LoadSprite(sprkey_t index)
return image;
}

void SpriteCache::RemapSpriteToSprite0(sprkey_t index)
void SpriteCache::RemapSpriteToPlaceholder(sprkey_t index)
{
assert((index > 0) && ((size_t)index < _spriteData.size()));
if (index <= 0)
return; // don't remap sprite 0 to itself
_sprInfos[index] = _sprInfos[0];
_spriteData[index].Flags |= SPRCACHEFLAG_REMAP0;
SprCacheLog("RemapSpriteToSprite0: %d", index);
_sprInfos[index] = SpriteInfo(_placeholder->GetWidth(), _placeholder->GetHeight(), _placeholder->GetColorDepth());
_spriteData[index].Flags |= SPRCACHEFLAG_ERROR;
SprCacheLog("RemapSpriteToPlaceholder: %d", index);
}

void SpriteCache::InitNullSprite(sprkey_t index)
Expand Down
16 changes: 7 additions & 9 deletions Common/ac/spritecache.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class SpriteCache :


SpriteCache(std::vector<SpriteInfo> &sprInfos, const Callbacks &callbacks);
~SpriteCache();
~SpriteCache() = default;

// Loads sprite reference information and inits sprite stream
HError InitFile(const String &filename, const String &sprindex_filename);
Expand Down Expand Up @@ -135,7 +135,7 @@ class SpriteCache :
// *Deletes* the previous sprite if one was found at the same index.
// "flags" are optional SPF_* constants that define sprite's behavior in game.
bool SetSprite(sprkey_t index, std::unique_ptr<Bitmap> image, int flags = 0);
// Assigns new dummy sprite for the given index, silently remapping it to sprite 0;
// Assigns new dummy sprite for the given index, silently remapping it to placeholder;
// optionally marks it as an asset placeholder.
// *Deletes* the previous sprite if one was found at the same index.
void SetEmptySprite(sprkey_t index, bool as_asset);
Expand All @@ -154,10 +154,7 @@ class SpriteCache :
// Load sprite from game resource and put into the cache
Bitmap * LoadSprite(sprkey_t index);
// Remap the given index to the sprite 0
void RemapSpriteToSprite0(sprkey_t index);
// Gets the index of a sprite which data is used for the given slot;
// in case of remapped sprite this will return the one given sprite is remapped to
sprkey_t GetDataIndex(sprkey_t index);
void RemapSpriteToPlaceholder(sprkey_t index);
// Initialize the empty sprite slot
void InitNullSprite(sprkey_t index);
//
Expand All @@ -180,8 +177,8 @@ class SpriteCache :
bool IsValid() const { return Flags != 0u; }
// Tells if there's a game resource corresponding to this slot
bool IsAssetSprite() const;
// Tells if a sprite is remapped to placeholder (e.g. failed to load)
bool IsRemapped() const;
// Tells if a sprite failed to load from assets, and should not be used
bool IsError() const;
// Tells if sprite was added externally, not loaded from game resources
bool IsExternalSprite() const;
// Tells if sprite is locked and should not be disposed by cache logic
Expand All @@ -192,10 +189,11 @@ class SpriteCache :
std::vector<SpriteInfo> &_sprInfos;
// Array of sprite references
std::vector<SpriteData> _spriteData;
// Placeholder sprite, returned from operator[] for a non-existing sprite
std::unique_ptr<Bitmap> _placeholder;

Callbacks _callbacks;
SpriteFile _file;

};

} // namespace Common
Expand Down
89 changes: 55 additions & 34 deletions Engine/ac/draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ ObjTexture debugMoveListObj;
// For in-game "console" surface
Bitmap *debugConsoleBuffer = nullptr;

// Draw cache: keep record of all kinds of things related to the previous drawing state
//
// Cached character and object states, used to determine
// whether these require texture update
std::vector<ObjectCache> charcache;
Expand Down Expand Up @@ -698,6 +700,8 @@ void init_draw_method()
init_room_drawdata();
if (gfxDriver->UsesMemoryBackBuffer())
gfxDriver->GetMemoryBackBuffer()->Clear();

play.spritemodified.resize(game.SpriteInfos.size());
}

void dispose_draw_method()
Expand Down Expand Up @@ -786,6 +790,10 @@ void clear_drawobj_cache()
for (auto &o : guiobjbg) o = ObjTexture();
overtxs.clear();

// Clear "modified sprite" flags
play.spritemodifiedlist.clear();
std::fill(play.spritemodified.begin(), play.spritemodified.end(), false);

dispose_debug_room_drawdata();
}

Expand Down Expand Up @@ -979,34 +987,6 @@ void mark_object_changed(int objid)
objcache[objid].y = -9999;
}

void reset_objcache_for_sprite(int sprnum, bool deleted)
{
// Check if this sprite is assigned to any game object, and mark these for update;
// if the sprite was deleted, also mark texture objects as invalid.
// IMPORTANT!!: do NOT dispose textures themselves here.
// * if the next valid image is of the same size, then the texture will be reused;
// * BACKWARD COMPAT: keep last images during room transition out!
// room objects cache
if (croom != nullptr)
{
for (size_t i = 0; i < (size_t)croom->numobj; ++i)
{
if (objcache[i].sppic == sprnum)
objcache[i].sppic = -1;
if (deleted && (actsps[i].SpriteID == sprnum))
actsps[i].SpriteID = UINT32_MAX; // invalid sprite ref
}
}
// character cache
for (size_t i = 0; i < (size_t)game.numcharacters; ++i)
{
if (charcache[i].sppic == sprnum)
charcache[i].sppic = -1;
if (deleted && (actsps[ACTSP_OBJSOFF + i].SpriteID == sprnum))
actsps[ACTSP_OBJSOFF + i].SpriteID = UINT32_MAX; // invalid sprite ref
}
}

void reset_drawobj_for_overlay(int objnum)
{
if (objnum > 0 && static_cast<size_t>(objnum) < overtxs.size())
Expand All @@ -1017,6 +997,27 @@ void reset_drawobj_for_overlay(int objnum)
}
}

void notify_sprite_changed(int sprnum, bool deleted)
{
assert(sprnum >= 0 && sprnum < game.SpriteInfos.size());
// Update texture cache (regen texture or clear from cache)
if (deleted)
clear_shared_texture(sprnum);
else
update_shared_texture(sprnum);

// For texture-based renderers updating a shared texture will already
// update all the related drawn objects on screen.
// For software renderer we should notify drawables that currently
// reference this sprite.
if (drawstate.SoftwareRender)
{
assert(sprnum < play.spritemodified.size());
play.spritemodified[sprnum] = true;
play.spritemodifiedlist.push_back(sprnum);
}
}

void texturecache_get_state(size_t &max_size, size_t &cur_size, size_t &locked_size, size_t &ext_size)
{
max_size = texturecache.GetMaxCacheSize();
Expand All @@ -1035,6 +1036,7 @@ void update_shared_texture(uint32_t sprite_id)
auto txdata = texturecache.Get(sprite_id);
if (!txdata)
return;

const auto &res = txdata->Res;
if (res.Width == game.SpriteInfos[sprite_id].Width &&
res.Height == game.SpriteInfos[sprite_id].Height)
Expand Down Expand Up @@ -1266,7 +1268,6 @@ IDriverDependantBitmap* recycle_ddb_sprite(IDriverDependantBitmap *ddb, uint32_t
return recycle_ddb_bitmap(ddb, source, has_alpha, opaque);
}

// TODO: how to test if sprite was modified, while NOT cached? Is GetRefID enough for this? maybe....
if (ddb && ddb->GetRefID() == sprite_id)
return ddb; // texture in sync

Expand Down Expand Up @@ -1298,7 +1299,6 @@ IDriverDependantBitmap* recycle_render_target(IDriverDependantBitmap *ddb, int w
// FIXME: make has_alpha and opaque properties of ObjTexture?!
static void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool opaque = false)
{
// TODO: test if source bitmap was modified, if not then return?
obj.Ddb = recycle_ddb_sprite(obj.Ddb, obj.SpriteID, obj.Bmp.get(), has_alpha, opaque);
}

Expand Down Expand Up @@ -1780,15 +1780,18 @@ static bool construct_object_gfx(const ViewFrame *vf, int pic,
// If we have the image cached, use it
if ((objsav.image != nullptr) &&
(objsav.sppic == specialpic) &&
// not a dynamic sprite, or not sprite modified lately
(!play.spritemodified[objsav.sppic]) &&
(objsav.tintamnt == tint_level) &&
(objsav.tintlight == tint_light) &&
(objsav.tintr == tint_red) &&
(objsav.tintg == tint_green) &&
(objsav.tintb == tint_blue) &&
(objsav.lightlev == light_level) &&
(objsav.zoom == objsrc.zoom) &&
(objsav.mirrored == is_mirrored)) {
// the image is the same, we can use it cached!
(objsav.mirrored == is_mirrored))
{
// If the image is the same, we can use it cached
if ((drawstate.WalkBehindMethod != DrawOverCharSprite) &&
(actsp.Bmp != nullptr))
{
Expand Down Expand Up @@ -2608,7 +2611,8 @@ static void construct_overlays()
if (over.type < 0) continue; // empty slot
if (over.transparency == 255) continue; // skip fully transparent

bool has_changed = over.HasChanged();
auto &overtx = overtxs[i];
bool has_changed = over.HasChanged() || play.spritemodified[over.GetSpriteNum()];
// If walk behinds are drawn over the cached object sprite, then check if positions were updated
if (crop_walkbehinds && over.IsRoomLayer())
{
Expand All @@ -2617,7 +2621,6 @@ static void construct_overlays()
overcache[i].X = pos.X; overcache[i].Y = pos.Y;
}

auto &overtx = overtxs[i];
if (has_changed)
{
overtx.SpriteID = over.GetSpriteNum();
Expand Down Expand Up @@ -2653,6 +2656,21 @@ static void construct_overlays()
}
}

static void reset_spritemodified()
{
if (play.spritemodifiedlist.size() > 0)
{
// Sort and remove duplicates;
// CHECKME: or is it more optimal to just run over raw list?
std::sort(play.spritemodifiedlist.begin(), play.spritemodifiedlist.end());
play.spritemodifiedlist.erase(
std::unique(play.spritemodifiedlist.begin(), play.spritemodifiedlist.end()), play.spritemodifiedlist.end());
for (auto sprnum : play.spritemodifiedlist)
play.spritemodified[sprnum] = false;
play.spritemodifiedlist.clear();
}
}

void construct_game_scene(bool full_redraw)
{
gfxDriver->ClearDrawLists();
Expand Down Expand Up @@ -2712,6 +2730,9 @@ void construct_game_scene(bool full_redraw)

// End the parent scene node
gfxDriver->EndSpriteBatch();

// Clear "modified sprite" flags
reset_spritemodified();
}

void construct_game_screen_overlay(bool draw_mouse)
Expand Down
Loading