Skip to content

Commit a651fd8

Browse files
authored
Merge pull request #17091 from hrydgard/fully-threaded-replacement
Replacement: Do all I/O on threaded tasks
2 parents c10d420 + 8e34284 commit a651fd8

File tree

4 files changed

+120
-131
lines changed

4 files changed

+120
-131
lines changed

GPU/Common/ReplacedTexture.cpp

+86-105
Original file line numberDiff line numberDiff line change
@@ -90,32 +90,49 @@ bool ReplacedTexture::IsReady(double budget) {
9090

9191
_assert_(!threadWaitable_);
9292
threadWaitable_ = new LimitedWaitable();
93+
SetState(ReplacementState::PENDING);
9394
g_threadManager.EnqueueTask(new ReplacedTextureTask(vfs_, *this, threadWaitable_));
9495
if (threadWaitable_->WaitFor(budget)) {
9596
// If we successfully wait here, we're done. The thread will set state accordingly.
9697
_assert_(State() == ReplacementState::ACTIVE || State() == ReplacementState::NOT_FOUND || State() == ReplacementState::CANCEL_INIT);
9798
return true;
9899
}
99-
SetState(ReplacementState::PENDING);
100100
// Still pending on thread.
101101
return false;
102102
}
103103

104-
void ReplacedTexture::FinishPopulate(const ReplacementDesc &desc) {
105-
logId_ = desc.logId;
106-
levelData_ = desc.cache;
104+
void ReplacedTexture::FinishPopulate(ReplacementDesc *desc) {
105+
logId_ = desc->logId;
106+
levelData_ = desc->cache;
107+
desc_ = desc;
108+
SetState(ReplacementState::POPULATED);
109+
110+
// TODO: What used to be here is now done on the thread task.
111+
}
107112

108-
// TODO: The rest can be done on the thread.
113+
void ReplacedTexture::Prepare(VFSBackend *vfs) {
114+
this->vfs_ = vfs;
115+
116+
std::unique_lock<std::mutex> lock(mutex_);
117+
118+
_assert_msg_(levelData_ != nullptr, "Level cache not set");
119+
120+
// We must lock around access to levelData_ in case two textures try to load it at once.
121+
std::lock_guard<std::mutex> guard(levelData_->lock);
122+
123+
for (int i = 0; i < std::min(MAX_REPLACEMENT_MIP_LEVELS, (int)desc_->filenames.size()); ++i) {
124+
if (State() == ReplacementState::CANCEL_INIT) {
125+
break;
126+
}
109127

110-
for (int i = 0; i < std::min(MAX_REPLACEMENT_MIP_LEVELS, (int)desc.filenames.size()); ++i) {
111-
if (desc.filenames[i].empty()) {
128+
if (desc_->filenames[i].empty()) {
112129
// Out of valid mip levels. Bail out.
113130
break;
114131
}
115132

116-
const Path filename = desc.basePath / desc.filenames[i];
133+
const Path filename = desc_->basePath / desc_->filenames[i];
117134

118-
VFSFileReference *fileRef = vfs_->GetFile(desc.filenames[i].c_str());
135+
VFSFileReference *fileRef = vfs_->GetFile(desc_->filenames[i].c_str());
119136
if (!fileRef) {
120137
// If the file doesn't exist, let's just bail immediately here.
121138
break;
@@ -130,58 +147,43 @@ void ReplacedTexture::FinishPopulate(const ReplacementDesc &desc) {
130147
fmt = Draw::DataFormat::R8G8B8A8_UNORM;
131148
}
132149

133-
bool good;
134-
135150
level.fileRef = fileRef;
136-
good = PopulateLevel(level, false);
137-
138-
// We pad files that have been hashrange'd so they are the same texture size.
139-
level.w = (level.w * desc.w) / desc.newW;
140-
level.h = (level.h * desc.h) / desc.newH;
141-
142-
if (good && i != 0) {
143-
// Check that the mipmap size is correct. Can't load mips of the wrong size.
144-
if (level.w != (levels_[0].w >> i) || level.h != (levels_[0].h >> i)) {
145-
WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d, '%s')", level.w, level.h, levels_[0].w >> i, levels_[0].h >> i, i, filename.c_str());
146-
good = false;
147-
}
148-
}
149151

150-
if (good)
152+
if (LoadLevelData(level, i)) {
151153
levels_.push_back(level);
152-
// Otherwise, we're done loading mips (bad PNG or bad size, either way.)
153-
else
154+
} else {
155+
// Otherwise, we're done loading mips (bad PNG or bad size, either way.)
154156
break;
157+
}
155158
}
156159

160+
delete desc_;
161+
desc_ = nullptr;
162+
157163
if (levels_.empty()) {
158164
// Bad.
159165
SetState(ReplacementState::NOT_FOUND);
160166
levelData_ = nullptr;
161167
return;
162168
}
163169

164-
// Populate the data pointer.
165-
SetState(ReplacementState::POPULATED);
170+
SetState(ReplacementState::ACTIVE);
171+
172+
if (threadWaitable_)
173+
threadWaitable_->Notify();
166174
}
167175

168-
bool ReplacedTexture::PopulateLevel(ReplacedTextureLevel &level, bool ignoreError) {
176+
bool ReplacedTexture::LoadLevelData(ReplacedTextureLevel &level, int mipLevel) {
169177
bool good = false;
170178

171-
if (!level.fileRef) {
172-
if (!ignoreError)
173-
ERROR_LOG(G3D, "Error opening replacement texture file '%s' in textures.zip", level.file.c_str());
174-
return false;
175-
}
176-
177179
size_t fileSize;
178-
VFSOpenFile *file = vfs_->OpenFileForRead(level.fileRef, &fileSize);
179-
if (!file) {
180+
VFSOpenFile *openFile = vfs_->OpenFileForRead(level.fileRef, &fileSize);
181+
if (!openFile) {
180182
return false;
181183
}
182184

183185
std::string magic;
184-
auto imageType = Identify(vfs_, file, &magic);
186+
ReplacedImageType imageType = Identify(vfs_, openFile, &magic);
185187

186188
if (imageType == ReplacedImageType::ZIM) {
187189
uint32_t ignore = 0;
@@ -191,13 +193,13 @@ bool ReplacedTexture::PopulateLevel(ReplacedTextureLevel &level, bool ignoreErro
191193
uint32_t h;
192194
uint32_t flags;
193195
} header;
194-
good = vfs_->Read(file, &header, sizeof(header)) == sizeof(header);
196+
good = vfs_->Read(openFile, &header, sizeof(header)) == sizeof(header);
195197
level.w = header.w;
196198
level.h = header.h;
197199
good = (header.flags & ZIM_FORMAT_MASK) == ZIM_RGBA8888;
198200
} else if (imageType == ReplacedImageType::PNG) {
199201
PNGHeaderPeek headerPeek;
200-
good = vfs_->Read(file, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek);
202+
good = vfs_->Read(openFile, &headerPeek, sizeof(headerPeek)) == sizeof(headerPeek);
201203
if (good && headerPeek.IsValidPNGHeader()) {
202204
level.w = headerPeek.Width();
203205
level.h = headerPeek.Height();
@@ -209,101 +211,80 @@ bool ReplacedTexture::PopulateLevel(ReplacedTextureLevel &level, bool ignoreErro
209211
} else {
210212
ERROR_LOG(G3D, "Could not load texture replacement info: %s - unsupported format %s", level.file.ToVisualString().c_str(), magic.c_str());
211213
}
212-
vfs_->CloseFile(file);
213214

214-
return good;
215-
}
216-
217-
void ReplacedTexture::Prepare(VFSBackend *vfs) {
218-
std::unique_lock<std::mutex> lock(mutex_);
219-
this->vfs_ = vfs;
215+
// Is this really the right place to do it?
216+
level.w = (level.w * desc_->w) / desc_->newW;
217+
level.h = (level.h * desc_->h) / desc_->newH;
220218

221-
if (State() == ReplacementState::CANCEL_INIT) {
222-
return;
219+
if (good && mipLevel != 0) {
220+
// Check that the mipmap size is correct. Can't load mips of the wrong size.
221+
if (level.w != (levels_[0].w >> mipLevel) || level.h != (levels_[0].h >> mipLevel)) {
222+
WARN_LOG(G3D, "Replacement mipmap invalid: size=%dx%d, expected=%dx%d (level %d)",
223+
level.w, level.h, levels_[0].w >> mipLevel, levels_[0].h >> mipLevel, mipLevel);
224+
good = false;
225+
}
223226
}
224227

225-
for (int i = 0; i < (int)levels_.size(); ++i) {
226-
if (State() == ReplacementState::CANCEL_INIT)
227-
break;
228-
PrepareData(i);
228+
if (!good) {
229+
return false;
229230
}
230231

231-
if (threadWaitable_)
232-
threadWaitable_->Notify();
233-
}
234-
235-
void ReplacedTexture::PrepareData(int level) {
236-
_assert_msg_((size_t)level < levels_.size(), "Invalid miplevel");
237-
_assert_msg_(levelData_ != nullptr, "Level cache not set");
238-
239-
// We must lock around access to levelData_ in case two textures try to load it at once.
240-
std::lock_guard<std::mutex> guard(levelData_->lock);
241-
242-
const ReplacedTextureLevel &info = levels_[level];
243-
244-
if (levelData_->data.size() <= level) {
245-
levelData_->data.resize(level + 1);
232+
if (levelData_->data.size() <= mipLevel) {
233+
levelData_->data.resize(mipLevel + 1);
246234
}
247235

248-
std::vector<uint8_t> &out = levelData_->data[level];
236+
std::vector<uint8_t> &out = levelData_->data[mipLevel];
249237

250238
// Already populated from cache.
251239
if (!out.empty()) {
252-
SetState(ReplacementState::ACTIVE);
253-
return;
240+
return true;
254241
}
255242

256-
ReplacedImageType imageType;
257-
258-
size_t fileSize;
259-
VFSOpenFile *openFile = vfs_->OpenFileForRead(info.fileRef, &fileSize);
260-
261-
std::string magic;
262-
imageType = Identify(vfs_, openFile, &magic);
263-
264243
auto cleanup = [&] {
265244
vfs_->CloseFile(openFile);
266245
};
267246

247+
vfs_->Rewind(openFile);
248+
268249
if (imageType == ReplacedImageType::ZIM) {
269250
std::unique_ptr<uint8_t[]> zim(new uint8_t[fileSize]);
270251
if (!zim) {
271252
ERROR_LOG(G3D, "Failed to allocate memory for texture replacement");
272253
SetState(ReplacementState::NOT_FOUND);
273254
cleanup();
274-
return;
255+
return false;
275256
}
276257

277258
if (vfs_->Read(openFile, &zim[0], fileSize) != fileSize) {
278-
ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", info.file.c_str());
259+
ERROR_LOG(G3D, "Could not load texture replacement: %s - failed to read ZIM", level.file.c_str());
279260
SetState(ReplacementState::NOT_FOUND);
280261
cleanup();
281-
return;
262+
return false;
282263
}
283264

284265
int w, h, f;
285266
uint8_t *image;
286267
if (LoadZIMPtr(&zim[0], fileSize, &w, &h, &f, &image)) {
287-
if (w > info.w || h > info.h) {
288-
ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str());
268+
if (w > level.w || h > level.h) {
269+
ERROR_LOG(G3D, "Texture replacement changed since header read: %s", level.file.c_str());
289270
SetState(ReplacementState::NOT_FOUND);
290271
cleanup();
291-
return;
272+
return false;
292273
}
293274

294-
out.resize(info.w * info.h * 4);
295-
if (w == info.w) {
296-
memcpy(&out[0], image, info.w * 4 * info.h);
275+
out.resize(level.w * level.h * 4);
276+
if (w == level.w) {
277+
memcpy(&out[0], image, level.w * 4 * level.h);
297278
} else {
298279
for (int y = 0; y < h; ++y) {
299-
memcpy(&out[info.w * 4 * y], image + w * 4 * y, w * 4);
280+
memcpy(&out[level.w * 4 * y], image + w * 4 * y, w * 4);
300281
}
301282
}
302283
free(image);
303284
}
304285

305-
CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], info.w, w, h, 0xFF000000);
306-
if (res == CHECKALPHA_ANY || level == 0) {
286+
CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], level.w, w, h, 0xFF000000);
287+
if (res == CHECKALPHA_ANY || mipLevel == 0) {
307288
alphaStatus_ = ReplacedTextureAlpha(res);
308289
}
309290
} else if (imageType == ReplacedImageType::PNG) {
@@ -314,49 +295,49 @@ void ReplacedTexture::PrepareData(int level) {
314295
pngdata.resize(fileSize);
315296
pngdata.resize(vfs_->Read(openFile, &pngdata[0], fileSize));
316297
if (!png_image_begin_read_from_memory(&png, &pngdata[0], pngdata.size())) {
317-
ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", info.file.c_str(), png.message);
298+
ERROR_LOG(G3D, "Could not load texture replacement info: %s - %s (zip)", level.file.c_str(), png.message);
318299
SetState(ReplacementState::NOT_FOUND);
319300
cleanup();
320-
return;
301+
return false;
321302
}
322-
if (png.width > (uint32_t)info.w || png.height > (uint32_t)info.h) {
323-
ERROR_LOG(G3D, "Texture replacement changed since header read: %s", info.file.c_str());
303+
if (png.width > (uint32_t)level.w || png.height > (uint32_t)level.h) {
304+
ERROR_LOG(G3D, "Texture replacement changed since header read: %s", level.file.c_str());
324305
SetState(ReplacementState::NOT_FOUND);
325306
cleanup();
326-
return;
307+
return false;
327308
}
328309

329310
bool checkedAlpha = false;
330311
if ((png.format & PNG_FORMAT_FLAG_ALPHA) == 0) {
331312
// Well, we know for sure it doesn't have alpha.
332-
if (level == 0) {
313+
if (mipLevel == 0) {
333314
alphaStatus_ = ReplacedTextureAlpha::FULL;
334315
}
335316
checkedAlpha = true;
336317
}
337318
png.format = PNG_FORMAT_RGBA;
338319

339-
out.resize(info.w * info.h * 4);
340-
if (!png_image_finish_read(&png, nullptr, &out[0], info.w * 4, nullptr)) {
341-
ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", info.file.c_str(), png.message);
320+
out.resize(level.w * level.h * 4);
321+
if (!png_image_finish_read(&png, nullptr, &out[0], level.w * 4, nullptr)) {
322+
ERROR_LOG(G3D, "Could not load texture replacement: %s - %s", level.file.c_str(), png.message);
342323
SetState(ReplacementState::NOT_FOUND);
343324
cleanup();
344325
out.resize(0);
345-
return;
326+
return false;
346327
}
347328
png_image_free(&png);
348329

349330
if (!checkedAlpha) {
350331
// This will only check the hashed bits.
351-
CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], info.w, png.width, png.height, 0xFF000000);
352-
if (res == CHECKALPHA_ANY || level == 0) {
332+
CheckAlphaResult res = CheckAlpha32Rect((u32 *)&out[0], level.w, png.width, png.height, 0xFF000000);
333+
if (res == CHECKALPHA_ANY || mipLevel == 0) {
353334
alphaStatus_ = ReplacedTextureAlpha(res);
354335
}
355336
}
356337
}
357338

358-
SetState(ReplacementState::ACTIVE);
359339
cleanup();
340+
return true;
360341
}
361342

362343
void ReplacedTexture::PurgeIfOlder(double t) {

GPU/Common/ReplacedTexture.h

+4-5
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,14 @@ struct ReplacedTexture {
129129
bool IsReady(double budget);
130130
bool CopyLevelTo(int level, void *out, int rowPitch);
131131

132-
void FinishPopulate(const ReplacementDesc &desc);
132+
void FinishPopulate(ReplacementDesc *desc);
133133
std::string logId_;
134134

135135
private:
136136
void Prepare(VFSBackend *vfs);
137-
void PrepareData(int level);
137+
bool LoadLevelData(ReplacedTextureLevel &info, int level);
138138
void PurgeIfOlder(double t);
139139

140-
bool PopulateLevel(ReplacedTextureLevel & level, bool ignoreError);
141-
142140
std::vector<ReplacedTextureLevel> levels_;
143141
ReplacedLevelsCache *levelData_ = nullptr;
144142

@@ -148,9 +146,10 @@ struct ReplacedTexture {
148146
std::mutex mutex_;
149147
Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED; // NOTE: Right now, the only supported format is Draw::DataFormat::R8G8B8A8_UNORM.
150148

151-
ReplacementState state_ = ReplacementState::UNINITIALIZED;
149+
std::atomic<ReplacementState> state_ = ReplacementState::UNINITIALIZED;
152150

153151
VFSBackend *vfs_ = nullptr;
152+
ReplacementDesc *desc_ = nullptr;
154153

155154
friend class TextureReplacer;
156155
friend class ReplacedTextureTask;

GPU/Common/TextureCacheCommon.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -2829,8 +2829,9 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt
28292829
if (plan.replaceValid) {
28302830
// We're replacing, so we won't scale.
28312831
plan.scaleFactor = 1;
2832+
// We're ignoring how many levels were specified - instead we just load all available from the replacer.
28322833
plan.levelsToLoad = plan.replaced->NumLevels();
2833-
plan.levelsToCreate = std::min(plan.levelsToLoad, plan.levelsToCreate);
2834+
plan.levelsToCreate = plan.levelsToLoad; // Or more, if we wanted to generate.
28342835
plan.badMipSizes = false;
28352836
// But, we still need to create the texture at a larger size.
28362837
plan.replaced->GetSize(0, &plan.createW, &plan.createH);

0 commit comments

Comments
 (0)