Skip to content
Draft
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
74 changes: 72 additions & 2 deletions code/bmpman/bmpman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ SCP_vector<std::array<bitmap_slot, BM_BLOCK_SIZE>> bm_blocks;
// --------------------------------------------------------------------------------------------------------------------
// Definition of private variables at file scope (static).
static bool bm_inited = false;

// Some objects with static storage duration (e.g. model_texture_replace held by a global shared_ptr) release bitmaps
// in their destructors. If the program exits without bm_close(), bm_blocks may be destroyed before they are, so this
// guard, which is destroyed before bm_blocks because it is defined after it, marks bmpman as uninitialized first.
static struct bm_static_destruction_guard {
~bm_static_destruction_guard() { bm_inited = false; }
} Bm_static_destruction_guard;
static uint Bm_next_signature = 0x1234;
static int Bm_low_mem = 0;

Expand Down Expand Up @@ -2920,6 +2927,9 @@ void bm_print_bitmaps() {
int bm_release(int handle, int clear_render_targets) {
Assert(handle >= 0);

if (!bm_inited)
return 0;

bitmap_entry *be;

be = bm_get_entry(handle);
Expand Down Expand Up @@ -3019,6 +3029,45 @@ int bm_release(int handle, int clear_render_targets) {
return 1;
}

int bm_add_ref(int handle) {
if (!bm_is_valid(handle))
return -1;

// render targets are not counted, because bm_release() will not release them anyway
if (bm_is_render_target(handle))
return handle;

// animations are counted on their first frame
int first_frame = bm_get_info(handle);
if (first_frame < 0)
return -1;

bm_get_entry(first_frame)->load_count++;

return first_frame;
}

int bm_release_ref(int handle) {
if (!bm_is_valid(handle))
return 0;

// animations are counted on their first frame
int first_frame = bm_get_info(handle);
if (first_frame < 0)
return 0;

// a locked bitmap can't be freed right now, but our reference can still be given up; the data is then freed by a
// later release (render targets are never counted, see bm_add_ref())
auto be = bm_get_entry(first_frame);
if (be->ref_count != 0 && !bm_is_render_target(first_frame)) {
if (be->load_count > 0)
be->load_count--;
return 0;
}

return bm_release(first_frame);
}

bool bm_release_rendertarget(int handle) {
Assert(handle >= 0);

Expand Down Expand Up @@ -3202,14 +3251,17 @@ bool bm_set_render_target(int handle, int face) {
return false;
}

int bm_unload(int handle, int clear_render_targets, bool nodebug) {
int bm_unload(int handle, int clear_render_targets, bool nodebug, bool keep_reference) {
bitmap_entry *be;
bitmap *bmp;

if (handle == -1) {
return -1;
}

if (!bm_inited)
return -1;

be = bm_get_entry(handle);
bmp = &be->bm;

Expand All @@ -3232,7 +3284,7 @@ int bm_unload(int handle, int clear_render_targets, bool nodebug) {
// kind of like ref_count except it gets around the lock/unlock usage problem
// this gets set for each bm_load() call so we can make sure and not unload it
// from memory, even if we *can*, until it's really not needed anymore
if (!Bm_ignore_load_count) {
if (!Bm_ignore_load_count && !keep_reference) {
if (be->load_count > 0)
be->load_count--;

Expand All @@ -3242,6 +3294,9 @@ int bm_unload(int handle, int clear_render_targets, bool nodebug) {
}
}

// freeing the data resets the load count, but paging out gives up no references, so remember it
int saved_load_count = be->load_count;

// be sure that all frames of an ani are unloaded - taylor
if (bm_is_anim(be) == true) {
int i, first = be->info.ani.first_frame;
Expand All @@ -3264,9 +3319,24 @@ int bm_unload(int handle, int clear_render_targets, bool nodebug) {
bm_free_data(bm_get_slot(handle)); // clears flags, bbp, data, etc
}

if (keep_reference)
be->load_count = saved_load_count;

return 1;
}

int bm_page_out(int handle) {
if (!bm_is_valid(handle))
return 0;

// animations are counted on their first frame
int first_frame = bm_get_info(handle);
if (first_frame < 0)
return 0;

return (bm_unload(first_frame, 0, false, true) == 1) ? 1 : 0;
}

void bm_unload_all() {
// since bm_unload_all() should only be called from game_shutdown() it should be
// safe to ignore load_count's and unload anyway
Expand Down
56 changes: 53 additions & 3 deletions code/bmpman/bmpman.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,17 @@ int bm_create_3d(int bpp, int w, int h, int d, void* data = nullptr);
* @param handle The index number of the bitmap to free
* @param clear_render_targets If true, release a render target
* @param nodebug If true, exclude certain debug messages
* @param keep_reference If true, no load-count reference is given up: the data is freed, but every reference to
* the bitmap is preserved, so it is simply reloaded the next time it is locked. For
* animations, pass the first frame, since that is where the references are counted.
* See bm_page_out()
*
* @returns 0 if not successful,
* @returns 1 if successful
* @returns 1 if the data was freed,
* @returns 0 if it was not (e.g. the bitmap is locked, or another holder still needs it), or
* @returns -1 if the handle is invalid, bmpman is not initialized, or the bitmap is a render target and
* clear_render_targets is not set
*/
int bm_unload(int handle, int clear_render_targets = 0, bool nodebug = false);
int bm_unload(int handle, int clear_render_targets = 0, bool nodebug = false, bool keep_reference = false);

/**
* @brief Quickly unloads a bitmap's data, ignoring the load_count
Expand All @@ -276,6 +282,20 @@ int bm_unload(int handle, int clear_render_targets = 0, bool nodebug = false);
*/
int bm_unload_fast(int handle, int clear_render_targets = 0);

/**
* @brief Frees a bitmap's data without giving up the caller's load-count reference
*
* @details Use this to page out a bitmap that will be needed again later: the slot and every reference to the bitmap are
* kept, and the data is reloaded the next time it is locked. Unlike bm_unload(), this never consumes a reference, so
* it works no matter how many holders the bitmap has. A bitmap that is currently locked is left alone.
*
* @param handle The bitmap handle. For animations, any frame may be passed; the whole animation is paged out.
*
* @returns 1 if the data was freed,
* @returns 0 otherwise
*/
int bm_page_out(int handle);

/**
* @brief Frees both a bitmap's data and it's associated slot.
*
Expand All @@ -293,6 +313,36 @@ int bm_unload_fast(int handle, int clear_render_targets = 0);
*/
int bm_release(int handle, int clear_render_targets = 0);

/**
* @brief Takes an additional load-count reference on a bitmap that is already loaded
*
* @details This is the equivalent of loading the bitmap a second time: the bitmap will not be freed until every
* reference has been released. Use it when storing a handle that was loaded by someone else, so that the stored
* handle stays valid even after the original loader releases it. Every call must be balanced by a call to
* bm_release_ref().
*
* @param handle The bitmap handle. For animations, any frame may be passed; the reference is taken on the first frame.
* Render targets are not counted, since bm_release() will not release them without being explicitly asked to.
*
* @returns the handle the reference was taken on (the first frame for animations), or the handle itself for a render
* target, on which no reference is taken, or
* @returns -1 if the handle is not a valid bitmap
*/
int bm_add_ref(int handle);

/**
* @brief Releases a load-count reference taken by bm_add_ref() or by loading the bitmap
*
* @details Same as bm_release(), except that any frame of an animation may be passed, and that if the bitmap is
* currently locked the reference is still given up; the bitmap is then freed by a later release, or at shutdown.
*
* @param handle The bitmap handle. For animations, any frame may be passed; the reference is released on the first frame.
*
* @returns 1 if the bitmap was freed,
* @returns 0 otherwise
*/
int bm_release_ref(int handle);

/**
* @brief Detaches the render target of a bitmap if it exists
*
Expand Down
17 changes: 1 addition & 16 deletions code/lab/manager/lab_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -856,22 +856,7 @@ void LabManager::changeShipInternal() {
ship_objp->special_exp_damage = -1;

// If the ship class defines replacement textures, load them and apply them to the ship
// load the texture
auto replacements = ship_infop->replacement_textures;
for (auto& tr : replacements) {
if (!stricmp(tr.new_texture, "invisible"))
{
// invisible is a special case
tr.new_texture_id = REPLACE_WITH_INVISIBLE;
}
else
{
// try to load texture or anim as normal
tr.new_texture_id = bm_load_either(tr.new_texture);
}
}

ship_objp->apply_replacement_textures(replacements);
ship_objp->load_and_apply_replacement_textures(ship_infop->replacement_textures);
ship_page_in_textures(ship_objp->ship_info_index);

if (!ship_infop->default_team_name.empty())
Expand Down
12 changes: 1 addition & 11 deletions code/menuui/techmenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,10 +537,6 @@ void techroom_ships_render(float frametime)
closeup_pos = sip->closeup_pos;
closeup_zoom = sip->closeup_zoom;

if (!sip->replacement_textures.empty()) {
render_info.set_replacement_textures(Techroom_modelnum, sip->replacement_textures);
}

if (sip->flags[Ship::Info_Flags::No_lighting])
noLighting = true;

Expand Down Expand Up @@ -600,13 +596,7 @@ void techroom_ships_render(float frametime)
render_info.set_detail_level_lock(0);

int model_instance = -1;
auto cache_result = model_get_cached_ui_render_instance(Techroom_modelnum, &model_instance);
// Only set up the instance when it was freshly created; the cached instance persists across
// frames, so re-running this every frame would re-apply initial animations on top of the
// already-animated pose and make animated submodels (e.g. turrets) flip/jitter.
if (Tab == SHIPS_DATA_TAB && cache_result == TriStateBool::TRUE_) {
model_set_up_techroom_instance(&Ship_info[Cur_entry_index], model_instance);
}
model_get_cached_ui_render_instance_for_class(render_info, Techroom_modelnum, (Tab == SHIPS_DATA_TAB) ? &Ship_info[Cur_entry_index] : nullptr, &model_instance);

if(shadow_maybe_start_frame(Shadow_disable_overrides.disable_techroom))
{
Expand Down
85 changes: 65 additions & 20 deletions code/mission/missionparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5561,6 +5561,41 @@ void post_process_mission_props()
}
}

// Loads the bitmaps for a parse object's replacement textures. The parse object holds a reference to each one for the
// lifetime of the mission, so that every ship created from it (e.g. each wave of a wing) can share them.
static void mission_load_replacement_textures(p_object &p_obj)
{
for (auto &tr : p_obj.replacement_textures)
{
if (!stricmp(tr.new_texture, "invisible"))
{
// invisible is a special case
tr.new_texture_id = REPLACE_WITH_INVISIBLE;
}
else
{
// try to load texture or anim as normal
tr.new_texture_id = bm_load_either(tr.new_texture);
}

// not found?
if (tr.new_texture_id == -1)
mprintf(("Could not load replacement texture %s for ship %s\n", tr.new_texture, p_obj.name));
}
}

// Releases the references taken by mission_load_replacement_textures()
static void mission_release_replacement_textures(p_object &p_obj)
{
for (auto &tr : p_obj.replacement_textures)
{
// the check skips REPLACE_WITH_INVISIBLE
if (tr.new_texture_id >= 0)
bm_release_ref(tr.new_texture_id);
tr.new_texture_id = -1;
}
}

// Goober5000
void post_process_ships_wings()
{
Expand Down Expand Up @@ -5628,28 +5663,14 @@ void post_process_ships_wings()
}

// also load any replacement textures (do this outside the parse loop because we may have ship class replacements too)
for (SCP_vector<texture_replace>::iterator tr = p_obj.replacement_textures.begin(); tr != p_obj.replacement_textures.end(); ++tr)
{
// load the texture
if (!stricmp(tr->new_texture, "invisible"))
{
// invisible is a special case
tr->new_texture_id = REPLACE_WITH_INVISIBLE;
}
else
{
// try to load texture or anim as normal
tr->new_texture_id = bm_load_either(tr->new_texture);
}

// not found?
if (tr->new_texture_id < 0)
mprintf(("Could not load replacement texture %s for ship %s\n", tr->new_texture, p_obj.name));
mission_load_replacement_textures(p_obj);

// account for FRED
if (Fred_running)
// account for FRED
if (Fred_running)
{
for (const auto &tr : p_obj.replacement_textures)
{
Fred_texture_replacements.push_back(*tr);
Fred_texture_replacements.push_back(tr);
Fred_texture_replacements.back().new_texture_id = -1;
}
}
Expand Down Expand Up @@ -7399,6 +7420,24 @@ void support_ship_info::reset()
}
}

// Releases the references to the replacement textures of every parse object (see mission_load_replacement_textures());
// ships created from those objects hold their own.
static void mission_release_replacement_textures()
{
for (auto &p_obj : Parse_objects)
mission_release_replacement_textures(p_obj);

mission_release_replacement_textures(Support_ship_pobj);
}

void mission_parse_level_close()
{
// The parse objects themselves are kept until the next mission is parsed, since the debriefing and red alert code
// still consult them, but nothing needs their replacement textures once the mission's ships are gone. (This is
// also done in mission_init(), for the paths that never reach a level close, such as loading a mission in FRED.)
mission_release_replacement_textures();
}

/**
* Initialize the mission and related data structures.
*/
Expand Down Expand Up @@ -7459,6 +7498,7 @@ void mission_init(mission *pm, bool quick_init)
}
Total_initially_docked = 0;

mission_release_replacement_textures();
Parse_objects.clear();
list_init(&Ship_arrival_list); // init list for arrival ships

Expand Down Expand Up @@ -7577,6 +7617,7 @@ void mission_parse_close()
}

// the destructor for each p_object will clear its dock list
mission_release_replacement_textures();
Parse_objects.clear();
}

Expand Down Expand Up @@ -9248,6 +9289,9 @@ void mission_bring_in_support_ship( object *requester_objp )
// object since I'm no longer working with a mission file. These exceptions will be noted with
// comments

// the previous support ship's parse object may still hold replacement texture references
mission_release_replacement_textures(Support_ship_pobj);

Support_ship_pobj = p_object(); // get a fresh p_object with default fields
Arriving_support_ship = &Support_ship_pobj;
pobj = Arriving_support_ship;
Expand All @@ -9263,6 +9307,7 @@ void mission_bring_in_support_ship( object *requester_objp )
Assert(pobj->ship_max_hull_strength > 0.0f); // Goober5000: div-0 check (not shield because we might not have one)
pobj->max_shield_recharge = sip->max_shield_recharge;
pobj->replacement_textures = sip->replacement_textures; // initialize our set with the ship class set, which may be empty
mission_load_replacement_textures(*pobj);
pobj->score = sip->score;

// get average position of all ships
Expand Down
Loading
Loading