2023-03-09 21:19:20 +01:00
// Copyright (c) 2016- PPSSPP Project.
// 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, version 2.0 or later versions.
// 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 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
2023-03-12 16:46:19 -07:00
# include <algorithm>
2023-03-13 14:26:28 +01:00
2023-03-09 21:19:20 +01:00
# include "ppsspp_config.h"
# include <png.h>
2023-03-12 23:50:30 +01:00
# include "ext/basis_universal/basisu_transcoder.h"
2023-03-13 14:26:28 +01:00
# include "ext/basis_universal/basisu_file_headers.h"
2023-03-12 23:50:30 +01:00
2023-03-09 21:11:53 +01:00
# include "GPU/Common/ReplacedTexture.h"
2023-03-09 21:19:20 +01:00
# include "GPU/Common/TextureReplacer.h"
# include "Common/Data/Format/IniFile.h"
2023-03-11 11:34:28 +01:00
# include "Common/Data/Format/DDSLoad.h"
2023-03-09 21:19:20 +01:00
# include "Common/Data/Format/ZIMLoad.h"
# include "Common/Data/Format/PNGLoad.h"
# include "Common/Thread/ParallelLoop.h"
# include "Common/Thread/Waitable.h"
# include "Common/Thread/ThreadManager.h"
# include "Common/Log.h"
# include "Common/TimeUtil.h"
2023-02-26 19:51:59 +01:00
# define MK_FOURCC(str) (str[0] | ((uint8_t)str[1] << 8) | ((uint8_t)str[2] << 16) | ((uint8_t)str[3] << 24))
static ReplacedImageType IdentifyMagic ( const uint8_t magic [ 4 ] ) {
2023-03-13 16:20:01 +01:00
if ( memcmp ( ( const char * ) magic , " ZIMG " , 4 ) = = 0 )
2023-02-26 19:51:59 +01:00
return ReplacedImageType : : ZIM ;
2023-03-13 16:20:01 +01:00
else if ( magic [ 0 ] = = 0x89 & & strncmp ( ( const char * ) & magic [ 1 ] , " PNG " , 3 ) = = 0 )
2023-02-26 19:51:59 +01:00
return ReplacedImageType : : PNG ;
2023-03-13 16:20:01 +01:00
else if ( memcmp ( ( const char * ) magic , " DDS " , 4 ) = = 0 )
2023-02-26 19:51:59 +01:00
return ReplacedImageType : : DDS ;
2023-03-13 16:20:01 +01:00
else if ( magic [ 0 ] = = ' s ' & & magic [ 1 ] = = ' B ' ) {
2023-03-13 14:26:28 +01:00
uint16_t ver = magic [ 2 ] | ( magic [ 3 ] < < 8 ) ;
if ( ver > = 0x10 ) {
return ReplacedImageType : : BASIS ;
}
2023-03-13 16:20:01 +01:00
} else if ( memcmp ( ( const char * ) magic , " \xab KTX " , 4 ) = = 0 ) {
// Technically, should read 12 bytes here, but this'll do.
return ReplacedImageType : : KTX2 ;
2023-03-13 14:26:28 +01:00
}
2023-02-26 19:51:59 +01:00
return ReplacedImageType : : INVALID ;
}
static ReplacedImageType Identify ( VFSBackend * vfs , VFSOpenFile * openFile , std : : string * outMagic ) {
uint8_t magic [ 4 ] ;
if ( vfs - > Read ( openFile , magic , 4 ) ! = 4 ) {
* outMagic = " FAIL " ;
return ReplacedImageType : : INVALID ;
}
// Turn the signature into a readable string that we can display in an error message.
* outMagic = std : : string ( ( const char * ) magic , 4 ) ;
for ( int i = 0 ; i < outMagic - > size ( ) ; i + + ) {
if ( ( s8 ) ( * outMagic ) [ i ] < 32 ) {
( * outMagic ) [ i ] = ' _ ' ;
}
}
vfs - > Rewind ( openFile ) ;
return IdentifyMagic ( magic ) ;
}
2023-03-09 21:19:20 +01:00
class ReplacedTextureTask : public Task {
public :
ReplacedTextureTask ( VFSBackend * vfs , ReplacedTexture & tex , LimitedWaitable * w ) : vfs_ ( vfs ) , tex_ ( tex ) , waitable_ ( w ) { }
2023-03-13 14:26:28 +01:00
TaskType Type ( ) const override { return TaskType : : IO_BLOCKING ; }
TaskPriority Priority ( ) const override { return TaskPriority : : NORMAL ; }
2023-03-09 21:19:20 +01:00
void Run ( ) override {
tex_ . Prepare ( vfs_ ) ;
waitable_ - > Notify ( ) ;
}
private :
VFSBackend * vfs_ ;
ReplacedTexture & tex_ ;
LimitedWaitable * waitable_ ;
} ;
2023-03-16 11:44:38 +01:00
ReplacedTexture : : ReplacedTexture ( VFSBackend * vfs , const ReplacementDesc & desc ) : vfs_ ( vfs ) , desc_ ( desc ) {
logId_ = desc . logId ;
}
2023-03-12 13:30:28 +01:00
ReplacedTexture : : ~ ReplacedTexture ( ) {
if ( threadWaitable_ ) {
SetState ( ReplacementState : : CANCEL_INIT ) ;
threadWaitable_ - > WaitAndRelease ( ) ;
threadWaitable_ = nullptr ;
}
for ( auto & level : levels_ ) {
vfs_ - > ReleaseFile ( level . fileRef ) ;
level . fileRef = nullptr ;
}
}
2023-03-16 11:53:39 +01:00
void ReplacedTexture : : PurgeIfNotUsedSinceTime ( double t ) {
if ( State ( ) ! = ReplacementState : : ACTIVE ) {
2023-03-12 13:30:28 +01:00
return ;
2023-03-16 11:53:39 +01:00
}
// If there's some leftover threadWaitable, get rid of it.
if ( threadWaitable_ ) {
if ( threadWaitable_ - > WaitFor ( 0.0 ) ) {
delete threadWaitable_ ;
threadWaitable_ = nullptr ;
// Continue with purging.
} else {
// Try next time.
return ;
}
}
2023-03-16 12:04:14 +01:00
// This is the only place except shutdown where a texture can transition
// from ACTIVE to anything else, so we don't actually need to lock here.
2023-03-16 11:53:39 +01:00
if ( lastUsed_ > = t ) {
2023-03-12 13:30:28 +01:00
return ;
2023-03-16 11:53:39 +01:00
}
2023-03-12 13:30:28 +01:00
2023-03-16 11:53:39 +01:00
data_ . clear ( ) ;
levels_ . clear ( ) ;
fmt = Draw : : DataFormat : : UNDEFINED ;
alphaStatus_ = ReplacedTextureAlpha : : UNKNOWN ;
// This means we have to reload. If we never purge any, there's no need.
2023-03-17 00:52:38 +01:00
SetState ( ReplacementState : : UNLOADED ) ;
2023-03-12 13:30:28 +01:00
}
2023-03-10 14:16:14 +01:00
// This can only return true if ACTIVE or NOT_FOUND.
2023-03-18 12:01:24 +01:00
bool ReplacedTexture : : Poll ( double budget ) {
2023-03-09 21:19:20 +01:00
_assert_ ( vfs_ ! = nullptr ) ;
2023-03-10 13:43:48 +01:00
2023-03-11 21:46:01 +01:00
double now = time_now_d ( ) ;
2023-03-10 13:43:48 +01:00
switch ( State ( ) ) {
case ReplacementState : : ACTIVE :
case ReplacementState : : NOT_FOUND :
if ( threadWaitable_ ) {
if ( ! threadWaitable_ - > WaitFor ( budget ) ) {
2023-03-11 21:46:01 +01:00
lastUsed_ = now ;
2023-03-10 13:43:48 +01:00
return false ;
}
// Successfully waited! Can get rid of it.
threadWaitable_ - > WaitAndRelease ( ) ;
threadWaitable_ = nullptr ;
2023-03-16 11:44:38 +01:00
lastUsed = now ;
2023-03-10 13:43:48 +01:00
}
2023-03-11 21:46:01 +01:00
lastUsed_ = now ;
2023-03-10 13:43:48 +01:00
return true ;
case ReplacementState : : CANCEL_INIT :
case ReplacementState : : PENDING :
return false ;
2023-03-17 00:52:38 +01:00
case ReplacementState : : UNLOADED :
2023-03-10 13:43:48 +01:00
// We're gonna need to spawn a task.
break ;
2024-12-07 00:34:47 +01:00
default :
break ;
2023-03-09 21:19:20 +01:00
}
2023-03-11 21:46:01 +01:00
lastUsed_ = now ;
2023-03-09 21:19:20 +01:00
// Let's not even start a new texture if we're already behind.
if ( budget < 0.0 )
return false ;
2023-03-10 13:43:48 +01:00
_assert_ ( ! threadWaitable_ ) ;
2023-03-09 21:19:20 +01:00
threadWaitable_ = new LimitedWaitable ( ) ;
2023-03-10 20:31:37 +01:00
SetState ( ReplacementState : : PENDING ) ;
2023-03-09 21:19:20 +01:00
g_threadManager . EnqueueTask ( new ReplacedTextureTask ( vfs_ , * this , threadWaitable_ ) ) ;
if ( threadWaitable_ - > WaitFor ( budget ) ) {
2023-03-10 13:43:48 +01:00
// If we successfully wait here, we're done. The thread will set state accordingly.
2023-03-10 14:58:44 +01:00
_assert_ ( State ( ) = = ReplacementState : : ACTIVE | | State ( ) = = ReplacementState : : NOT_FOUND | | State ( ) = = ReplacementState : : CANCEL_INIT ) ;
2023-03-15 16:18:09 +01:00
delete threadWaitable_ ;
threadWaitable_ = nullptr ;
2023-03-10 13:43:48 +01:00
return true ;
2023-03-09 21:19:20 +01:00
}
// Still pending on thread.
return false ;
}
2023-03-17 13:58:36 +01:00
inline uint32_t RoundUpTo4 ( uint32_t value ) {
return ( value + 3 ) & ~ 3 ;
}
2023-03-09 21:19:20 +01:00
void ReplacedTexture : : Prepare ( VFSBackend * vfs ) {
2024-10-02 00:58:50 +02:00
_assert_ ( vfs ! = nullptr ) ;
2023-03-09 21:19:20 +01:00
this - > vfs_ = vfs ;
2023-03-10 13:43:48 +01:00
2023-03-16 11:44:38 +01:00
std : : unique_lock < std : : mutex > lock ( lock_ ) ;
2023-03-10 23:28:11 +01:00
2023-03-14 18:34:22 +01:00
fmt = Draw : : DataFormat : : UNDEFINED ;
2023-03-13 16:20:01 +01:00
Draw : : DataFormat pixelFormat ;
2023-03-14 18:34:22 +01:00
LoadLevelResult result = LoadLevelResult : : LOAD_ERROR ;
2023-03-16 11:44:38 +01:00
if ( desc_ . filenames . empty ( ) ) {
2023-03-14 18:34:22 +01:00
result = LoadLevelResult : : DONE ;
}
2023-03-16 11:44:38 +01:00
for ( int i = 0 ; i < std : : min ( MAX_REPLACEMENT_MIP_LEVELS , ( int ) desc_ . filenames . size ( ) ) ; + + i ) {
2023-03-10 20:41:43 +01:00
if ( State ( ) = = ReplacementState : : CANCEL_INIT ) {
break ;
}
2023-03-16 11:44:38 +01:00
if ( desc_ . filenames [ i ] . empty ( ) ) {
2023-03-10 20:31:37 +01:00
// Out of valid mip levels. Bail out.
break ;
}
2023-03-16 11:44:38 +01:00
VFSFileReference * fileRef = vfs_ - > GetFile ( desc_ . filenames [ i ] . c_str ( ) ) ;
2023-03-10 20:31:37 +01:00
if ( ! fileRef ) {
2023-03-27 15:43:18 +02:00
if ( i = = 0 ) {
2024-11-25 23:25:05 +01:00
INFO_LOG ( Log : : TexReplacement , " Texture replacement file '%s' not found in %s " , desc_ . filenames [ i ] . c_str ( ) , vfs_ - > toString ( ) . c_str ( ) ) ;
2023-03-27 15:43:18 +02:00
// No file at all. Mark as NOT_FOUND.
SetState ( ReplacementState : : NOT_FOUND ) ;
return ;
}
2023-03-10 20:31:37 +01:00
// If the file doesn't exist, let's just bail immediately here.
2023-03-14 18:34:22 +01:00
// Mark as DONE, not error.
result = LoadLevelResult : : DONE ;
2023-03-10 20:31:37 +01:00
break ;
}
if ( i = = 0 ) {
fmt = Draw : : DataFormat : : R8G8B8A8_UNORM ;
}
2023-03-16 11:44:38 +01:00
result = LoadLevelData ( fileRef , desc_ . filenames [ i ] , i , & pixelFormat ) ;
2023-03-14 18:34:22 +01:00
if ( result = = LoadLevelResult : : DONE ) {
// Loaded all the levels we're gonna get.
fmt = pixelFormat ;
break ;
} else if ( result = = LoadLevelResult : : CONTINUE ) {
2023-03-11 11:34:28 +01:00
if ( i = = 0 ) {
fmt = pixelFormat ;
} else {
if ( fmt ! = pixelFormat ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Replacement mipmap %d doesn't have the same pixel format as mipmap 0. Stopping. " , i ) ;
2023-03-11 11:34:28 +01:00
break ;
}
}
2023-03-10 23:36:00 +01:00
} else {
2023-03-14 18:34:22 +01:00
// Error state.
2023-03-10 20:31:37 +01:00
break ;
2023-03-10 23:36:00 +01:00
}
2023-03-10 20:31:37 +01:00
}
if ( levels_ . empty ( ) ) {
2023-03-14 17:52:40 +01:00
// No replacement found.
2023-03-16 11:44:38 +01:00
std : : string name = TextureReplacer : : HashName ( desc_ . cachekey , desc_ . hash , 0 ) ;
2023-03-14 18:34:22 +01:00
if ( result = = LoadLevelResult : : LOAD_ERROR ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Failed to load replacement texture '%s' " , name . c_str ( ) ) ;
2023-03-14 18:34:22 +01:00
}
2023-03-10 20:31:37 +01:00
SetState ( ReplacementState : : NOT_FOUND ) ;
return ;
}
2023-03-17 13:08:35 +01:00
// Update the level dimensions.
for ( auto & level : levels_ ) {
level . fullW = ( level . w * desc_ . w ) / desc_ . newW ;
level . fullH = ( level . h * desc_ . h ) / desc_ . newH ;
2023-03-17 13:58:36 +01:00
int blockSize ;
bool bc = Draw : : DataFormatIsBlockCompressed ( fmt , & blockSize ) ;
if ( ! bc ) {
level . fullDataSize = level . fullW * level . fullH * 4 ;
} else {
level . fullDataSize = RoundUpTo4 ( level . fullW ) * RoundUpTo4 ( level . fullH ) * blockSize / 16 ;
}
2023-03-17 13:08:35 +01:00
}
2023-03-10 20:41:43 +01:00
SetState ( ReplacementState : : ACTIVE ) ;
2023-03-09 21:19:20 +01:00
2023-03-27 15:43:18 +02:00
// the caller calls threadWaitable->notify().
2023-03-09 21:19:20 +01:00
}
2023-03-12 16:14:42 +01:00
// Returns true if Prepare should keep calling this to load more levels.
2023-03-14 18:34:22 +01:00
ReplacedTexture : : LoadLevelResult ReplacedTexture : : LoadLevelData ( VFSFileReference * fileRef , const std : : string & filename , int mipLevel , Draw : : DataFormat * pixelFormat ) {
2023-03-10 21:06:33 +01:00
bool good = false ;
2023-03-16 11:44:38 +01:00
if ( data_ . size ( ) < = mipLevel ) {
data_ . resize ( mipLevel + 1 ) ;
2023-03-11 12:22:20 +01:00
}
2024-10-02 00:58:50 +02:00
if ( ! vfs_ ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Unexpected null vfs_ pointer in LoadLevelData " ) ;
2024-10-02 00:58:50 +02:00
return LoadLevelResult : : LOAD_ERROR ;
}
2023-03-12 23:04:50 +01:00
ReplacedTextureLevel level ;
2023-03-10 21:06:33 +01:00
size_t fileSize ;
2023-03-12 23:04:50 +01:00
VFSOpenFile * openFile = vfs_ - > OpenFileForRead ( fileRef , & fileSize ) ;
2023-03-10 23:36:00 +01:00
if ( ! openFile ) {
2023-03-14 18:34:22 +01:00
// File missing, no more levels. This is alright.
return LoadLevelResult : : DONE ;
2023-03-10 21:06:33 +01:00
}
std : : string magic ;
2023-03-10 23:36:00 +01:00
ReplacedImageType imageType = Identify ( vfs_ , openFile , & magic ) ;
2023-03-10 21:06:33 +01:00
2023-03-11 12:22:20 +01:00
bool ddsDX10 = false ;
int numMips = 1 ;
2023-03-13 16:20:01 +01:00
if ( imageType = = ReplacedImageType : : KTX2 ) {
KTXHeader header ;
good = vfs_ - > Read ( openFile , & header , sizeof ( header ) ) = = sizeof ( header ) ;
level . w = header . pixelWidth ;
level . h = header . pixelHeight ;
numMips = header . levelCount ;
// Additional quick checks
good = good & & header . layerCount < = 1 ;
} else if ( imageType = = ReplacedImageType : : BASIS ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " The basis texture format is not supported. Use KTX2 (basisu texture.png -uastc -ktx2 -mipmap) " ) ;
2023-03-13 14:26:28 +01:00
2023-03-14 14:09:21 +01:00
// We simply don't support basis files currently.
2023-03-13 14:26:28 +01:00
good = false ;
} else if ( imageType = = ReplacedImageType : : DDS ) {
2023-03-11 11:34:28 +01:00
DDSHeader header ;
DDSHeaderDXT10 header10 { } ;
good = vfs_ - > Read ( openFile , & header , sizeof ( header ) ) = = sizeof ( header ) ;
* pixelFormat = Draw : : DataFormat : : UNDEFINED ;
u32 format ;
if ( good & & ( header . ddspf . dwFlags & DDPF_FOURCC ) ) {
char * fcc = ( char * ) & header . ddspf . dwFourCC ;
2024-11-25 23:25:05 +01:00
// INFO_LOG(Log::TexReplacement, "DDS fourcc: %c%c%c%c", fcc[0], fcc[1], fcc[2], fcc[3]);
2023-03-11 12:22:20 +01:00
if ( header . ddspf . dwFourCC = = MK_FOURCC ( " DX10 " ) ) {
ddsDX10 = true ;
2023-03-11 11:34:28 +01:00
good = good & & vfs_ - > Read ( openFile , & header10 , sizeof ( header10 ) ) = = sizeof ( header10 ) ;
format = header10 . dxgiFormat ;
switch ( format ) {
2023-05-01 20:55:31 +02:00
case 71 : // DXGI_FORMAT_BC1_UNORM
case 72 : // DXGI_FORMAT_BC1_UNORM_SRGB
if ( ! desc_ . formatSupport . bc123 ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " BC1 format not supported, skipping texture " ) ;
2023-05-01 20:55:31 +02:00
good = false ;
}
* pixelFormat = Draw : : DataFormat : : BC1_RGBA_UNORM_BLOCK ;
break ;
case 74 : // DXGI_FORMAT_BC2_UNORM
case 75 : // DXGI_FORMAT_BC2_UNORM_SRGB
if ( ! desc_ . formatSupport . bc123 ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " BC2 format not supported, skipping texture " ) ;
2023-05-01 20:55:31 +02:00
good = false ;
}
* pixelFormat = Draw : : DataFormat : : BC2_UNORM_BLOCK ;
break ;
case 77 : // DXGI_FORMAT_BC3_UNORM
case 78 : // DXGI_FORMAT_BC3_UNORM_SRGB
if ( ! desc_ . formatSupport . bc123 ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " BC3 format not supported, skipping texture " ) ;
2023-05-01 20:55:31 +02:00
good = false ;
}
* pixelFormat = Draw : : DataFormat : : BC3_UNORM_BLOCK ;
break ;
2023-03-11 11:34:28 +01:00
case 98 : // DXGI_FORMAT_BC7_UNORM:
case 99 : // DXGI_FORMAT_BC7_UNORM_SRGB:
2023-03-16 11:44:38 +01:00
if ( ! desc_ . formatSupport . bc7 ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " BC7 format not supported, skipping texture " ) ;
2023-03-12 13:19:01 +01:00
good = false ;
}
2023-03-11 11:34:28 +01:00
* pixelFormat = Draw : : DataFormat : : BC7_UNORM_BLOCK ;
break ;
default :
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " DXGI pixel format %d not supported. " , header10 . dxgiFormat ) ;
2023-03-11 11:34:28 +01:00
good = false ;
}
} else {
2023-03-16 11:44:38 +01:00
if ( ! desc_ . formatSupport . bc123 ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " BC1-3 formats not supported " ) ;
2023-03-12 13:19:01 +01:00
good = false ;
}
2023-03-11 11:34:28 +01:00
format = header . ddspf . dwFourCC ;
// OK, there are a number of possible formats we might have ended up with. We choose just a few
// to support for now.
switch ( format ) {
case MK_FOURCC ( " DXT1 " ) :
* pixelFormat = Draw : : DataFormat : : BC1_RGBA_UNORM_BLOCK ;
break ;
case MK_FOURCC ( " DXT3 " ) :
* pixelFormat = Draw : : DataFormat : : BC2_UNORM_BLOCK ;
break ;
case MK_FOURCC ( " DXT5 " ) :
* pixelFormat = Draw : : DataFormat : : BC3_UNORM_BLOCK ;
break ;
default :
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " DDS pixel format not supported. " ) ;
2023-03-11 11:34:28 +01:00
good = false ;
}
}
} else if ( good ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " DDS non-fourCC format not supported. " ) ;
2023-03-11 11:34:28 +01:00
good = false ;
}
level . w = header . dwWidth ;
level . h = header . dwHeight ;
2023-03-11 12:22:20 +01:00
numMips = header . dwMipMapCount ;
2023-03-11 11:34:28 +01:00
} else if ( imageType = = ReplacedImageType : : ZIM ) {
2023-03-10 21:06:33 +01:00
uint32_t ignore = 0 ;
struct ZimHeader {
uint32_t magic ;
uint32_t w ;
uint32_t h ;
uint32_t flags ;
} header ;
2023-03-10 23:36:00 +01:00
good = vfs_ - > Read ( openFile , & header , sizeof ( header ) ) = = sizeof ( header ) ;
2023-03-10 21:06:33 +01:00
level . w = header . w ;
level . h = header . h ;
2023-03-11 11:34:28 +01:00
good = good & & ( header . flags & ZIM_FORMAT_MASK ) = = ZIM_RGBA8888 ;
* pixelFormat = Draw : : DataFormat : : R8G8B8A8_UNORM ;
2023-03-10 21:06:33 +01:00
} else if ( imageType = = ReplacedImageType : : PNG ) {
PNGHeaderPeek headerPeek ;
2023-03-10 23:36:00 +01:00
good = vfs_ - > Read ( openFile , & headerPeek , sizeof ( headerPeek ) ) = = sizeof ( headerPeek ) ;
2023-03-10 21:06:33 +01:00
if ( good & & headerPeek . IsValidPNGHeader ( ) ) {
level . w = headerPeek . Width ( ) ;
level . h = headerPeek . Height ( ) ;
good = true ;
} else {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Could not get PNG dimensions: %s (zip) " , filename . c_str ( ) ) ;
2023-03-10 21:06:33 +01:00
good = false ;
}
2023-03-11 11:34:28 +01:00
* pixelFormat = Draw : : DataFormat : : R8G8B8A8_UNORM ;
2023-03-10 21:06:33 +01:00
} else {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Could not load texture replacement info: %s - unsupported format %s " , filename . c_str ( ) , magic . c_str ( ) ) ;
2023-03-10 21:06:33 +01:00
}
2023-03-17 14:06:46 +01:00
// TODO: We no longer really need to have a split in this function, the upper and lower parts can be merged now.
2023-03-11 12:22:20 +01:00
2023-03-10 23:28:11 +01:00
if ( good & & mipLevel ! = 0 ) {
2023-03-17 13:08:35 +01:00
// If loading a low mip directly (through png most likely), check that the mipmap size is correct.
// Can't load mips of the wrong size.
if ( level . w ! = std : : max ( 1 , ( levels_ [ 0 ] . w > > mipLevel ) ) | | level . h ! = std : : max ( 1 , ( levels_ [ 0 ] . h > > mipLevel ) ) ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d) " ,
2023-03-10 23:28:11 +01:00
level . w , level . h , levels_ [ 0 ] . w > > mipLevel , levels_ [ 0 ] . h > > mipLevel , mipLevel ) ;
good = false ;
}
}
2023-03-10 23:36:00 +01:00
if ( ! good ) {
2023-03-12 19:20:01 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-10 23:36:00 +01:00
}
2023-03-10 21:06:33 +01:00
2023-03-10 23:36:00 +01:00
vfs_ - > Rewind ( openFile ) ;
2023-03-12 23:04:50 +01:00
level . fileRef = fileRef ;
2023-03-13 16:20:01 +01:00
if ( imageType = = ReplacedImageType : : KTX2 ) {
// Just slurp the whole file in one go and feed to the decoder.
std : : vector < uint8_t > buffer ;
buffer . resize ( fileSize ) ;
buffer . resize ( vfs_ - > Read ( openFile , & buffer [ 0 ] , buffer . size ( ) ) ) ;
2024-11-30 11:56:27 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-13 16:20:01 +01:00
basist : : ktx2_transcoder transcoder ;
if ( ! transcoder . init ( buffer . data ( ) , ( int ) buffer . size ( ) ) ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Error reading KTX file " ) ;
vfs_ - > CloseFile ( openFile ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-13 16:20:01 +01:00
}
// Figure out the target format.
basist : : transcoder_texture_format transcoderFormat ;
if ( transcoder . is_etc1s ( ) ) {
2023-03-14 18:34:22 +01:00
// We only support opaque colors with this compression method.
alphaStatus_ = ReplacedTextureAlpha : : FULL ;
2023-03-13 16:20:01 +01:00
// Let's pick a suitable compatible format.
2023-03-16 11:44:38 +01:00
if ( desc_ . formatSupport . bc123 ) {
2023-03-13 16:20:01 +01:00
transcoderFormat = basist : : transcoder_texture_format : : cTFBC1 ;
* pixelFormat = Draw : : DataFormat : : BC1_RGBA_UNORM_BLOCK ;
2023-03-16 11:44:38 +01:00
} else if ( desc_ . formatSupport . etc2 ) {
2023-03-13 16:20:01 +01:00
transcoderFormat = basist : : transcoder_texture_format : : cTFETC1_RGB ;
* pixelFormat = Draw : : DataFormat : : ETC2_R8G8B8_UNORM_BLOCK ;
} else {
2023-03-14 18:52:42 +01:00
// Transcode to RGBA8 instead as a fallback. A bit slow and takes a lot of memory, but better than nothing.
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Replacement texture format not supported - transcoding to RGBA8888 " ) ;
2023-03-14 18:52:42 +01:00
transcoderFormat = basist : : transcoder_texture_format : : cTFRGBA32 ;
* pixelFormat = Draw : : DataFormat : : R8G8B8A8_UNORM ;
2023-03-13 16:20:01 +01:00
}
} else if ( transcoder . is_uastc ( ) ) {
2023-03-14 18:34:22 +01:00
// TODO: Try to recover some indication of alpha from the actual data blocks.
alphaStatus_ = ReplacedTextureAlpha : : UNKNOWN ;
2023-03-13 16:20:01 +01:00
// Let's pick a suitable compatible format.
2023-03-16 11:44:38 +01:00
if ( desc_ . formatSupport . bc7 ) {
2023-03-13 16:20:01 +01:00
transcoderFormat = basist : : transcoder_texture_format : : cTFBC7_RGBA ;
* pixelFormat = Draw : : DataFormat : : BC7_UNORM_BLOCK ;
2023-03-16 11:44:38 +01:00
} else if ( desc_ . formatSupport . astc ) {
2023-03-13 16:20:01 +01:00
transcoderFormat = basist : : transcoder_texture_format : : cTFASTC_4x4_RGBA ;
* pixelFormat = Draw : : DataFormat : : ASTC_4x4_UNORM_BLOCK ;
} else {
2023-03-14 18:52:42 +01:00
// Transcode to RGBA8 instead as a fallback. A bit slow and takes a lot of memory, but better than nothing.
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Replacement texture format not supported - transcoding to RGBA8888 " ) ;
2023-03-14 18:52:42 +01:00
transcoderFormat = basist : : transcoder_texture_format : : cTFRGBA32 ;
* pixelFormat = Draw : : DataFormat : : R8G8B8A8_UNORM ;
2023-03-13 16:20:01 +01:00
}
} else {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " PPSSPP currently only supports KTX for basis/UASTC textures. This may change in the future. " ) ;
vfs_ - > CloseFile ( openFile ) ;
2023-03-14 18:52:42 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-13 16:20:01 +01:00
}
2023-03-17 00:02:01 +01:00
int blockSize = 0 ;
2023-03-13 16:20:01 +01:00
bool bc = Draw : : DataFormatIsBlockCompressed ( * pixelFormat , & blockSize ) ;
2023-03-14 18:52:42 +01:00
_dbg_assert_ ( bc | | * pixelFormat = = Draw : : DataFormat : : R8G8B8A8_UNORM ) ;
2023-03-13 16:20:01 +01:00
2023-07-23 21:52:06 +02:00
if ( bc & & ( ( level . w & 3 ) ! = 0 | | ( level . h & 3 ) ! = 0 ) ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Block compressed replacement texture '%s' not divisible by 4x4 (%dx%d). In D3D11 (only!) we will have to expand (potentially causing glitches). " , filename . c_str ( ) , level . w , level . h ) ;
2023-07-20 19:44:00 +02:00
}
2023-03-16 11:44:38 +01:00
data_ . resize ( numMips ) ;
2023-03-13 16:20:01 +01:00
basist : : ktx2_transcoder_state transcodeState ; // Each thread needs one of these.
transcoder . start_transcoding ( ) ;
2023-12-12 15:13:43 +03:00
levels_ . reserve ( numMips ) ;
2023-03-13 16:20:01 +01:00
for ( int i = 0 ; i < numMips ; i + + ) {
2023-03-16 11:44:38 +01:00
std : : vector < uint8_t > & out = data_ [ mipLevel + i ] ;
2023-03-13 16:20:01 +01:00
2023-03-17 00:02:01 +01:00
basist : : ktx2_image_level_info levelInfo { } ;
2023-03-13 16:20:01 +01:00
bool result = transcoder . get_image_level_info ( levelInfo , i , 0 , 0 ) ;
_dbg_assert_ ( result ) ;
2023-03-14 18:52:42 +01:00
size_t dataSizeBytes = levelInfo . m_total_blocks * blockSize ;
size_t outputSize = levelInfo . m_total_blocks ;
size_t outputPitch = levelInfo . m_num_blocks_x ;
// Support transcoded-to-RGBA8888 images too.
if ( ! bc ) {
dataSizeBytes = levelInfo . m_orig_width * levelInfo . m_orig_height * 4 ;
outputSize = levelInfo . m_orig_width * levelInfo . m_orig_height ;
outputPitch = levelInfo . m_orig_width ;
}
2023-03-16 11:44:38 +01:00
data_ [ i ] . resize ( dataSizeBytes ) ;
2023-03-13 16:20:01 +01:00
2023-03-17 00:02:01 +01:00
transcodeState . clear ( ) ;
2023-03-15 09:56:32 +01:00
transcoder . transcode_image_level ( i , 0 , 0 , & out [ 0 ] , ( uint32_t ) outputSize , transcoderFormat , 0 , ( uint32_t ) outputPitch , level . h , - 1 , - 1 , & transcodeState ) ;
2023-03-13 16:20:01 +01:00
level . w = levelInfo . m_orig_width ;
level . h = levelInfo . m_orig_height ;
if ( i ! = 0 )
level . fileRef = nullptr ;
levels_ . push_back ( level ) ;
}
transcoder . clear ( ) ;
2023-07-20 19:44:00 +02:00
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : DONE ; // don't read more levels
2023-03-13 16:20:01 +01:00
} else if ( imageType = = ReplacedImageType : : DDS ) {
2023-03-14 18:34:22 +01:00
// TODO: Do better with alphaStatus, it's possible.
alphaStatus_ = ReplacedTextureAlpha : : UNKNOWN ;
2023-03-11 11:34:28 +01:00
DDSHeader header ;
DDSHeaderDXT10 header10 { } ;
vfs_ - > Read ( openFile , & header , sizeof ( header ) ) ;
2023-03-11 12:22:20 +01:00
if ( ddsDX10 ) {
vfs_ - > Read ( openFile , & header10 , sizeof ( header10 ) ) ;
}
2023-03-12 23:04:50 +01:00
int blockSize = 0 ;
bool bc = Draw : : DataFormatIsBlockCompressed ( * pixelFormat , & blockSize ) ;
_dbg_assert_ ( bc ) ;
2023-07-23 21:52:06 +02:00
if ( bc & & ( ( level . w & 3 ) ! = 0 | | ( level . h & 3 ) ! = 0 ) ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Block compressed replacement texture '%s' not divisible by 4x4 (%dx%d). In D3D11 (only!) we will have to expand (potentially causing glitches). " , filename . c_str ( ) , level . w , level . h ) ;
2023-07-20 19:44:00 +02:00
}
2023-03-16 11:44:38 +01:00
data_ . resize ( numMips ) ;
2023-03-12 23:04:50 +01:00
// A DDS File can contain multiple mipmaps.
2023-12-12 15:13:43 +03:00
levels_ . reserve ( numMips ) ;
2023-03-12 23:04:50 +01:00
for ( int i = 0 ; i < numMips ; i + + ) {
2023-03-16 11:44:38 +01:00
std : : vector < uint8_t > & out = data_ [ mipLevel + i ] ;
2023-03-12 23:04:50 +01:00
int bytesToRead = RoundUpTo4 ( level . w ) * RoundUpTo4 ( level . h ) * blockSize / 16 ;
out . resize ( bytesToRead ) ;
size_t read_bytes = vfs_ - > Read ( openFile , & out [ 0 ] , bytesToRead ) ;
if ( read_bytes ! = bytesToRead ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " DDS: Expected %d bytes, got %d " , bytesToRead , ( int ) read_bytes ) ;
2023-03-12 23:04:50 +01:00
}
levels_ . push_back ( level ) ;
2023-03-13 16:20:01 +01:00
level . w = std : : max ( level . w / 2 , 1 ) ;
level . h = std : : max ( level . h / 2 , 1 ) ;
if ( i ! = 0 )
level . fileRef = nullptr ; // We only provide a fileref on level 0 if we have mipmaps.
2023-03-11 11:34:28 +01:00
}
2023-03-17 14:06:46 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-07-20 19:44:00 +02:00
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : DONE ; // don't read more levels
2023-03-17 00:02:01 +01:00
2023-03-11 11:34:28 +01:00
} else if ( imageType = = ReplacedImageType : : ZIM ) {
2023-03-12 23:04:50 +01:00
2023-12-14 15:44:16 +03:00
auto zim = std : : make_unique < uint8_t [ ] > ( fileSize ) ;
2023-03-09 21:19:20 +01:00
if ( ! zim ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Failed to allocate memory for texture replacement " ) ;
2023-03-17 14:06:46 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
if ( vfs_ - > Read ( openFile , & zim [ 0 ] , fileSize ) ! = fileSize ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Could not load texture replacement: %s - failed to read ZIM " , filename . c_str ( ) ) ;
2023-03-17 14:06:46 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
2024-11-30 11:56:27 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-09 21:19:20 +01:00
int w , h , f ;
uint8_t * image ;
2023-03-16 11:44:38 +01:00
std : : vector < uint8_t > & out = data_ [ mipLevel ] ;
2023-03-14 18:34:22 +01:00
// TODO: Zim files can actually hold mipmaps (although no tool has ever been made to create them :P)
2023-03-09 21:19:20 +01:00
if ( LoadZIMPtr ( & zim [ 0 ] , fileSize , & w , & h , & f , & image ) ) {
2023-03-10 23:28:11 +01:00
if ( w > level . w | | h > level . h ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Texture replacement changed since header read: %s " , filename . c_str ( ) ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
2023-03-10 23:28:11 +01:00
out . resize ( level . w * level . h * 4 ) ;
if ( w = = level . w ) {
memcpy ( & out [ 0 ] , image , level . w * 4 * level . h ) ;
2023-03-09 21:19:20 +01:00
} else {
for ( int y = 0 ; y < h ; + + y ) {
2023-03-10 23:28:11 +01:00
memcpy ( & out [ level . w * 4 * y ] , image + w * 4 * y , w * 4 ) ;
2023-03-09 21:19:20 +01:00
}
}
free ( image ) ;
2023-03-12 23:04:50 +01:00
CheckAlphaResult res = CheckAlpha32Rect ( ( u32 * ) & out [ 0 ] , level . w , w , h , 0xFF000000 ) ;
if ( res = = CHECKALPHA_ANY | | mipLevel = = 0 ) {
alphaStatus_ = ReplacedTextureAlpha ( res ) ;
}
levels_ . push_back ( level ) ;
} else {
good = false ;
2023-03-09 21:19:20 +01:00
}
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : CONTINUE ;
2023-03-09 21:19:20 +01:00
} else if ( imageType = = ReplacedImageType : : PNG ) {
png_image png = { } ;
png . version = PNG_IMAGE_VERSION ;
std : : string pngdata ;
pngdata . resize ( fileSize ) ;
pngdata . resize ( vfs_ - > Read ( openFile , & pngdata [ 0 ] , fileSize ) ) ;
2024-11-25 23:25:05 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-09 21:19:20 +01:00
if ( ! png_image_begin_read_from_memory ( & png , & pngdata [ 0 ] , pngdata . size ( ) ) ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Could not load texture replacement info: %s - %s (zip) " , filename . c_str ( ) , png . message ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
2023-03-10 23:28:11 +01:00
if ( png . width > ( uint32_t ) level . w | | png . height > ( uint32_t ) level . h ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Texture replacement changed since header read: %s " , filename . c_str ( ) ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
bool checkedAlpha = false ;
if ( ( png . format & PNG_FORMAT_FLAG_ALPHA ) = = 0 ) {
// Well, we know for sure it doesn't have alpha.
2023-03-10 23:28:11 +01:00
if ( mipLevel = = 0 ) {
2023-03-09 21:19:20 +01:00
alphaStatus_ = ReplacedTextureAlpha : : FULL ;
}
checkedAlpha = true ;
}
png . format = PNG_FORMAT_RGBA ;
2023-03-16 11:44:38 +01:00
std : : vector < uint8_t > & out = data_ [ mipLevel ] ;
2023-09-11 17:45:54 +02:00
// TODO: Should probably try to handle out-of-memory gracefully here.
2023-03-10 23:28:11 +01:00
out . resize ( level . w * level . h * 4 ) ;
if ( ! png_image_finish_read ( & png , nullptr , & out [ 0 ] , level . w * 4 , nullptr ) ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Could not load texture replacement: %s - %s " , filename . c_str ( ) , png . message ) ;
vfs_ - > CloseFile ( openFile ) ;
2023-03-09 21:19:20 +01:00
out . resize ( 0 ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
png_image_free ( & png ) ;
if ( ! checkedAlpha ) {
// This will only check the hashed bits.
2023-03-10 23:28:11 +01:00
CheckAlphaResult res = CheckAlpha32Rect ( ( u32 * ) & out [ 0 ] , level . w , png . width , png . height , 0xFF000000 ) ;
if ( res = = CHECKALPHA_ANY | | mipLevel = = 0 ) {
2023-03-09 21:19:20 +01:00
alphaStatus_ = ReplacedTextureAlpha ( res ) ;
}
}
2023-03-12 23:04:50 +01:00
levels_ . push_back ( level ) ;
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : CONTINUE ;
2023-03-11 11:34:28 +01:00
} else {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Don't know how to load this image type! %d " , ( int ) imageType ) ;
2023-03-17 14:06:46 +01:00
vfs_ - > CloseFile ( openFile ) ;
2023-03-09 21:19:20 +01:00
}
2023-03-14 18:34:22 +01:00
return LoadLevelResult : : LOAD_ERROR ;
2023-03-09 21:19:20 +01:00
}
2023-03-17 13:58:36 +01:00
bool ReplacedTexture : : CopyLevelTo ( int level , uint8_t * out , size_t outDataSize , int rowPitch ) {
2023-03-09 21:19:20 +01:00
_assert_msg_ ( ( size_t ) level < levels_ . size ( ) , " Invalid miplevel " ) ;
_assert_msg_ ( out ! = nullptr & & rowPitch > 0 , " Invalid out/pitch " ) ;
2023-03-10 13:43:48 +01:00
if ( State ( ) ! = ReplacementState : : ACTIVE ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Init not done yet " ) ;
2023-03-09 21:19:20 +01:00
return false ;
}
2023-03-17 13:08:35 +01:00
// We pad the images right here during the copy.
// TODO: Add support for the texture cache to scale texture coordinates instead.
// It already supports this for render target textures that aren't powers of 2.
int outW = levels_ [ level ] . fullW ;
int outH = levels_ [ level ] . fullH ;
2023-03-09 21:19:20 +01:00
// We probably could avoid this lock, but better to play it safe.
2023-03-16 11:44:38 +01:00
std : : lock_guard < std : : mutex > guard ( lock_ ) ;
2023-03-09 21:19:20 +01:00
const ReplacedTextureLevel & info = levels_ [ level ] ;
2023-03-16 11:44:38 +01:00
const std : : vector < uint8_t > & data = data_ [ level ] ;
2023-03-09 21:19:20 +01:00
if ( data . empty ( ) ) {
2024-11-25 23:25:05 +01:00
WARN_LOG ( Log : : TexReplacement , " Level %d is empty " , level ) ;
2023-03-09 21:19:20 +01:00
return false ;
}
2023-03-15 14:48:32 +01:00
# define PARALLEL_COPY
2023-03-17 13:58:36 +01:00
int blockSize ;
if ( ! Draw : : DataFormatIsBlockCompressed ( fmt , & blockSize ) ) {
if ( fmt ! = Draw : : DataFormat : : R8G8B8A8_UNORM ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Unexpected linear data format " ) ;
2023-03-17 13:58:36 +01:00
return false ;
}
2023-03-11 11:34:28 +01:00
if ( rowPitch < info . w * 4 ) {
2024-11-25 23:25:05 +01:00
ERROR_LOG ( Log : : TexReplacement , " Replacement rowPitch=%d, but w=%d (level=%d) (too small) " , rowPitch , info . w * 4 , level ) ;
2023-03-11 11:34:28 +01:00
return false ;
}
2023-03-09 21:19:20 +01:00
2023-03-11 11:34:28 +01:00
_assert_msg_ ( data . size ( ) = = info . w * info . h * 4 , " Data has wrong size " ) ;
if ( rowPitch = = info . w * 4 ) {
2023-03-15 14:48:32 +01:00
# ifdef PARALLEL_COPY
2023-03-16 11:44:38 +01:00
ParallelMemcpy ( & g_threadManager , out , data . data ( ) , info . w * 4 * info . h ) ;
2023-03-15 20:31:27 +01:00
# else
memcpy ( out , data . data ( ) , info . w * 4 * info . h ) ;
2023-03-15 14:48:32 +01:00
# endif
2023-03-11 11:34:28 +01:00
} else {
2023-03-15 14:48:32 +01:00
# ifdef PARALLEL_COPY
2023-03-11 11:34:28 +01:00
const int MIN_LINES_PER_THREAD = 4 ;
ParallelRangeLoop ( & g_threadManager , [ & ] ( int l , int h ) {
2023-03-17 13:08:35 +01:00
int extraPixels = outW - info . w ;
2023-03-11 11:34:28 +01:00
for ( int y = l ; y < h ; + + y ) {
2023-03-16 11:44:38 +01:00
memcpy ( ( uint8_t * ) out + rowPitch * y , data . data ( ) + info . w * 4 * y , info . w * 4 ) ;
2023-03-17 13:08:35 +01:00
// Fill the rest of the line with black.
memset ( ( uint8_t * ) out + rowPitch * y + info . w * 4 , 0 , extraPixels * 4 ) ;
2023-03-11 11:34:28 +01:00
}
} , 0 , info . h , MIN_LINES_PER_THREAD ) ;
2023-03-15 20:31:27 +01:00
# else
2023-03-25 13:24:56 -07:00
int extraPixels = outW - info . w ;
2023-03-15 20:31:27 +01:00
for ( int y = 0 ; y < info . h ; + + y ) {
2023-03-16 11:44:38 +01:00
memcpy ( ( uint8_t * ) out + rowPitch * y , data . data ( ) + info . w * 4 * y , info . w * 4 ) ;
2023-03-25 13:24:56 -07:00
memset ( ( uint8_t * ) out + rowPitch * y + info . w * 4 , 0 , extraPixels * 4 ) ;
2023-03-15 20:31:27 +01:00
}
2023-03-15 14:48:32 +01:00
# endif
2023-03-17 13:58:36 +01:00
// Memset the rest of the padding to avoid leaky edge pixels. Guess we could parallelize this too, but meh.
for ( int y = info . h ; y < outH ; y + + ) {
uint8_t * dest = ( uint8_t * ) out + rowPitch * y ;
memset ( dest , 0 , outW * 4 ) ;
}
2023-03-11 11:34:28 +01:00
}
2023-03-09 21:19:20 +01:00
} else {
2023-03-15 14:48:32 +01:00
# ifdef PARALLEL_COPY
2023-03-17 13:58:36 +01:00
// Only parallel copy in the simple case for now.
if ( info . w = = outW & & info . h = = outH ) {
// TODO: Add sanity checks here for other formats?
ParallelMemcpy ( & g_threadManager , out , data . data ( ) , data . size ( ) ) ;
return true ;
}
2023-03-15 14:48:32 +01:00
# endif
2023-03-17 13:58:36 +01:00
// Alright, so careful copying of blocks it is, padding with zero-blocks as needed.
int inBlocksW = ( info . w + 3 ) / 4 ;
int inBlocksH = ( info . h + 3 ) / 4 ;
int outBlocksW = ( info . fullW + 3 ) / 4 ;
int outBlocksH = ( info . fullH + 3 ) / 4 ;
int paddingBlocksX = outBlocksW - inBlocksW ;
// Copy all the known blocks, and zero-fill out the lines.
for ( int y = 0 ; y < inBlocksH ; y + + ) {
const uint8_t * input = data . data ( ) + y * inBlocksW * blockSize ;
uint8_t * output = ( uint8_t * ) out + y * outBlocksW * blockSize ;
memcpy ( output , input , inBlocksW * blockSize ) ;
memset ( output + inBlocksW * blockSize , 0 , paddingBlocksX * blockSize ) ;
}
// Vertical zero-padding.
for ( int y = inBlocksH ; y < outBlocksH ; y + + ) {
uint8_t * output = ( uint8_t * ) out + y * outBlocksW * blockSize ;
memset ( output , 0 , outBlocksW * blockSize ) ;
}
2023-03-09 21:19:20 +01:00
}
return true ;
}
2023-03-10 13:43:48 +01:00
const char * StateString ( ReplacementState state ) {
switch ( state ) {
2023-03-17 14:06:46 +01:00
case ReplacementState : : UNLOADED : return " UNLOADED " ;
2023-03-10 13:43:48 +01:00
case ReplacementState : : PENDING : return " PENDING " ;
case ReplacementState : : NOT_FOUND : return " NOT_FOUND " ;
case ReplacementState : : ACTIVE : return " ACTIVE " ;
case ReplacementState : : CANCEL_INIT : return " CANCEL_INIT " ;
default : return " N/A " ;
}
}