Skip to content

Commit

Permalink
Merge pull request #17134 from hrydgard/more-replacement-work
Browse files Browse the repository at this point in the history
Refactor the replacement cache
  • Loading branch information
hrydgard authored Mar 16, 2023
2 parents 2157610 + e34a5a6 commit ae8c804
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 213 deletions.
2 changes: 1 addition & 1 deletion Common/GPU/Vulkan/VulkanMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ void VulkanPushBuffer::GetDebugString(char *buffer, size_t bufSize) const {
sum += size_ * (buffers_.size() - 1);
sum += offset_;
size_t capacity = size_ * buffers_.size();
snprintf(buffer, bufSize, "Push %s: %s/%s", name_, NiceSizeFormat(capacity).c_str(), NiceSizeFormat(sum).c_str());
snprintf(buffer, bufSize, "Push %s: %s / %s", name_, NiceSizeFormat(sum).c_str(), NiceSizeFormat(capacity).c_str());
}

void VulkanPushBuffer::Map() {
Expand Down
131 changes: 65 additions & 66 deletions GPU/Common/ReplacedTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,15 @@ class ReplacedTextureTask : public Task {
LimitedWaitable *waitable_;
};

ReplacedTexture::ReplacedTexture(VFSBackend *vfs, const ReplacementDesc &desc) : vfs_(vfs), desc_(desc) {
logId_ = desc.logId;
}

ReplacedTexture::~ReplacedTexture() {
if (threadWaitable_) {
SetState(ReplacementState::CANCEL_INIT);

std::unique_lock<std::mutex> lock(mutex_);
std::unique_lock<std::mutex> lock(lock_);
threadWaitable_->WaitAndRelease();
threadWaitable_ = nullptr;
}
Expand All @@ -108,19 +112,36 @@ ReplacedTexture::~ReplacedTexture() {
}
}

void ReplacedTexture::PurgeIfOlder(double t) {
if (threadWaitable_ && !threadWaitable_->WaitFor(0.0))
return;
if (lastUsed_ >= t)
void ReplacedTexture::PurgeIfNotUsedSinceTime(double t) {
if (State() != ReplacementState::ACTIVE) {
return;
}

// 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;
}
}

if (levelData_ && levelData_->lastUsed < t) {
// We have to lock since multiple textures might reference this same data.
std::lock_guard<std::mutex> guard(levelData_->lock);
levelData_->data.clear();
// This means we have to reload. If we never purge any, there's no need.
SetState(ReplacementState::POPULATED);
// 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.
if (lastUsed_ >= t) {
return;
}

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.
SetState(ReplacementState::POPULATED);
}

// This can only return true if ACTIVE or NOT_FOUND.
Expand All @@ -140,9 +161,7 @@ bool ReplacedTexture::IsReady(double budget) {
// Successfully waited! Can get rid of it.
threadWaitable_->WaitAndRelease();
threadWaitable_ = nullptr;
if (levelData_) {
levelData_->lastUsed = now;
}
lastUsed = now;
}
lastUsed_ = now;
return true;
Expand Down Expand Up @@ -176,43 +195,29 @@ bool ReplacedTexture::IsReady(double budget) {
return false;
}

void ReplacedTexture::FinishPopulate(ReplacementDesc *desc) {
logId_ = desc->logId;
levelData_ = desc->cache;
desc_ = desc;
SetState(ReplacementState::POPULATED);

// TODO: What used to be here is now done on the thread task.
}

void ReplacedTexture::Prepare(VFSBackend *vfs) {
this->vfs_ = vfs;

std::unique_lock<std::mutex> lock(mutex_);

_assert_msg_(levelData_ != nullptr, "Level cache not set");

// 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);
std::unique_lock<std::mutex> lock(lock_);

fmt = Draw::DataFormat::UNDEFINED;

Draw::DataFormat pixelFormat;
LoadLevelResult result = LoadLevelResult::LOAD_ERROR;
if (desc_->filenames.empty()) {
if (desc_.filenames.empty()) {
result = LoadLevelResult::DONE;
}
for (int i = 0; i < std::min(MAX_REPLACEMENT_MIP_LEVELS, (int)desc_->filenames.size()); ++i) {
for (int i = 0; i < std::min(MAX_REPLACEMENT_MIP_LEVELS, (int)desc_.filenames.size()); ++i) {
if (State() == ReplacementState::CANCEL_INIT) {
break;
}

if (desc_->filenames[i].empty()) {
if (desc_.filenames[i].empty()) {
// Out of valid mip levels. Bail out.
break;
}

VFSFileReference *fileRef = vfs_->GetFile(desc_->filenames[i].c_str());
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.
Expand All @@ -224,7 +229,7 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) {
fmt = Draw::DataFormat::R8G8B8A8_UNORM;
}

result = 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;
Expand All @@ -246,23 +251,17 @@ void ReplacedTexture::Prepare(VFSBackend *vfs) {

if (levels_.empty()) {
// No replacement found.
std::string name = TextureReplacer::HashName(desc_->cachekey, desc_->hash, 0);
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;
fmt = fmt;
SetState(ReplacementState::ACTIVE);

delete desc_;
desc_ = nullptr;

if (threadWaitable_)
threadWaitable_->Notify();
}
Expand All @@ -275,8 +274,8 @@ inline uint32_t RoundUpTo4(uint32_t value) {
ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int mipLevel, Draw::DataFormat *pixelFormat) {
bool good = false;

if (levelData_->data.size() <= mipLevel) {
levelData_->data.resize(mipLevel + 1);
if (data_.size() <= mipLevel) {
data_.resize(mipLevel + 1);
}

ReplacedTextureLevel level;
Expand Down Expand Up @@ -325,7 +324,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
switch (format) {
case 98: // DXGI_FORMAT_BC7_UNORM:
case 99: // DXGI_FORMAT_BC7_UNORM_SRGB:
if (!desc_->formatSupport.bc7) {
if (!desc_.formatSupport.bc7) {
WARN_LOG(G3D, "BC1-3 formats not supported, skipping texture");
good = false;
}
Expand All @@ -336,7 +335,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
good = false;
}
} else {
if (!desc_->formatSupport.bc123) {
if (!desc_.formatSupport.bc123) {
WARN_LOG(G3D, "BC1-3 formats not supported");
good = false;
}
Expand Down Expand Up @@ -397,15 +396,15 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference


// Already populated from cache. TODO: Move this above the first read, and take level.w/h from the cache.
if (!levelData_->data[mipLevel].empty()) {
if (!data_[mipLevel].empty()) {
vfs_->CloseFile(openFile);
*pixelFormat = levelData_->fmt;
*pixelFormat = fmt;
return LoadLevelResult::DONE;
}

// Is this really the right place to do it?
level.w = (level.w * desc_->w) / desc_->newW;
level.h = (level.h * desc_->h) / desc_->newH;
level.w = (level.w * desc_.w) / desc_.newW;
level.h = (level.h * desc_.h) / desc_.newH;

if (good && mipLevel != 0) {
// Check that the mipmap size is correct. Can't load mips of the wrong size.
Expand Down Expand Up @@ -448,10 +447,10 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
// We only support opaque colors with this compression method.
alphaStatus_ = ReplacedTextureAlpha::FULL;
// Let's pick a suitable compatible format.
if (desc_->formatSupport.bc123) {
if (desc_.formatSupport.bc123) {
transcoderFormat = basist::transcoder_texture_format::cTFBC1;
*pixelFormat = Draw::DataFormat::BC1_RGBA_UNORM_BLOCK;
} else if (desc_->formatSupport.etc2) {
} else if (desc_.formatSupport.etc2) {
transcoderFormat = basist::transcoder_texture_format::cTFETC1_RGB;
*pixelFormat = Draw::DataFormat::ETC2_R8G8B8_UNORM_BLOCK;
} else {
Expand All @@ -464,10 +463,10 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
// 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) {
if (desc_.formatSupport.bc7) {
transcoderFormat = basist::transcoder_texture_format::cTFBC7_RGBA;
*pixelFormat = Draw::DataFormat::BC7_UNORM_BLOCK;
} else if (desc_->formatSupport.astc) {
} else if (desc_.formatSupport.astc) {
transcoderFormat = basist::transcoder_texture_format::cTFASTC_4x4_RGBA;
*pixelFormat = Draw::DataFormat::ASTC_4x4_UNORM_BLOCK;
} else {
Expand All @@ -486,13 +485,13 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize);
_dbg_assert_(bc || *pixelFormat == Draw::DataFormat::R8G8B8A8_UNORM);

levelData_->data.resize(numMips);
data_.resize(numMips);

basist::ktx2_transcoder_state transcodeState; // Each thread needs one of these.

transcoder.start_transcoding();
for (int i = 0; i < numMips; i++) {
std::vector<uint8_t> &out = levelData_->data[mipLevel + i];
std::vector<uint8_t> &out = data_[mipLevel + i];

basist::ktx2_image_level_info levelInfo;
bool result = transcoder.get_image_level_info(levelInfo, i, 0, 0);
Expand All @@ -507,7 +506,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
outputSize = levelInfo.m_orig_width * levelInfo.m_orig_height;
outputPitch = levelInfo.m_orig_width;
}
levelData_->data[i].resize(dataSizeBytes);
data_[i].resize(dataSizeBytes);

transcoder.transcode_image_level(i, 0, 0, &out[0], (uint32_t)outputSize, transcoderFormat, 0, (uint32_t)outputPitch, level.h, -1, -1, &transcodeState);
level.w = levelInfo.m_orig_width;
Expand Down Expand Up @@ -535,11 +534,11 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
bool bc = Draw::DataFormatIsBlockCompressed(*pixelFormat, &blockSize);
_dbg_assert_(bc);

levelData_->data.resize(numMips);
data_.resize(numMips);

// A DDS File can contain multiple mipmaps.
for (int i = 0; i < numMips; i++) {
std::vector<uint8_t> &out = levelData_->data[mipLevel + i];
std::vector<uint8_t> &out = data_[mipLevel + i];

int bytesToRead = RoundUpTo4(level.w) * RoundUpTo4(level.h) * blockSize / 16;
out.resize(bytesToRead);
Expand Down Expand Up @@ -574,7 +573,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference

int w, h, f;
uint8_t *image;
std::vector<uint8_t> &out = levelData_->data[mipLevel];
std::vector<uint8_t> &out = 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) {
Expand Down Expand Up @@ -633,7 +632,7 @@ ReplacedTexture::LoadLevelResult ReplacedTexture::LoadLevelData(VFSFileReference
}
png.format = PNG_FORMAT_RGBA;

std::vector<uint8_t> &out = levelData_->data[mipLevel];
std::vector<uint8_t> &out = data_[mipLevel];
out.resize(level.w * level.h * 4);
if (!png_image_finish_read(&png, nullptr, &out[0], level.w * 4, nullptr)) {
ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", filename.c_str(), png.message);
Expand Down Expand Up @@ -672,10 +671,10 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) {
}

// We probably could avoid this lock, but better to play it safe.
std::lock_guard<std::mutex> guard(levelData_->lock);
std::lock_guard<std::mutex> guard(lock_);

const ReplacedTextureLevel &info = levels_[level];
const std::vector<uint8_t> &data = levelData_->data[level];
const std::vector<uint8_t> &data = data_[level];

if (data.empty()) {
WARN_LOG(G3D, "Level %d is empty", level);
Expand All @@ -694,7 +693,7 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) {

if (rowPitch == info.w * 4) {
#ifdef PARALLEL_COPY
ParallelMemcpy(&g_threadManager, out, &data[0], info.w * 4 * info.h);
ParallelMemcpy(&g_threadManager, out, data.data(), info.w * 4 * info.h);
#else
memcpy(out, data.data(), info.w * 4 * info.h);
#endif
Expand All @@ -703,12 +702,12 @@ bool ReplacedTexture::CopyLevelTo(int level, void *out, int rowPitch) {
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);
memcpy((uint8_t *)out + rowPitch * y, data.data() + info.w * 4 * y, info.w * 4);
}
}, 0, info.h, MIN_LINES_PER_THREAD);
#else
for (int y = 0; y < info.h; ++y) {
memcpy((uint8_t *)out + rowPitch * y, &data[0] + info.w * 4 * y, info.w * 4);
memcpy((uint8_t *)out + rowPitch * y, data.data() + info.w * 4 * y, info.w * 4);
}
#endif
}
Expand Down
Loading

0 comments on commit ae8c804

Please sign in to comment.