/* ** 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 . */ #include "ddsfile.h" #include "ffactory.h" #include "bufffile.h" #include "formconv.h" #include "dx8wrapper.h" #include "bitmaphandler.h" #include // ---------------------------------------------------------------------------- DDSFileClass::DDSFileClass(const char* name,unsigned reduction_factor) : DDSMemory(NULL), Width(0), Height(0), FullWidth(0), FullHeight(0), LevelSizes(NULL), LevelOffsets(NULL), MipLevels(0), ReductionFactor(reduction_factor), Format(WW3D_FORMAT_UNKNOWN), DateTime(0) { strncpy(Name,name,sizeof(Name)); // The name could be given in .tga or .dds format, so ensure we're opening .dds... int len=strlen(Name); Name[len-3]='d'; Name[len-2]='d'; Name[len-1]='s'; file_auto_ptr file(_TheFileFactory,Name); if (!file->Is_Available()) { return; } file->Open(); DateTime=file->Get_Date_Time(); char header[4]; file->Read(header,4); // Now, we read DDSURFACEDESC2 defining the compressed data unsigned read_bytes=file->Read(&SurfaceDesc,sizeof(LegacyDDSURFACEDESC2)); // Verify the structure size matches the read size if (read_bytes!=SurfaceDesc.Size) { StringClass tmp(0,true); tmp.Format("File %s loading failed.\nTried to read %d bytes, got %d. (SurfDesc.size=%d)\n",name,sizeof(LegacyDDSURFACEDESC2),read_bytes,SurfaceDesc.Size); WWASSERT_PRINT(0,tmp); return; } Format=D3DFormat_To_WW3DFormat((D3DFORMAT)SurfaceDesc.PixelFormat.FourCC); WWASSERT( Format==WW3D_FORMAT_DXT1 || Format==WW3D_FORMAT_DXT2 || Format==WW3D_FORMAT_DXT3 || Format==WW3D_FORMAT_DXT4 || Format==WW3D_FORMAT_DXT5); MipLevels=SurfaceDesc.MipMapCount; if (MipLevels==0) MipLevels=1; if (MipLevels>ReductionFactor) MipLevels-=ReductionFactor; else { MipLevels=1; ReductionFactor=ReductionFactor-MipLevels; } // Drop the two lowest miplevels! if (MipLevels>2) MipLevels-=2; else MipLevels=1; FullWidth=SurfaceDesc.Width; FullHeight=SurfaceDesc.Height; Width=SurfaceDesc.Width>>ReductionFactor; Height=SurfaceDesc.Height>>ReductionFactor; unsigned level_size=Calculate_DXTC_Surface_Size(SurfaceDesc.Width,SurfaceDesc.Height,Format); unsigned level_offset=0; LevelSizes=new unsigned[MipLevels]; LevelOffsets=new unsigned[MipLevels]; for (unsigned level=0;level16) { // If surface is bigger than one block (8 or 16 bytes)... level_size/=4; } } for (level=0;level16) { // If surface is bigger than one block (8 or 16 bytes)... level_size/=4; } } file->Close(); } // ---------------------------------------------------------------------------- DDSFileClass::~DDSFileClass() { delete[] DDSMemory; delete[] LevelSizes; delete[] LevelOffsets; } unsigned DDSFileClass::Get_Width(unsigned level) const { WWASSERT(level>level; if (width<4) width=4; return width; } unsigned DDSFileClass::Get_Height(unsigned level) const { WWASSERT(level>level; if (height<4) height=4; return height; } const unsigned char* DDSFileClass::Get_Memory_Pointer(unsigned level) const { WWASSERT(levelIs_Available()) { return false; } file->Open(); // Data size is file size minus the header and info block unsigned size=file->Size()-SurfaceDesc.Size-4; // Skip mip levels if reduction factor is not zero unsigned level_size=Calculate_DXTC_Surface_Size(SurfaceDesc.Width,SurfaceDesc.Height,Format); unsigned skipped_offset=0; for (unsigned i=0;i16) { // If surface is bigger than one block (8 or 16 bytes)... level_size/=4; } } // Skip the header and info block and possible unused mip levels unsigned seek_size=file->Seek(SurfaceDesc.Size+4+skipped_offset); WWASSERT(seek_size==(SurfaceDesc.Size+4+skipped_offset)); if (size) { // Allocate memory for the data excluding the headers DDSMemory=new unsigned char[size]; // Read data unsigned read_size=file->Read(DDSMemory,size); // Verify we got all the data WWASSERT(read_size==size); } file->Close(); return true; } // ---------------------------------------------------------------------------- // // Copy mipmap level to D3D surface. The copying is performed using another // Copy_Level_To_Surface function (see below). // // ---------------------------------------------------------------------------- void DDSFileClass::Copy_Level_To_Surface(unsigned level,IDirect3DSurface8* d3d_surface) { WWASSERT(d3d_surface); // Verify that the destination surface size matches the source surface size D3DSURFACE_DESC surface_desc; DX8_ErrorCode(d3d_surface->GetDesc(&surface_desc)); // First lock the surface D3DLOCKED_RECT locked_rect; DX8_ErrorCode(d3d_surface->LockRect(&locked_rect,NULL,0)); Copy_Level_To_Surface( level, D3DFormat_To_WW3DFormat(surface_desc.Format), surface_desc.Width, surface_desc.Height, reinterpret_cast(locked_rect.pBits), locked_rect.Pitch); // Finally, unlock the surface DX8_ErrorCode(d3d_surface->UnlockRect()); } // ---------------------------------------------------------------------------- // // Copy one mipmap level of texture to a memory surface. Surface type conversion // is performed if the destination is of different format. Scaling will be done // one of these days as well. Conversions between different types of compressed // surfaces are not performed and scaling of compressed surfaces is also not // possible. // // ---------------------------------------------------------------------------- void DDSFileClass::Copy_Level_To_Surface( unsigned level, WW3DFormat dest_format, unsigned dest_width, unsigned dest_height, unsigned char* dest_surface, unsigned dest_pitch) { WWASSERT(DDSMemory); WWASSERT(dest_surface); // If the format and size is a match just copy the contents if (dest_format==Format && dest_width==Get_Width(level) && dest_height==Get_Height(level)) { unsigned compressed_size=Get_Level_Size(level); memcpy(dest_surface,Get_Memory_Pointer(level),compressed_size); } else { // If size matches, copy each pixel linearly with color space conversion if (dest_width==Get_Width(level) && dest_height==Get_Height(level)) { // An exception here - if the source format is DXT1 and the destination // is DXT2, just copy the contents and create an empty alpha channel. // This is needed on NVidia cards that have problems with DXT1 compression. if (Format==WW3D_FORMAT_DXT1 && dest_format==WW3D_FORMAT_DXT2) { const unsigned* src_ptr=reinterpret_cast(Get_Memory_Pointer(level)); unsigned* dest_ptr=reinterpret_cast(dest_surface); for (unsigned y=0;y>=8; r_b_col1&=R_B_MASK; unsigned g_col1=col1&G_MASK; g_col1*=rel; unsigned g_col2=col2&G_MASK; g_col2*=rel2; g_col1+=g_col2; g_col1>>=8; g_col1&=G_MASK; return r_b_col1|g_col1; /* float f=float(rel)/256.0f; unsigned new_col=0; new_col|=int(float(int(col1&0x00ff0000))*f+float(int(col2&0x00ff0000))*(1.0f-f))&0x00ff0000; new_col|=int(float(int(col1&0x0000ff00))*f+float(int(col2&0x0000ff00))*(1.0f-f))&0x0000ff00; new_col|=int(float(int(col1&0x000000ff))*f+float(int(col2&0x000000ff))*(1.0f-f))&0x000000ff; return new_col; */ } // ---------------------------------------------------------------------------- // // Note that this is NOT an efficient way of extracting pixels from compressed image - we should implement // faster block-copy method for non-scaled copying. // // ---------------------------------------------------------------------------- unsigned DDSFileClass::Get_Pixel(unsigned level,unsigned x,unsigned y) const { WWASSERT(level>=(x%4)*2; line=(line&3); if (col0>col1) { switch (line) { case 0: return col0|0xff000000; case 1: return col1|0xff000000; case 2: return Combine_Colors(col1,col0,85)|0xff000000; case 3: return Combine_Colors(col0,col1,85)|0xff000000; } } else { switch (line) { case 0: return col0|0xff000000; case 1: return col1|0xff000000; case 2: return Combine_Colors(col1,col0,128)|0xff000000; case 3: return 0x00000000; } } } break; case WW3D_FORMAT_DXT2: return 0xffffffff; case WW3D_FORMAT_DXT3: return 0xffffffff; case WW3D_FORMAT_DXT4: return 0xffffffff; case WW3D_FORMAT_DXT5: { const unsigned char* alpha_block=Get_Memory_Pointer(level)+(x/4)*16+((y/4)*(Get_Width(level)/4))*16; unsigned alpha0=alpha_block[0]; unsigned alpha1=alpha_block[1]; unsigned bit_idx=((x%4)+4*(y%4))*3; unsigned byte_idx=bit_idx/8; bit_idx%=8; unsigned alpha_index=0; for (int i=0;i<3;++i) { WWASSERT(byte_idx<6); unsigned alpha_bit=(alpha_block[2+byte_idx]>>(bit_idx))&1; alpha_index|=alpha_bit<<(i); bit_idx++; if (bit_idx>=8) { bit_idx=0; byte_idx++; } } WWASSERT(alpha_index<8); // 8-alpha or 6-alpha block? unsigned alpha_value=0; if (alpha0>alpha1) { // 8-alpha block: derive the other six alphas. // Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated. switch (alpha_index) { case 0: alpha_value=alpha0; break; case 1: alpha_value=alpha1; break; case 2: alpha_value=(6*alpha0+1*alpha1+3) / 7; break; // bit code 010 case 3: alpha_value=(5*alpha0+2*alpha1+3) / 7; break; // bit code 011 case 4: alpha_value=(4*alpha0+3*alpha1+3) / 7; break; // bit code 100 case 5: alpha_value=(3*alpha0+4*alpha1+3) / 7; break; // bit code 101 case 6: alpha_value=(2*alpha0+5*alpha1+3) / 7; break; // bit code 110 case 7: alpha_value=(1*alpha0+6*alpha1+3) / 7; break; // bit code 111 } } else { // 6-alpha block. // Bit code 000 = alpha_0, 001 = alpha_1, others are interpolated. switch (alpha_index) { case 0: alpha_value=alpha0; break; case 1: alpha_value=alpha1; break; case 2: alpha_value=(4*alpha0+1*alpha1+2) / 5; break; // Bit code 010 case 3: alpha_value=(3*alpha0+2*alpha1+2) / 5; break; // Bit code 011 case 4: alpha_value=(2*alpha0+3*alpha1+2) / 5; break; // Bit code 100 case 5: alpha_value=(1*alpha0+4*alpha1+2) / 5; break; // Bit code 101 case 6: alpha_value=0; break; // Bit code 110 case 7: alpha_value=255; break; // Bit code 111 } } alpha_value<<=24; // Extract color const unsigned char* color_block=alpha_block+8; unsigned col0=RGB565_To_ARGB8888(*(unsigned short*)&color_block[0]); unsigned col1=RGB565_To_ARGB8888(*(unsigned short*)&color_block[2]); unsigned char line=color_block[4+(y%4)]; line>>=(x%4)*2; line=(line&3); switch (line) { case 0: return col0|alpha_value; case 1: return col1|alpha_value; case 2: return Combine_Colors(col1,col0,85)|alpha_value; case 3: return Combine_Colors(col0,col1,85)|alpha_value; } } break; } return 0xffffffff; } // ---------------------------------------------------------------------------- // // Uncompress one 4x4 block from the compressed image. // // Returns: true if block contained alpha, false is not // // Note: Destination can't be DXT or paletted surface! // // ---------------------------------------------------------------------------- bool DDSFileClass::Get_4x4_Block( unsigned char* dest_ptr, // Destination surface pointer unsigned dest_pitch, // Destination surface pitch, in bytes WW3DFormat dest_format, // Destination surface format, A8R8G8B8 is fastest unsigned level, // DDS mipmap level to copy from unsigned source_x, // DDS x offset to copy from, must be aligned by 4! unsigned source_y) const // DDS y offset to copy from, must be aligned by 4! { // Verify the block alignment WWASSERT((source_x&3)==0); WWASSERT((source_y&3)==0); // Verify level WWASSERT(levelcol1) { for (int y=0;y<4;++y) { unsigned char* tmp_dest_ptr=dest_ptr; dest_ptr+=dest_pitch; unsigned char line=block_memory[4+y]; for (int x=0;x<4;++x) { switch (line&3) { case 0: dest_pixel=col0|0xff000000; break; case 1: dest_pixel=col1|0xff000000; break; case 2: dest_pixel=Combine_Colors(col1,col0,85)|0xff000000; break; case 3: dest_pixel=Combine_Colors(col0,col1,85)|0xff000000; break; } line>>=2; BitmapHandlerClass::Write_B8G8R8A8(tmp_dest_ptr,dest_format,dest_pixel); tmp_dest_ptr+=dest_bpp; } } return false; // No alpha found in the block } else { bool contains_alpha=false; for (int y=0;y<4;++y) { unsigned char* tmp_dest_ptr=dest_ptr; dest_ptr+=dest_pitch; unsigned char line=block_memory[4+y]; for (int x=0;x<4;++x) { switch (line&3) { case 0: dest_pixel=col0|0xff000000; break; case 1: dest_pixel=col1|0xff000000; break; case 2: dest_pixel=Combine_Colors(col1,col0,128)|0xff000000; break; case 3: dest_pixel=0x00000000; contains_alpha=true; break; } line>>=2; BitmapHandlerClass::Write_B8G8R8A8(tmp_dest_ptr,dest_format,dest_pixel); tmp_dest_ptr+=dest_bpp; } } return contains_alpha; // Alpha block...? } } break; case WW3D_FORMAT_DXT2: return false; case WW3D_FORMAT_DXT3: return false; case WW3D_FORMAT_DXT4: return false; case WW3D_FORMAT_DXT5: { // Init alphas const unsigned char* alpha_block=Get_Memory_Pointer(level)+(source_x/4)*16+((source_y/4)*(Get_Width(level)/4))*16; unsigned alphas[8]; alphas[0]=alpha_block[0]; alphas[1]=alpha_block[1]; // 8-alpha or 6-alpha block? if (alphas[0]>alphas[1]) { alphas[2]=(6*alphas[0]+1*alphas[1]+3) / 7; // bit code 010 alphas[3]=(5*alphas[0]+2*alphas[1]+3) / 7; // bit code 011 alphas[4]=(4*alphas[0]+3*alphas[1]+3) / 7; // bit code 100 alphas[5]=(3*alphas[0]+4*alphas[1]+3) / 7; // bit code 101 alphas[6]=(2*alphas[0]+5*alphas[1]+3) / 7; // bit code 110 alphas[7]=(1*alphas[0]+6*alphas[1]+3) / 7; // bit code 111 } else { alphas[2]=(4*alphas[0]+1*alphas[1]+2) / 5; // Bit code 010 alphas[3]=(3*alphas[0]+2*alphas[1]+2) / 5; // Bit code 011 alphas[4]=(2*alphas[0]+3*alphas[1]+2) / 5; // Bit code 100 alphas[5]=(1*alphas[0]+4*alphas[1]+2) / 5; // Bit code 101 alphas[6]=0; // Bit code 110 alphas[7]=255; // Bit code 111 } // Init colors const unsigned char* color_block=alpha_block+8; unsigned col0=RGB565_To_ARGB8888(*(unsigned short*)&color_block[0]); unsigned col1=RGB565_To_ARGB8888(*(unsigned short*)&color_block[2]); unsigned dest_pixel=0; unsigned bit_idx=0; unsigned contains_alpha=0xff; unsigned alpha_indices[16]; unsigned* ai_ptr=alpha_indices; for (int a=0;a<2;++a) { ai_ptr[0]=alpha_block[2]&0x7; ai_ptr[1]=(alpha_block[2]>>3)&0x7; ai_ptr[2]=(alpha_block[2]>>6)|((alpha_block[3]&1)<<2); ai_ptr[3]=(alpha_block[3]>>1)&0x7; ai_ptr[4]=(alpha_block[3]>>4)&0x7; ai_ptr[5]=(alpha_block[3]>>7)|((alpha_block[4]&3)<<1); ai_ptr[6]=(alpha_block[4]>>2)&0x7; ai_ptr[7]=(alpha_block[4]>>5); ai_ptr+=8; alpha_block+=3; } unsigned aii=0; for (int y=0;y<4;++y) { unsigned char* tmp_dest_ptr=dest_ptr; dest_ptr+=dest_pitch; unsigned char line=color_block[4+y]; for (int x=0;x<4;++x,bit_idx+=3) { unsigned alpha_value=alphas[alpha_indices[aii++]]; contains_alpha&=alpha_value; alpha_value<<=24; // Extract color switch (line&3) { case 0: dest_pixel=col0|alpha_value; break; case 1: dest_pixel=col1|alpha_value; break; case 2: dest_pixel=Combine_Colors(col1,col0,85)|alpha_value; break; case 3: dest_pixel=Combine_Colors(col0,col1,85)|alpha_value; break; } line>>=2; BitmapHandlerClass::Write_B8G8R8A8(tmp_dest_ptr,dest_format,dest_pixel); tmp_dest_ptr+=dest_bpp; } } /* for (int y=0;y<4;++y) { unsigned char* tmp_dest_ptr=dest_ptr; dest_ptr+=dest_pitch; unsigned char line=color_block[4+y]; for (int x=0;x<4;++x,bit_idx+=3) { unsigned byte_idx=bit_idx/8; unsigned tmp_bit_idx=bit_idx&7; unsigned alpha_index=0; for (int i=0;i<3;++i) { WWASSERT(byte_idx<6); unsigned alpha_bit=(alpha_block[2+byte_idx]>>(tmp_bit_idx))&1; alpha_index|=alpha_bit<<(i); tmp_bit_idx++; if (tmp_bit_idx>=8) { tmp_bit_idx=0; byte_idx++; } } WWASSERT(alpha_index<8); unsigned alpha_value=alphas[alpha_index]; contains_alpha&=alpha_value; alpha_value<<=24; // Extract color switch (line&3) { case 0: dest_pixel=col0|alpha_value; break; case 1: dest_pixel=col1|alpha_value; break; case 2: dest_pixel=Combine_Colors(col1,col0,85)|alpha_value; break; case 3: dest_pixel=Combine_Colors(col0,col1,85)|alpha_value; break; } line>>=2; BitmapHandlerClass::Write_B8G8R8A8(tmp_dest_ptr,dest_format,dest_pixel); tmp_dest_ptr+=dest_bpp; } } */ return contains_alpha!=0xff; // Alpha block... DXT5 should only be used when the image needs alpha // but for now check anyway... } } return false; }