Skip to content

Commit

Permalink
Merge pull request #17120 from hrydgard/fix-zip-texture-pack
Browse files Browse the repository at this point in the history
More texture replacement fixes
  • Loading branch information
hrydgard authored Mar 14, 2023
2 parents 42aa5f0 + 13e63c1 commit 38517a4
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 40 deletions.
113 changes: 79 additions & 34 deletions GPU/Common/ReplacedTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,13 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) {
// We must lock around access to levelData_ in case two textures try to load it at once.
std::lock_guard<std::mutex> guard(levelData_->lock);

fmt = Draw::DataFormat::UNDEFINED;

Draw::DataFormat pixelFormat;
LoadLevelResult result = LoadLevelResult::LOAD_ERROR;
if (desc_->filenames.empty()) {
result = LoadLevelResult::DONE;
}
for (int i = 0; i < std::min(MAX_REPLACEMENT_MIP_LEVELS, (int)desc_->filenames.size()); ++i) {
if (State() == ReplacementState::CANCEL_INIT) {
break;
Expand All @@ -209,14 +215,21 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) {
VFSFileReference *fileRef = vfs_->GetFile(desc_->filenames[i].c_str());
if (!fileRef) {
// If the file doesn't exist, let's just bail immediately here.
// Mark as DONE, not error.
result = LoadLevelResult::DONE;
break;
}

if (i == 0) {
fmt = Draw::DataFormat::R8G8B8A8_UNORM;
}

if (LoadLevelData(fileRef, desc_->filenames[i], i, &pixelFormat)) {
result = LoadLevelData(fileRef, desc_->filenames[i], i, &pixelFormat);
if (result == LoadLevelResult::DONE) {
// Loaded all the levels we're gonna get.
fmt = pixelFormat;
break;
} else if (result == LoadLevelResult::CONTINUE) {
if (i == 0) {
fmt = pixelFormat;
} else {
Expand All @@ -226,26 +239,30 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) {
}
}
} else {
fmt = pixelFormat;
// Otherwise, we're done loading mips (bad PNG or bad size, either way.)
// Error state.
break;
}
}

delete desc_;
desc_ = nullptr;

if (levels_.empty()) {
// Bad.
WARN_LOG(G3D, "Failed to load texture");
// No replacement found.
std::string name = TextureReplacer::HashName(desc_->cachekey, desc_->hash, 0);
if (result == LoadLevelResult::LOAD_ERROR) {
WARN_LOG(G3D, "Failed to load replacement texture '%s'", name.c_str());
}
SetState(ReplacementState::NOT_FOUND);
levelData_ = nullptr;
delete desc_;
desc_ = nullptr;
return;
}

levelData_->fmt = fmt;
SetState(ReplacementState::ACTIVE);

delete desc_;
desc_ = nullptr;

if (threadWaitable_)
threadWaitable_->Notify();
}
Expand All @@ -255,7 +272,7 @@ inline uint32_t RoundUpTo4(uint32_t value) {
}

// Returns true if Prepare should keep calling this to load more levels.
bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int mipLevel, Draw::DataFormat *pixelFormat) {
ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int mipLevel, Draw::DataFormat *pixelFormat) {
bool good = false;

if (levelData_->data.size() <= mipLevel) {
Expand All @@ -266,7 +283,8 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
size_t fileSize;
VFSOpenFile *openFile = vfs_->OpenFileForRead(fileRef, &fileSize);
if (!openFile) {
return false;
// File missing, no more levels. This is alright.
return LoadLevelResult::DONE;
}

std::string magic;
Expand Down Expand Up @@ -382,7 +400,7 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
if (!levelData_->data[mipLevel].empty()) {
vfs_->CloseFile(openFile);
*pixelFormat = levelData_->fmt;
return true;
return LoadLevelResult::DONE;
}

// Is this really the right place to do it?
Expand All @@ -400,7 +418,7 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string

if (!good) {
vfs_->CloseFile(openFile);
return false;
return LoadLevelResult::LOAD_ERROR;
}

auto cleanup = [&] {
Expand All @@ -421,12 +439,14 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
if (!transcoder.init(buffer.data(), (int)buffer.size())) {
WARN_LOG(G3D, "Error reading KTX file");
cleanup();
return false;
return LoadLevelResult::LOAD_ERROR;
}

// Figure out the target format.
basist::transcoder_texture_format transcoderFormat;
if (transcoder.is_etc1s()) {
// We only support opaque colors with this compression method.
alphaStatus_ = ReplacedTextureAlpha::FULL;
// Let's pick a suitable compatible format.
if (desc_->formatSupport.bc123) {
transcoderFormat = basist::transcoder_texture_format::cTFBC1;
Expand All @@ -435,11 +455,14 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
transcoderFormat = basist::transcoder_texture_format::cTFETC1_RGB;
*pixelFormat = Draw::DataFormat::ETC2_R8G8B8_UNORM_BLOCK;
} else {
// TODO: Transcode to RGBA8 instead as a fallback.
cleanup();
return false;
// Transcode to RGBA8 instead as a fallback. A bit slow and takes a lot of memory, but better than nothing.
WARN_LOG(G3D, "Replacement texture format not supported - transcoding to RGBA8888");
transcoderFormat = basist::transcoder_texture_format::cTFRGBA32;
*pixelFormat = Draw::DataFormat::R8G8B8A8_UNORM;
}
} else if (transcoder.is_uastc()) {
// TODO: Try to recover some indication of alpha from the actual data blocks.
alphaStatus_ = ReplacedTextureAlpha::UNKNOWN;
// Let's pick a suitable compatible format.
if (desc_->formatSupport.bc7) {
transcoderFormat = basist::transcoder_texture_format::cTFBC7_RGBA;
Expand All @@ -448,17 +471,20 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
transcoderFormat = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;
*pixelFormat = Draw::DataFormat::ASTC_4x4_UNORM_BLOCK;
} else {
// TODO: Transcode to RGBA8 instead as a fallback.
cleanup();
return false;
// Transcode to RGBA8 instead as a fallback. A bit slow and takes a lot of memory, but better than nothing.
WARN_LOG(G3D, "Replacement texture format not supported - transcoding to RGBA8888");
transcoderFormat = basist::transcoder_texture_format::cTFRGBA32;
*pixelFormat = Draw::DataFormat::R8G8B8A8_UNORM;
}
} else {
WARN_LOG(G3D, "PPSSPP currently only supports KTX for basis/UASTC textures. This may change in the future.");
cleanup();
return LoadLevelResult::LOAD_ERROR;
}

int blockSize;
bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize);
_dbg_assert_(bc);
_dbg_assert_(bc || *pixelFormat == Draw::DataFormat::R8G8B8A8_UNORM);

levelData_->data.resize(numMips);

Expand All @@ -472,9 +498,18 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
bool result = transcoder.get_image_level_info(levelInfo, i, 0, 0);
_dbg_assert_(result);

levelData_->data[i].resize(levelInfo.m_total_blocks * blockSize);
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;
}
levelData_->data[i].resize(dataSizeBytes);

transcoder.transcode_image_level(i, 0, 0, &out[0], levelInfo.m_total_blocks, transcoderFormat, 0, levelInfo.m_num_blocks_x, level.h, -1, -1, &transcodeState);
transcoder.transcode_image_level(i, 0, 0, &out[0], outputSize, transcoderFormat, 0, outputPitch, level.h, -1, -1, &transcodeState);
level.w = levelInfo.m_orig_width;
level.h = levelInfo.m_orig_height;
if (i != 0)
Expand All @@ -483,8 +518,12 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
}
transcoder.clear();
cleanup();
return false; // don't read more levels

return LoadLevelResult::DONE; // don't read more levels
} else if (imageType == ReplacedImageType::DDS) {
// TODO: Do better with alphaStatus, it's possible.
alphaStatus_ = ReplacedTextureAlpha::UNKNOWN;

DDSHeader header;
DDSHeaderDXT10 header10{};
vfs_->Read(openFile, &header, sizeof(header));
Expand Down Expand Up @@ -517,30 +556,31 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
level.fileRef = nullptr; // We only provide a fileref on level 0 if we have mipmaps.
}
cleanup();
return false; // don't read more levels
return LoadLevelResult::DONE; // don't read more levels
} else if (imageType == ReplacedImageType::ZIM) {

std::unique_ptr<uint8_t[]> zim(new uint8_t[fileSize]);
if (!zim) {
ERROR_LOG(G3D, "Failed to allocate memory for texture replacement");
cleanup();
return false;
return LoadLevelResult::LOAD_ERROR;
}

if (vfs_->Read(openFile, &zim[0], fileSize) != fileSize) {
ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", filename.c_str());
cleanup();
return false;
return LoadLevelResult::LOAD_ERROR;
}

int w, h, f;
uint8_t *image;
std::vector<uint8_t> &out = levelData_->data[mipLevel];
// TODO: Zim files can actually hold mipmaps (although no tool has ever been made to create them :P)
if (LoadZIMPtr(&zim[0], fileSize, &w, &h, &f, &image)) {
if (w > level.w || h > level.h) {
ERROR_LOG(G3D, "Texture replacement changed since header read: %s", filename.c_str());
cleanup();
return false;
return LoadLevelResult::LOAD_ERROR;
}

out.resize(level.w * level.h * 4);
Expand All @@ -561,6 +601,10 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
} else {
good = false;
}

cleanup();
return LoadLevelResult::CONTINUE;

} else if (imageType == ReplacedImageType::PNG) {
png_image png = {};
png.version = PNG_IMAGE_VERSION;
Expand All @@ -571,12 +615,12 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) {
ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", filename.c_str(), png.message);
cleanup();
return false;
return LoadLevelResult::LOAD_ERROR;
}
if (png.width > (uint32_t)level.w || png.height > (uint32_t)level.h) {
ERROR_LOG(G3D, "Texture replacement changed since header read: %s", filename.c_str());
cleanup();
return false;
return LoadLevelResult::LOAD_ERROR;
}

bool checkedAlpha = false;
Expand All @@ -595,7 +639,7 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", filename.c_str(), png.message);
cleanup();
out.resize(0);
return false;
return LoadLevelResult::LOAD_ERROR;
}
png_image_free(&png);

Expand All @@ -608,13 +652,14 @@ bool ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string
}

levels_.push_back(level);

cleanup();
return LoadLevelResult::CONTINUE;
} else {
WARN_LOG(G3D, "Don't know how to load this image type! %d", (int)imageType);
cleanup();
}

cleanup();

return good;
return LoadLevelResult::LOAD_ERROR;
}

bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) {
Expand Down
8 changes: 7 additions & 1 deletion GPU/Common/ReplacedTexture.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,14 @@ struct ReplacedTexture {
std::string logId_;

private:
enum class LoadLevelResult {
LOAD_ERROR = 0,
CONTINUE = 1,
DONE = 2,
};

void Prepare(VFSBackend *vfs);
bool LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int level, Draw::DataFormat *pixelFormat);
LoadLevelResult LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int level, Draw::DataFormat *pixelFormat);
void PurgeIfOlder(double t);

std::vector<ReplacedTextureLevel> levels_;
Expand Down
8 changes: 6 additions & 2 deletions GPU/Common/TextureReplacer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ bool TextureReplacer::LoadIni() {
}

IniFile ini;
bool iniLoaded = ini.LoadFromVFS(g_VFS, (basePath_ / INI_FILENAME).ToString());
bool iniLoaded = ini.LoadFromVFS(*dir, INI_FILENAME);

if (iniLoaded) {
if (!LoadIniValues(ini)) {
Expand Down Expand Up @@ -183,7 +183,11 @@ bool TextureReplacer::LoadIni() {
repl.second->vfs_ = vfs_;
}

INFO_LOG(G3D, "Texture pack activated from '%s'", basePath_.c_str());
if (vfsIsZip_) {
INFO_LOG(G3D, "Texture pack activated from '%s'", (basePath_ / ZIP_FILENAME).c_str());
} else {
INFO_LOG(G3D, "Texture pack activated from '%s'", basePath_.c_str());
}

// The ini doesn't have to exist for the texture directory or zip to be valid.
return true;
Expand Down
3 changes: 1 addition & 2 deletions GPU/D3D11/TextureCacheD3D11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) {
if (plan.replaceValid) {
int blockSize = 0;
if (Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), &blockSize)) {
stride = ((mipWidth + 3) & ~3) * 4; // This stride value doesn't quite make sense to me, but it works.
stride = ((mipWidth + 3) & ~3) * blockSize / 4; // This stride value doesn't quite make sense to me, but it works?
dataSize = plan.replaced->GetLevelDataSize(i);
} else {
int bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format());
Expand Down Expand Up @@ -364,7 +364,6 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) {
desc.Format = dstFmt;
desc.MipLevels = levels;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;

ASSERT_SUCCESS(device_->CreateTexture2D(&desc, subresData, &tex));
texture = tex;
} else {
Expand Down
4 changes: 3 additions & 1 deletion GPU/Directx9/DrawEngineDX9.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ void DrawEngineDX9::InitDeviceObjects() {
}

void DrawEngineDX9::DestroyDeviceObjects() {
draw_->SetInvalidationCallback(InvalidationCallback());
if (draw_) {
draw_->SetInvalidationCallback(InvalidationCallback());
}
ClearTrackedVertexArrays();
}

Expand Down

0 comments on commit 38517a4

Please sign in to comment.