/ src / video_core / custom_textures / material.cpp
material.cpp
  1  // Copyright 2023 Citra Emulator Project
  2  // Licensed under GPLv2 or any later version
  3  // Refer to the license.txt file included.
  4  
  5  #include "common/file_util.h"
  6  #include "common/logging/log.h"
  7  #include "common/texture.h"
  8  #include "core/frontend/image_interface.h"
  9  #include "video_core/custom_textures/material.h"
 10  
 11  namespace VideoCore {
 12  
 13  namespace {
 14  
 15  CustomPixelFormat ToCustomPixelFormat(ddsktx_format format) {
 16      switch (format) {
 17      case DDSKTX_FORMAT_RGBA8:
 18          return CustomPixelFormat::RGBA8;
 19      case DDSKTX_FORMAT_BC1:
 20          return CustomPixelFormat::BC1;
 21      case DDSKTX_FORMAT_BC3:
 22          return CustomPixelFormat::BC3;
 23      case DDSKTX_FORMAT_BC5:
 24          return CustomPixelFormat::BC5;
 25      case DDSKTX_FORMAT_BC7:
 26          return CustomPixelFormat::BC7;
 27      case DDSKTX_FORMAT_ASTC4x4:
 28          return CustomPixelFormat::ASTC4;
 29      case DDSKTX_FORMAT_ASTC6x6:
 30          return CustomPixelFormat::ASTC6;
 31      case DDSKTX_FORMAT_ASTC8x6:
 32          return CustomPixelFormat::ASTC8;
 33      default:
 34          LOG_ERROR(Common, "Unknown dds/ktx pixel format {}", format);
 35          return CustomPixelFormat::RGBA8;
 36      }
 37  }
 38  
 39  std::string_view MapTypeName(MapType type) {
 40      switch (type) {
 41      case MapType::Color:
 42          return "Color";
 43      case MapType::Normal:
 44          return "Normal";
 45      default:
 46          return "Invalid";
 47      }
 48  }
 49  
 50  } // Anonymous namespace
 51  
 52  CustomTexture::CustomTexture(Frontend::ImageInterface& image_interface_)
 53      : image_interface{image_interface_} {}
 54  
 55  CustomTexture::~CustomTexture() = default;
 56  
 57  void CustomTexture::LoadFromDisk(bool flip_png) {
 58      std::scoped_lock lock{decode_mutex};
 59      if (IsLoaded()) {
 60          return;
 61      }
 62  
 63      FileUtil::IOFile file{path, "rb"};
 64      std::vector<u8> input(file.GetSize());
 65      if (file.ReadBytes(input.data(), input.size()) != input.size()) {
 66          LOG_CRITICAL(Render, "Failed to open custom texture: {}", path);
 67          return;
 68      }
 69      switch (file_format) {
 70      case CustomFileFormat::PNG:
 71          LoadPNG(input, flip_png);
 72          break;
 73      case CustomFileFormat::DDS:
 74      case CustomFileFormat::KTX:
 75          LoadDDS(input);
 76          break;
 77      default:
 78          LOG_ERROR(Render, "Unknown file format {}", file_format);
 79      }
 80  }
 81  
 82  void CustomTexture::LoadPNG(std::span<const u8> input, bool flip_png) {
 83      if (!image_interface.DecodePNG(data, width, height, input)) {
 84          LOG_ERROR(Render, "Failed to decode png: {}", path);
 85          return;
 86      }
 87      if (flip_png) {
 88          Common::FlipRGBA8Texture(data, width, height);
 89      }
 90      format = CustomPixelFormat::RGBA8;
 91  }
 92  
 93  void CustomTexture::LoadDDS(std::span<const u8> input) {
 94      ddsktx_format dds_format{};
 95      image_interface.DecodeDDS(data, width, height, dds_format, input);
 96      format = ToCustomPixelFormat(dds_format);
 97  }
 98  
 99  void Material::LoadFromDisk(bool flip_png) noexcept {
100      if (IsDecoded()) {
101          return;
102      }
103      for (CustomTexture* const texture : textures) {
104          if (!texture || texture->IsLoaded()) {
105              continue;
106          }
107          texture->LoadFromDisk(flip_png);
108          size += texture->data.size();
109          LOG_DEBUG(Render, "Loading {} map {}", MapTypeName(texture->type), texture->path);
110      }
111      if (!textures[0]) {
112          LOG_ERROR(Render, "Unable to create material without color texture!");
113          state = DecodeState::Failed;
114          return;
115      }
116      width = textures[0]->width;
117      height = textures[0]->height;
118      format = textures[0]->format;
119      for (const CustomTexture* texture : textures) {
120          if (!texture) {
121              continue;
122          }
123          if (texture->width != width || texture->height != height) {
124              LOG_ERROR(Render,
125                        "{} map {} of material with hash {:#016X} has dimentions {}x{} "
126                        "which do not match the color texture dimentions {}x{}",
127                        MapTypeName(texture->type), texture->path, hash, texture->width,
128                        texture->height, width, height);
129              state = DecodeState::Failed;
130              return;
131          }
132          if (texture->format != format) {
133              LOG_ERROR(
134                  Render, "{} map {} is stored with {} format which does not match color format {}",
135                  MapTypeName(texture->type), texture->path,
136                  CustomPixelFormatAsString(texture->format), CustomPixelFormatAsString(format));
137              state = DecodeState::Failed;
138              return;
139          }
140      }
141      state = DecodeState::Decoded;
142  }
143  
144  void Material::AddMapTexture(CustomTexture* texture) noexcept {
145      const std::size_t index = static_cast<std::size_t>(texture->type);
146      if (textures[index]) {
147          LOG_ERROR(Render, "Textures {} and {} are assigned to the same material, ignoring!",
148                    textures[index]->path, texture->path);
149          return;
150      }
151      textures[index] = texture;
152  }
153  
154  } // namespace VideoCore