/*
** 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 "textureloader.h"
#include "mutex.h"
#include "thread.h"
#include "wwdebug.h"
#include "texture.h"
#include "ffactory.h"
#include "wwstring.h"
#include "bufffile.h"
#include "ww3d.h"
#include "texfcach.h"
#include "assetmgr.h"
#include "dx8wrapper.h"
#include "dx8caps.h"
#include "missingtexture.h"
#include "targa.h"
#include
#include
#include "wwmemlog.h"
#include "texture.h"
#include "formconv.h"
#include "texturethumbnail.h"
#include "ddsfile.h"
#include "bitmaphandler.h"
static TextureLoadTaskClass* LoadListHead;
static TextureLoadTaskClass* DeferredListHead;
static TextureLoadTaskClass* FinishedListHead;
static TextureLoadTaskClass* ThumbnailListHead;
static TextureLoadTaskClass* DeleteTaskListHead;
TextureLoadTaskClass* TextureLoadTaskClass::FreeTaskListHead;
static bool Is_Format_Compressed(WW3DFormat texture_format,bool allow_compression)
{
// Verify that the user isn't requesting compressed texture without hardware support
bool compressed=false;
if (texture_format!=WW3D_FORMAT_UNKNOWN) {
if (!DX8Caps::Support_DXTC() || !allow_compression) {
WWASSERT(texture_format!=WW3D_FORMAT_DXT1);
WWASSERT(texture_format!=WW3D_FORMAT_DXT2);
WWASSERT(texture_format!=WW3D_FORMAT_DXT3);
WWASSERT(texture_format!=WW3D_FORMAT_DXT4);
WWASSERT(texture_format!=WW3D_FORMAT_DXT5);
}
if (texture_format==WW3D_FORMAT_DXT1 ||
texture_format==WW3D_FORMAT_DXT2 ||
texture_format==WW3D_FORMAT_DXT3 ||
texture_format==WW3D_FORMAT_DXT4 ||
texture_format==WW3D_FORMAT_DXT5) {
compressed=true;
}
}
// If hardware supports DXTC compression, load a compressed texture. Proceed only if the texture format hasn't been
// defined as non-compressed.
compressed|=(
texture_format==WW3D_FORMAT_UNKNOWN &&
DX8Caps::Support_DXTC() &&
WW3D::Get_Texture_Compression_Mode()==WW3D::TEXTURE_COMPRESSION_ENABLE &&
allow_compression);
return compressed;
}
// ----------------------------------------------------------------------------
static CriticalSectionClass mutex;
static class LoaderThreadClass : public ThreadClass
{
TextureLoadTaskClass* Get_Task_From_Load_List();
static void Add_Task_To_Finished_List(TextureLoadTaskClass* task);
public:
LoaderThreadClass::LoaderThreadClass() : ThreadClass() {}
void Thread_Function();
static void Add_Task_To_Load_List(TextureLoadTaskClass* task);
} thread;
// ----------------------------------------------------------------------------
void TextureLoader::Init()
{
WWASSERT(!thread.Is_Running());
ThumbnailClass::Init();
thread.Execute();
thread.Set_Priority(-3);
}
// ----------------------------------------------------------------------------
void TextureLoader::Deinit()
{
CriticalSectionClass::LockClass m(mutex);
thread.Stop();
ThumbnailClass::Deinit();
}
// ----------------------------------------------------------------------------
//
// Modify given texture size to nearest valid size on current hardware.
//
// ----------------------------------------------------------------------------
void TextureLoader::Validate_Texture_Size(unsigned& width, unsigned& height)
{
const D3DCAPS8& dx8caps=DX8Caps::Get_Default_Caps();
unsigned poweroftwowidth = 1;
while (poweroftwowidth < width) {
poweroftwowidth <<= 1;
}
unsigned poweroftwoheight = 1;
while (poweroftwoheight < height) {
poweroftwoheight <<= 1;
}
// unsigned size = MAX (width, height);
// unsigned poweroftwosize = 1;
// while (poweroftwosize < size) {
// poweroftwosize <<= 1;
// }
if (poweroftwowidth>dx8caps.MaxTextureWidth) {
poweroftwowidth=dx8caps.MaxTextureWidth;
}
if (poweroftwoheight>dx8caps.MaxTextureHeight) {
poweroftwoheight=dx8caps.MaxTextureHeight;
}
if (poweroftwowidth>poweroftwoheight) {
while (poweroftwowidth/poweroftwoheight>8) {
poweroftwoheight*=2;
}
}
else {
while (poweroftwoheight/poweroftwowidth>8) {
poweroftwowidth*=2;
}
}
// width = height = poweroftwosize;
width=poweroftwowidth;
height=poweroftwoheight;
}
IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename,WW3DFormat texture_format)
{
ThumbnailClass* thumb=ThumbnailClass::Peek_Instance(filename);
if (!thumb) {
thumb=W3DNEW ThumbnailClass(filename);
// If load failed, return missing texture
if (!thumb->Peek_Bitmap()) {
delete thumb;
return MissingTexture::_Get_Missing_Texture();
}
}
unsigned src_pitch=thumb->Get_Width()*4; // Thumbs are always 32 bits
WW3DFormat dest_format;
if (texture_format==WW3D_FORMAT_UNKNOWN) {
dest_format=Get_Valid_Texture_Format(WW3D_FORMAT_A8R8G8B8,false); // no compressed formats please
}
else {
dest_format=Get_Valid_Texture_Format(texture_format,false); // no compressed formats please
WWASSERT(dest_format==texture_format);
}
IDirect3DTexture8* d3d_texture = DX8Wrapper::_Create_DX8_Texture(
thumb->Get_Width(),
thumb->Get_Height(),
dest_format,
TextureClass::MIP_LEVELS_ALL);
unsigned level=0;
D3DLOCKED_RECT locked_rects[12];
WWASSERT(d3d_texture->GetLevelCount()<=12);
// Lock all surfaces
for (level=0;levelGetLevelCount();++level) {
DX8_ErrorCode(
d3d_texture->LockRect(
level,
&locked_rects[level],
NULL,
0));
}
unsigned char* src_surface=thumb->Peek_Bitmap();
WW3DFormat src_format=WW3D_FORMAT_A8R8G8B8;
unsigned width=thumb->Get_Width();
unsigned height=thumb->Get_Height();
for (level=0;levelGetLevelCount()-1;++level) {
BitmapHandlerClass::Copy_Image_Generate_Mipmap(
width,
height,
(unsigned char*)locked_rects[level].pBits,
locked_rects[level].Pitch,
dest_format,
src_surface,
src_pitch,
src_format,
(unsigned char*)locked_rects[level+1].pBits, // mipmap
locked_rects[level+1].Pitch);// mipmap
src_format=dest_format;
src_surface=(unsigned char*)locked_rects[level].pBits;
src_pitch=locked_rects[level].Pitch;
width>>=1;
height>>=1;
}
// Unlock all surfaces
for (level=0;levelGetLevelCount();++level) {
DX8_ErrorCode(d3d_texture->UnlockRect(level));
}
return d3d_texture;
}
static bool Is_Power_Of_Two(unsigned i)
{
if (!i) return false;
unsigned n=i;
unsigned shift=0;
while (n) {
shift++;
n>>=1;
}
return ((i>>(shift-1))<<(shift-1))==i;
}
// ----------------------------------------------------------------------------
// TODO: Legacy - remove this call!
IDirect3DTexture8* Load_Compressed_Texture(
const StringClass& filename,
unsigned reduction_factor,
TextureClass::MipCountType mip_level_count,
WW3DFormat dest_format)
{
// If DDS file isn't available, use TGA file to convert to DDS.
DDSFileClass dds_file(filename,reduction_factor);
if (!dds_file.Is_Available()) return NULL;
if (!dds_file.Load()) return NULL;
unsigned width=dds_file.Get_Width(0);
unsigned height=dds_file.Get_Height(0);
unsigned mips=dds_file.Get_Mip_Level_Count();
// If format isn't defined get the nearest valid texture format to the compressed file format
// Note that the nearest valid format could be anything, even uncompressed.
if (dest_format==WW3D_FORMAT_UNKNOWN) dest_format=Get_Valid_Texture_Format(dds_file.Get_Format(),true);
IDirect3DTexture8* d3d_texture = DX8Wrapper::_Create_DX8_Texture(
width,
height,
dest_format,
(TextureClass::MipCountType)mips);
for (unsigned level=0;levelGetSurfaceLevel(level/*-reduction_factor*/,&d3d_surface));
dds_file.Copy_Level_To_Surface(level,d3d_surface);
d3d_surface->Release();
}
return d3d_texture;
}
// ----------------------------------------------------------------------------
//
// Load image to a surface. The function tries to create texture that matches
// targa format. If suitable format is not available, it selects closest matching
// format and performs color space conversion.
//
// ----------------------------------------------------------------------------
IDirect3DSurface8* TextureLoader::Load_Surface_Immediate(
const StringClass& filename,
WW3DFormat texture_format,
bool allow_compression)
{
bool compressed=Is_Format_Compressed(texture_format,allow_compression);
if (compressed) {
IDirect3DTexture8* comp_tex=Load_Compressed_Texture(filename,0,TextureClass::MIP_LEVELS_1,WW3D_FORMAT_UNKNOWN);
if (comp_tex) {
IDirect3DSurface8* d3d_surface=NULL;
DX8_ErrorCode(comp_tex->GetSurfaceLevel(0,&d3d_surface));
comp_tex->Release();
return d3d_surface;
}
}
// Make sure the file can be opened. If not, return missing texture.
Targa targa;
if (TARGA_ERROR_HANDLER(targa.Open(filename, TGA_READMODE),filename)) return MissingTexture::_Create_Missing_Surface();
// DX8 uses image upside down compared to TGA
targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN;
WW3DFormat src_format,dest_format;
unsigned src_bpp=0;
Get_WW3D_Format(dest_format,src_format,src_bpp,targa);
if (texture_format!=WW3D_FORMAT_UNKNOWN) {
dest_format=texture_format;
}
// Destination size will be the next power of two square from the larger width and height...
unsigned width, height;
width=targa.Header.Width;
height=targa.Header.Height;
unsigned src_width=targa.Header.Width;
unsigned src_height=targa.Header.Height;
// NOTE: We load the palette but we do not yet support paletted textures!
char palette[256*4];
targa.SetPalette(palette);
if (TARGA_ERROR_HANDLER(targa.Load(filename, TGAF_IMAGE, false),filename)) return MissingTexture::_Create_Missing_Surface();
unsigned char* src_surface=(unsigned char*)targa.GetImage();
// No paletted destination format allowed
unsigned char* converted_surface=NULL;
if (src_format==WW3D_FORMAT_A1R5G5B5 || src_format==WW3D_FORMAT_R5G6B5 || src_format==WW3D_FORMAT_A4R4G4B4 ||
src_format==WW3D_FORMAT_P8 || src_format==WW3D_FORMAT_L8 || src_width!=width || src_height!=height) {
converted_surface=W3DNEWARRAY unsigned char[width*height*4];
dest_format=Get_Valid_Texture_Format(WW3D_FORMAT_A8R8G8B8,false);
BitmapHandlerClass::Copy_Image(
converted_surface,
width,
height,
width*4,
WW3D_FORMAT_A8R8G8B8,//dest_format,
src_surface,
src_width,
src_height,
src_width*src_bpp,
src_format,
(unsigned char*)targa.GetPalette(),
targa.Header.CMapDepth>>3,
false);
src_surface=converted_surface;
src_format=WW3D_FORMAT_A8R8G8B8;//dest_format;
src_width=width;
src_height=height;
src_bpp=Get_Bytes_Per_Pixel(src_format);
}
unsigned src_pitch=src_width*src_bpp;
IDirect3DSurface8* d3d_surface = DX8Wrapper::_Create_DX8_Surface(width,height,dest_format);
WWASSERT(d3d_surface);
D3DLOCKED_RECT locked_rect;
DX8_ErrorCode(
d3d_surface->LockRect(
&locked_rect,
NULL,
0));
BitmapHandlerClass::Copy_Image(
(unsigned char*)locked_rect.pBits,
width,
height,
locked_rect.Pitch,
dest_format,
src_surface,
src_width,
src_height,
src_pitch,
src_format,
(unsigned char*)targa.GetPalette(),
targa.Header.CMapDepth>>3,
false); // No mipmap
DX8_ErrorCode(d3d_surface->UnlockRect());
if (converted_surface) delete[] converted_surface;
return d3d_surface;
}
// ----------------------------------------------------------------------------
//
// Load mipmap levels to a pre-generated and locked texture object based on
// information in load task object. Try loading from a DDS file first and if
// that fails try a TGA.
//
// ----------------------------------------------------------------------------
void TextureLoader::Load_Mipmap_Levels(TextureLoadTaskClass* task)
{
WWASSERT(task->Peek_D3D_Texture());
WWMEMLOG(MEM_TEXTURE);
if (task->Peek_Texture()->Is_Compression_Allowed()) {
DDSFileClass dds_file(task->Peek_Texture()->Get_Full_Path(),task->Get_Reduction());
if (dds_file.Is_Available() && dds_file.Load()) {
unsigned width=task->Get_Width();
unsigned height=task->Get_Height();
for (unsigned level=0;levelGet_Mip_Level_Count();++level) {
WWASSERT(width && height);
dds_file.Copy_Level_To_Surface(
level,
task->Get_Format(),
width,
height,
task->Get_Locked_Surface_Ptr(level),
task->Get_Locked_Surface_Pitch(level));
width>>=1;
height>>=1;
}
return;
}
}
if (Load_Uncompressed_Mipmap_Levels_From_TGA(task)) return;
}
// ----------------------------------------------------------------------------
//
// Use load task to load texture surface from a targa file. Calculate mipmaps
// if needed.
//
// ----------------------------------------------------------------------------
bool TextureLoader::Load_Uncompressed_Mipmap_Levels_From_TGA(TextureLoadTaskClass* task)
{
if (!task->Get_Mip_Level_Count()) return false;
TextureClass* texture=task->Peek_Texture();
Targa targa;
if (TARGA_ERROR_HANDLER(targa.Open(texture->Get_Full_Path(), TGA_READMODE),texture->Get_Full_Path())) {
task->Set_Fail(true);
return false;
}
// DX8 uses image upside down compared to TGA
targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN;
WW3DFormat src_format,dest_format;
unsigned src_bpp=0;
Get_WW3D_Format(dest_format,src_format,src_bpp,targa);
// WWASSERT(task->Get_Format()==dest_format);
dest_format=task->Get_Format(); // Texture can be requested in different format than the most obvious from the TGA
char palette[256*4];
targa.SetPalette(palette);
unsigned src_width=targa.Header.Width;
unsigned src_height=targa.Header.Height;
unsigned width=task->Get_Width();
unsigned height=task->Get_Height();
// NOTE: We load the palette but we do not yet support paletted textures!
if (TARGA_ERROR_HANDLER(targa.Load(texture->Get_Full_Path(), TGAF_IMAGE, false),texture->Get_Full_Path())) {
task->Set_Fail(true);
return false;
}
unsigned char* src_surface=(unsigned char*)targa.GetImage();
// No paletted format allowed when generating mipmaps
unsigned char* converted_surface=NULL;
if (src_format==WW3D_FORMAT_A1R5G5B5 || src_format==WW3D_FORMAT_R5G6B5 || src_format==WW3D_FORMAT_A4R4G4B4 ||
src_format==WW3D_FORMAT_P8 || src_format==WW3D_FORMAT_L8 || src_width!=width || src_height!=height) {
converted_surface=W3DNEWARRAY unsigned char[width*height*4];
dest_format=Get_Valid_Texture_Format(WW3D_FORMAT_A8R8G8B8,false);
BitmapHandlerClass::Copy_Image(
converted_surface,
width,
height,
width*4,
WW3D_FORMAT_A8R8G8B8, //dest_format,
src_surface,
src_width,
src_height,
src_width*src_bpp,
src_format,
(unsigned char*)targa.GetPalette(),
targa.Header.CMapDepth>>3,
false);
src_surface=converted_surface;
src_format=WW3D_FORMAT_A8R8G8B8; //dest_format;
src_width=width;
src_height=height;
src_bpp=Get_Bytes_Per_Pixel(src_format);
}
unsigned src_pitch=src_width*src_bpp;
for (unsigned level=0;levelGet_Mip_Level_Count();++level) {
WWASSERT(task->Get_Locked_Surface_Ptr(level));
BitmapHandlerClass::Copy_Image(
task->Get_Locked_Surface_Ptr(level),
width,
height,
task->Get_Locked_Surface_Pitch(level),
task->Get_Format(),
src_surface,
src_width,
src_height,
src_pitch,
src_format,
NULL,
0,
true);
width>>=1;
height>>=1;
src_width>>=1;
src_height>>=1;
if (!width || !height || !src_width || !src_height) break;
}
if (converted_surface) delete[] converted_surface;
return true;
}
// ----------------------------------------------------------------------------
//
// Return a task from the load list head. The loading thread uses this function
// to retrieve tasks from the load list.
//
// ----------------------------------------------------------------------------
TextureLoadTaskClass* LoaderThreadClass::Get_Task_From_Load_List()
{
CriticalSectionClass::LockClass m(mutex);
TextureLoadTaskClass* task=LoadListHead;
if (task) {
LoadListHead=task->Peek_Succ();
task->Set_Succ(NULL);
}
return task;
}
// ----------------------------------------------------------------------------
//
// This function adds a load task to the head of the loading thread task list.
// The latest added task will be the next processed (There are good reasons
// for such ordering). The loading thread will process tasks from this list
// as soons as it can and then move the tasks to finished list.
//
// ----------------------------------------------------------------------------
void LoaderThreadClass::Add_Task_To_Load_List(TextureLoadTaskClass* task)
{
CriticalSectionClass::LockClass m(mutex);
WWASSERT(task->Peek_Succ()==NULL);
task->Set_Succ(LoadListHead);
LoadListHead=task;
}
// ----------------------------------------------------------------------------
//
// After the loading thread is done with the texture, it is moved to the list
// of finished tasks so that the main thread can then finish up by unlocking
// the surfaces and applying the changes to the texture class object.
//
// ----------------------------------------------------------------------------
void LoaderThreadClass::Add_Task_To_Finished_List(TextureLoadTaskClass* task)
{
CriticalSectionClass::LockClass m(mutex);
WWASSERT(task->Peek_Succ()==NULL);
task->Set_Succ(FinishedListHead);
FinishedListHead=task;
}
// ----------------------------------------------------------------------------
//
// If we need to find out if the load task list is empty this is the function
// to use. We can't use Get_Task_From_Load_List() as if the list isn't empty
// it also removes the head node from the list.
//
// ----------------------------------------------------------------------------
bool Is_Load_List_Empty()
{
return !LoadListHead;
}
// ----------------------------------------------------------------------------
//
// Texture loading thread loads textures that appear in loading_task_list.
// If the list is empty the thread sleeps.
//
// ----------------------------------------------------------------------------
void LoaderThreadClass::Thread_Function()
{
while (running) {
TextureLoadTaskClass* task=Get_Task_From_Load_List();
if (task) {
TextureLoader::Load_Mipmap_Levels(task);
Add_Task_To_Finished_List(task);
}
Switch_Thread();
}
}
// ----------------------------------------------------------------------------
//
// Update refreshes all completed texture loading tasks
//
// ----------------------------------------------------------------------------
TextureLoadTaskClass* Get_Finished_Task()
{
CriticalSectionClass::LockClass m(mutex);
TextureLoadTaskClass* task=FinishedListHead;
if (task) {
FinishedListHead=task->Peek_Succ();
task->Set_Succ(NULL);
}
return task;
}
TextureLoadTaskClass* Get_Thumbnail_Task()
{
CriticalSectionClass::LockClass m(mutex);
TextureLoadTaskClass* task=ThumbnailListHead;
if (task) {
ThumbnailListHead=task->Peek_Succ();
task->Set_Succ(NULL);
}
return task;
}
void Add_Thumbnail_Task(TextureLoadTaskClass* task)
{
CriticalSectionClass::LockClass m(mutex);
WWASSERT(task->Peek_Succ()==NULL);
task->Set_Succ(ThumbnailListHead);
ThumbnailListHead=task;
}
// ----------------------------------------------------------------------------
//
// The main thread's update function deletes tasks from the load task list
// once a frame.
//
// ----------------------------------------------------------------------------
TextureLoadTaskClass* Get_Task_From_Delete_List()
{
WWASSERT(ThreadClass::_Get_Current_Thread_ID()==DX8Wrapper::_Get_Main_Thread_ID());
TextureLoadTaskClass* task=DeleteTaskListHead;
if (task) {
DeleteTaskListHead=task->Peek_Succ();
task->Set_Succ(NULL);
}
return task;
}
// ----------------------------------------------------------------------------
//
// When task wants to delete itself it adds itself to a delete list. This list
// can only be accessed from the main thread.
//
// ----------------------------------------------------------------------------
void Add_Task_To_Delete_List(TextureLoadTaskClass* task)
{
WWASSERT(ThreadClass::_Get_Current_Thread_ID()==DX8Wrapper::_Get_Main_Thread_ID());
WWASSERT(task->Peek_Succ()==NULL);
task->Set_Succ(DeleteTaskListHead);
DeleteTaskListHead=task;
}
TextureLoadTaskClass* Get_Deferred_Task()
{
CriticalSectionClass::LockClass m(mutex);
TextureLoadTaskClass* task=DeferredListHead;
if (task) {
DeferredListHead=task->Peek_Succ();
task->Set_Succ(NULL);
}
return task;
}
void Add_Deferred_Task(TextureLoadTaskClass* task)
{
CriticalSectionClass::LockClass m(mutex);
WWASSERT(task->Peek_Succ()==NULL);
task->Set_Succ(DeferredListHead);
DeferredListHead=task;
}
void TextureLoader::Flush_Pending_Load_Tasks()
{
while (!Is_Load_List_Empty()) {
Update();
ThreadClass::Switch_Thread();
}
}
void TextureLoader::Update()
{
WWASSERT_PRINT(DX8Wrapper::_Get_Main_Thread_ID()==ThreadClass::_Get_Current_Thread_ID(),"TextureLoader::Update must be called from the main thread!");
while (TextureLoadTaskClass* task=Get_Deferred_Task()) {
task->Begin_Texture_Load(); // This will add the task to load list
}
while (TextureLoadTaskClass* task=Get_Finished_Task()) {
task->End_Load();
task->Apply(true);
TextureLoadTaskClass::Release_Instance(task);
}
while (TextureLoadTaskClass* task=Get_Thumbnail_Task()) {
task->Begin_Thumbnail_Load();
}
while (TextureLoadTaskClass* task=Get_Task_From_Delete_List()) {
// delete task;
TextureLoadTaskClass::Release_Instance(task);
}
}
// ----------------------------------------------------------------------------
static DWORD VectortoRGBA( D3DXVECTOR3* v, FLOAT fHeight )
{
DWORD r = (DWORD)( 127.0f * v->x + 128.0f );
DWORD g = (DWORD)( 127.0f * v->y + 128.0f );
DWORD b = (DWORD)( 127.0f * v->z + 128.0f );
DWORD a = (DWORD)( 255.0f * fHeight );
return( (a<<24L) + (r<<16L) + (g<<8L) + (b<<0L) );
}
IDirect3DTexture8* TextureLoader::Generate_Bumpmap(TextureClass* texture)
{
WW3DFormat bump_format=WW3D_FORMAT_U8V8;
if (!DX8Caps::Support_Texture_Format(bump_format)) {
return MissingTexture::_Get_Missing_Texture();
}
D3DSURFACE_DESC desc;
IDirect3DTexture8* src_d3d_tex=texture->Peek_DX8_Texture();
WWASSERT(src_d3d_tex);
DX8_ErrorCode(src_d3d_tex->GetLevelDesc(0,&desc));
unsigned width=desc.Width;
unsigned height=desc.Height;
IDirect3DTexture8* d3d_texture = DX8Wrapper::_Create_DX8_Texture(
width,
height,
bump_format,
TextureClass::MIP_LEVELS_1);
D3DLOCKED_RECT src_locked_rect;
DX8_ErrorCode(
texture->Peek_DX8_Texture()->LockRect(
0,
&src_locked_rect,
NULL,
D3DLOCK_READONLY));
D3DLOCKED_RECT dest_locked_rect;
DX8_ErrorCode(
d3d_texture->LockRect(
0,
&dest_locked_rect,
NULL,
0));
WW3DFormat format=D3DFormat_To_WW3DFormat(desc.Format);
unsigned bpp=Get_Bytes_Per_Pixel(format);
for( unsigned y=0; y1 ) ? 63 : 127;
switch( bump_format )
{
case WW3D_FORMAT_U8V8:
*dest_ptr++ = (unsigned char)iDu;
*dest_ptr++ = (unsigned char)iDv;
break;
case WW3D_FORMAT_L6V5U5:
*(unsigned short*)dest_ptr = (unsigned short)( ( (iDu>>3) & 0x1f ) << 0 );
*(unsigned short*)dest_ptr |= (unsigned short)( ( (iDv>>3) & 0x1f ) << 5 );
*(unsigned short*)dest_ptr |= (unsigned short)( ( ( uL>>2) & 0x3f ) << 10 );
dest_ptr += 2;
break;
case WW3D_FORMAT_X8L8V8U8:
*dest_ptr++ = (unsigned char)iDu;
*dest_ptr++ = (unsigned char)iDv;
*dest_ptr++ = (unsigned char)uL;
*dest_ptr++ = (unsigned char)0L;
break;
}
// Move one pixel to the left (src is 32-bpp)
src_ptr_mid+=bpp;
src_ptr_prev_line+=bpp;
src_ptr_next_line+=bpp;
}
}
DX8_ErrorCode(d3d_texture->UnlockRect(0));
DX8_ErrorCode(texture->Peek_DX8_Texture()->UnlockRect(0));
return d3d_texture;
}
// ----------------------------------------------------------------------------
//
// Public texture request functions. These functions can be used to request
// texture loading.
//
// ----------------------------------------------------------------------------
void TextureLoader::Add_Load_Task(TextureClass* tc)
{
// If the texture is already being loaded we just exit here.
if (tc->TextureLoadTask) return;
TextureLoadTaskClass* task=TextureLoadTaskClass::Get_Instance(tc,false);
task->Begin_Texture_Load();
}
// ----------------------------------------------------------------------------
void TextureLoader::Request_High_Priority_Loading(
TextureClass* tc,
TextureClass::MipCountType mip_level_count)
{
TextureLoadTaskClass* task=TextureLoadTaskClass::Get_Instance(tc,true);
task->Begin_Texture_Load();
}
// ----------------------------------------------------------------------------
void TextureLoader::Request_Thumbnail(TextureClass* tc)
{
// If the texture is already being loaded we just exit here.
if (tc->TextureLoadTask) return;
TextureLoadTaskClass* task=TextureLoadTaskClass::Get_Instance(tc,false);
task->Begin_Thumbnail_Load();
}
// ----------------------------------------------------------------------------
//
// Texture loader task handler
//
// ----------------------------------------------------------------------------
TextureLoadTaskClass::TextureLoadTaskClass()
:
Texture(0),
Succ(0),
D3DTexture(0),
Width(0),
Height(0),
Format(WW3D_FORMAT_UNKNOWN),
IsLoading(false),
HasFailed(false),
MipLevelCount(0),
HighPriorityRequested(false),
Reduction(0)
{
}
// ----------------------------------------------------------------------------
//
// Destruct texture load task. The load task deinitialization will fail from
// any other than the main thread so the task must be either deleted from the
// main thread or deinitialized in the main thread prior to deleting it in
// another thread.
//
// ----------------------------------------------------------------------------
TextureLoadTaskClass::~TextureLoadTaskClass()
{
Deinit();
}
void TextureLoadTaskClass::Init(TextureClass* tc,bool high_priority)
{
// Make sure texture has a filename.
REF_PTR_SET(Texture,tc);
WWASSERT(Texture->Get_Full_Path() != NULL);
Reduction=Texture->Get_Reduction();
HighPriorityRequested=high_priority;
IsLoading=false;
HasFailed=false;
MipLevelCount=tc->MipLevelCount;
D3DTexture=0;
Width=0;
Height=0;
Format=Texture->Get_Texture_Format();
Texture->TextureLoadTask=this;
for (int i=0;iTextureLoadTask==this);
Texture->TextureLoadTask=NULL;
}
REF_PTR_RELEASE(Texture);
WWASSERT(!D3DTexture);
}
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::Begin_Texture_Load()
{
// If we're in main thread, init for loading and add to the load list
if (ThreadClass::_Get_Current_Thread_ID()==DX8Wrapper::_Get_Main_Thread_ID()) {
bool loaded=false;
if (Texture->Is_Compression_Allowed()) {
DDSFileClass dds_file(Texture->Get_Full_Path(),Get_Reduction());
if (dds_file.Is_Available()) {
// Destination size will be the next power of two square from the larger width and height...
unsigned width, height;
width=dds_file.Get_Width(0);
height=dds_file.Get_Height(0);
TextureLoader::Validate_Texture_Size(width,height);
// If the size doesn't match, try and see if texture reduction would help... (mainly for
// cases where loaded texture is larger than hardware limit)
if (width!=dds_file.Get_Width(0) || height!=dds_file.Get_Height(0)) {
for (unsigned i=1;iIs_Compression_Allowed());
unsigned mip_level_count=Get_Mip_Level_Count();
// If texture wants all mip levels, take as many as the file contains (not necessarily all)
// Otherwise take as many mip levels as the texture wants, not to exceed the count in file...
if (!mip_level_count) mip_level_count=dds_file.Get_Mip_Level_Count();
else if (mip_level_count>dds_file.Get_Mip_Level_Count()) mip_level_count=dds_file.Get_Mip_Level_Count();
// Once more, verify that the mip level count is correct (in case it was changed here it might not
// match the size...well actually it doesn't have to match but it can't be bigger than the size)
unsigned max_mip_level_count=1;
unsigned w=4;
unsigned h=4;
while (wmax_mip_level_count) mip_level_count=max_mip_level_count;
D3DTexture=DX8Wrapper::_Create_DX8_Texture(Width,Height,Format,(TextureClass::MipCountType)mip_level_count);
MipLevelCount=mip_level_count;
//Texture->MipLevelCount);
loaded=true;
}
}
if (!loaded) {
Targa targa;
if (TARGA_ERROR_HANDLER(targa.Open(Texture->Get_Full_Path(), TGA_READMODE),Texture->Get_Full_Path())) {
D3DTexture=MissingTexture::_Get_Missing_Texture();
HasFailed=true;
IsLoading=false;
End_Load();
Apply(true);
Add_Task_To_Delete_List(this);
return;
}
unsigned bpp;
WW3DFormat src_format,dest_format;
Get_WW3D_Format(dest_format,src_format,bpp,targa);
if (src_format!=WW3D_FORMAT_A8R8G8B8 &&
src_format!=WW3D_FORMAT_R8G8B8 &&
src_format!=WW3D_FORMAT_X8R8G8B8) {
WWDEBUG_SAY(("Invalid TGA format used in %s - only 24 and 32 bit formats should be used!\n",Texture->Get_Full_Path()));
}
// Destination size will be the next power of two square from the larger width and height...
unsigned width=targa.Header.Width, height=targa.Header.Height;
int ReductionFactor=Get_Reduction();
int MipLevels=0;
//Figure out how many mip levels this texture will occupy
for (int i=width, j=height; i > 0 && j > 0; i>>=1, j>>=1)
MipLevels++;
//Adjust the reduction factor to keep textures above some minimum dimensions
if (MipLevels <= WW3D::Get_Texture_Min_Mip_Levels())
ReductionFactor=0;
else
{ int mipToDrop=MipLevels-WW3D::Get_Texture_Min_Mip_Levels();
if (ReductionFactor >= mipToDrop)
ReductionFactor=mipToDrop;
}
width=targa.Header.Width>>ReductionFactor;
height=targa.Header.Height>>ReductionFactor;
unsigned ow=width;
unsigned oh=height;
TextureLoader::Validate_Texture_Size(width,height);
if (width!=ow || height!=oh) {
WWDEBUG_SAY(("Invalid texture size, scaling required. Texture: %s, size: %d x %d -> %d x %d\n",Texture->Get_Full_Path(),ow,oh,width,height));
}
IsLoading=true;
Width=width;
Height=height;
if (Format==WW3D_FORMAT_UNKNOWN) {
Format=Get_Valid_Texture_Format(dest_format,false);
}
else {
Format=Get_Valid_Texture_Format(Format,false);
}
D3DTexture=DX8Wrapper::_Create_DX8_Texture(Width,Height,Format,Texture->MipLevelCount);
}
MipLevelCount=D3DTexture->GetLevelCount();
for (unsigned i=0;iLockRect(
i,
&locked_rect,
NULL,
0));
LockedSurfacePtr[i]=(unsigned char*)locked_rect.pBits;
LockedSurfacePitch[i]=locked_rect.Pitch;
}
if (HighPriorityRequested) {
TextureLoader::Load_Mipmap_Levels(this);
End_Load();
Apply(true);
Add_Task_To_Delete_List(this);
}
else {
LoaderThreadClass::Add_Task_To_Load_List(this);
}
}
// Otherwise add to deferred list which will be handled by main thread
else {
Add_Deferred_Task(this);
}
}
/* file_auto_ptr my_tga_file(_TheFileFactory,Texture->Get_Full_Path());
if (my_tga_file->Is_Available()) {
my_tga_file->Open();
unsigned size=my_tga_file->Size();
char* tga_memory=W3DNEWARRAY char[size];
my_tga_file->Read(tga_memory,size);
my_tga_file->Close();
StringClass pth("data\\");
pth+=Texture->Get_Texture_Name();
RawFileClass tmp_tga_file(pth);
tmp_tga_file.Create();
tmp_tga_file.Write(tga_memory,size);
tmp_tga_file.Close();
delete[] tga_memory;
}
*/
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::Begin_Thumbnail_Load()
{
// CriticalSectionClass::LockClass m(mutex);
unsigned thread_id=ThreadClass::_Get_Current_Thread_ID();
if (thread_id==DX8Wrapper::_Get_Main_Thread_ID()) {
WW3DFormat format=Texture->Get_Texture_Format();
// No compressed thumbnails
switch (format) {
case WW3D_FORMAT_DXT1:
case WW3D_FORMAT_DXT2:
case WW3D_FORMAT_DXT3:
case WW3D_FORMAT_DXT4:
case WW3D_FORMAT_DXT5:
format=WW3D_FORMAT_A8R8G8B8; break;
default:
break;
}
D3DTexture=TextureLoader::Load_Thumbnail(Texture->Get_Full_Path(),format);
// Thumbnail loads are always high priority, so apply immediatelly
End_Load();
Apply(false);
Add_Task_To_Delete_List(this);
return;
}
else {
Add_Thumbnail_Task(this);
}
}
// ----------------------------------------------------------------------------
//
// Deinit can be called multiple times. If any surfaces are locked this call
// can only be called from the main thread.
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::End_Load()
{
for (unsigned i=0;iUnlockRect(i));
}
LockedSurfacePtr[i]=NULL;
}
IsLoading=false;
}
// ----------------------------------------------------------------------------
//
// Link the node to another task node. This can only be done if the node isn't
// linked to something else already. If the node is linked to some other node,
// the only acceptable parameter to this function is NULL, which will unlink
// the connection.
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::Set_Succ(TextureLoadTaskClass* succ)
{
WWASSERT((succ && !Succ) || (!succ)); // Can't set successor pointer if it has been set already
Succ=succ;
}
// ----------------------------------------------------------------------------
//
//
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::Set_D3D_Texture(IDirect3DTexture8* texture)
{
WWASSERT(D3DTexture==0);
D3DTexture=texture;
}
// ----------------------------------------------------------------------------
//
// Apply the D3D texture surface to the client texture. The parameter 'initialize'
// determines whether the client texture will be set to initialized state or
// not. Generally thumbnail tasks will not set texture to initialised state but
// all other loads do.
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::Apply(bool initialize)
{
WWASSERT(D3DTexture);
// Verify that none of the mip levels are locked
for (unsigned i=0;iApply_New_Surface(initialize);
D3DTexture->Release();
D3DTexture=NULL;
}
// ----------------------------------------------------------------------------
//
// Return locked surface pointer at a specific level. The call will
// assert if level is greater or equal to the number of mip levels or if the
// requested level has not been locked.
//
// ----------------------------------------------------------------------------
unsigned char* TextureLoadTaskClass::Get_Locked_Surface_Ptr(unsigned level)
{
WWASSERT(levelPeek_Succ();
task->Set_Succ(NULL);
}
else {
task=W3DNEW TextureLoadTaskClass();
}
task->Init(tc,high_priority);
return task;
}
// ----------------------------------------------------------------------------
//
// When task is no longer needed it is returned to the pool.
//
// ----------------------------------------------------------------------------
void TextureLoadTaskClass::Release_Instance(TextureLoadTaskClass* task)
{
if (!task) return;
CriticalSectionClass::LockClass m(mutex);
task->Deinit();
// Task must not be in any list when it is being freed
WWASSERT(task->Peek_Succ()==NULL);
task->Set_Succ(FreeTaskListHead);
FreeTaskListHead=task;
}