2017-03-11 13:43:42 +00:00
|
|
|
#include <algorithm>
|
2023-12-12 21:13:15 +00:00
|
|
|
#include <cstring>
|
2017-03-11 13:43:42 +00:00
|
|
|
|
2020-10-04 21:24:14 +00:00
|
|
|
#include "Common/GPU/thin3d.h"
|
2016-12-27 21:26:49 +00:00
|
|
|
#include "ext/jpge/jpgd.h"
|
2020-10-04 18:48:47 +00:00
|
|
|
#include "Common/UI/View.h"
|
|
|
|
#include "Common/UI/Context.h"
|
2020-10-04 21:24:14 +00:00
|
|
|
#include "Common/Render/DrawBuffer.h"
|
2020-08-15 18:53:08 +00:00
|
|
|
|
2020-10-04 18:48:47 +00:00
|
|
|
#include "Common/Data/Color/RGBAUtil.h"
|
|
|
|
#include "Common/Data/Format/ZIMLoad.h"
|
|
|
|
#include "Common/Data/Format/PNGLoad.h"
|
|
|
|
#include "Common/Math/math_util.h"
|
|
|
|
#include "Common/Math/curves.h"
|
|
|
|
#include "Common/File/VFS/VFS.h"
|
2018-03-27 21:10:33 +00:00
|
|
|
#include "Common/Log.h"
|
2020-08-15 18:53:08 +00:00
|
|
|
#include "Common/TimeUtil.h"
|
2022-11-21 19:15:22 +00:00
|
|
|
#include "Common/Render/ManagedTexture.h"
|
2023-12-13 10:45:48 +00:00
|
|
|
#include "Common/Thread/ThreadManager.h"
|
|
|
|
#include "Common/Thread/Waitable.h"
|
|
|
|
|
|
|
|
// TODO: It really feels like we should be able to simplify this.
|
|
|
|
class TextureLoadTask : public Task {
|
|
|
|
public:
|
|
|
|
TextureLoadTask(std::string_view filename, ImageFileType type, bool generateMips, TempImage *tempImage, ManagedTexture::LoadState *state, LimitedWaitable *waitable)
|
|
|
|
: filename_(filename), type_(type), generateMips_(generateMips), tempImage_(tempImage), state_(state), waitable_(waitable) {}
|
|
|
|
|
|
|
|
TaskType Type() const override { return TaskType::IO_BLOCKING; }
|
|
|
|
TaskPriority Priority() const override { return TaskPriority::NORMAL; }
|
|
|
|
|
|
|
|
void Run() override {
|
|
|
|
size_t fileSize;
|
|
|
|
uint8_t *buffer = g_VFS.ReadFile(filename_.c_str(), &fileSize);
|
|
|
|
if (!buffer) {
|
|
|
|
filename_.clear();
|
|
|
|
ERROR_LOG(IO, "Failed to read file '%s'", filename_.c_str());
|
|
|
|
*state_ = ManagedTexture::LoadState::FAILED;
|
|
|
|
return;
|
|
|
|
}
|
2016-12-27 21:26:49 +00:00
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
if (!tempImage_->LoadTextureLevels(buffer, fileSize, type_)) {
|
|
|
|
*state_ = ManagedTexture::LoadState::FAILED;
|
|
|
|
return;
|
2023-12-12 21:13:15 +00:00
|
|
|
}
|
2023-12-13 10:45:48 +00:00
|
|
|
delete[] buffer;
|
|
|
|
*state_ = ManagedTexture::LoadState::SUCCESS;
|
|
|
|
waitable_->Notify();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
LimitedWaitable *waitable_;
|
|
|
|
std::string filename_;
|
|
|
|
TempImage *tempImage_;
|
|
|
|
ImageFileType type_;
|
|
|
|
bool generateMips_;
|
|
|
|
ManagedTexture::LoadState *state_;
|
2023-12-12 21:13:15 +00:00
|
|
|
};
|
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
TempImage::~TempImage() {
|
|
|
|
// Make sure you haven't forgotten to call Free.
|
|
|
|
_dbg_assert_(levels[0] == nullptr);
|
|
|
|
}
|
|
|
|
|
2016-12-27 21:26:49 +00:00
|
|
|
static Draw::DataFormat ZimToT3DFormat(int zim) {
|
|
|
|
switch (zim) {
|
|
|
|
case ZIM_RGBA8888: return Draw::DataFormat::R8G8B8A8_UNORM;
|
|
|
|
default: return Draw::DataFormat::R8G8B8A8_UNORM;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static ImageFileType DetectImageFileType(const uint8_t *data, size_t size) {
|
2020-08-18 13:31:16 +00:00
|
|
|
if (size < 4) {
|
2023-12-13 10:45:48 +00:00
|
|
|
return ImageFileType::UNKNOWN;
|
2020-08-18 13:31:16 +00:00
|
|
|
}
|
2016-12-27 21:26:49 +00:00
|
|
|
if (!memcmp(data, "ZIMG", 4)) {
|
2023-12-13 10:45:48 +00:00
|
|
|
return ImageFileType::ZIM;
|
2023-12-12 21:13:15 +00:00
|
|
|
} else if (!memcmp(data, "\x89\x50\x4E\x47", 4)) {
|
2023-12-13 10:45:48 +00:00
|
|
|
return ImageFileType::PNG;
|
2023-12-12 21:13:15 +00:00
|
|
|
} else if (!memcmp(data, "\xff\xd8\xff\xe0", 4) || !memcmp(data, "\xff\xd8\xff\xe1", 4)) {
|
2023-12-13 10:45:48 +00:00
|
|
|
return ImageFileType::JPEG;
|
2023-12-12 21:13:15 +00:00
|
|
|
} else {
|
2023-12-13 10:45:48 +00:00
|
|
|
return ImageFileType::UNKNOWN;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-12 21:13:15 +00:00
|
|
|
bool TempImage::LoadTextureLevels(const uint8_t *data, size_t size, ImageFileType typeSuggestion) {
|
2023-12-13 10:45:48 +00:00
|
|
|
if (typeSuggestion == ImageFileType::DETECT) {
|
2023-12-12 21:13:15 +00:00
|
|
|
typeSuggestion = DetectImageFileType(data, size);
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
2023-12-13 10:45:48 +00:00
|
|
|
if (typeSuggestion == ImageFileType::UNKNOWN) {
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(G3D, "File (size: %d) has unknown format", (int)size);
|
2016-12-27 21:26:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-12-12 21:13:15 +00:00
|
|
|
type = typeSuggestion;
|
|
|
|
numLevels = 0;
|
|
|
|
zimFlags = 0;
|
2016-12-27 21:26:49 +00:00
|
|
|
|
2023-12-12 21:13:15 +00:00
|
|
|
switch (typeSuggestion) {
|
2023-12-13 10:45:48 +00:00
|
|
|
case ImageFileType::ZIM:
|
2023-12-12 21:13:15 +00:00
|
|
|
numLevels = LoadZIMPtr((const uint8_t *)data, size, width, height, &zimFlags, levels);
|
|
|
|
fmt = ZimToT3DFormat(zimFlags & ZIM_FORMAT_MASK);
|
|
|
|
break;
|
2016-12-27 21:26:49 +00:00
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
case ImageFileType::PNG:
|
2023-12-12 21:13:15 +00:00
|
|
|
if (1 == pngLoadPtr((const unsigned char *)data, size, &width[0], &height[0], &levels[0])) {
|
|
|
|
numLevels = 1;
|
|
|
|
fmt = Draw::DataFormat::R8G8B8A8_UNORM;
|
|
|
|
if (!levels[0]) {
|
|
|
|
ERROR_LOG(IO, "pngLoadPtr failed (input size = %d)", (int)size);
|
2017-03-05 11:30:42 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "PNG load failed");
|
2017-03-05 11:30:42 +00:00
|
|
|
return false;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
case ImageFileType::JPEG:
|
2016-12-27 21:26:49 +00:00
|
|
|
{
|
|
|
|
int actual_components = 0;
|
|
|
|
unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(data, (int)size, &width[0], &height[0], &actual_components, 4);
|
|
|
|
if (jpegBuf) {
|
2023-12-12 21:13:15 +00:00
|
|
|
numLevels = 1;
|
|
|
|
fmt = Draw::DataFormat::R8G8B8A8_UNORM;
|
|
|
|
levels[0] = (uint8_t *)jpegBuf;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
2023-12-12 21:13:15 +00:00
|
|
|
break;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "Unsupported image format %d", (int)type);
|
2016-12-27 21:26:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-12-12 21:13:15 +00:00
|
|
|
return numLevels > 0;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 09:45:42 +00:00
|
|
|
Draw::Texture *CreateTextureFromTempImage(Draw::DrawContext *draw, const TempImage &image, bool generateMips, const char *name) {
|
2017-01-16 16:43:07 +00:00
|
|
|
using namespace Draw;
|
2023-12-12 21:24:13 +00:00
|
|
|
_assert_(image.levels[0] != nullptr && image.width[0] > 0 && image.height[0] > 0);
|
2017-03-05 11:30:42 +00:00
|
|
|
|
2023-12-12 21:13:15 +00:00
|
|
|
int numLevels = image.numLevels;
|
|
|
|
if (numLevels < 0 || numLevels >= 16) {
|
|
|
|
ERROR_LOG(IO, "Invalid num_levels: %d. Falling back to one. Image: %dx%d", numLevels, image.width[0], image.height[0]);
|
|
|
|
numLevels = 1;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
2017-01-16 16:34:53 +00:00
|
|
|
|
2023-12-12 21:24:13 +00:00
|
|
|
int potentialLevels = std::min(log2i(image.width[0]), log2i(image.height[0]));
|
|
|
|
TextureDesc desc{};
|
|
|
|
desc.type = TextureType::LINEAR2D;
|
|
|
|
desc.format = image.fmt;
|
|
|
|
desc.width = image.width[0];
|
|
|
|
desc.height = image.height[0];
|
|
|
|
desc.depth = 1;
|
|
|
|
desc.mipLevels = generateMips ? potentialLevels : image.numLevels;
|
|
|
|
desc.generateMips = generateMips && potentialLevels > image.numLevels;
|
|
|
|
desc.tag = name;
|
|
|
|
for (int i = 0; i < image.numLevels; i++) {
|
|
|
|
desc.initData.push_back(image.levels[i]);
|
|
|
|
}
|
|
|
|
return draw->CreateTexture(desc);
|
|
|
|
}
|
|
|
|
|
|
|
|
Draw::Texture *CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, size_t dataSize, ImageFileType type, bool generateMips, const char *name) {
|
|
|
|
TempImage image;
|
|
|
|
if (!image.LoadTextureLevels(data, dataSize, type)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2023-12-13 09:45:42 +00:00
|
|
|
Draw::Texture *texture = CreateTextureFromTempImage(draw, image, generateMips, name);
|
2023-12-12 21:24:13 +00:00
|
|
|
image.Free();
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
2023-12-12 22:10:46 +00:00
|
|
|
Draw::Texture *CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type, bool generateMips) {
|
|
|
|
size_t fileSize;
|
|
|
|
uint8_t *buffer = g_VFS.ReadFile(filename, &fileSize);
|
|
|
|
if (!buffer) {
|
|
|
|
ERROR_LOG(IO, "Failed to read file '%s'", filename);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
Draw::Texture *texture = CreateTextureFromFileData(draw, buffer, fileSize, type, generateMips, filename);
|
|
|
|
delete[] buffer;
|
|
|
|
return texture;
|
|
|
|
}
|
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
Draw::Texture *ManagedTexture::GetTexture() {
|
2017-01-28 22:16:37 +00:00
|
|
|
if (texture_) {
|
2023-12-13 10:45:48 +00:00
|
|
|
return texture_;
|
|
|
|
} else if (state_ == LoadState::SUCCESS) {
|
|
|
|
if (taskWaitable_) {
|
|
|
|
taskWaitable_->WaitAndRelease();
|
|
|
|
taskWaitable_ = nullptr;
|
|
|
|
}
|
|
|
|
// Image load is done, texture creation is not.
|
|
|
|
texture_ = CreateTextureFromTempImage(draw_, pendingImage_, generateMips_, filename_.c_str());
|
2023-12-15 09:47:20 +00:00
|
|
|
if (!texture_) {
|
|
|
|
// Failed to create the texture for whatever reason, like dimensions. Don't retry next time.
|
|
|
|
state_ = LoadState::FAILED;
|
|
|
|
}
|
2023-12-13 10:45:48 +00:00
|
|
|
pendingImage_.Free();
|
2017-01-28 22:16:37 +00:00
|
|
|
}
|
2023-12-13 10:45:48 +00:00
|
|
|
return texture_;
|
|
|
|
}
|
2017-01-16 16:34:53 +00:00
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
ManagedTexture::ManagedTexture(Draw::DrawContext *draw, std::string_view filename, ImageFileType type, bool generateMips)
|
|
|
|
: draw_(draw), filename_(filename), type_(type), generateMips_(generateMips)
|
|
|
|
{
|
|
|
|
StartLoadTask();
|
|
|
|
}
|
2016-12-27 21:26:49 +00:00
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
ManagedTexture::~ManagedTexture() {
|
|
|
|
// Stop any pending loads.
|
|
|
|
if (taskWaitable_) {
|
|
|
|
taskWaitable_->WaitAndRelease();
|
|
|
|
pendingImage_.Free();
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
2023-12-13 10:45:48 +00:00
|
|
|
if (texture_)
|
|
|
|
texture_->Release();
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
void ManagedTexture::StartLoadTask() {
|
|
|
|
_dbg_assert_(!taskWaitable_);
|
|
|
|
taskWaitable_ = new LimitedWaitable();
|
|
|
|
g_threadManager.EnqueueTask(new TextureLoadTask(filename_, type_, generateMips_, &pendingImage_, &state_, taskWaitable_));
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
|
2018-03-27 21:10:33 +00:00
|
|
|
void ManagedTexture::DeviceLost() {
|
2020-08-15 14:13:24 +00:00
|
|
|
INFO_LOG(G3D, "ManagedTexture::DeviceLost(%s)", filename_.c_str());
|
2023-12-13 10:45:48 +00:00
|
|
|
if (taskWaitable_) {
|
|
|
|
taskWaitable_->WaitAndRelease();
|
2023-12-31 15:48:44 +00:00
|
|
|
taskWaitable_ = nullptr;
|
2023-12-13 10:45:48 +00:00
|
|
|
pendingImage_.Free();
|
|
|
|
}
|
2018-03-27 21:10:33 +00:00
|
|
|
if (texture_)
|
|
|
|
texture_->Release();
|
|
|
|
texture_ = nullptr;
|
2023-12-13 10:45:48 +00:00
|
|
|
if (state_ == LoadState::SUCCESS) {
|
|
|
|
state_ = LoadState::PENDING;
|
|
|
|
}
|
2018-03-27 21:10:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ManagedTexture::DeviceRestored(Draw::DrawContext *draw) {
|
2020-08-15 14:13:24 +00:00
|
|
|
INFO_LOG(G3D, "ManagedTexture::DeviceRestored(%s)", filename_.c_str());
|
2023-10-09 16:24:43 +00:00
|
|
|
|
2018-03-27 21:10:33 +00:00
|
|
|
draw_ = draw;
|
2023-10-09 16:24:43 +00:00
|
|
|
|
|
|
|
_dbg_assert_(!texture_);
|
|
|
|
if (texture_) {
|
|
|
|
ERROR_LOG(G3D, "ManagedTexture: Unexpected - texture already present: %s", filename_.c_str());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-12-13 10:45:48 +00:00
|
|
|
if (state_ == LoadState::PENDING) {
|
|
|
|
// Kick off a new load task.
|
|
|
|
StartLoadTask();
|
2018-03-27 21:10:33 +00:00
|
|
|
}
|
|
|
|
}
|