Skip to content

Commit

Permalink
Engine: changed the rules for updating drawables on dyn sprite change
Browse files Browse the repository at this point in the history
1. Do not reset the game objects' Graphic properties when the sprite is deleted.
2. Record that the sprite was in use only at the drawing stage, in a more centralized manner. When the sprite is updated or deleted, reset the drawable caches and mark game objects for update, but only if a sprite was marked as in use.
Sprite being in use flag is only removed when the sprite is deleted.
This saves unnecessary objects check for sprites that were never assigned to anything; if they were only used as intermediate stage in raw drawing, for instance.
  • Loading branch information
ivan-mogilko committed Aug 29, 2023
1 parent db6b49e commit c556b0a
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 95 deletions.
33 changes: 30 additions & 3 deletions Engine/ac/draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ 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
//
std::vector<uint8_t> spriteuse;
// Cached character and object states, used to determine
// whether these require texture update
std::vector<ObjectCache> charcache;
Expand Down Expand Up @@ -728,6 +731,8 @@ void init_game_drawdata()
guio_num += gui.GetControlCount();
}
guiobjbg.resize(guio_num);

spriteuse.resize(game.SpriteInfos.size());
}

void dispose_game_drawdata()
Expand Down Expand Up @@ -788,6 +793,8 @@ void clear_drawobj_cache()
overlaybmp.clear();

dispose_debug_room_drawdata();

std::fill(spriteuse.begin(), spriteuse.end(), kSprUse_None);
}

void release_drawobj_rendertargets()
Expand Down Expand Up @@ -980,8 +987,12 @@ void mark_object_changed(int objid)
objcache[objid].y = -9999;
}

void reset_objcache_for_sprite(int sprnum, bool deleted)
SpriteUseFlags reset_objcache_for_sprite(int sprnum, bool deleted)
{
assert(sprnum >= 0 && sprnum < spriteuse.size());
if (spriteuse[sprnum] == kSprUse_None)
return kSprUse_None;

// 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.
Expand All @@ -1006,6 +1017,11 @@ void reset_objcache_for_sprite(int sprnum, bool deleted)
if (deleted && (actsps[ACTSP_OBJSOFF + i].SpriteID == sprnum))
actsps[ACTSP_OBJSOFF + i].SpriteID = UINT32_MAX; // invalid sprite ref
}

auto use = static_cast<SpriteUseFlags>(spriteuse[sprnum]);
if (deleted)
spriteuse[sprnum] = kSprUse_None;
return use;
}

void reset_drawobj_for_overlay(int objnum)
Expand Down Expand Up @@ -1035,6 +1051,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 @@ -1300,6 +1317,9 @@ static void sync_object_texture(ObjTexture &obj, bool has_alpha = false, bool op
{
// 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);
// mark sprite as used; TODO: pass a flag to set a specific object type?
if (obj.SpriteID != UINT32_MAX)
spriteuse[obj.SpriteID] = kSprUse_Any;
}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -2620,6 +2640,7 @@ static void construct_overlays()
// For software mode - prepare transformed bitmap if necessary;
// for hardware-accelerated - use the sprite ID if possible, to avoid redundant sprite load
Bitmap *use_bmp = nullptr;
const int sprnum = over.GetSpriteNum();
if (is_software_mode)
{
use_bmp = transform_sprite(over.GetImage(), over.HasAlphaChannel(), overlaybmp[i], Size(over.scaleWidth, over.scaleHeight));
Expand All @@ -2635,13 +2656,15 @@ static void construct_overlays()
use_bmp = overlaybmp[i].get();
}
}
else if (over.GetSpriteNum() < 0)
else if (sprnum < 0)
{
use_bmp = over.GetImage();
}

over.ddb = recycle_ddb_sprite(over.ddb, over.GetSpriteNum(), use_bmp, over.HasAlphaChannel());
over.ddb = recycle_ddb_sprite(over.ddb, sprnum, use_bmp, over.HasAlphaChannel());
over.ClearChanged();
if (sprnum >= 0)
spriteuse[sprnum] = kSprUse_Any;
}

assert(over.ddb); // Test for missing texture, might happen if not marked for update
Expand Down Expand Up @@ -2672,6 +2695,10 @@ void construct_game_scene(bool full_redraw)
if (full_redraw || play.screen_tint > 0 || play.shakesc_length > 0)
invalidate_screen();

// Prepare sprite flag array, matching with the game sprite count
if (spriteuse.size() < game.SpriteInfos.size())
spriteuse.resize(game.SpriteInfos.size());

// Overlays may be both in rooms and ui layer, prepare their textures beforehand
construct_overlays();

Expand Down
15 changes: 13 additions & 2 deletions Engine/ac/draw.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ int MakeColor(int color_index);
class Viewport;
class Camera;

// Sprite use flags are used to tell which objects the sprite was drawn for;
// this is to know if any objects has to be updated/redrawn if a sprite has
// been modified or deleted.
// 8-bit for now, increase if necessary.
enum SpriteUseFlags
{
kSprUse_None = 0,
kSprUse_Any = 0xFF
};

// Initializes drawing methods and optimisation
void init_draw_method();
// Initializes global game drawing resources
Expand Down Expand Up @@ -71,8 +81,9 @@ void detect_roomviewport_overlaps(size_t z_index);
void on_roomcamera_changed(Camera *cam);
// Marks particular object as need to update the texture
void mark_object_changed(int objid);
// Resets all object caches which reference this sprite
void reset_objcache_for_sprite(int sprnum, bool deleted);
// Resets all object caches which reference this sprite;
// returns the sprite use flags for this sprite
SpriteUseFlags reset_objcache_for_sprite(int sprnum, bool deleted);
// TODO: write a generic drawable/objcache system where each object
// allocates a drawable for itself, and disposes one if being removed.
void reset_drawobj_for_overlay(int objnum);
Expand Down
2 changes: 1 addition & 1 deletion Engine/ac/dynamicsprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ void free_dynamic_sprite(int slot) {
return;

spriteset.DisposeSprite(slot);
game_sprite_deleted(slot);
game_sprite_updated(slot, true);
}

//=============================================================================
Expand Down
91 changes: 6 additions & 85 deletions Engine/ac/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1396,11 +1396,14 @@ void get_message_text (int msnum, char *buffer, char giveErr) {
replace_tokens(get_translation(thisroom.Messages[msnum].GetCStr()), buffer, maxlen);
}

void game_sprite_updated(int sprnum)
void game_sprite_updated(int sprnum, bool deleted)
{
update_shared_texture(sprnum);
// character and object draw caches
reset_objcache_for_sprite(sprnum, false);
SpriteUseFlags use = reset_objcache_for_sprite(sprnum, deleted);
if (use == kSprUse_None)
return;

// gui backgrounds
for (auto &gui : guis)
{
Expand Down Expand Up @@ -1432,89 +1435,7 @@ void game_sprite_updated(int sprnum)
if (over.GetSpriteNum() == sprnum)
over.MarkChanged();
}
}

void game_sprite_deleted(int sprnum)
{
clear_shared_texture(sprnum);
// character and object draw caches
reset_objcache_for_sprite(sprnum, true);

// This is ugly, but apparently there are few games that may rely
// (either with or without author's intent) on newly created sprite
// being assigned same index as a recently deleted one, which results
// in new sprite "secretly" taking place of an old one on the GUI, etc.
// So for old games we keep only partial reset (full cleanup is 3.5.0+).
const bool reset_sprindex_oldstyle =
loaded_game_file_version < kGameVersion_350;

// room object graphics
if (croom != nullptr)
{
for (size_t i = 0; i < (size_t)croom->numobj; ++i)
{
if (objs[i].num == sprnum)
objs[i].num = 0;
}
}
// gui buttons
for (auto &but : guibuts)
{
if (but.Image == sprnum)
but.Image = 0;
if (but.MouseOverImage == sprnum)
but.MouseOverImage = 0;
if (but.PushedImage == sprnum)
but.PushedImage = 0;

if (but.CurrentImage == sprnum)
{
but.CurrentImage = 0;
but.MarkChanged();
}
}

if (reset_sprindex_oldstyle)
return; // stop here for < 3.5.0 games

// gui backgrounds
for (size_t i = 0; i < (size_t)game.numgui; ++i)
{
if (guis[i].BgImage == sprnum)
{
guis[i].BgImage = 0;
guis[i].MarkChanged();
}
}
// gui sliders
for (auto &slider : guislider)
{
if ((slider.BgImage == sprnum) || (slider.HandleImage == sprnum))
slider.MarkChanged();
if (slider.BgImage == sprnum)
slider.BgImage = 0;
if (slider.HandleImage == sprnum)
slider.HandleImage = 0;
}
// views
for (size_t v = 0; v < (size_t)game.numviews; ++v)
{
for (size_t l = 0; l < (size_t)views[v].numLoops; ++l)
{
for (size_t f = 0; f < (size_t)views[v].loops[l].numFrames; ++f)
{
if (views[v].loops[l].frames[f].pic == sprnum)
views[v].loops[l].frames[f].pic = 0;
}
}
}
// overlays
auto &overs = get_overlays();
for (auto &over : overs)
{
if (over.GetSpriteNum() == sprnum)
over.SetSpriteNum(0);
}

}

//=============================================================================
Expand Down
5 changes: 1 addition & 4 deletions Engine/ac/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,7 @@ void get_message_text (int msnum, char *buffer, char giveErr = 1);

// Notifies the game objects that certain sprite was updated.
// This make them update their render states, caches, and so on.
void game_sprite_updated(int sprnum);
// Notifies the game objects that certain sprite was deleted.
// Those which used that sprite will reset to dummy sprite 0, update their render states and caches.
void game_sprite_deleted(int sprnum);
void game_sprite_updated(int sprnum, bool deleted = false);

extern int in_new_room;
extern int new_room_pos;
Expand Down

0 comments on commit c556b0a

Please sign in to comment.