/**********************************************************************
This file is part of Crack dot Com's free source code release of
Golgotha.
for
information about compiling & licensing issues visit this URL
If that doesn't help, contact Jonathan Clark at
golgotha_source@usa.net (Subject should have "GOLG" in it)
***********************************************************************/
#include "tmanage.hh"
#include "memory/malloc.hh"
#include "error/error.hh"
#include "checksum/checksum.hh"
#include "image/color.hh"
#include "string/str_checksum.hh"
#include "error/alert.hh"
#include "time/profile.hh"
#include "string/string.hh"
#include "status/status.hh"
#include "file/ram_file.hh"
#include "tex_cache.hh"
#include "file/file.hh"
#include "search.hh"
#include "r1_res.hh"
#include "image/image.hh"
#include "image/context.hh"
#include "time/profile.hh"
i4_profile_class pf_register_texture("register_texture");
w32 R1_CHROMA_COLOR = (254<<16) | (2<<8) | (166);
i4_profile_class pf_get_texture("tmanage get texture");
r1_texture_manager_class *r1_texture_manager=0;
r1_texture_manager_class::r1_texture_manager_class(const i4_pal *pal)
: pal(pal),
registered_tnames(0,128)
{
textures_loaded=i4_F;
square_textures = i4_F;
}
void r1_texture_manager_class::init()
{
texture_load_toggle = i4_T;
texture_resolution_changed = i4_F;
textures_loaded=i4_F;
frame_count = 0;
entries = 0;
tanims = 0;
total_tanims = 0;
total_textures = 0;
registered_tnames.clear();
}
char *r1_texture_manager_class::get_texture_name(r1_texture_handle handle)
{
if (handlevram_handle)
{
free_mip(m->vram_handle);
m->vram_handle = 0;
}
delete m;
m = 0;
}
}
// delete (*entries)[i];
}
if (entries)
{
delete entries;
entries = 0;
}
registered_tnames.uninit();
texture_load_toggle = i4_T;
texture_resolution_changed=i4_F;
frame_count = 0;
tanims = 0;
total_tanims = 0;
total_textures = 0;
textures_loaded = i4_F;
}
void r1_texture_manager_class::reset()
{
uninit();
init();
}
int entry_compare(const r1_texture_entry_struct *a, const r1_texture_entry_struct *b)
{
if (a->idid)
return -1;
else if (a->id>b->id)
return 1;
else return 0;
}
r1_texture_handle r1_texture_manager_class::find_texture(w32 id)
{
sw32 lo=1,hi=total_textures-1,mid;
if (!entries)
return 0;
r1_texture_entry_struct search_entry;
search_entry.id = id;
sw32 location = entries->binary_search(&search_entry,entry_compare);
if (location==-1)
return 0;
return location;
}
void r1_texture_manager_class::matchup_textures()
{
sw32 i;
for (i=0; iname, b->name);
}
r1_texture_handle r1_texture_manager_class::register_texture(const i4_const_str &tname,
const i4_const_str &error_string,
i4_bool *has_been_loaded)
{
pf_register_texture.start();
if (textures_loaded)
i4_error("textures already loaded");
int found = -1;
r1_texture_matchup_struct t;
i4_os_string(tname, t.name, 128);
t.id = r1_get_texture_id(tname);
t.handle = 0;
t.left = -1;
t.right = -1;
if (has_been_loaded)
*has_been_loaded=0;
// search the tree for the name
if (registered_tnames.size())
{
int root=0, parent=-1;
while (found<0)
{
parent=root;
int res=strcmp(registered_tnames[root].name, t.name);
if (res==0)
{
if (has_been_loaded)
*has_been_loaded=1;
found = root;
}
else if (res<0)
root=registered_tnames[root].left;
else
root=registered_tnames[root].right;
if (root==-1)
{
if (res<0)
found = registered_tnames[parent].left=registered_tnames.add(t);
else
found = registered_tnames[parent].right=registered_tnames.add(t);
}
}
}
else
found=registered_tnames.add(t);
pf_register_texture.stop();
return found;
}
int w32_compare(const w32 *a, const w32 *b)
{
if (*a < *b)
return -1;
else
if (*a > *b)
return 1;
else
return 0;
}
w32 r1_get_file_id(const i4_const_str &fname)
{
int x;
char st[30], *s;
s=st;
i4_const_str::iterator l=fname.begin();
while (l!=fname.end() && l.get().ascii_value()!='.')
{
*(s++)=l.get().ascii_value();
++l;
}
*s=0;
if (sscanf(st,"%x",&x))
return x;
else return 0;
}
void r1_texture_manager_class::toggle_texture_loading()
{
texture_load_toggle = (i4_bool)(!texture_load_toggle);
}
r1_miplevel_t *r1_texture_manager_class::get_texture(r1_texture_handle _handle,
w32 frame_counter,
sw32 desired_width,
sw32 &ret_w, sw32 &ret_h)
{
pf_get_texture.start();
if (desired_width > max_texture_dimention)
desired_width = max_texture_dimention;
r1_texture_entry_struct *e;
r1_texture_handle handle = registered_tnames[_handle].handle;
if (handle==0)
i4_error("get_texture called with invalid handle");
if (handle>=total_textures)
i4_error("asking for bad texture");
if (handle<0) // this is an animation
{
r1_texture_animation_entry_struct *ta=tanims+(-handle-1);
handle=ta->frames[frame_counter%ta->total_frames];
if (!handle)
{
pf_get_texture.stop();
return 0;
}
}
e = &(*entries)[handle];
sw32 i = 0;
sw32 highest_resident = -1;
sw32 need_to_use = 0;
r1_miplevel_t *t = 0;
//find the highest resident mip. can be precalculated and updated ?
for (i=0; imipmaps[i];
if (t)
{
if (t->width >= desired_width)
need_to_use = i;
if (t->vram_handle)
{
if (t->width < desired_width)
{
//if there were no more higher than this,
//this is the best we can do
if (highest_resident==-1)
highest_resident = i;
break;
}
else
highest_resident = i;
}
}
else
{
break;
}
}
if (highest_resident==-1)
{
i4_warning("No textures resident for handle %d", handle);
return 0;
}
r1_miplevel_t *return_mip = e->mipmaps[highest_resident];
if (!return_mip)
i4_error("catastophic error - r1_texture_manager_class::return_mip is 0");
if (!return_mip->vram_handle)
i4_warning("returned a mip w/no vram handle");
//this is to prevent the load below from deleting this miplevel from vram
sw32 old_last_frame = return_mip->last_frame_used;
return_mip->last_frame_used = frame_count;
//loading information
r1_mip_load_info load_info;
//i4_warning("texture loading is OFF");
if (texture_load_toggle && (highest_resident != need_to_use) && e->mipmaps[need_to_use])
{
load_info.src_file = 0;
load_info.dest_mip = e->mipmaps[need_to_use];
//should be asynchronous
//prevents from trying to load this same mip level twice at the same time
if (!(load_info.dest_mip->flags & R1_MIPLEVEL_IS_LOADING))
{
//if there is no room for this mip, load up as high a level as possible
while (!async_mip_load(&load_info))
{
if (load_info.error==R1_MIP_LOAD_NO_ROOM)
{
//try to load a lower res
need_to_use++;
if (need_to_use==highest_resident) break;
if (!e->mipmaps[need_to_use]) break;
if (e->mipmaps[need_to_use]->flags & R1_MIPLEVEL_IS_LOADING) break;
load_info.dest_mip = e->mipmaps[need_to_use];
}
else
break;
}
}
//else
//{
// i4_warning("texture still loading, wont queue");
//}
}
pf_get_texture.stop();
//store the old information back.
return_mip->last_frame_used = old_last_frame;
return return_mip;
}
w32 r1_texture_manager_class::average_texture_color(r1_texture_handle _handle, w32 frame_num)
{
r1_texture_handle handle = registered_tnames[_handle].handle;
r1_texture_entry_struct *e;
if (handle>=total_textures)
i4_error("asking for bad texture");
if (handle<0) // this is an animation
{
r1_texture_animation_entry_struct *ta=tanims+(-handle-1);
handle=ta->frames[frame_num%ta->total_frames];
if (!handle)
{
pf_get_texture.stop();
return 0;
}
}
e = &(*entries)[handle];
return e->average_color;
}
void r1_texture_manager_class::next_frame()
{
frame_count++;
//eventually (after a long fucking time) this could
//wrap around. just make sure its always > 0
if (frame_count<0) frame_count=0;
}
int tex_entry_compare(const tex_cache_entry_t *a, const tex_cache_entry_t *b)
{
if (a->id > b->id)
return 1;
else
if (a->id < b->id)
return -1;
else
return 0;
}
tex_cache_entry_t *find_id_in_tex_cache(tex_cache_entry_t *entries, w32 num_entries, w32 search_id)
{
if (!entries || num_entries==0)
return 0;
w32 res;
tex_cache_entry_t search;
search.id = search_id;
if (!i4_base_bsearch(&search,res,entries,
sizeof(tex_cache_entry_t),
num_entries,
(i4_bsearch_compare_function_type)tex_entry_compare))
return 0;
return entries+res;
}
i4_bool palettes_are_same(i4_pixel_format *a, i4_pixel_format *b)
{
sw32 i;
w8 *compare_a = (w8 *)a;
w8 *compare_b = (w8 *)b;
for (i=0;imipmaps[j];
if (mip && (mip->width <= desired_width))
{
mip->last_frame_used = -1;
if ((mip->vram_handle==0) && (!(mip->flags & R1_MIPLEVEL_IS_LOADING)))
{
load_info.dest_mip = mip;
if (!immediate_mip_load(&load_info))
{
//these absolutely must work
i4_error("keep_resident::could not load texture, cannot continue");
}
}
}
}
}
i4_bool r1_texture_manager_class::load_textures()
{
sw32 i,j;
if (textures_loaded)
return i4_F;
r1_texture_ref *p=r1_texture_ref::first;
for (; p; p=p->next)
p->texture_handle=register_texture(p->name, "code reference");
//create an array of texture id's from names[] and sort it
i4_array texture_file_ids(128,128);
for (i=0; i new_texture_entries(128,128);
i4_status_class *stat = i4_create_status(r1_gets("loading_textures"));
for (i=0; ilowmipoffset==0xFFFFFFFF))
{
for (int k=0; kmipmaps,0,sizeof(r1_miplevel_t *) * (R1_MAX_MIP_LEVELS+1));
new_entry->flags = t->flags;
new_entry->id = t->id;
new_entry->average_color = t->average_color;
generate_mip_offsets(t->base_width,t->base_height,t->num_mip_levels,(sw32 *)new_entry->file_offsets,2);
//fill in this structure. information on mip levels
for (j=0; jnum_mip_levels; j++)
{
new_entry->mipmaps[j] = new r1_miplevel_t;
r1_miplevel_t *mip = new_entry->mipmaps[j];
mip->level = j;
mip->width = t->base_width /(1<height = t->base_height/(1<entry = new_entry;
mip->flags = 0;
}
//seek to the low mip offset (stored IN the cache file)
cache_file->seek(t->lowmipoffset+8);
r1_mip_load_info load_info;
//the dst_mip is the very last one
load_info.src_file = cache_file;
load_info.dest_mip = new_entry->mipmaps[t->num_mip_levels-1];
//dont want these to ever be thrown out of texture memory
load_info.dest_mip->last_frame_used = -1;
//load that low mip level
if (!immediate_mip_load(&load_info))
{
//check the error field in load_info
i4_error("Could not load lowest miplevel of a texture, cannot continue");
}
}
if (stat)
stat->update((float)(i+1) / (float)texture_file_ids.size());
}
if (stat)
delete stat;
if (cache_file)
delete cache_file;
if (tex_cache_header.entries)
i4_free(tex_cache_header.entries);
else
{
//there were no texture cache entries? just get rid of the file.
i4_unlink(r1_get_cache_file());
}
//theres 1 additional texture, which will be entries[0], the default texture
total_textures = new_texture_entries.size() + 1;
entries = new i4_array(total_textures,8);
r1_texture_entry_struct blank_entry;
memset(&blank_entry,0,sizeof(r1_texture_entry_struct));
blank_entry.average_color = 0x00FFFFFF;
entries->add(blank_entry);
//should be sorted already but go ahead, sort again just in case
if (new_texture_entries.size())
new_texture_entries.sort(entry_compare);
for (i=0; iadd();
memset(&(*entries)[i+1],0,sizeof(r1_texture_entry_struct));
(*entries)[i+1] = new_texture_entries[i];
//crap. have to update the entry pointers since the mip's ->entry
//references are in new_texture_entries (instead of entries[])
for (j=0; jentry = &(*entries)[i+1];
}
}
}
matchup_textures();
/*
i4_array anim_a(128,128);
for (i=0; iid=fp->read_32();
a->total_frames=fp->read_16();
a->frames=(r1_texture_handle *)i4_malloc(sizeof(r1_texture_handle)*a->total_frames,
"animation frames");
for (j=0; jtotal_frames; j++)
{
w32 id=fp->read_32();
a->frames[j]=find_texture(id);
}
}
delete fn;
}
total_tanims=anim_a.size();
if (total_tanims)
{
tanims=(r1_texture_animation_entry_struct *)i4_malloc(sizeof(r1_texture_animation_entry_struct)
* total_tanims, "animations");
for (i=0; isize())
load_textures();
//check to make sure image is power of 2
sw32 i,j,new_width,new_height;
for (i=1; i < image->width(); i = i<<1);
new_width = i;
for (i=1; i < image->height(); i = i<<1);
new_height = i;
r1_texture_handle return_handle = 0;
i4_draw_context_class context(0,0,image->width()-1,image->height()-1);
r1_texture_entry_struct new_entry;
memset(&new_entry,0,sizeof(r1_texture_entry_struct));
const i4_pal *put_pal=pal;
if (image->pal->source.alpha_bits)
{
put_pal=i4_pal_man.register_pal(&alpha_format);
new_entry.flags|=R1_MIP_IS_ALPHATEXTURE;
}
i4_image_class *temp_image = i4_create_image(image->width(),image->height(), put_pal);
i4_image_class *texture_image = temp_image;
image->put_image(temp_image,0,0,context);
if (new_width != temp_image->width() || new_height != temp_image->height())
{
texture_image = i4_create_image(new_width,new_height, pal);
sw32 old_width = temp_image->width();
sw32 old_height = temp_image->height();
w16 *old_tex = (w16 *)temp_image->data;
w16 *new_tex = (w16 *)texture_image->data;
w16 *dst = new_tex;
float width_ratio = (float)old_width / (float)new_width;
float height_ratio = (float)old_height / (float)new_height;
//now scale the old to fit the new
for (j=0; jdata,
texture_image->width() *
texture_image->height() * 2);
new_entry.id = (*entries)[entries->size()-1].id + 1; //maintains the sorted order of the array
new_entry.average_color = 0xFFFFFFFF;
new_entry.mipmaps[0] = new r1_miplevel_t;
r1_miplevel_t *mip = new_entry.mipmaps[0];
mip->level = 0;
mip->width = texture_image->width();
mip->height = texture_image->height();
mip->last_frame_used = -1;
mip->vram_handle = 0;
mip->flags = R1_MIP_IS_ALPHATEXTURE;
entries->add(new_entry);
total_textures++;
//update entry pointers for all miplevels
for (i=0; ientry = &(*entries)[i+1];
}
}
}
r1_mip_load_info load_info;
load_info.dest_mip = mip;
load_info.src_file = fake_file;
if (!immediate_mip_load(&load_info))
{
i4_warning("tmanager:: register_image failed.");
}
else
{
return_handle = total_textures-1;
}
delete fake_file;
delete texture_image;
char name[256];
sprintf(name, "memory_image_%d", return_handle);
r1_texture_handle han=register_texture(name, name);
registered_tnames[han].handle=return_handle;
return han;
}