diff --git a/GPU/Common/ReplacedTexture.cpp b/GPU/Common/ReplacedTexture.cpp index 73c9e67d6e..116a8e2de8 100644 --- a/GPU/Common/ReplacedTexture.cpp +++ b/GPU/Common/ReplacedTexture.cpp @@ -23,6 +23,7 @@ #include "GPU/Common/TextureReplacer.h" #include "Common/Data/Format/IniFile.h" +#include "Common/Data/Format/DDSLoad.h" #include "Common/Data/Format/ZIMLoad.h" #include "Common/Data/Format/PNGLoad.h" #include "Common/Thread/ParallelLoop.h" @@ -178,7 +179,16 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) { level.fileRef = fileRef; - if (LoadLevelData(level, i)) { + Draw::DataFormat pixelFormat; + if (LoadLevelData(level, i, &pixelFormat)) { + if (i == 0) { + fmt = pixelFormat; + } else { + if (fmt != pixelFormat) { + ERROR_LOG(G3D, "Replacement mipmap %d doesn't have the same pixel format as mipmap 0. Stopping.", i); + break; + } + } levels_.push_back(level); } else { // Otherwise, we're done loading mips (bad PNG or bad size, either way.) @@ -202,7 +212,7 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) { threadWaitable_->Notify(); } -bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) { +bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel, Draw::DataFormat *pixelFormat) { bool good = false; size_t fileSize; @@ -214,7 +224,55 @@ bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) { std::string magic; ReplacedImageType imageType = Identify(vfs_, openFile, &magic); - if (imageType == ReplacedImageType::ZIM) { + if (imageType == ReplacedImageType::DDS) { + 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; + INFO_LOG(G3D, "DDS fourcc: %c%c%c%c", fcc[0], fcc[1], fcc[2], fcc[3]); + if (header.ddspf.dwFourCC == 'DX10') { + good = good && vfs_->Read(openFile, &header10, sizeof(header10)) == sizeof(header10); + format = header10.dxgiFormat; + switch (format) { + case 98: // DXGI_FORMAT_BC7_UNORM: + case 99: // DXGI_FORMAT_BC7_UNORM_SRGB: + *pixelFormat = Draw::DataFormat::BC7_UNORM_BLOCK; + break; + default: + ERROR_LOG(G3D, "DDS pixel format not supported."); + good = false; + } + } else { + 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: + ERROR_LOG(G3D, "DDS pixel format not supported."); + good = false; + } + } + } else if (good) { + ERROR_LOG(G3D, "DDS non-fourCC format not supported."); + good = false; + } + + level.w = header.dwWidth; + level.h = header.dwHeight; + } else if (imageType == ReplacedImageType::ZIM) { uint32_t ignore = 0; struct ZimHeader { uint32_t magic; @@ -225,7 +283,8 @@ bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) { good = vfs_->Read(openFile, &header, sizeof(header)) == sizeof(header); level.w = header.w; level.h = header.h; - good = (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; + good = good && (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888; + *pixelFormat = Draw::DataFormat::R8G8B8A8_UNORM; } else if (imageType == ReplacedImageType::PNG) { PNGHeaderPeek headerPeek; good = vfs_->Read(openFile, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek); @@ -237,6 +296,7 @@ bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) { ERROR_LOG(G3D, "Could not get PNG dimensions: %s (zip)", level.file.ToVisualString().c_str()); good = false; } + *pixelFormat = Draw::DataFormat::R8G8B8A8_UNORM; } else { ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format %s", level.file.ToVisualString().c_str(), magic.c_str()); } @@ -275,7 +335,18 @@ bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) { vfs_->Rewind(openFile); - if (imageType == ReplacedImageType::ZIM) { + if (imageType == ReplacedImageType::DDS) { + DDSHeader header; + DDSHeaderDXT10 header10{}; + vfs_->Read(openFile, &header, sizeof(header)); + // For compressed formats (we don't support uncompressed DDS files yet), this is supposed to be the linear size. + size_t bytes = header.dwPitchOrLinearSize; + out.resize(bytes); + size_t read_bytes = vfs_->Read(openFile, &out[0], bytes); + if (read_bytes != bytes) { + WARN_LOG(G3D, "DDS: Expected %d bytes, got %d", bytes, read_bytes); + } + } else if (imageType == ReplacedImageType::ZIM) { std::unique_ptr zim(new uint8_t[fileSize]); if (!zim) { ERROR_LOG(G3D, "Failed to allocate memory for texture replacement"); @@ -363,6 +434,8 @@ bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) { alphaStatus_ = ReplacedTextureAlpha(res); } } + } else { + WARN_LOG(G3D, "Don't know how to load this image type! %d", (int)imageType); } cleanup(); @@ -419,21 +492,28 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) { return false; } - if (rowPitch < info.w * 4) { - ERROR_LOG(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); - return false; - } - _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); + if (fmt == Draw::DataFormat::R8G8B8A8_UNORM) { + if (rowPitch < info.w * 4) { + ERROR_LOG(G3D, "Replacement rowPitch=%d, but w=%d (level=%d)", rowPitch, info.w * 4, level); + return false; + } - if (rowPitch == info.w * 4) { - ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h); + _assert_msg_(data.size() == info.w * info.h * 4, "Data has wrong size"); + + if (rowPitch == info.w * 4) { + ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h); + } else { + const int MIN_LINES_PER_THREAD = 4; + ParallelRangeLoop(&g_threadManager, [&](int l, int h) { + for (int y = l; y < h; ++y) { + memcpy((uint8_t *)out + rowPitch * y, &data[0] + info.w * 4 * y, info.w * 4); + } + }, 0, info.h, MIN_LINES_PER_THREAD); + } } else { - const int MIN_LINES_PER_THREAD = 4; - ParallelRangeLoop(&g_threadManager, [&](int l, int h) { - for (int y = l; y < h; ++y) { - memcpy((uint8_t *)out + rowPitch * y, &data[0] + info.w * 4 * y, info.w * 4); - } - }, 0, info.h, MIN_LINES_PER_THREAD); + // TODO: Add sanity checks here for other formats? + // Just gonna do a memcpy, slightly scared of the parallel ones. + memcpy(out, data.data(), data.size()); } return true; diff --git a/GPU/Common/ReplacedTexture.h b/GPU/Common/ReplacedTexture.h index c3891d3668..24bbef6662 100644 --- a/GPU/Common/ReplacedTexture.h +++ b/GPU/Common/ReplacedTexture.h @@ -87,6 +87,12 @@ struct ReplacementDesc { ReplacedLevelsCache *cache; }; +struct ReplacedLevelsCache { + std::mutex lock; + std::vector> data; + double lastUsed = 0.0; +}; + // These aren't actually all replaced, they can also represent a placeholder for a not-found // replacement (state_ == NOT_FOUND). struct ReplacedTexture { @@ -111,6 +117,11 @@ struct ReplacedTexture { *h = levels_[level].h; } + int GetLevelDataSize(int level) const { + _dbg_assert_(State() == ReplacementState::ACTIVE); + return (int)levelData_->data[level].size(); + } + int NumLevels() const { _dbg_assert_(State() == ReplacementState::ACTIVE); return (int)levels_.size(); @@ -133,7 +144,7 @@ struct ReplacedTexture { private: void Prepare(VFSBackend *vfs); - bool LoadLevelData(ReplacedTextureLevel &info, int level); + bool LoadLevelData(ReplacedTextureLevel &info, int level, Draw::DataFormat *pixelFormat); void PurgeIfOlder(double t); std::vector levels_; diff --git a/GPU/Common/TextureReplacer.h b/GPU/Common/TextureReplacer.h index ed0df878c9..4766351b2a 100644 --- a/GPU/Common/TextureReplacer.h +++ b/GPU/Common/TextureReplacer.h @@ -48,12 +48,6 @@ struct SavedTextureCacheData { double lastTimeSaved = 0.0; }; -struct ReplacedLevelsCache { - std::mutex lock; - std::vector> data; - double lastUsed = 0.0; -}; - struct ReplacementCacheKey { u64 cachekey; u32 hash; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 2ec829f511..20a8345c84 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -405,7 +405,14 @@ static Draw::DataFormat FromVulkanFormat(VkFormat fmt) { static VkFormat ToVulkanFormat(Draw::DataFormat fmt) { switch (fmt) { - case Draw::DataFormat::R8G8B8A8_UNORM: default: return VULKAN_8888_FORMAT; + case Draw::DataFormat::BC1_RGBA_UNORM_BLOCK: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK; + case Draw::DataFormat::BC2_UNORM_BLOCK: return VK_FORMAT_BC2_UNORM_BLOCK; + case Draw::DataFormat::BC3_UNORM_BLOCK: return VK_FORMAT_BC3_UNORM_BLOCK; + case Draw::DataFormat::BC4_UNORM_BLOCK: return VK_FORMAT_BC4_UNORM_BLOCK; + case Draw::DataFormat::BC5_UNORM_BLOCK: return VK_FORMAT_BC5_UNORM_BLOCK; + case Draw::DataFormat::BC7_UNORM_BLOCK: return VK_FORMAT_BC7_UNORM_BLOCK; + case Draw::DataFormat::R8G8B8A8_UNORM: return VULKAN_8888_FORMAT; + default: _assert_msg_(false, "Bad texture pixel format"); return VULKAN_8888_FORMAT; } } @@ -442,8 +449,15 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // Any texture scaling is gonna move away from the original 16-bit format, if any. VkFormat actualFmt = plan.scaleFactor > 1 ? VULKAN_8888_FORMAT : dstFmt; + bool bcFormat = false; + int bcAlign = 0; if (plan.replaceValid) { - actualFmt = ToVulkanFormat(plan.replaced->Format()); + Draw::DataFormat fmt = plan.replaced->Format(); + bcFormat = Draw::DataFormatIsBlockCompressed(fmt, &bcAlign); + actualFmt = ToVulkanFormat(fmt); + if (actualFmt != VULKAN_8888_FORMAT) { + actualFmt = actualFmt; + } } bool computeUpload = false; @@ -570,6 +584,12 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { bool dataScaled = true; if (plan.replaceValid) { + int bufferRowLength = byteStride; + if (bcFormat) { + // For block compressed formats, we just set the upload size to the data size.. + uploadSize = plan.replaced->GetLevelDataSize(plan.baseLevelSrc + i); + bufferRowLength = mipWidth; + } // Directly load the replaced image. data = pushBuffer->PushAligned(uploadSize, &bufferOffset, &texBuf, pushAlignment); double replaceStart = time_now_d();