/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
*/
#include "texturefile.h"
#include "textureloader.h"
#include "ww3d.h"
#include "wwstring.h"
#include "rawfile.h"
#include "ffactory.h"
#include "nstrdup.h"
#include "texfcach.h"
#include "assetmgr.h"
#ifdef WW3D_DX8
#include
#include
#include "texture.h"
static TextureFileClass* head;
static bool mipmaps=true;
static int texture_count;
static int locked_surface_count;
static int texture_id;
/*
** Definitions of static members:
*/
unsigned int TextureFileClass::_CurrentTimeStamp = 0U;
float TextureFileClass::_SwitchThreshold = 0.75f; // Default value
// ----------------------------------------------------------------------------
static int Calculate_Size(srColorSurfaceIFace* surface,int red_factor=0)
{
// Set performance statistics
int p=surface->getPitch();
int h=surface->getHeight();
int size=0;
p<<=red_factor;
h<<=red_factor;
while (h) {
size+=p*h;
p>>=1;
h>>=1;
}
return size;
}
// ----------------------------------------------------------------------------
void TextureFileClass::Switch_Mipmaping_Debug()
{
mipmaps=!mipmaps;
TextureFileClass* n=head;
while (n) {
if (mipmaps) n->params.setMipmap(MIPMAP_DEFAULT);
else n->params.setMipmap(MIPMAP_NONE);
n=n->Succ;
}
}
// ----------------------------------------------------------------------------
int TextureFileClass::Get_Total_Locked_Surface_Size()
{
int total_locked_surface_size=0;
TextureFileClass* n=head;
while (n) {
total_locked_surface_size+=n->Get_Locked_Surface_Size();
n=n->Succ;
}
return total_locked_surface_size;
}
// ----------------------------------------------------------------------------
int TextureFileClass::Get_Total_Texture_Size()
{
int total_texture_size=0;
TextureFileClass* n=head;
while (n) {
total_texture_size+=n->Get_Texture_Size();
n=n->Succ;
}
return total_texture_size;
}
// ----------------------------------------------------------------------------
int TextureFileClass::Get_Total_Non_Reduced_Texture_Size()
{
int total_texture_size=0;
TextureFileClass* n=head;
while (n) {
total_texture_size+=n->Get_Non_Reduced_Texture_Size();
n=n->Succ;
}
return total_texture_size;
}
// ----------------------------------------------------------------------------
int TextureFileClass::Get_Total_Texture_Count()
{
return texture_count;
}
// ----------------------------------------------------------------------------
int TextureFileClass::Get_Total_Locked_Surface_Count()
{
return locked_surface_count;
}
StringClass TextureFileClass::List_Missing_Files() // Print out a list of missing files
{
StringClass s(0,true);
TextureFileClass* n=head;
s=
"Missing textures\n\n"
"id name\n"
"------------------------\n";
int missing_count=0;
while (n) {
if (n->Get_File_Error()) {
StringClass tmp;
tmp.Format("%4.4d %s\n",n->ID(),n->Get_File_Name());
s+=tmp;
}
n=n->Succ;
}
if (!missing_count) s="No missing textures\n\n";
else {
StringClass tmp;
tmp.Format("\nTotal missing textures: %d",missing_count);
s+=tmp;
}
return s;
}
// ----------------------------------------------------------------------------
TextureFileClass::TextureFileClass(const char * filename) :
LockedSurface(0),
LockedSurfaceReductionFactor(0),
LockedSurfaceSize(0),
TextureSize(0),
NonReducedTextureSize(0),
CurrentReductionFactor(0),
DesiredReductionFactor(0.0f),
TimeStampOfLastRequestReductionCall(_CurrentTimeStamp - 1),
TimeStampOfLastProcessReductionCall(_CurrentTimeStamp - 1),
FileName(0),
TextureFrameHandle(getNewFrameHandle()),
TempSurfacePtr(0),
id(texture_id++),
flash(false),
flash_store_surface(NULL),
file_error(false)
{
if(filename && *filename) {
FileName = nstrdup(filename);
} else {
FileName = 0;
}
if(FileName) {
setName(FileName);
if (!TextureLoader::Texture_File_Exists(FileName)) {
file_error=true;
}
}
// Load locked surface (or not) according to current locked surface reduction factor set in
// texture loader
Load_Locked_Surface();
texture_count++;
Succ=head;
head=this;
flags.clear(GENERATESURFACE_FAILURE);
flags.set(DIRTY_DEFAULTS);
if (WW3D::Get_Texture_Thumbnail_Mode()!=WW3D::TEXTURE_THUMBNAIL_MODE_RESIZING) {
ReductionEnabled=false;
}
}
TextureFileClass::~TextureFileClass()
{
texture_count--;
if (flash_store_surface) flash_store_surface->release();
if (LockedSurface) locked_surface_count--;
TextureFileClass* n=head;
if (n==this) {
head=Succ;
}
else {
while (n) {
if (n->Succ==this) {
n->Succ=Succ;
break;
}
n=n->Succ;
}
}
invalidate();
if (FileName) delete [] FileName;
if (LockedSurface) LockedSurface->release();
}
// Copy CTor and assignment operator assert for now - if anyone hits the
// assert we might need to actually implement them 8^).
TextureFileClass::TextureFileClass(const TextureFileClass & src)
{
WWASSERT(0);
}
TextureFileClass & TextureFileClass::operator = (const TextureFileClass &that)
{
if (this != &that) {
srTexture::operator = (that);
WWASSERT(0);
}
return *this;
}
srTextureIFace::FrameHandle TextureFileClass::getTextureFrameHandle(void)
{
if (flags.test(GENERATESURFACE_FAILURE))
return 0;
return TextureFrameHandle;
}
void TextureFileClass::getMipmapData(MultiRequest& m)
{
// First some assertions...
WWASSERT(m.largeLOD <= m.smallLOD && m.smallLOD < MAX_LOD);
WWASSERT_PRINT(m.levels[m.largeLOD],"TextureFileClass::getMipmapData() -- NULL surface passed in!\n");
if (!m.levels[m.largeLOD]) return;
/*
** If the temporary surface pointer points to a surface (this will be a new surface which just
** got loaded in this frame) use it and release it - done.
** Otherwise, if we have a locked surface, then normally the current reduction factor should
** be equal or greater to the locked surface reduction factor so we can use the locked surface
** (possibly scaled down). If the current reduction factor is less than that of the locked
** surface, this means that in the past we have loaded up a surface larger than the locked
** surface, and it has just been thrown out of the API/GERD/HW texture cache. In this case we
** have no choice but to scale the locked surface up to fill the surface handed to us, and we
** will request a background load task for the real surface.
** If there is no locked surface, this means that the texture is never reduced or loaded in
** the background - we perform a normal blocking load of the surface, use it, and release it
** (if you want behavior similar to "cached mode" in srTextureFile you should set the locked
** surface reduction factor to 0, instead of having no locked surface).
*/
if (TempSurfacePtr) {
Fill_Multi_Request_From_Surface(m, TempSurfacePtr);
Release_Temp_Surface();
} else {
TextureSize=0;
NonReducedTextureSize=0;
if (LockedSurface) {
Fill_Multi_Request_From_Surface(m, LockedSurface);
if (CurrentReductionFactor < LockedSurfaceReductionFactor) {
WWDEBUG_SAY(("Scaling locked surface up!\n"));
CurrentReductionFactor = LockedSurfaceReductionFactor;
float des_red_factor = floor(DesiredReductionFactor + 0.5f);
WWASSERT(des_red_factor >= 0.0f);
unsigned int int_desired_reduction_factor = (int)des_red_factor;
// If int_desired_reduction_factor is smaller than the locked surface reduction
// factor (meaning we want a larger surface), request to load it.
if (int_desired_reduction_factor < LockedSurfaceReductionFactor) {
texture_loader_info.reduction_factor = int_desired_reduction_factor;
TextureLoader::Add_Load_Task(this);
}
}
else {
// This else clause should only be entered in cases where Process_Reduction() is not
// called every frame on the texture for some reason. Such textures will not resize,
// but this at least ensures that they will get loaded.
Process_Reduction();
}
} else {
Load_Temp_Surface();
if (TempSurfacePtr) {
TextureSize=Calculate_Size(TempSurfacePtr);
NonReducedTextureSize=TextureSize;
Fill_Multi_Request_From_Surface(m, TempSurfacePtr);
Release_Temp_Surface();
}
}
}
}
void TextureFileClass::invalidate(void)
{
Release_Temp_Surface();
invalidateFrameHandle(TextureFrameHandle);
flags.clear(GENERATESURFACE_FAILURE);
}
void TextureFileClass::setupDefaultValues(void)
{
if (flags.test(DIRTY_DEFAULTS))
{
flags.clear (DIRTY_DEFAULTS);
/*
** If the temporary surface pointer points to a surface (this will be a new surface which
** just got loaded in this frame by Apply_New_Surface) get defaults from it - done.
** Otherwise, if we have a locked surface, then normally the current reduction factor should
** be equal or greater to the locked surface reduction factor so we can get defaults from
** the locked surface (possibly scaled down). If the current reduction factor is less than
** that of the locked surface, this means that in the past we have loaded up a surface
** larger than the locked surface, and it has just been thrown out of the API/GERD/HW
** texture cache. In this case we set the current reduction factor to be equal to that of
** the locked surface, and get defaults from the locked surface.
** If there is no locked surface, this means that the texture is never reduced or loaded in
** the background - we perform a normal blocking load of the surface, store it in the temp
** surface pointer (so getMipmapData can also use it), and get defaults from it.
*/
if (TempSurfacePtr) {
setupDefaultValuesFromSurface(TempSurfacePtr);
TextureSize=Calculate_Size(TempSurfacePtr);
NonReducedTextureSize=Calculate_Size(TempSurfacePtr,CurrentReductionFactor);
} else {
if (LockedSurface) {
setupDefaultValuesFromSurface(LockedSurface);
if (CurrentReductionFactor >= LockedSurfaceReductionFactor) {
// Scale size down by difference between current and locked reduction factors
unsigned int diff = CurrentReductionFactor - LockedSurfaceReductionFactor;
defaultDimensions.width = defaultDimensions.width >> diff;
if (defaultDimensions.width == 0) defaultDimensions.width = 1;
defaultDimensions.height = defaultDimensions.height >> diff;
if (defaultDimensions.height == 0) defaultDimensions.height = 1;
} else {
CurrentReductionFactor = LockedSurfaceReductionFactor;
}
TextureSize=0;
NonReducedTextureSize=0;
} else {
Load_Temp_Surface();
if (TempSurfacePtr) {
setupDefaultValuesFromSurface(TempSurfacePtr);
TextureSize=Calculate_Size(TempSurfacePtr);
NonReducedTextureSize=TextureSize;
}
}
}
}
}
void TextureFileClass::Apply_New_Surface()
{
invalidate();
TempSurfacePtr = texture_loader_info.new_surface;
texture_loader_info.new_surface = 0;
flags.set(DIRTY_DEFAULTS);
CurrentReductionFactor = texture_loader_info.reduction_factor;
}
// This is used by an object to request a reduction factor on all its textures. Only the smallest
// reduction factor requested in a given frame is preserved.
void TextureFileClass::Request_Reduction(float reduction_factor)
{
if (!ReductionEnabled) return;
if (reduction_factor < 0.0f) reduction_factor = 0.0f;
// If this is the first request in this time-stamp, overwrite desired reduction factor -
// otherwise current factor is the lesser of current and requested.
if (TimeStampOfLastRequestReductionCall != _CurrentTimeStamp) {
DesiredReductionFactor = reduction_factor;
// Update time stamp
TimeStampOfLastRequestReductionCall = _CurrentTimeStamp;
} else {
DesiredReductionFactor = MIN(reduction_factor, DesiredReductionFactor);
}
}
// This is used during rendering - if a textures desired reduction level is "different enough"
// from its current one, we fix it: either by updating from the locked surface (if the reduction
// factor is equal or greater to that of the locked surface) or by asking the texture loader to
// load a new reduction level of the texture in the background.
// NOTE - if there is no locked surface, we do nothing (since textures without locked surfaces do
// not perform reduction).
void TextureFileClass::Process_Reduction(void)
{
// Check if this is the first Process_Reduction() call this frame - otherwise do nothing
if (TimeStampOfLastProcessReductionCall != _CurrentTimeStamp) {
// If we are doing reductions and the difference between the current and desired reduction
// factors is above the threshold, fix it.
if (LockedSurface && fabs(float(CurrentReductionFactor) - DesiredReductionFactor) > _SwitchThreshold) {
// Find the integer desired reduction factor
float des_red_factor = floor(DesiredReductionFactor + 0.5f);
WWASSERT(des_red_factor >= 0.0f);
unsigned int int_desired_reduction_factor = (int)des_red_factor;
if (int_desired_reduction_factor >= LockedSurfaceReductionFactor) {
// Can update from the locked surface:
CurrentReductionFactor = int_desired_reduction_factor;
invalidate();
} else {
// Need to load bigger surface
texture_loader_info.reduction_factor = int_desired_reduction_factor;
TextureLoader::Add_Load_Task(this);
}
}
// Update time stamp
TimeStampOfLastProcessReductionCall = _CurrentTimeStamp;
}
}
void TextureFileClass::_Set_Switch_Threshold(float switch_threshold)
{
_SwitchThreshold = MIN(switch_threshold, 0.999);
_SwitchThreshold = MAX(_SwitchThreshold, 0.5);
}
float TextureFileClass::_Get_Switch_Threshold(void)
{
return _SwitchThreshold;
}
void TextureFileClass::Load_Temp_Surface(void)
{
if (TempSurfacePtr) invalidate();
if (!FileName) return;
TempSurfacePtr = 0;
//
// Use the texture file caching mechanism to load the surface. If no cache
// is initialized, then simply load the surface from the file.
//
TextureFileCache* cache=WW3DAssetManager::Get_Instance()->Texture_File_Cache();
if (cache != NULL) {
cache->Validate_Texture(FileName);
TempSurfacePtr = cache->Get_Surface(
Get_File_Name(),
0); // No reduction!
} else {
TempSurfacePtr = ::Load_Surface(FileName);
}
}
void TextureFileClass::Release_Temp_Surface(void)
{
if (TempSurfacePtr)
{
TempSurfacePtr->release();
TempSurfacePtr = 0;
}
}
void TextureFileClass::Fill_Multi_Request_From_Surface(MultiRequest& m, srColorSurfaceIFace* surface)
{
m.levels[m.largeLOD]->copy (*surface);
for (LOD i = m.largeLOD+1; i <= m.smallLOD; i++)
{
WWASSERT(m.levels[i] && m.levels[i-1]);
if (m.levels[i] && m.levels[i-1]) // just debug check
m.levels[i]->copy (*(m.levels[i-1]));
}
}
// Debug features
// Make texture flash (Warning! Slow, for debug only!!!)
void TextureFileClass::Set_Texture_Flash(bool b)
{
flash=b;
if (flash) {
Load_Temp_Surface();
flash_store_surface=TempSurfacePtr;
flash_store_surface->addReference();
}
else {
// Restore the original surface... the surface will be released after applying to use
invalidateFrameHandle(TextureFrameHandle);
Release_Temp_Surface();
TempSurfacePtr=flash_store_surface;
flash_store_surface=NULL;
}
}
// Return texture flash state
bool TextureFileClass::Get_Texture_Flash() const
{
return flash;
}
TextureFileClass* TextureFileClass::Get_Texture(int id)
{
TextureFileClass* n=head;
while (n) {
if (n->ID()==id) return n;
n=n->Succ;
}
return NULL;
}
static unsigned latest_time;
static bool flash_state;
static unsigned flash_counter;
const int flash_time=500;
void TextureFileClass::Update_Texture_Flash()
{
int cur_time=WW3D::Get_Sync_Time();
int delta=cur_time-latest_time;
latest_time=cur_time;
flash_counter+=delta;
if (flash_counter>flash_time) {
flash_counter-=flash_time;
if (flash_counter>flash_time) flash_counter=0;
flash_state=!flash_state;
}
else return;
srColorSurface* s=NULL;
if (!flash_state) {
int w=srCore.getSurface()->getWidth();
srColorSurfaceIFace::PixelFormat pf;
srCore.getSurface()->getPixelFormat(pf);
s=W3DNEW srColorSurface(pf,w,w);
s->copy(*srCore.getSurface());
}
TextureFileClass* n=head;
while (n) {
if (n->flash) {
n->invalidateFrameHandle(n->TextureFrameHandle);
n->Release_Temp_Surface();
if (s) {
n->TempSurfacePtr=s;
s->addReference(); // Surface will be release after the use
}
else {
n->TempSurfacePtr=n->flash_store_surface;
n->TempSurfacePtr->addReference(); // Surface will be release after the use
}
}
n=n->Succ;
}
s->release();
}
// If false is passed, the texture will load the largest LOD size and stick to that
void TextureFileClass::Enable_Reduction(bool b)
{
if (ReductionEnabled==b) return;
ReductionEnabled=b;
if (b) {
Load_Locked_Surface();
}
else {
if (LockedSurface) LockedSurface->release();
LockedSurface=NULL;
LockedSurfaceReductionFactor=0;
CurrentReductionFactor=0;
DesiredReductionFactor=0;
Load_Temp_Surface();
if (TempSurfacePtr) {
setupDefaultValuesFromSurface(TempSurfacePtr);
TextureSize=Calculate_Size(TempSurfacePtr);
NonReducedTextureSize=TextureSize;
}
}
}
void TextureFileClass::Load_Locked_Surface()
{
if (LockedSurface) LockedSurface->release();
LockedSurface=NULL;
LockedSurfaceReductionFactor=0;
if (FileName) {
TextureLoader::Load_Locked_Surface_Immediate(FileName, LockedSurface, LockedSurfaceReductionFactor);
if (LockedSurface) {
// If we have a locked surface, set the current reduction factor to be equal to the
// locked surface reduction factor (this is the highest resolution that we can render
// with at this time)
CurrentReductionFactor = LockedSurfaceReductionFactor;
LockedSurfaceSize=Calculate_Size(LockedSurface);
locked_surface_count++;
}
}
}
#endif //WW3D_DX8