2017-03-11 13:43:42 +00:00
|
|
|
#include <algorithm>
|
|
|
|
|
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"
|
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) {
|
|
|
|
return TYPE_UNKNOWN;
|
|
|
|
}
|
2016-12-27 21:26:49 +00:00
|
|
|
if (!memcmp(data, "ZIMG", 4)) {
|
|
|
|
return ZIM;
|
|
|
|
}
|
|
|
|
else if (!memcmp(data, "\x89\x50\x4E\x47", 4)) {
|
|
|
|
return PNG;
|
|
|
|
}
|
2020-08-18 13:31:16 +00:00
|
|
|
else if (!memcmp(data, "\xff\xd8\xff\xe0", 4) || !memcmp(data, "\xff\xd8\xff\xe1", 4)) {
|
2016-12-27 21:26:49 +00:00
|
|
|
return JPEG;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return TYPE_UNKNOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool LoadTextureLevels(const uint8_t *data, size_t size, ImageFileType type, int width[16], int height[16], int *num_levels, Draw::DataFormat *fmt, uint8_t *image[16], int *zim_flags) {
|
|
|
|
if (type == DETECT) {
|
|
|
|
type = DetectImageFileType(data, size);
|
|
|
|
}
|
|
|
|
if (type == TYPE_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
*num_levels = 0;
|
|
|
|
*zim_flags = 0;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case ZIM:
|
|
|
|
{
|
|
|
|
*num_levels = LoadZIMPtr((const uint8_t *)data, size, width, height, zim_flags, image);
|
|
|
|
*fmt = ZimToT3DFormat(*zim_flags & ZIM_FORMAT_MASK);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PNG:
|
2020-10-04 18:48:47 +00:00
|
|
|
if (1 == pngLoadPtr((const unsigned char *)data, size, &width[0], &height[0], &image[0])) {
|
2016-12-27 21:26:49 +00:00
|
|
|
*num_levels = 1;
|
|
|
|
*fmt = Draw::DataFormat::R8G8B8A8_UNORM;
|
2017-03-05 11:30:42 +00:00
|
|
|
if (!image[0]) {
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "WTF");
|
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;
|
|
|
|
|
|
|
|
case JPEG:
|
|
|
|
{
|
|
|
|
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) {
|
|
|
|
*num_levels = 1;
|
|
|
|
*fmt = Draw::DataFormat::R8G8B8A8_UNORM;
|
|
|
|
image[0] = (uint8_t *)jpegBuf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return *num_levels > 0;
|
|
|
|
}
|
|
|
|
|
2020-08-08 21:18:17 +00:00
|
|
|
bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, ImageFileType type, bool generateMips, const char *name) {
|
2018-03-27 21:10:33 +00:00
|
|
|
generateMips_ = generateMips;
|
2017-01-16 16:43:07 +00:00
|
|
|
using namespace Draw;
|
|
|
|
|
2017-01-28 22:16:37 +00:00
|
|
|
int width[16]{}, height[16]{};
|
|
|
|
uint8_t *image[16]{};
|
2016-12-27 21:26:49 +00:00
|
|
|
|
2017-01-28 22:16:37 +00:00
|
|
|
int num_levels = 0;
|
|
|
|
int zim_flags = 0;
|
2017-01-16 16:43:07 +00:00
|
|
|
DataFormat fmt;
|
2016-12-27 21:26:49 +00:00
|
|
|
if (!LoadTextureLevels(data, dataSize, type, width, height, &num_levels, &fmt, image, &zim_flags)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-16 12:11:56 +00:00
|
|
|
_assert_(image[0] != nullptr);
|
2017-03-05 11:30:42 +00:00
|
|
|
|
2016-12-27 21:26:49 +00:00
|
|
|
if (num_levels < 0 || num_levels >= 16) {
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "Invalid num_levels: %d. Falling back to one. Image: %dx%d", num_levels, width[0], height[0]);
|
2016-12-27 21:26:49 +00:00
|
|
|
num_levels = 1;
|
|
|
|
}
|
2017-01-16 16:34:53 +00:00
|
|
|
|
2018-11-22 22:53:58 +00:00
|
|
|
// Free the old texture, if any.
|
2017-01-28 22:16:37 +00:00
|
|
|
if (texture_) {
|
2017-01-16 16:34:53 +00:00
|
|
|
delete texture_;
|
2017-01-28 22:16:37 +00:00
|
|
|
texture_ = nullptr;
|
|
|
|
}
|
2017-01-16 16:34:53 +00:00
|
|
|
|
2017-03-11 13:43:42 +00:00
|
|
|
int potentialLevels = std::min(log2i(width[0]), log2i(height[0]));
|
2021-02-13 10:33:15 +00:00
|
|
|
if (width[0] > 0 && height[0] > 0) {
|
|
|
|
TextureDesc desc{};
|
|
|
|
desc.type = TextureType::LINEAR2D;
|
|
|
|
desc.format = fmt;
|
|
|
|
desc.width = width[0];
|
|
|
|
desc.height = height[0];
|
|
|
|
desc.depth = 1;
|
|
|
|
desc.mipLevels = generateMips ? potentialLevels : num_levels;
|
|
|
|
desc.generateMips = generateMips && potentialLevels > num_levels;
|
|
|
|
desc.tag = name;
|
|
|
|
for (int i = 0; i < num_levels; i++) {
|
|
|
|
desc.initData.push_back(image[i]);
|
|
|
|
}
|
|
|
|
texture_ = draw_->CreateTexture(desc);
|
2017-01-17 17:31:44 +00:00
|
|
|
}
|
2016-12-27 21:26:49 +00:00
|
|
|
for (int i = 0; i < num_levels; i++) {
|
2017-01-17 17:31:44 +00:00
|
|
|
if (image[i])
|
2016-12-27 21:26:49 +00:00
|
|
|
free(image[i]);
|
|
|
|
}
|
2021-02-13 10:33:15 +00:00
|
|
|
return texture_ != nullptr;
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
|
2017-03-11 13:43:42 +00:00
|
|
|
bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) {
|
2018-03-27 21:10:33 +00:00
|
|
|
generateMips_ = generateMips;
|
2016-12-27 21:26:49 +00:00
|
|
|
size_t fileSize;
|
2023-03-06 13:23:56 +00:00
|
|
|
uint8_t *buffer = g_VFS.ReadFile(filename.c_str(), &fileSize);
|
2016-12-27 21:26:49 +00:00
|
|
|
if (!buffer) {
|
2022-09-30 09:26:30 +00:00
|
|
|
filename_.clear();
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "Failed to read file '%s'", filename.c_str());
|
2016-12-27 21:26:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
2020-08-08 21:18:17 +00:00
|
|
|
bool retval = LoadFromFileData(buffer, fileSize, type, generateMips, filename.c_str());
|
2016-12-27 21:26:49 +00:00
|
|
|
if (retval) {
|
|
|
|
filename_ = filename;
|
2017-02-06 10:55:54 +00:00
|
|
|
} else {
|
2022-09-30 09:26:30 +00:00
|
|
|
filename_.clear();
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "Failed to load texture '%s'", filename.c_str());
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
|
|
|
delete[] buffer;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2017-05-18 10:52:03 +00:00
|
|
|
std::unique_ptr<ManagedTexture> CreateTextureFromFile(Draw::DrawContext *draw, const char *filename, ImageFileType type, bool generateMips) {
|
2017-02-06 10:55:54 +00:00
|
|
|
if (!draw)
|
2017-05-18 10:52:03 +00:00
|
|
|
return std::unique_ptr<ManagedTexture>();
|
2017-05-18 10:41:42 +00:00
|
|
|
// TODO: Load the texture on a background thread.
|
2016-12-27 21:26:49 +00:00
|
|
|
ManagedTexture *mtex = new ManagedTexture(draw);
|
2017-03-11 13:43:42 +00:00
|
|
|
if (!mtex->LoadFromFile(filename, type, generateMips)) {
|
2016-12-27 21:26:49 +00:00
|
|
|
delete mtex;
|
2017-05-18 10:52:03 +00:00
|
|
|
return std::unique_ptr<ManagedTexture>();
|
2016-12-27 21:26:49 +00:00
|
|
|
}
|
2018-03-01 12:50:56 +00:00
|
|
|
return std::unique_ptr<ManagedTexture>(mtex);
|
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());
|
2018-03-27 21:10:33 +00:00
|
|
|
if (texture_)
|
|
|
|
texture_->Release();
|
|
|
|
texture_ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ManagedTexture::DeviceRestored(Draw::DrawContext *draw) {
|
2020-08-15 14:13:24 +00:00
|
|
|
INFO_LOG(G3D, "ManagedTexture::DeviceRestored(%s)", filename_.c_str());
|
2018-03-27 21:10:33 +00:00
|
|
|
_assert_(!texture_);
|
|
|
|
draw_ = draw;
|
|
|
|
// Vulkan: Can't load textures before the first frame has started.
|
|
|
|
// Should probably try to lift that restriction again someday..
|
|
|
|
loadPending_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Draw::Texture *ManagedTexture::GetTexture() {
|
|
|
|
if (loadPending_) {
|
|
|
|
if (!LoadFromFile(filename_, ImageFileType::DETECT, generateMips_)) {
|
2020-08-15 14:13:24 +00:00
|
|
|
ERROR_LOG(IO, "ManagedTexture failed: '%s'", filename_.c_str());
|
2018-03-27 21:10:33 +00:00
|
|
|
}
|
|
|
|
loadPending_ = false;
|
|
|
|
}
|
|
|
|
return texture_;
|
|
|
|
}
|
|
|
|
|
2016-12-27 21:26:49 +00:00
|
|
|
// TODO: Remove the code duplication between this and LoadFromFileData
|
2020-08-09 07:35:56 +00:00
|
|
|
std::unique_ptr<ManagedTexture> CreateTextureFromFileData(Draw::DrawContext *draw, const uint8_t *data, int size, ImageFileType type, bool generateMips, const char *name) {
|
2017-02-06 10:55:54 +00:00
|
|
|
if (!draw)
|
2017-05-18 10:52:03 +00:00
|
|
|
return std::unique_ptr<ManagedTexture>();
|
2016-12-27 21:26:49 +00:00
|
|
|
ManagedTexture *mtex = new ManagedTexture(draw);
|
2020-08-09 07:35:56 +00:00
|
|
|
if (mtex->LoadFromFileData(data, size, type, generateMips, name)) {
|
2018-11-19 07:27:46 +00:00
|
|
|
return std::unique_ptr<ManagedTexture>(mtex);
|
|
|
|
} else {
|
|
|
|
// Best to return a null pointer if we fail!
|
|
|
|
delete mtex;
|
|
|
|
return std::unique_ptr<ManagedTexture>();
|
|
|
|
}
|
2018-03-01 11:11:10 +00:00
|
|
|
}
|