2023-04-27 07:38:28 +03:00
|
|
|
// Copyright 2023 Citra Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include "common/file_util.h"
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
#include "common/texture.h"
|
|
|
|
#include "core/frontend/image_interface.h"
|
|
|
|
#include "video_core/custom_textures/material.h"
|
|
|
|
|
|
|
|
namespace VideoCore {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
CustomPixelFormat ToCustomPixelFormat(ddsktx_format format) {
|
|
|
|
switch (format) {
|
|
|
|
case DDSKTX_FORMAT_RGBA8:
|
|
|
|
return CustomPixelFormat::RGBA8;
|
|
|
|
case DDSKTX_FORMAT_BC1:
|
|
|
|
return CustomPixelFormat::BC1;
|
|
|
|
case DDSKTX_FORMAT_BC3:
|
|
|
|
return CustomPixelFormat::BC3;
|
|
|
|
case DDSKTX_FORMAT_BC5:
|
|
|
|
return CustomPixelFormat::BC5;
|
|
|
|
case DDSKTX_FORMAT_BC7:
|
|
|
|
return CustomPixelFormat::BC7;
|
|
|
|
case DDSKTX_FORMAT_ASTC4x4:
|
|
|
|
return CustomPixelFormat::ASTC4;
|
|
|
|
case DDSKTX_FORMAT_ASTC6x6:
|
|
|
|
return CustomPixelFormat::ASTC6;
|
|
|
|
case DDSKTX_FORMAT_ASTC8x6:
|
|
|
|
return CustomPixelFormat::ASTC8;
|
|
|
|
default:
|
|
|
|
LOG_ERROR(Common, "Unknown dds/ktx pixel format {}", format);
|
|
|
|
return CustomPixelFormat::RGBA8;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string_view MapTypeName(MapType type) {
|
|
|
|
switch (type) {
|
|
|
|
case MapType::Color:
|
|
|
|
return "Color";
|
|
|
|
case MapType::Normal:
|
|
|
|
return "Normal";
|
|
|
|
default:
|
|
|
|
return "Invalid";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // Anonymous namespace
|
|
|
|
|
|
|
|
CustomTexture::CustomTexture(Frontend::ImageInterface& image_interface_)
|
|
|
|
: image_interface{image_interface_} {}
|
|
|
|
|
|
|
|
CustomTexture::~CustomTexture() = default;
|
|
|
|
|
|
|
|
void CustomTexture::LoadFromDisk(bool flip_png) {
|
2023-05-07 02:34:28 +03:00
|
|
|
std::scoped_lock lock{decode_mutex};
|
|
|
|
if (IsLoaded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-27 07:38:28 +03:00
|
|
|
FileUtil::IOFile file{path, "rb"};
|
|
|
|
std::vector<u8> input(file.GetSize());
|
|
|
|
if (file.ReadBytes(input.data(), input.size()) != input.size()) {
|
|
|
|
LOG_CRITICAL(Render, "Failed to open custom texture: {}", path);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (file_format) {
|
|
|
|
case CustomFileFormat::PNG:
|
|
|
|
LoadPNG(input, flip_png);
|
|
|
|
break;
|
|
|
|
case CustomFileFormat::DDS:
|
|
|
|
case CustomFileFormat::KTX:
|
|
|
|
LoadDDS(input);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
LOG_ERROR(Render, "Unknown file format {}", file_format);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CustomTexture::LoadPNG(std::span<const u8> input, bool flip_png) {
|
|
|
|
if (!image_interface.DecodePNG(data, width, height, input)) {
|
|
|
|
LOG_ERROR(Render, "Failed to decode png: {}", path);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (flip_png) {
|
|
|
|
Common::FlipRGBA8Texture(data, width, height);
|
|
|
|
}
|
|
|
|
format = CustomPixelFormat::RGBA8;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CustomTexture::LoadDDS(std::span<const u8> input) {
|
|
|
|
ddsktx_format dds_format{};
|
|
|
|
image_interface.DecodeDDS(data, width, height, dds_format, input);
|
|
|
|
format = ToCustomPixelFormat(dds_format);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Material::LoadFromDisk(bool flip_png) noexcept {
|
|
|
|
if (IsDecoded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (CustomTexture* const texture : textures) {
|
|
|
|
if (!texture || texture->IsLoaded()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
texture->LoadFromDisk(flip_png);
|
|
|
|
size += texture->data.size();
|
2023-05-07 02:34:28 +03:00
|
|
|
LOG_DEBUG(Render, "Loading {} map {}", MapTypeName(texture->type), texture->path);
|
2023-04-27 07:38:28 +03:00
|
|
|
}
|
|
|
|
if (!textures[0]) {
|
|
|
|
LOG_ERROR(Render, "Unable to create material without color texture!");
|
|
|
|
state = DecodeState::Failed;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
width = textures[0]->width;
|
|
|
|
height = textures[0]->height;
|
|
|
|
format = textures[0]->format;
|
|
|
|
for (const CustomTexture* texture : textures) {
|
|
|
|
if (!texture) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (texture->width != width || texture->height != height) {
|
|
|
|
LOG_ERROR(Render,
|
|
|
|
"{} map {} of material with hash {:#016X} has dimentions {}x{} "
|
|
|
|
"which do not match the color texture dimentions {}x{}",
|
2023-05-07 02:34:28 +03:00
|
|
|
MapTypeName(texture->type), texture->path, hash, texture->width,
|
2023-04-27 07:38:28 +03:00
|
|
|
texture->height, width, height);
|
|
|
|
state = DecodeState::Failed;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (texture->format != format) {
|
|
|
|
LOG_ERROR(
|
|
|
|
Render, "{} map {} is stored with {} format which does not match color format {}",
|
|
|
|
MapTypeName(texture->type), texture->path,
|
|
|
|
CustomPixelFormatAsString(texture->format), CustomPixelFormatAsString(format));
|
|
|
|
state = DecodeState::Failed;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state = DecodeState::Decoded;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Material::AddMapTexture(CustomTexture* texture) noexcept {
|
|
|
|
const std::size_t index = static_cast<std::size_t>(texture->type);
|
|
|
|
if (textures[index]) {
|
|
|
|
LOG_ERROR(Render, "Textures {} and {} are assigned to the same material, ignoring!",
|
|
|
|
textures[index]->path, texture->path);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
textures[index] = texture;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace VideoCore
|