1595 lines
42 KiB
C++
1595 lines
42 KiB
C++
/*
|
|
** Command & Conquer Renegade(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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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 <D3dx8tex.h>
|
|
#include <cstdio>
|
|
#include "wwmemlog.h"
|
|
#include "texture.h"
|
|
#include "formconv.h"
|
|
#include "texturethumbnail.h"
|
|
#include "ddsfile.h"
|
|
#include "bitmaphandler.h"
|
|
|
|
bool TextureLoader::TextureLoadSuspended;
|
|
|
|
#define USE_MANAGED_TEXTURES
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TextureLoadTaskListClass implementation
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TextureLoadTaskListClass::TextureLoadTaskListClass(void)
|
|
: Root()
|
|
{
|
|
Root.Next = Root.Prev = &Root;
|
|
}
|
|
|
|
void TextureLoadTaskListClass::Push_Front (TextureLoadTaskClass *task)
|
|
{
|
|
// task should non-null and not on any list
|
|
WWASSERT(task != NULL && task->Next == NULL && task->Prev == NULL);
|
|
|
|
// update inserted task to point to list
|
|
task->Next = Root.Next;
|
|
task->Prev = &Root;
|
|
task->List = this;
|
|
|
|
// update list to point to inserted task
|
|
Root.Next->Prev = task;
|
|
Root.Next = task;
|
|
}
|
|
|
|
void TextureLoadTaskListClass::Push_Back(TextureLoadTaskClass *task)
|
|
{
|
|
// task should be non-null and not on any list
|
|
WWASSERT(task != NULL && task->Next == NULL && task->Prev == NULL);
|
|
|
|
// update inserted task to point to list
|
|
task->Next = &Root;
|
|
task->Prev = Root.Prev;
|
|
task->List = this;
|
|
|
|
// update list to point to inserted task
|
|
Root.Prev->Next = task;
|
|
Root.Prev = task;
|
|
}
|
|
|
|
TextureLoadTaskClass *TextureLoadTaskListClass::Pop_Front(void)
|
|
{
|
|
// exit early if list is empty
|
|
if (Is_Empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// otherwise, grab first task and remove it.
|
|
TextureLoadTaskClass *task = (TextureLoadTaskClass *)Root.Next;
|
|
Remove(task);
|
|
return task;
|
|
|
|
}
|
|
|
|
TextureLoadTaskClass *TextureLoadTaskListClass::Pop_Back(void)
|
|
{
|
|
// exit early if list is empty
|
|
if (Is_Empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// otherwise, grab last task and remove it.
|
|
TextureLoadTaskClass *task = (TextureLoadTaskClass *)Root.Prev;
|
|
Remove(task);
|
|
return task;
|
|
}
|
|
|
|
void TextureLoadTaskListClass::Remove(TextureLoadTaskClass *task)
|
|
{
|
|
// exit early if task is not on this list.
|
|
if (task->List != this) {
|
|
return;
|
|
}
|
|
|
|
// update list to skip task
|
|
task->Prev->Next = task->Next;
|
|
task->Next->Prev = task->Prev;
|
|
|
|
// update task to no longer point at list
|
|
task->Prev = 0;
|
|
task->Next = 0;
|
|
task->List = 0;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// SynchronizedTextureLoadTaskListClass implementation
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SynchronizedTextureLoadTaskListClass::SynchronizedTextureLoadTaskListClass(void)
|
|
: TextureLoadTaskListClass(),
|
|
CriticalSection()
|
|
{
|
|
}
|
|
|
|
void SynchronizedTextureLoadTaskListClass::Push_Front(TextureLoadTaskClass *task)
|
|
{
|
|
FastCriticalSectionClass::LockClass lock(CriticalSection);
|
|
TextureLoadTaskListClass::Push_Front(task);
|
|
}
|
|
|
|
void SynchronizedTextureLoadTaskListClass::Push_Back(TextureLoadTaskClass *task)
|
|
{
|
|
FastCriticalSectionClass::LockClass lock(CriticalSection);
|
|
TextureLoadTaskListClass::Push_Back(task);
|
|
}
|
|
|
|
TextureLoadTaskClass *SynchronizedTextureLoadTaskListClass::Pop_Front(void)
|
|
{
|
|
// this duplicates code inside base class, but saves us an unnecessary lock.
|
|
if (Is_Empty()) {
|
|
return 0;
|
|
}
|
|
|
|
FastCriticalSectionClass::LockClass lock(CriticalSection);
|
|
return TextureLoadTaskListClass::Pop_Front();
|
|
|
|
}
|
|
|
|
TextureLoadTaskClass *SynchronizedTextureLoadTaskListClass::Pop_Back(void)
|
|
{
|
|
// this duplicates code inside base class, but saves us an unnecessary lock.
|
|
if (Is_Empty()) {
|
|
return 0;
|
|
}
|
|
|
|
FastCriticalSectionClass::LockClass lock(CriticalSection);
|
|
return TextureLoadTaskListClass::Pop_Back();
|
|
}
|
|
|
|
void SynchronizedTextureLoadTaskListClass::Remove(TextureLoadTaskClass *task)
|
|
{
|
|
FastCriticalSectionClass::LockClass lock(CriticalSection);
|
|
TextureLoadTaskListClass::Remove(task);
|
|
}
|
|
|
|
|
|
// Locks
|
|
|
|
// To prevent deadlock, threads should acquire locks in the order in which
|
|
// they are defined below. No ordering is necessary for the task list locks,
|
|
// since one thread can never hold two at once.
|
|
|
|
static FastCriticalSectionClass _ForegroundCriticalSection;
|
|
static FastCriticalSectionClass _BackgroundCriticalSection;
|
|
|
|
// Lists
|
|
|
|
static SynchronizedTextureLoadTaskListClass _ForegroundQueue;
|
|
static SynchronizedTextureLoadTaskListClass _BackgroundQueue;
|
|
static TextureLoadTaskListClass _FreeList;
|
|
|
|
|
|
// The background texture loading thread.
|
|
static class LoaderThreadClass : public ThreadClass
|
|
{
|
|
public:
|
|
#ifdef Exception_Handler
|
|
LoaderThreadClass(const char *thread_name = "Texture loader thread") : ThreadClass(thread_name, &Exception_Handler) {}
|
|
#else
|
|
LoaderThreadClass(const char *thread_name = "Texture loader thread") : ThreadClass(thread_name) {}
|
|
#endif
|
|
|
|
void Thread_Function();
|
|
} _TextureLoadThread;
|
|
|
|
|
|
// 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;level<mips;++level) {
|
|
IDirect3DSurface8* d3d_surface=NULL;
|
|
WWASSERT(d3d_texture);
|
|
DX8_ErrorCode(d3d_texture->GetSurfaceLevel(level/*-reduction_factor*/,&d3d_surface));
|
|
dds_file.Copy_Level_To_Surface(level,d3d_surface);
|
|
d3d_surface->Release();
|
|
}
|
|
return d3d_texture;
|
|
}
|
|
|
|
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 (!DX8Wrapper::Get_Current_Caps()->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 &&
|
|
DX8Wrapper::Get_Current_Caps()->Support_DXTC() &&
|
|
allow_compression);
|
|
|
|
return compressed;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TextureLoader implementation
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TextureLoader::Init()
|
|
{
|
|
WWASSERT(!_TextureLoadThread.Is_Running());
|
|
|
|
ThumbnailManagerClass::Init();
|
|
|
|
_TextureLoadThread.Execute();
|
|
_TextureLoadThread.Set_Priority(-4);
|
|
}
|
|
|
|
|
|
void TextureLoader::Deinit()
|
|
{
|
|
FastCriticalSectionClass::LockClass lock(_BackgroundCriticalSection);
|
|
_TextureLoadThread.Stop();
|
|
|
|
ThumbnailManagerClass::Deinit();
|
|
TextureLoadTaskClass::Delete_Free_Pool();
|
|
}
|
|
|
|
|
|
bool TextureLoader::Is_DX8_Thread(void)
|
|
{
|
|
return (ThreadClass::_Get_Current_Thread_ID() == DX8Wrapper::_Get_Main_Thread_ID());
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Modify given texture size to nearest valid size on current hardware.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void TextureLoader::Validate_Texture_Size(unsigned& width, unsigned& height)
|
|
{
|
|
const D3DCAPS8& dx8caps=DX8Wrapper::Get_Current_Caps()->Get_DX8_Caps();
|
|
|
|
unsigned poweroftwowidth = 1;
|
|
while (poweroftwowidth < width) {
|
|
poweroftwowidth <<= 1;
|
|
}
|
|
|
|
unsigned poweroftwoheight = 1;
|
|
while (poweroftwoheight < height) {
|
|
poweroftwoheight <<= 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=poweroftwowidth;
|
|
height=poweroftwoheight;
|
|
}
|
|
|
|
IDirect3DTexture8* TextureLoader::Load_Thumbnail(const StringClass& filename)//,WW3DFormat texture_format)
|
|
{
|
|
WWASSERT(Is_DX8_Thread());
|
|
|
|
ThumbnailClass* thumb=NULL;
|
|
ThumbnailManagerClass* thumb_man=ThumbnailManagerClass::Peek_List().Head();
|
|
while (thumb_man) {
|
|
thumb=thumb_man->Peek_Thumbnail_Instance(filename);
|
|
if (thumb) break;
|
|
thumb_man=thumb_man->Succ();
|
|
}
|
|
|
|
// If no thumb is found return a missing texture
|
|
if (!thumb) {
|
|
return MissingTexture::_Get_Missing_Texture();
|
|
}
|
|
|
|
WWASSERT(thumb->Get_Format()==WW3D_FORMAT_A4R4G4B4);
|
|
unsigned src_pitch=thumb->Get_Width()*2; // Thumbs are always 16 bits
|
|
WW3DFormat dest_format;
|
|
WW3DFormat texture_format=WW3D_FORMAT_UNKNOWN;
|
|
if (texture_format==WW3D_FORMAT_UNKNOWN) {
|
|
dest_format=Get_Valid_Texture_Format(WW3D_FORMAT_A4R4G4B4,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* sysmem_texture = DX8Wrapper::_Create_DX8_Texture(
|
|
thumb->Get_Width(),
|
|
thumb->Get_Height(),
|
|
dest_format,
|
|
TextureClass::MIP_LEVELS_ALL,
|
|
#ifdef USE_MANAGED_TEXTURES
|
|
D3DPOOL_MANAGED);
|
|
#else
|
|
D3DPOOL_SYSTEMMEM);
|
|
#endif
|
|
|
|
unsigned level=0;
|
|
D3DLOCKED_RECT locked_rects[12];
|
|
WWASSERT(sysmem_texture->GetLevelCount()<=12);
|
|
|
|
// Lock all surfaces
|
|
for (level=0;level<sysmem_texture->GetLevelCount();++level) {
|
|
DX8_ErrorCode(
|
|
sysmem_texture->LockRect(
|
|
level,
|
|
&locked_rects[level],
|
|
NULL,
|
|
0));
|
|
}
|
|
|
|
unsigned char* src_surface=thumb->Peek_Bitmap();
|
|
WW3DFormat src_format=thumb->Get_Format();
|
|
unsigned width=thumb->Get_Width();
|
|
unsigned height=thumb->Get_Height();
|
|
|
|
for (level=0;level<sysmem_texture->GetLevelCount()-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;level<sysmem_texture->GetLevelCount();++level) {
|
|
DX8_ErrorCode(sysmem_texture->UnlockRect(level));
|
|
}
|
|
#ifdef USE_MANAGED_TEXTURES
|
|
return sysmem_texture;
|
|
#else
|
|
IDirect3DTexture8* d3d_texture = DX8Wrapper::_Create_DX8_Texture(
|
|
thumb->Get_Width(),
|
|
thumb->Get_Height(),
|
|
dest_format,
|
|
TextureClass::MIP_LEVELS_ALL,
|
|
D3DPOOL_DEFAULT);
|
|
DX8CALL(UpdateTexture(sysmem_texture,d3d_texture));
|
|
sysmem_texture->Release();
|
|
|
|
WWDEBUG_SAY(("Created non-managed texture (%s)\n",filename));
|
|
return d3d_texture;
|
|
#endif
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// 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)
|
|
{
|
|
WWASSERT(Is_DX8_Thread());
|
|
|
|
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=new 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;
|
|
}
|
|
|
|
|
|
void TextureLoader::Request_Thumbnail(TextureClass *tc)
|
|
{
|
|
// Grab the foreground lock. This prevents the foreground thread
|
|
// from retiring any tasks related to this texture. It also
|
|
// serializes calls to Request_Thumbnail from multiple threads.
|
|
FastCriticalSectionClass::LockClass lock(_ForegroundCriticalSection);
|
|
|
|
// Has a Direct3D texture already been loaded?
|
|
if (tc->Peek_DX8_Texture()) {
|
|
return;
|
|
}
|
|
|
|
TextureLoadTaskClass *task = tc->ThumbnailLoadTask;
|
|
|
|
if (Is_DX8_Thread()) {
|
|
// load the thumbnail immediately
|
|
TextureLoader::Load_Thumbnail(tc);
|
|
|
|
// clear any pending thumbnail load
|
|
if (task) {
|
|
_ForegroundQueue.Remove(task);
|
|
task->Destroy();
|
|
}
|
|
|
|
} else {
|
|
TextureLoadTaskClass *load_task = tc->TextureLoadTask;
|
|
|
|
// if texture is not already loading a thumbnail and there is no
|
|
// background load near completion. (a background load waiting
|
|
// to be applied will be ready at the same time as a queued thumbnail.
|
|
// Why do the extra work?)
|
|
if (!task && (!load_task || load_task->Get_State() < TextureLoadTaskClass::STATE_LOAD_MIPMAP)) {
|
|
|
|
// create a thumbnail load task and add to foreground queue.
|
|
task = TextureLoadTaskClass::Create(tc, TextureLoadTaskClass::TASK_THUMBNAIL, TextureLoadTaskClass::PRIORITY_LOW);
|
|
_ForegroundQueue.Push_Back(task);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoader::Request_Background_Loading(TextureClass *tc)
|
|
{
|
|
// Grab the foreground lock. This prevents the foreground thread
|
|
// from retiring any tasks related to this texture. It also
|
|
// serializes calls to Request_Background_Loading from other
|
|
// threads.
|
|
FastCriticalSectionClass::LockClass foreground_lock(_ForegroundCriticalSection);
|
|
|
|
// Has the texture already been loaded?
|
|
if (tc->Is_Initialized()) {
|
|
return;
|
|
}
|
|
|
|
TextureLoadTaskClass *task = tc->TextureLoadTask;
|
|
|
|
// if texture already has a load task, we don't need to create another one.
|
|
if (task) {
|
|
return;
|
|
}
|
|
|
|
task = TextureLoadTaskClass::Create(tc, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_LOW);
|
|
|
|
if (Is_DX8_Thread()) {
|
|
Begin_Load_And_Queue(task);
|
|
} else {
|
|
_ForegroundQueue.Push_Back(task);
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoader::Request_Foreground_Loading(TextureClass *tc)
|
|
{
|
|
// Grab the foreground lock. This prevents the foreground thread
|
|
// from retiring the load tasks for this texture. It also
|
|
// serializes calls to Request_Foreground_Loading from other
|
|
// threads.
|
|
FastCriticalSectionClass::LockClass foreground_lock(_ForegroundCriticalSection);
|
|
|
|
// Has the texture already been loaded?
|
|
if (tc->Is_Initialized()) {
|
|
return;
|
|
}
|
|
|
|
TextureLoadTaskClass *task = tc->TextureLoadTask;
|
|
TextureLoadTaskClass *task_thumb = tc->ThumbnailLoadTask;
|
|
|
|
if (Is_DX8_Thread()) {
|
|
|
|
// since we're in the DX8 thread, we can load the entire
|
|
// texture right now.
|
|
|
|
// if we have a thumbnail task waiting, kill it.
|
|
if (task_thumb) {
|
|
_ForegroundQueue.Remove(task_thumb);
|
|
task_thumb->Destroy();
|
|
}
|
|
|
|
if (task) {
|
|
// we need to remove the task from any queue, since we're going
|
|
// to finish it up right now.
|
|
|
|
// halt background thread. After we're holding this lock,
|
|
// we know the background thread cannot begin loading
|
|
// mipmap levels for this texture.
|
|
FastCriticalSectionClass::LockClass background_lock(_BackgroundCriticalSection);
|
|
_ForegroundQueue.Remove(task);
|
|
_BackgroundQueue.Remove(task);
|
|
} else {
|
|
// Since the task manages all the state associated with loading
|
|
// a texture, we temporarily create one.
|
|
task = TextureLoadTaskClass::Create(tc, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_HIGH);
|
|
}
|
|
|
|
// finish loading the task and destroy it.
|
|
task->Finish_Load();
|
|
task->Destroy();
|
|
|
|
} else {
|
|
// we are not in the DX8 thread. We need to add a high-priority loading
|
|
// task to the foreground queue.
|
|
|
|
// Grab the background lock. After we're holding this lock, we
|
|
// know the background thread cannot begin loading mipmap levels
|
|
// for this texture.
|
|
FastCriticalSectionClass::LockClass background_lock(_BackgroundCriticalSection);
|
|
|
|
// if we have a thumbnail task, we should cancel it. Since we are not
|
|
// the foreground thread, we are not allowed to call Destroy(). Instead,
|
|
// leave it queued in the completed state so it will be destroyed by Update().
|
|
if (task_thumb) {
|
|
task_thumb->Set_State(TextureLoadTaskClass::STATE_COMPLETE);
|
|
}
|
|
|
|
if (task) {
|
|
// if a load task is waiting on the background queue, we need to
|
|
// move it to the foreground queue.
|
|
if (task->Get_List() == &_BackgroundQueue) {
|
|
|
|
// remove task from list
|
|
_BackgroundQueue.Remove(task);
|
|
|
|
// add to foreground queue.
|
|
_ForegroundQueue.Push_Back(task);
|
|
}
|
|
|
|
// upgrade the task priority
|
|
task->Set_Priority(TextureLoadTaskClass::PRIORITY_HIGH);
|
|
|
|
} else {
|
|
// allocate high priority load task
|
|
task = TextureLoadTaskClass::Create(tc, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_HIGH);
|
|
|
|
// add to back of foreground queue.
|
|
_ForegroundQueue.Push_Back(task);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoader::Flush_Pending_Load_Tasks(void)
|
|
{
|
|
// This function can only be called from the main thread.
|
|
// (Only the main thread can make the DX8 calls necessary
|
|
// to complete texture loading. If we wanted to flush
|
|
// the pending tasks from another thread, we'd probably
|
|
// want to set a bool that is checked by Update().
|
|
WWASSERT(Is_DX8_Thread());
|
|
|
|
for (;;) {
|
|
bool done = false;
|
|
|
|
{
|
|
// we have no pending load tasks when both queues are empty
|
|
// and the background thread is not processing a texture.
|
|
|
|
// Grab the background lock. Once we're holding it, we
|
|
// know that the background thread is not processing any
|
|
// textures.
|
|
|
|
// NOTE: It's important that we do only hold on to the background
|
|
// lock while we check for completion. Otherwise, we will either
|
|
// violate the lock order when we call Update() (which grabs
|
|
// the foreground lock) or never give the background thread
|
|
// a chance to empty its queue.
|
|
FastCriticalSectionClass::LockClass background_lock(_BackgroundCriticalSection);
|
|
done = _BackgroundQueue.Is_Empty() && _ForegroundQueue.Is_Empty();
|
|
}
|
|
|
|
// exit loop if no entries in list
|
|
if (done) {
|
|
break;
|
|
}
|
|
|
|
Update();
|
|
ThreadClass::Switch_Thread();
|
|
}
|
|
}
|
|
|
|
|
|
// Nework update macro for texture loader.
|
|
#pragma warning(disable:4201) // warning C4201: nonstandard extension used : nameless struct/union
|
|
#include <mmsystem.h>
|
|
#define UPDATE_NETWORK \
|
|
if (network_callback) { \
|
|
unsigned long time2 = timeGetTime(); \
|
|
if (time2 - time > 20) { \
|
|
network_callback(); \
|
|
time = time2; \
|
|
} \
|
|
} \
|
|
|
|
|
|
void TextureLoader::Update(void (*network_callback)(void))
|
|
{
|
|
WWASSERT_PRINT(Is_DX8_Thread(), "TextureLoader::Update must be called from the main thread!");
|
|
|
|
if (TextureLoadSuspended) {
|
|
return;
|
|
}
|
|
|
|
// grab foreground lock to prevent any other thread from
|
|
// modifying texture tasks.
|
|
FastCriticalSectionClass::LockClass lock(_ForegroundCriticalSection);
|
|
|
|
unsigned long time = timeGetTime();
|
|
|
|
// while we have tasks on the foreground queue
|
|
while (TextureLoadTaskClass *task = _ForegroundQueue.Pop_Front()) {
|
|
UPDATE_NETWORK;
|
|
// dispatch to proper task handler
|
|
switch (task->Get_Type()) {
|
|
case TextureLoadTaskClass::TASK_THUMBNAIL:
|
|
Process_Foreground_Thumbnail(task);
|
|
break;
|
|
|
|
case TextureLoadTaskClass::TASK_LOAD:
|
|
Process_Foreground_Load(task);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TextureClass::Invalidate_Old_Unused_Textures(0);
|
|
}
|
|
|
|
void TextureLoader::Suspend_Texture_Load()
|
|
{
|
|
WWASSERT_PRINT(Is_DX8_Thread(),"TextureLoader::Suspend_Texture_Load must be called from the main thread!");
|
|
TextureLoadSuspended=true;
|
|
}
|
|
|
|
void TextureLoader::Continue_Texture_Load()
|
|
{
|
|
WWASSERT_PRINT(Is_DX8_Thread(),"TextureLoader::Continue_Texture_Load must be called from the main thread!");
|
|
TextureLoadSuspended=false;
|
|
}
|
|
|
|
void TextureLoader::Process_Foreground_Thumbnail(TextureLoadTaskClass *task)
|
|
{
|
|
switch (task->Get_State()) {
|
|
case TextureLoadTaskClass::STATE_NONE:
|
|
Load_Thumbnail(task->Peek_Texture());
|
|
// NOTE: fall-through is intentional
|
|
|
|
case TextureLoadTaskClass::STATE_COMPLETE:
|
|
task->Destroy();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoader::Process_Foreground_Load(TextureLoadTaskClass *task)
|
|
{
|
|
// Is high-priority task?
|
|
if (task->Get_Priority() == TextureLoadTaskClass::PRIORITY_HIGH) {
|
|
task->Finish_Load();
|
|
task->Destroy();
|
|
return;
|
|
}
|
|
|
|
// otherwise, must be a low-priority task.
|
|
|
|
switch (task->Get_State()) {
|
|
case TextureLoadTaskClass::STATE_NONE:
|
|
Begin_Load_And_Queue(task);
|
|
break;
|
|
|
|
case TextureLoadTaskClass::STATE_LOAD_MIPMAP:
|
|
task->End_Load();
|
|
task->Destroy();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoader::Begin_Load_And_Queue(TextureLoadTaskClass *task)
|
|
{
|
|
// should only be called from the DX8 thread.
|
|
WWASSERT(Is_DX8_Thread());
|
|
|
|
if (task->Begin_Load()) {
|
|
// add to front of background queue. This means the
|
|
// background load thread will service tasks in LIFO
|
|
// (last in, first out) order.
|
|
|
|
// NOTE: this was how the old code did it, with a
|
|
// comment that mentioned good reasons for doing so,
|
|
// without actually listing the reasons. I suspect
|
|
// it has something to do with visually important textures,
|
|
// like those in the foreground, starting their load last.
|
|
_BackgroundQueue.Push_Front(task);
|
|
} else {
|
|
// unable to load.
|
|
task->Apply_Missing_Texture();
|
|
task->Destroy();
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoader::Load_Thumbnail(TextureClass *tc)
|
|
{
|
|
// All D3D operations must run from main thread
|
|
WWASSERT(Is_DX8_Thread());
|
|
|
|
// load thumbnail texture
|
|
IDirect3DTexture8 *d3d_texture = Load_Thumbnail(tc->Get_Full_Path());
|
|
|
|
// apply thumbnail to texture
|
|
tc->Apply_New_Surface(d3d_texture, false);
|
|
|
|
// release our reference to thumbnail texture
|
|
d3d_texture->Release();
|
|
d3d_texture = 0;
|
|
}
|
|
|
|
|
|
void LoaderThreadClass::Thread_Function(void)
|
|
{
|
|
while (running) {
|
|
// if there are no tasks on the background queue, no need to grab background lock.
|
|
if (!_BackgroundQueue.Is_Empty()) {
|
|
// Grab background load so other threads know we could be
|
|
// loading a texture.
|
|
FastCriticalSectionClass::LockClass lock(_BackgroundCriticalSection);
|
|
|
|
// try to remove a task from the background queue. This could fail
|
|
// if another thread modified the queue between our test above and
|
|
// grabbing the lock.
|
|
TextureLoadTaskClass* task = _BackgroundQueue.Pop_Front();
|
|
if (task) {
|
|
// verify task is in proper state for background processing.
|
|
WWASSERT(task->Get_Type() == TextureLoadTaskClass::TASK_LOAD);
|
|
WWASSERT(task->Get_State() == TextureLoadTaskClass::STATE_LOAD_BEGUN);
|
|
|
|
// load mip map levels and return to foreground queue for final step.
|
|
task->Load();
|
|
_ForegroundQueue.Push_Back(task);
|
|
}
|
|
}
|
|
|
|
Switch_Thread();
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TextureLoaderTaskClass implementation
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TextureLoadTaskClass::TextureLoadTaskClass()
|
|
: Texture (0),
|
|
D3DTexture (0),
|
|
Format (WW3D_FORMAT_UNKNOWN),
|
|
Width (0),
|
|
Height (0),
|
|
MipLevelCount (0),
|
|
Reduction (0),
|
|
Type (TASK_NONE),
|
|
Priority (PRIORITY_LOW),
|
|
State (STATE_NONE)
|
|
{
|
|
// because texture load tasks are pooled, the constructor and destructor
|
|
// don't need to do much. The work of attaching a task to a texture is
|
|
// is done by Init() and Deinit().
|
|
|
|
for (int i = 0; i < TextureClass::MIP_LEVELS_MAX; ++i) {
|
|
LockedSurfacePtr[i] = NULL;
|
|
LockedSurfacePitch[i] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
TextureLoadTaskClass::~TextureLoadTaskClass(void)
|
|
{
|
|
Deinit();
|
|
}
|
|
|
|
|
|
TextureLoadTaskClass *TextureLoadTaskClass::Create(TextureClass *tc, TaskType type, PriorityType priority)
|
|
{
|
|
// recycle or create a new texture load task with the given type
|
|
// and priority, then associate the texture with the task.
|
|
|
|
// pull a load task from front of free list
|
|
TextureLoadTaskClass *task = _FreeList.Pop_Front();
|
|
|
|
// if no tasks on free list, allocate a new task
|
|
if (!task) {
|
|
task = new TextureLoadTaskClass;
|
|
}
|
|
|
|
task->Init(tc, type, priority);
|
|
return task;
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Destroy(void)
|
|
{
|
|
// detach the task from its texture, and return to free pool.
|
|
Deinit();
|
|
_FreeList.Push_Front(this);
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Delete_Free_Pool(void)
|
|
{
|
|
// free memory for every task in the free pool.
|
|
while (TextureLoadTaskClass *task = _FreeList.Pop_Front()) {
|
|
delete task;
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Init(TextureClass* tc, TaskType type, PriorityType priority)
|
|
{
|
|
WWASSERT(tc);
|
|
|
|
// NOTE: we must be in the main thread to avoid corrupting the texture's refcount.
|
|
WWASSERT(TextureLoader::Is_DX8_Thread());
|
|
REF_PTR_SET(Texture, tc);
|
|
|
|
// Make sure texture has a filename.
|
|
WWASSERT(Texture->Get_Full_Path() != "");
|
|
|
|
Type = type;
|
|
Priority = priority;
|
|
State = STATE_NONE;
|
|
|
|
D3DTexture = 0;
|
|
|
|
Format = Texture->Get_Texture_Format();
|
|
Width = 0;
|
|
Height = 0;
|
|
MipLevelCount = Texture->MipLevelCount;
|
|
Reduction = Texture->Get_Reduction();
|
|
|
|
|
|
for (int i = 0; i < TextureClass::MIP_LEVELS_MAX; ++i) {
|
|
LockedSurfacePtr[i] = NULL;
|
|
LockedSurfacePitch[i] = 0;
|
|
}
|
|
|
|
switch (Type) {
|
|
case TASK_THUMBNAIL:
|
|
WWASSERT(Texture->ThumbnailLoadTask == NULL);
|
|
Texture->ThumbnailLoadTask = this;
|
|
break;
|
|
|
|
case TASK_LOAD:
|
|
WWASSERT(Texture->TextureLoadTask == NULL);
|
|
Texture->TextureLoadTask = this;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Deinit()
|
|
{
|
|
// task should not be on any list when it is being detached from texture.
|
|
WWASSERT(Next == NULL);
|
|
WWASSERT(Prev == NULL);
|
|
|
|
WWASSERT(D3DTexture == NULL);
|
|
|
|
for (int i = 0; i < TextureClass::MIP_LEVELS_MAX; ++i) {
|
|
WWASSERT(LockedSurfacePtr[i] == NULL);
|
|
}
|
|
|
|
if (Texture) {
|
|
switch (Type) {
|
|
case TASK_THUMBNAIL:
|
|
WWASSERT(Texture->ThumbnailLoadTask == this);
|
|
Texture->ThumbnailLoadTask = NULL;
|
|
break;
|
|
|
|
case TASK_LOAD:
|
|
WWASSERT(Texture->TextureLoadTask == this);
|
|
Texture->TextureLoadTask = NULL;
|
|
break;
|
|
}
|
|
|
|
// NOTE: we must be in main thread to avoid corrupting Texture's refcount.
|
|
WWASSERT(TextureLoader::Is_DX8_Thread());
|
|
REF_PTR_RELEASE(Texture);
|
|
}
|
|
}
|
|
|
|
|
|
bool TextureLoadTaskClass::Begin_Load(void)
|
|
{
|
|
WWASSERT(TextureLoader::Is_DX8_Thread());
|
|
|
|
bool loaded = false;
|
|
|
|
// if allowed, begin a compressed load
|
|
if (Texture->Is_Compression_Allowed()) {
|
|
loaded = Begin_Compressed_Load();
|
|
}
|
|
|
|
// otherwise, begin an uncompressed load
|
|
if (!loaded) {
|
|
loaded = Begin_Uncompressed_Load();
|
|
}
|
|
|
|
// if not loaded, abort.
|
|
if (!loaded) {
|
|
return false;
|
|
}
|
|
|
|
// lock surfaces in preparation for copy
|
|
Lock_Surfaces();
|
|
|
|
State = STATE_LOAD_BEGUN;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// 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.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
bool TextureLoadTaskClass::Load(void)
|
|
{
|
|
WWMEMLOG(MEM_TEXTURE);
|
|
WWASSERT(Peek_D3D_Texture());
|
|
|
|
bool loaded = false;
|
|
|
|
// if allowed, try to load compressed mipmaps
|
|
if (Texture->Is_Compression_Allowed()) {
|
|
loaded = Load_Compressed_Mipmap();
|
|
}
|
|
|
|
// otherwise, load uncompressed mipmaps
|
|
if (!loaded) {
|
|
loaded = Load_Uncompressed_Mipmap();
|
|
}
|
|
|
|
State = STATE_LOAD_MIPMAP;
|
|
|
|
return loaded;
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::End_Load(void)
|
|
{
|
|
WWASSERT(TextureLoader::Is_DX8_Thread());
|
|
|
|
Unlock_Surfaces();
|
|
Apply(true);
|
|
|
|
State = STATE_LOAD_COMPLETE;
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Finish_Load(void)
|
|
{
|
|
switch (State) {
|
|
// NOTE: fall-through below is intentional.
|
|
|
|
case STATE_NONE:
|
|
if (!Begin_Load()) {
|
|
Apply_Missing_Texture();
|
|
break;
|
|
}
|
|
|
|
case STATE_LOAD_BEGUN:
|
|
Load();
|
|
|
|
case STATE_LOAD_MIPMAP:
|
|
End_Load();
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Apply_Missing_Texture(void)
|
|
{
|
|
WWASSERT(TextureLoader::Is_DX8_Thread());
|
|
WWASSERT(!D3DTexture);
|
|
|
|
D3DTexture = MissingTexture::_Get_Missing_Texture();
|
|
Apply(true);
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Apply(bool initialize)
|
|
{
|
|
WWASSERT(D3DTexture);
|
|
|
|
// Verify that none of the mip levels are locked
|
|
for (unsigned i=0;i<MipLevelCount;++i) {
|
|
WWASSERT(LockedSurfacePtr[i]==NULL);
|
|
}
|
|
|
|
Texture->Apply_New_Surface(D3DTexture, initialize);
|
|
|
|
D3DTexture->Release();
|
|
D3DTexture = NULL;
|
|
}
|
|
|
|
static bool Get_Texture_Information(
|
|
const char* filename,
|
|
unsigned reduction,
|
|
unsigned& w,
|
|
unsigned& h,
|
|
WW3DFormat& format,
|
|
unsigned& mip_count,
|
|
bool compressed)
|
|
{
|
|
ThumbnailClass* thumb=NULL;
|
|
ThumbnailManagerClass* thumb_man=ThumbnailManagerClass::Peek_List().Head();
|
|
while (thumb_man) {
|
|
thumb=thumb_man->Peek_Thumbnail_Instance(filename);
|
|
if (thumb) break;
|
|
thumb_man=thumb_man->Succ();
|
|
}
|
|
if (!thumb) {
|
|
if (compressed) {
|
|
DDSFileClass dds_file(filename, reduction);
|
|
if (!dds_file.Is_Available()) return false;
|
|
|
|
// Destination size will be the next power of two square from the larger width and height...
|
|
w = dds_file.Get_Width(0);
|
|
h = dds_file.Get_Height(0);
|
|
format = dds_file.Get_Format();
|
|
mip_count = dds_file.Get_Mip_Level_Count();
|
|
return true;
|
|
}
|
|
|
|
Targa targa;
|
|
if (TARGA_ERROR_HANDLER(targa.Open(filename, TGA_READMODE), filename)) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int bpp;
|
|
WW3DFormat dest_format;
|
|
Get_WW3D_Format(dest_format,format,bpp,targa);
|
|
|
|
// Destination size will be the next power of two square from the larger width and height...
|
|
w = targa.Header.Width >> reduction;
|
|
h = targa.Header.Height >> reduction;
|
|
mip_count = 0;
|
|
return true;
|
|
}
|
|
|
|
if (compressed &&
|
|
thumb->Get_Original_Texture_Format()!=WW3D_FORMAT_DXT1 &&
|
|
thumb->Get_Original_Texture_Format()!=WW3D_FORMAT_DXT2 &&
|
|
thumb->Get_Original_Texture_Format()!=WW3D_FORMAT_DXT3 &&
|
|
thumb->Get_Original_Texture_Format()!=WW3D_FORMAT_DXT4 &&
|
|
thumb->Get_Original_Texture_Format()!=WW3D_FORMAT_DXT5) {
|
|
return false;
|
|
}
|
|
|
|
w=thumb->Get_Original_Texture_Width() >> reduction;
|
|
h=thumb->Get_Original_Texture_Height() >> reduction;
|
|
mip_count=thumb->Get_Original_Texture_Mip_Level_Count();
|
|
format=thumb->Get_Original_Texture_Format();
|
|
return true;
|
|
}
|
|
|
|
bool TextureLoadTaskClass::Begin_Compressed_Load(void)
|
|
{
|
|
unsigned orig_w,orig_h,orig_mip_count;
|
|
WW3DFormat orig_format;
|
|
if (!Get_Texture_Information(Texture->Get_Full_Path(),Get_Reduction(),orig_w,orig_h,orig_format,orig_mip_count,true)) {
|
|
return false;
|
|
}
|
|
|
|
// Destination size will be the next power of two square from the larger width and height...
|
|
unsigned int width = orig_w;
|
|
unsigned int height = orig_h;
|
|
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 != orig_w || height != orig_h) {
|
|
for (unsigned int i = 1; i < orig_mip_count; ++i) {
|
|
unsigned w=orig_w>>i;
|
|
if (w<4) w=4;
|
|
unsigned h=orig_h>>i;
|
|
if (h<4) h=4;
|
|
unsigned tmp_w=w;
|
|
unsigned tmp_h=h;
|
|
|
|
TextureLoader::Validate_Texture_Size(w,h);
|
|
|
|
if (w == tmp_w && h == tmp_h) {
|
|
Reduction += i;
|
|
width = w;
|
|
height = h;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Width = width;
|
|
Height = height;
|
|
Format = Get_Valid_Texture_Format(orig_format, Texture->Is_Compression_Allowed());
|
|
|
|
unsigned int 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 = orig_mip_count;//dds_file.Get_Mip_Level_Count();
|
|
} else if (mip_level_count > orig_mip_count) {//dds_file.Get_Mip_Level_Count()) {
|
|
mip_level_count = orig_mip_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 int max_mip_level_count = 1;
|
|
unsigned int w = 4;
|
|
unsigned int h = 4;
|
|
|
|
while (w < Width && h < Height) {
|
|
w += w;
|
|
h += h;
|
|
max_mip_level_count++;
|
|
}
|
|
|
|
if (mip_level_count > max_mip_level_count) {
|
|
mip_level_count = max_mip_level_count;
|
|
}
|
|
|
|
D3DTexture = DX8Wrapper::_Create_DX8_Texture(
|
|
Width,
|
|
Height,
|
|
Format,
|
|
(TextureClass::MipCountType)mip_level_count,
|
|
#ifdef USE_MANAGED_TEXTURES
|
|
D3DPOOL_MANAGED);
|
|
#else
|
|
D3DPOOL_SYSTEMMEM);
|
|
#endif
|
|
MipLevelCount = mip_level_count;
|
|
return true;
|
|
}
|
|
|
|
bool TextureLoadTaskClass::Begin_Uncompressed_Load(void)
|
|
{
|
|
unsigned width,height,orig_mip_count;
|
|
WW3DFormat orig_format;
|
|
if (!Get_Texture_Information(Texture->Get_Full_Path(),Get_Reduction(),width,height,orig_format,orig_mip_count,false)) {
|
|
return false;
|
|
}
|
|
|
|
WW3DFormat src_format=orig_format;
|
|
WW3DFormat dest_format=src_format;
|
|
dest_format=Get_Valid_Texture_Format(dest_format,false); // No compressed destination format if reading from 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 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));
|
|
}
|
|
|
|
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,
|
|
#ifdef USE_MANAGED_TEXTURES
|
|
D3DPOOL_MANAGED);
|
|
#else
|
|
D3DPOOL_SYSTEMMEM);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Lock_Surfaces(void)
|
|
{
|
|
MipLevelCount = D3DTexture->GetLevelCount();
|
|
for (unsigned int i = 0; i < MipLevelCount; ++i) {
|
|
D3DLOCKED_RECT locked_rect;
|
|
DX8_ErrorCode(
|
|
D3DTexture->LockRect(
|
|
i,
|
|
&locked_rect,
|
|
NULL,
|
|
0));
|
|
LockedSurfacePtr[i] = (unsigned char *)locked_rect.pBits;
|
|
LockedSurfacePitch[i] = locked_rect.Pitch;
|
|
}
|
|
}
|
|
|
|
|
|
void TextureLoadTaskClass::Unlock_Surfaces(void)
|
|
{
|
|
for (unsigned int i = 0; i < MipLevelCount; ++i) {
|
|
if (LockedSurfacePtr[i]) {
|
|
WWASSERT(ThreadClass::_Get_Current_Thread_ID() == DX8Wrapper::_Get_Main_Thread_ID());
|
|
DX8_ErrorCode(D3DTexture->UnlockRect(i));
|
|
}
|
|
LockedSurfacePtr[i] = NULL;
|
|
}
|
|
|
|
#ifndef USE_MANAGED_TEXTURES
|
|
IDirect3DTexture8* tex = DX8Wrapper::_Create_DX8_Texture(Width, Height, Format, Texture->MipLevelCount,D3DPOOL_DEFAULT);
|
|
DX8CALL(UpdateTexture(D3DTexture,tex));
|
|
D3DTexture->Release();
|
|
D3DTexture=tex;
|
|
WWDEBUG_SAY(("Created non-managed texture (%s)\n",Texture->Get_Full_Path()));
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
bool TextureLoadTaskClass::Load_Compressed_Mipmap(void)
|
|
{
|
|
DDSFileClass dds_file(Texture->Get_Full_Path(), Get_Reduction());
|
|
|
|
// if we can't load from file, indicate rror.
|
|
if (!dds_file.Is_Available() || !dds_file.Load()) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int width = Get_Width();
|
|
unsigned int height = Get_Height();
|
|
|
|
for (unsigned int level = 0; level < Get_Mip_Level_Count(); ++level) {
|
|
WWASSERT(width && height);
|
|
dds_file.Copy_Level_To_Surface(
|
|
level,
|
|
Get_Format(),
|
|
width,
|
|
height,
|
|
Get_Locked_Surface_Ptr(level),
|
|
Get_Locked_Surface_Pitch(level));
|
|
|
|
width >>= 1;
|
|
height >>= 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool TextureLoadTaskClass::Load_Uncompressed_Mipmap(void)
|
|
{
|
|
if (!Get_Mip_Level_Count()) {
|
|
return false;
|
|
}
|
|
|
|
Targa targa;
|
|
if (TARGA_ERROR_HANDLER(targa.Open(Texture->Get_Full_Path(), TGA_READMODE), Texture->Get_Full_Path())) {
|
|
return false;
|
|
}
|
|
|
|
// DX8 uses image upside down compared to TGA
|
|
targa.Header.ImageDescriptor ^= TGAIDF_YORIGIN;
|
|
|
|
WW3DFormat src_format;
|
|
WW3DFormat dest_format;
|
|
unsigned int src_bpp = 0;
|
|
Get_WW3D_Format(dest_format,src_format,src_bpp,targa);
|
|
if (src_format==WW3D_FORMAT_UNKNOWN) return false;
|
|
|
|
dest_format = Get_Format(); // Texture can be requested in different format than the most obvious from the TGA
|
|
|
|
char palette[256*4];
|
|
targa.SetPalette(palette);
|
|
|
|
unsigned int src_width = targa.Header.Width;
|
|
unsigned int src_height = targa.Header.Height;
|
|
unsigned int width = Get_Width();
|
|
unsigned int height = 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())) {
|
|
return false;
|
|
}
|
|
|
|
unsigned char * src_surface = (unsigned char*)targa.GetImage();
|
|
unsigned char * converted_surface = NULL;
|
|
|
|
// No paletted format allowed when generating mipmaps
|
|
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 = new 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 int level = 0; level < Get_Mip_Level_Count(); ++level) {
|
|
WWASSERT(Get_Locked_Surface_Ptr(level));
|
|
BitmapHandlerClass::Copy_Image(
|
|
Get_Locked_Surface_Ptr(level),
|
|
width,
|
|
height,
|
|
Get_Locked_Surface_Pitch(level),
|
|
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;
|
|
}
|
|
|
|
|
|
unsigned char * TextureLoadTaskClass::Get_Locked_Surface_Ptr(unsigned int level)
|
|
{
|
|
WWASSERT(level<MipLevelCount);
|
|
WWASSERT(LockedSurfacePtr[level]);
|
|
return LockedSurfacePtr[level];
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
//
|
|
// Return locked surface pitch (in bytes) 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 int TextureLoadTaskClass::Get_Locked_Surface_Pitch(unsigned int level) const
|
|
{
|
|
WWASSERT(level<MipLevelCount);
|
|
WWASSERT(LockedSurfacePtr[level]);
|
|
return LockedSurfacePitch[level];
|
|
}
|