Files
WickedEngine/WickedEngine/wiResourceManager.cpp
T
2024-10-09 16:56:13 +02:00

1736 lines
58 KiB
C++

#include "wiResourceManager.h"
#include "wiRenderer.h"
#include "wiHelper.h"
#include "wiTextureHelper.h"
#include "wiUnorderedMap.h"
#include "wiBacklog.h"
#include "wiJobSystem.h"
#include "Utility/qoi.h"
#include "Utility/stb_image.h"
#include "Utility/basis_universal/transcoder/basisu_transcoder.h"
#include "Utility/dds.h"
#include <algorithm>
#include <mutex>
#include <unordered_map>
using namespace wi::graphics;
namespace wi
{
struct StreamingTexture
{
struct StreamingSubresourceData
{
size_t data_offset = 0;
uint32_t row_pitch = 0;
uint32_t slice_pitch = 0;
};
StreamingSubresourceData streaming_data[16] = {};
uint32_t mip_count = 0; // mip count of full resource
float min_lod_clamp_absolute = 0; // relative to mip_count of full resource
};
//static constexpr size_t streaming_texture_min_size = 4096; // 4KB is the minimum texture memory alignment
static constexpr size_t streaming_texture_min_size = 64 * 1024; // 64KB is the usual texture memory alignment, this allows higher base tex size than 4KB
struct ResourceInternal
{
resourcemanager::Flags flags = resourcemanager::Flags::NONE;
wi::graphics::Texture texture;
int srgb_subresource = -1;
wi::audio::Sound sound;
std::string script;
size_t script_hash = 0;
wi::video::Video video;
wi::vector<uint8_t> filedata;
int font_style = -1;
// Original filename:
std::string filename;
// Container file is different from original filename when
// multiple resources are embedded inside one file:
std::string container_filename;
size_t container_filesize = ~0ull;
size_t container_fileoffset = 0;
uint64_t timestamp = 0;
// Streaming parameters:
StreamingTexture streaming_texture;
std::atomic<uint32_t> streaming_resolution{ 0 };
uint32_t streaming_unload_delay = 0;
// Virtual texture things:
wi::graphics::GPUBuffer tile_pool;
wi::graphics::Texture texture_feedback;
wi::graphics::Texture texture_residency;
};
const wi::vector<uint8_t>& Resource::GetFileData() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->filedata;
}
const wi::graphics::Texture& Resource::GetTexture() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->texture;
}
const wi::audio::Sound& Resource::GetSound() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->sound;
}
const std::string& Resource::GetScript() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->script;
}
size_t Resource::GetScriptHash() const
{
if (internal_state == nullptr)
return 0;
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->script_hash;
}
const wi::video::Video& Resource::GetVideo() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->video;
}
int Resource::GetTextureSRGBSubresource() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->srgb_subresource;
}
int Resource::GetFontStyle() const
{
const ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
return resourceinternal->font_style;
}
void Resource::SetFileData(const wi::vector<uint8_t>& data)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->filedata = data;
}
void Resource::SetFileData(wi::vector<uint8_t>&& data)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->filedata = data;
}
void Resource::SetTexture(const wi::graphics::Texture& texture, int srgb_subresource)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->texture = texture;
resourceinternal->srgb_subresource = srgb_subresource;
}
void Resource::SetTextureVirtual(const GPUBuffer& tile_pool, const Texture& residency, const Texture& feedback)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->tile_pool = tile_pool;
resourceinternal->texture_residency = residency;
resourceinternal->texture_feedback = feedback;
}
void Resource::SetSound(const wi::audio::Sound& sound)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->sound = sound;
}
void Resource::SetScript(const std::string& script)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->script = script;
}
void Resource::SetVideo(const wi::video::Video& video)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->video = video;
}
void Resource::SetOutdated()
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->timestamp = 0;
}
void Resource::StreamingRequestResolution(uint32_t resolution)
{
if (internal_state == nullptr)
{
internal_state = std::make_shared<ResourceInternal>();
}
ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get();
resourceinternal->streaming_resolution.fetch_or(resolution);
}
namespace resourcemanager
{
static std::mutex locker;
static std::unordered_map<std::string, std::weak_ptr<ResourceInternal>> resources;
static Mode mode = Mode::NO_EMBEDDING;
void SetMode(Mode param)
{
mode = param;
}
Mode GetMode()
{
return mode;
}
enum class DataType
{
IMAGE,
SOUND,
SCRIPT,
VIDEO,
FONTSTYLE,
};
static const wi::unordered_map<std::string, DataType> types = {
{"BASIS", DataType::IMAGE},
{"KTX2", DataType::IMAGE},
{"JPG", DataType::IMAGE},
{"JPEG", DataType::IMAGE},
{"PNG", DataType::IMAGE},
{"BMP", DataType::IMAGE},
{"DDS", DataType::IMAGE},
{"TGA", DataType::IMAGE},
{"QOI", DataType::IMAGE},
{"HDR", DataType::IMAGE},
{"WAV", DataType::SOUND},
{"OGG", DataType::SOUND},
{"LUA", DataType::SCRIPT},
{"MP4", DataType::VIDEO},
{"TTF", DataType::FONTSTYLE},
};
wi::vector<std::string> GetSupportedImageExtensions()
{
wi::vector<std::string> ret;
for (auto& x : types)
{
if (x.second == DataType::IMAGE)
{
ret.push_back(x.first);
}
}
return ret;
}
wi::vector<std::string> GetSupportedSoundExtensions()
{
wi::vector<std::string> ret;
for (auto& x : types)
{
if (x.second == DataType::SOUND)
{
ret.push_back(x.first);
}
}
return ret;
}
wi::vector<std::string> GetSupportedVideoExtensions()
{
wi::vector<std::string> ret;
for (auto& x : types)
{
if (x.second == DataType::VIDEO)
{
ret.push_back(x.first);
}
}
return ret;
}
wi::vector<std::string> GetSupportedScriptExtensions()
{
wi::vector<std::string> ret;
for (auto& x : types)
{
if (x.second == DataType::SCRIPT)
{
ret.push_back(x.first);
}
}
return ret;
}
wi::vector<std::string> GetSupportedFontStyleExtensions()
{
wi::vector<std::string> ret;
for (auto& x : types)
{
if (x.second == DataType::FONTSTYLE)
{
ret.push_back(x.first);
}
}
return ret;
}
bool LoadResourceDirectly(
const std::string& name,
Flags flags,
const uint8_t* filedata,
size_t filesize,
ResourceInternal* resource
)
{
std::string ext = wi::helper::toUpper(wi::helper::GetExtensionFromFileName(name));
DataType type;
// dynamic type selection:
{
auto it = types.find(ext);
if (it != types.end())
{
type = it->second;
}
else
{
return false;
}
}
bool success = false;
switch (type)
{
case DataType::IMAGE:
{
GraphicsDevice* device = wi::graphics::GetDevice();
if (!ext.compare("KTX2"))
{
flags &= ~Flags::STREAMING; // disable streaming
basist::ktx2_transcoder transcoder;
if (transcoder.init(filedata, (uint32_t)filesize))
{
TextureDesc desc;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.width = transcoder.get_width();
desc.height = transcoder.get_height();
desc.array_size = std::max(desc.array_size, transcoder.get_layers() * transcoder.get_faces());
desc.mip_levels = transcoder.get_levels();
desc.misc_flags = ResourceMiscFlag::TYPED_FORMAT_CASTING;
if (transcoder.get_faces() == 6)
{
desc.misc_flags |= ResourceMiscFlag::TEXTURECUBE;
}
basist::transcoder_texture_format fmt = basist::transcoder_texture_format::cTFRGBA32;
desc.format = Format::R8G8B8A8_UNORM;
bool import_compressed = has_flag(flags, Flags::IMPORT_BLOCK_COMPRESSED);
if (import_compressed)
{
// BC5 is disabled because it's missing green channel!
//if (has_flag(flags, Flags::IMPORT_NORMALMAP))
//{
// fmt = basist::transcoder_texture_format::cTFBC5_RG;
// desc.format = Format::BC5_UNORM;
// desc.swizzle.r = ComponentSwizzle::R;
// desc.swizzle.g = ComponentSwizzle::G;
// desc.swizzle.b = ComponentSwizzle::ONE;
// desc.swizzle.a = ComponentSwizzle::ONE;
//}
//else
{
if (transcoder.get_has_alpha())
{
fmt = basist::transcoder_texture_format::cTFBC3_RGBA;
desc.format = Format::BC3_UNORM;
}
else
{
fmt = basist::transcoder_texture_format::cTFBC1_RGB;
desc.format = Format::BC1_UNORM;
}
}
}
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(fmt);
if (transcoder.start_transcoding())
{
// all subresources will use one allocation for transcoder destination, so compute combined size:
size_t transcoded_data_size = 0;
const uint32_t layers = std::max(1u, transcoder.get_layers());
const uint32_t faces = transcoder.get_faces();
const uint32_t levels = transcoder.get_levels();
for (uint32_t layer = 0; layer < layers; ++layer)
{
for (uint32_t face = 0; face < faces; ++face)
{
for (uint32_t mip = 0; mip < levels; ++mip)
{
basist::ktx2_image_level_info level_info;
if (transcoder.get_image_level_info(level_info, mip, layer, face))
{
uint32_t pixel_or_block_count = (import_compressed
? level_info.m_total_blocks
: (level_info.m_orig_width * level_info.m_orig_height));
transcoded_data_size += bytes_per_block * pixel_or_block_count;
}
}
}
}
wi::vector<uint8_t> transcoded_data(transcoded_data_size);
wi::vector<SubresourceData> InitData;
size_t transcoded_data_offset = 0;
for (uint32_t layer = 0; layer < layers; ++layer)
{
for (uint32_t face = 0; face < faces; ++face)
{
for (uint32_t mip = 0; mip < levels; ++mip)
{
basist::ktx2_image_level_info level_info;
if (transcoder.get_image_level_info(level_info, mip, layer, face))
{
void* data_ptr = transcoded_data.data() + transcoded_data_offset;
uint32_t pixel_or_block_count = (import_compressed
? level_info.m_total_blocks
: (level_info.m_orig_width * level_info.m_orig_height));
transcoded_data_offset += bytes_per_block * pixel_or_block_count;
if (transcoder.transcode_image_level(
mip,
layer,
face,
data_ptr,
pixel_or_block_count,
fmt
))
{
SubresourceData subresourceData;
subresourceData.data_ptr = data_ptr;
subresourceData.row_pitch = (import_compressed ? level_info.m_num_blocks_x : level_info.m_orig_width) * bytes_per_block;
subresourceData.slice_pitch = subresourceData.row_pitch * (import_compressed ? level_info.m_num_blocks_y : level_info.m_orig_height);
InitData.push_back(subresourceData);
}
else
{
wi::backlog::post("KTX2 transcoding error while loading image!", wi::backlog::LogLevel::Error);
assert(0);
}
}
else
{
wi::backlog::post("KTX2 transcoding error while loading image level info!", wi::backlog::LogLevel::Error);
assert(0);
}
}
}
}
if (!InitData.empty())
{
success = device->CreateTexture(&desc, InitData.data(), &resource->texture);
device->SetName(&resource->texture, name.c_str());
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
resource->srgb_subresource = device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format
);
}
}
}
transcoder.clear();
}
}
else if (!ext.compare("BASIS"))
{
flags &= ~Flags::STREAMING; // disable streaming
basist::basisu_transcoder transcoder;
if (transcoder.validate_header(filedata, (uint32_t)filesize))
{
basist::basisu_file_info fileInfo;
if (transcoder.get_file_info(filedata, (uint32_t)filesize, fileInfo))
{
uint32_t image_index = 0;
basist::basisu_image_info info;
if (transcoder.get_image_info(filedata, (uint32_t)filesize, info, image_index))
{
TextureDesc desc;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.width = info.m_width;
desc.height = info.m_height;
desc.mip_levels = info.m_total_levels;
desc.misc_flags = ResourceMiscFlag::TYPED_FORMAT_CASTING;
basist::transcoder_texture_format fmt = basist::transcoder_texture_format::cTFRGBA32;
desc.format = Format::R8G8B8A8_UNORM;
bool import_compressed = has_flag(flags, Flags::IMPORT_BLOCK_COMPRESSED);
if (import_compressed)
{
// BC5 is disabled because it's missing green channel!
//if (has_flag(flags, Flags::IMPORT_NORMALMAP))
//{
// fmt = basist::transcoder_texture_format::cTFBC5_RG;
// desc.format = Format::BC5_UNORM;
// desc.swizzle.r = ComponentSwizzle::R;
// desc.swizzle.g = ComponentSwizzle::G;
// desc.swizzle.b = ComponentSwizzle::ONE;
// desc.swizzle.a = ComponentSwizzle::ONE;
//}
//else
{
if (info.m_alpha_flag)
{
fmt = basist::transcoder_texture_format::cTFBC3_RGBA;
desc.format = Format::BC3_UNORM;
}
else
{
fmt = basist::transcoder_texture_format::cTFBC1_RGB;
desc.format = Format::BC1_UNORM;
}
}
}
uint32_t bytes_per_block = basis_get_bytes_per_block_or_pixel(fmt);
if (transcoder.start_transcoding(filedata, (uint32_t)filesize))
{
// all subresources will use one allocation for transcoder destination, so compute combined size:
size_t transcoded_data_size = 0;
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
basist::basisu_image_level_info level_info;
if (transcoder.get_image_level_info(filedata, (uint32_t)filesize, level_info, image_index, mip))
{
uint32_t pixel_or_block_count = (import_compressed
? level_info.m_total_blocks
: (level_info.m_orig_width * level_info.m_orig_height));
transcoded_data_size += bytes_per_block * pixel_or_block_count;
}
}
wi::vector<uint8_t> transcoded_data(transcoded_data_size);
wi::vector<SubresourceData> InitData;
size_t transcoded_data_offset = 0;
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
basist::basisu_image_level_info level_info;
if (transcoder.get_image_level_info(filedata, (uint32_t)filesize, level_info, 0, mip))
{
void* data_ptr = transcoded_data.data() + transcoded_data_offset;
uint32_t pixel_or_block_count = (import_compressed
? level_info.m_total_blocks
: (level_info.m_orig_width * level_info.m_orig_height));
transcoded_data_offset += pixel_or_block_count * bytes_per_block;
if (transcoder.transcode_image_level(
filedata,
(uint32_t)filesize,
image_index,
mip,
data_ptr,
pixel_or_block_count,
fmt
))
{
SubresourceData subresourceData;
subresourceData.data_ptr = data_ptr;
subresourceData.row_pitch = (import_compressed ? level_info.m_num_blocks_x : level_info.m_orig_width) * bytes_per_block;
subresourceData.slice_pitch = subresourceData.row_pitch * (import_compressed ? level_info.m_num_blocks_y : level_info.m_orig_height);
InitData.push_back(subresourceData);
}
else
{
wi::backlog::post("BASIS transcoding error while loading image!", wi::backlog::LogLevel::Error);
assert(0);
}
}
else
{
wi::backlog::post("BASIS transcoding error while loading image level info!", wi::backlog::LogLevel::Error);
assert(0);
}
}
if (!InitData.empty())
{
success = device->CreateTexture(&desc, InitData.data(), &resource->texture);
device->SetName(&resource->texture, name.c_str());
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
resource->srgb_subresource = device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format
);
}
}
}
}
}
}
}
else if (!ext.compare("DDS"))
{
dds::Header header = dds::read_header(filedata, filesize);
if (header.is_valid())
{
TextureDesc desc;
desc.array_size = 1;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.width = header.width();
desc.height = header.height();
desc.depth = header.depth();
desc.mip_levels = header.mip_levels();
desc.array_size = header.array_size();
desc.format = Format::R8G8B8A8_UNORM;
desc.layout = ResourceState::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::TYPED_FORMAT_CASTING;
if (header.is_cubemap())
{
desc.misc_flags |= ResourceMiscFlag::TEXTURECUBE;
}
if (desc.mip_levels == 1 || desc.depth > 1 || desc.array_size > 1)
{
// don't allow streaming for single mip, array and 3D textures
flags &= ~Flags::STREAMING;
}
auto ddsFormat = header.format();
switch (ddsFormat)
{
case dds::DXGI_FORMAT_R32G32B32A32_FLOAT: desc.format = Format::R32G32B32A32_FLOAT; break;
case dds::DXGI_FORMAT_R32G32B32A32_UINT: desc.format = Format::R32G32B32A32_UINT; break;
case dds::DXGI_FORMAT_R32G32B32A32_SINT: desc.format = Format::R32G32B32A32_SINT; break;
case dds::DXGI_FORMAT_R32G32B32_FLOAT: desc.format = Format::R32G32B32_FLOAT; break;
case dds::DXGI_FORMAT_R32G32B32_UINT: desc.format = Format::R32G32B32_UINT; break;
case dds::DXGI_FORMAT_R32G32B32_SINT: desc.format = Format::R32G32B32_SINT; break;
case dds::DXGI_FORMAT_R16G16B16A16_FLOAT: desc.format = Format::R16G16B16A16_FLOAT; break;
case dds::DXGI_FORMAT_R16G16B16A16_UNORM: desc.format = Format::R16G16B16A16_UNORM; break;
case dds::DXGI_FORMAT_R16G16B16A16_UINT: desc.format = Format::R16G16B16A16_UINT; break;
case dds::DXGI_FORMAT_R16G16B16A16_SNORM: desc.format = Format::R16G16B16A16_SNORM; break;
case dds::DXGI_FORMAT_R16G16B16A16_SINT: desc.format = Format::R16G16B16A16_SINT; break;
case dds::DXGI_FORMAT_R32G32_FLOAT: desc.format = Format::R32G32_FLOAT; break;
case dds::DXGI_FORMAT_R32G32_UINT: desc.format = Format::R32G32_UINT; break;
case dds::DXGI_FORMAT_R32G32_SINT: desc.format = Format::R32G32_SINT; break;
case dds::DXGI_FORMAT_R10G10B10A2_UNORM: desc.format = Format::R10G10B10A2_UNORM; break;
case dds::DXGI_FORMAT_R10G10B10A2_UINT: desc.format = Format::R10G10B10A2_UINT; break;
case dds::DXGI_FORMAT_R11G11B10_FLOAT: desc.format = Format::R11G11B10_FLOAT; break;
case dds::DXGI_FORMAT_R9G9B9E5_SHAREDEXP: desc.format = Format::R9G9B9E5_SHAREDEXP; break;
case dds::DXGI_FORMAT_B8G8R8X8_UNORM: desc.format = Format::B8G8R8A8_UNORM; break;
case dds::DXGI_FORMAT_B8G8R8A8_UNORM: desc.format = Format::B8G8R8A8_UNORM; break;
case dds::DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: desc.format = Format::B8G8R8A8_UNORM_SRGB; break;
case dds::DXGI_FORMAT_R8G8B8A8_UNORM: desc.format = Format::R8G8B8A8_UNORM; break;
case dds::DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: desc.format = Format::R8G8B8A8_UNORM_SRGB; break;
case dds::DXGI_FORMAT_R8G8B8A8_UINT: desc.format = Format::R8G8B8A8_UINT; break;
case dds::DXGI_FORMAT_R8G8B8A8_SNORM: desc.format = Format::R8G8B8A8_SNORM; break;
case dds::DXGI_FORMAT_R8G8B8A8_SINT: desc.format = Format::R8G8B8A8_SINT; break;
case dds::DXGI_FORMAT_R16G16_FLOAT: desc.format = Format::R16G16_FLOAT; break;
case dds::DXGI_FORMAT_R16G16_UNORM: desc.format = Format::R16G16_UNORM; break;
case dds::DXGI_FORMAT_R16G16_UINT: desc.format = Format::R16G16_UINT; break;
case dds::DXGI_FORMAT_R16G16_SNORM: desc.format = Format::R16G16_SNORM; break;
case dds::DXGI_FORMAT_R16G16_SINT: desc.format = Format::R16G16_SINT; break;
case dds::DXGI_FORMAT_D32_FLOAT: desc.format = Format::D32_FLOAT; break;
case dds::DXGI_FORMAT_R32_FLOAT: desc.format = Format::R32_FLOAT; break;
case dds::DXGI_FORMAT_R32_UINT: desc.format = Format::R32_UINT; break;
case dds::DXGI_FORMAT_R32_SINT: desc.format = Format::R32_SINT; break;
case dds::DXGI_FORMAT_R8G8_UNORM: desc.format = Format::R8G8_UNORM; break;
case dds::DXGI_FORMAT_R8G8_UINT: desc.format = Format::R8G8_UINT; break;
case dds::DXGI_FORMAT_R8G8_SNORM: desc.format = Format::R8G8_SNORM; break;
case dds::DXGI_FORMAT_R8G8_SINT: desc.format = Format::R8G8_SINT; break;
case dds::DXGI_FORMAT_R16_FLOAT: desc.format = Format::R16_FLOAT; break;
case dds::DXGI_FORMAT_D16_UNORM: desc.format = Format::D16_UNORM; break;
case dds::DXGI_FORMAT_R16_UNORM: desc.format = Format::R16_UNORM; break;
case dds::DXGI_FORMAT_R16_UINT: desc.format = Format::R16_UINT; break;
case dds::DXGI_FORMAT_R16_SNORM: desc.format = Format::R16_SNORM; break;
case dds::DXGI_FORMAT_R16_SINT: desc.format = Format::R16_SINT; break;
case dds::DXGI_FORMAT_R8_UNORM: desc.format = Format::R8_UNORM; break;
case dds::DXGI_FORMAT_R8_UINT: desc.format = Format::R8_UINT; break;
case dds::DXGI_FORMAT_R8_SNORM: desc.format = Format::R8_SNORM; break;
case dds::DXGI_FORMAT_R8_SINT: desc.format = Format::R8_SINT; break;
case dds::DXGI_FORMAT_BC1_UNORM: desc.format = Format::BC1_UNORM; break;
case dds::DXGI_FORMAT_BC1_UNORM_SRGB: desc.format = Format::BC1_UNORM_SRGB; break;
case dds::DXGI_FORMAT_BC2_UNORM: desc.format = Format::BC2_UNORM; break;
case dds::DXGI_FORMAT_BC2_UNORM_SRGB: desc.format = Format::BC2_UNORM_SRGB; break;
case dds::DXGI_FORMAT_BC3_UNORM: desc.format = Format::BC3_UNORM; break;
case dds::DXGI_FORMAT_BC3_UNORM_SRGB: desc.format = Format::BC3_UNORM_SRGB; break;
case dds::DXGI_FORMAT_BC4_UNORM: desc.format = Format::BC4_UNORM; break;
case dds::DXGI_FORMAT_BC4_SNORM: desc.format = Format::BC4_SNORM; break;
case dds::DXGI_FORMAT_BC5_UNORM: desc.format = Format::BC5_UNORM; break;
case dds::DXGI_FORMAT_BC5_SNORM: desc.format = Format::BC5_SNORM; break;
case dds::DXGI_FORMAT_BC6H_SF16: desc.format = Format::BC6H_SF16; break;
case dds::DXGI_FORMAT_BC6H_UF16: desc.format = Format::BC6H_UF16; break;
case dds::DXGI_FORMAT_BC7_UNORM: desc.format = Format::BC7_UNORM; break;
case dds::DXGI_FORMAT_BC7_UNORM_SRGB: desc.format = Format::BC7_UNORM_SRGB; break;
default:
assert(0); // incoming format is not supported
break;
}
if (desc.format == Format::BC5_UNORM)
{
desc.swizzle.r = ComponentSwizzle::R;
desc.swizzle.g = ComponentSwizzle::G;
desc.swizzle.b = ComponentSwizzle::ONE;
desc.swizzle.a = ComponentSwizzle::ONE;
}
if (header.is_1d())
{
desc.type = TextureDesc::Type::TEXTURE_1D;
}
else if (header.is_3d())
{
desc.type = TextureDesc::Type::TEXTURE_3D;
}
if (IsFormatBlockCompressed(desc.format))
{
desc.width = AlignTo(desc.width, GetFormatBlockSize(desc.format));
desc.height = AlignTo(desc.height, GetFormatBlockSize(desc.format));
}
wi::vector<SubresourceData> initdata_heap;
SubresourceData initdata_stack[16] = {};
SubresourceData* initdata = nullptr;
// Determine if we need heap allocation for initdata, or it is small enough for stack:
if (desc.array_size * desc.mip_levels < arraysize(initdata_stack))
{
initdata = initdata_stack;
}
else
{
initdata_heap.resize(desc.array_size * desc.mip_levels);
initdata = initdata_heap.data();
}
uint32_t subresource_index = 0;
for (uint32_t slice = 0; slice < desc.array_size; ++slice)
{
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
SubresourceData& subresourceData = initdata[subresource_index++];
subresourceData.data_ptr = filedata + header.mip_offset(mip, slice);
subresourceData.row_pitch = header.row_pitch(mip);
subresourceData.slice_pitch = header.slice_pitch(mip);
}
}
int mip_offset = 0;
if (has_flag(flags, Flags::STREAMING))
{
// Remember full mipcount for streaming:
resource->streaming_texture.mip_count = desc.mip_levels;
// For streaming, remember relative memory offsets for mip levels:
for (uint32_t slice = 0; slice < desc.array_size; ++slice)
{
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
auto& streaming_data = resource->streaming_texture.streaming_data[mip];
streaming_data.data_offset = header.mip_offset(mip, slice);
streaming_data.row_pitch = header.row_pitch(mip);
streaming_data.slice_pitch = header.slice_pitch(mip);
}
}
// Reduce mip map count that will be uploaded to GPU:
while (desc.mip_levels > 1 && desc.depth == 1 && desc.array_size == 1 && ComputeTextureMemorySizeInBytes(desc) > streaming_texture_min_size)
{
desc.width >>= 1;
desc.height >>= 1;
desc.mip_levels -= 1;
mip_offset++;
}
resource->streaming_texture.min_lod_clamp_absolute = (float)mip_offset;
}
success = device->CreateTexture(&desc, initdata + mip_offset, &resource->texture);
device->SetName(&resource->texture, name.c_str());
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
resource->srgb_subresource = device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format
);
}
}
else assert(0); // failed to load DDS
}
else if (!ext.compare("HDR"))
{
flags &= ~Flags::STREAMING; // disable streaming
int height, width, channels; // stb_image
float* data = stbi_loadf_from_memory(filedata, (int)filesize, &width, &height, &channels, 0);
static constexpr bool allow_packing = true; // we now always assume that we won't need full precision float textures, so pack them for memory saving
if (data != nullptr)
{
TextureDesc desc;
desc.width = (uint32_t)width;
desc.height = (uint32_t)height;
switch (channels)
{
default:
case 4:
if (allow_packing)
{
desc.format = Format::R16G16B16A16_FLOAT;
const XMFLOAT4* data_full = (const XMFLOAT4*)data;
XMHALF4* data_packed = (XMHALF4*)data;
for (int i = 0; i < width * height; ++i)
{
XMStoreHalf4(data_packed + i, XMLoadFloat4(data_full + i));
}
}
else
{
desc.format = Format::R32G32B32A32_FLOAT;
}
break;
case 3:
if (allow_packing)
{
desc.format = Format::R9G9B9E5_SHAREDEXP;
const XMFLOAT3* data_full = (const XMFLOAT3*)data;
XMFLOAT3SE* data_packed = (XMFLOAT3SE*)data;
for (int i = 0; i < width * height; ++i)
{
XMStoreFloat3SE(data_packed + i, XMLoadFloat3(data_full + i));
}
}
else
{
desc.format = Format::R32G32B32_FLOAT;
}
break;
case 2:
if (allow_packing)
{
desc.format = Format::R16G16_FLOAT;
const XMFLOAT2* data_full = (const XMFLOAT2*)data;
XMHALF2* data_packed = (XMHALF2*)data;
for (int i = 0; i < width * height; ++i)
{
XMStoreHalf2(data_packed + i, XMLoadFloat2(data_full + i));
}
}
else
{
desc.format = Format::R32G32_FLOAT;
}
break;
case 1:
if (allow_packing)
{
desc.format = Format::R16_FLOAT;
HALF* data_packed = (HALF*)data;
for (int i = 0; i < width * height; ++i)
{
data_packed[i] = XMConvertFloatToHalf(data[i]);
}
}
else
{
desc.format = Format::R32_FLOAT;
}
break;
}
desc.bind_flags = BindFlag::SHADER_RESOURCE;
desc.mip_levels = 1;
SubresourceData InitData;
InitData.data_ptr = data;
InitData.row_pitch = width * GetFormatStride(desc.format);
success = device->CreateTexture(&desc, &InitData, &resource->texture);
device->SetName(&resource->texture, name.c_str());
stbi_image_free(data);
}
}
else
{
// qoi, png, tga, jpg, etc. loader:
flags &= ~Flags::STREAMING; // disable streaming
int height = 0, width = 0, channels = 0;
bool is_16bit = false;
Format format = Format::R8G8B8A8_UNORM;
Format bc_format = Format::BC3_UNORM;
Swizzle swizzle = { ComponentSwizzle::R, ComponentSwizzle::G, ComponentSwizzle::B, ComponentSwizzle::A };
void* rgba;
if (!ext.compare("QOI"))
{
qoi_desc desc = {};
rgba = qoi_decode(filedata, (int)filesize, &desc, 4);
// redefine width, height to avoid further conditionals
height = desc.height;
width = desc.width;
channels = 4;
}
else
{
if (!has_flag(flags, Flags::IMPORT_COLORGRADINGLUT) && stbi_is_16_bit_from_memory(filedata, (int)filesize))
{
is_16bit = true;
rgba = stbi_load_16_from_memory(filedata, (int)filesize, &width, &height, &channels, 0);
switch (channels)
{
case 1:
format = Format::R16_UNORM;
bc_format = Format::BC4_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::ONE };
break;
case 2:
format = Format::R16G16_UNORM;
bc_format = Format::BC5_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::G };
break;
case 3:
{
// Graphics API doesn't support 3 channel formats, so need to expand to RGBA:
struct Color3
{
uint16_t r, g, b;
};
const Color3* color3 = (const Color3*)rgba;
wi::Color16* color4 = (wi::Color16*)malloc(width * height * sizeof(wi::Color16));
for (int i = 0; i < width * height; ++i)
{
color4[i].setR(color3[i].r);
color4[i].setG(color3[i].g);
color4[i].setB(color3[i].b);
color4[i].setA(65535);
}
free(rgba);
rgba = color4;
format = Format::R16G16B16A16_UNORM;
bc_format = Format::BC1_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::G, ComponentSwizzle::B, ComponentSwizzle::ONE };
}
break;
case 4:
default:
format = Format::R16G16B16A16_UNORM;
bc_format = Format::BC3_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::G, ComponentSwizzle::B, ComponentSwizzle::A };
break;
}
}
else
{
rgba = stbi_load_from_memory(filedata, (int)filesize, &width, &height, &channels, 0);
switch (channels)
{
case 1:
format = Format::R8_UNORM;
bc_format = Format::BC4_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::ONE };
break;
case 2:
format = Format::R8G8_UNORM;
bc_format = Format::BC5_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::R, ComponentSwizzle::G };
break;
case 3:
{
// Graphics API doesn't support 3 channel formats, so need to expand to RGBA:
struct Color3
{
uint8_t r, g, b;
};
const Color3* color3 = (const Color3*)rgba;
wi::Color* color4 = (wi::Color*)malloc(width * height * sizeof(wi::Color));
for (int i = 0; i < width * height; ++i)
{
color4[i].setR(color3[i].r);
color4[i].setG(color3[i].g);
color4[i].setB(color3[i].b);
color4[i].setA(255);
}
free(rgba);
rgba = color4;
format = Format::R8G8B8A8_UNORM;
bc_format = Format::BC1_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::G, ComponentSwizzle::B, ComponentSwizzle::ONE };
}
break;
case 4:
default:
format = Format::R8G8B8A8_UNORM;
bc_format = Format::BC3_UNORM;
swizzle = { ComponentSwizzle::R, ComponentSwizzle::G, ComponentSwizzle::B, ComponentSwizzle::A };
break;
}
}
}
if (rgba != nullptr)
{
TextureDesc desc;
desc.height = uint32_t(height);
desc.width = uint32_t(width);
desc.layout = ResourceState::SHADER_RESOURCE;
desc.format = format;
desc.swizzle = swizzle;
if (has_flag(flags, Flags::IMPORT_COLORGRADINGLUT))
{
if (desc.type != TextureDesc::Type::TEXTURE_2D ||
desc.width != 256 ||
desc.height != 16 ||
format != Format::R8G8B8A8_UNORM)
{
wi::helper::messageBox("The Dimensions must be 256 x 16 for color grading LUT and format must be RGB or RGBA!", "Error");
}
else
{
uint32_t data[16 * 16 * 16];
int pixel = 0;
for (int z = 0; z < 16; ++z)
{
for (int y = 0; y < 16; ++y)
{
for (int x = 0; x < 16; ++x)
{
int coord = x + y * 256 + z * 16;
data[pixel++] = ((uint32_t*)rgba)[coord];
}
}
}
desc.type = TextureDesc::Type::TEXTURE_3D;
desc.width = 16;
desc.height = 16;
desc.depth = 16;
desc.bind_flags = BindFlag::SHADER_RESOURCE;
SubresourceData InitData;
InitData.data_ptr = data;
InitData.row_pitch = 16 * sizeof(uint32_t);
InitData.slice_pitch = 16 * InitData.row_pitch;
success = device->CreateTexture(&desc, &InitData, &resource->texture);
device->SetName(&resource->texture, name.c_str());
}
}
else
{
desc.bind_flags = BindFlag::SHADER_RESOURCE | BindFlag::UNORDERED_ACCESS;
desc.mip_levels = GetMipCount(desc.width, desc.height);
desc.usage = Usage::DEFAULT;
desc.layout = ResourceState::SHADER_RESOURCE;
desc.misc_flags = ResourceMiscFlag::TYPED_FORMAT_CASTING;
uint32_t mipwidth = width;
SubresourceData init_data[16];
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
init_data[mip].data_ptr = rgba; // attention! we don't fill the mips here correctly, just always point to the mip0 data by default. Mip levels will be created using compute shader when needed!
init_data[mip].row_pitch = uint32_t(mipwidth * GetFormatStride(desc.format));
mipwidth = std::max(1u, mipwidth / 2);
}
success = device->CreateTexture(&desc, init_data, &resource->texture);
device->SetName(&resource->texture, name.c_str());
for (uint32_t i = 0; i < resource->texture.desc.mip_levels; ++i)
{
int subresource_index;
subresource_index = device->CreateSubresource(&resource->texture, SubresourceType::SRV, 0, 1, i, 1);
assert(subresource_index == i);
subresource_index = device->CreateSubresource(&resource->texture, SubresourceType::UAV, 0, 1, i, 1);
assert(subresource_index == i);
}
// This part must be AFTER mip level subresource creation:
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
resource->srgb_subresource = device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format
);
}
wi::renderer::AddDeferredMIPGen(resource->texture, true);
if (has_flag(flags, Flags::IMPORT_BLOCK_COMPRESSED))
{
// Schedule additional task to compress into BC format and replace resource texture:
Texture uncompressed_src = std::move(resource->texture);
resource->srgb_subresource = -1;
desc.format = bc_format;
if (has_flag(flags, Flags::IMPORT_NORMALMAP))
{
desc.format = Format::BC5_UNORM;
desc.swizzle = { ComponentSwizzle::R, ComponentSwizzle::G, ComponentSwizzle::ONE, ComponentSwizzle::ONE };
}
desc.bind_flags = BindFlag::SHADER_RESOURCE;
const uint32_t block_size = GetFormatBlockSize(desc.format);
desc.width = AlignTo(desc.width, block_size);
desc.height = AlignTo(desc.height, block_size);
desc.mip_levels = GetMipCount(desc.width, desc.height, desc.depth, block_size, block_size);
success = device->CreateTexture(&desc, nullptr, &resource->texture);
device->SetName(&resource->texture, name.c_str());
// This part must be AFTER mip level subresource creation:
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
resource->srgb_subresource = device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format
);
}
wi::renderer::AddDeferredBlockCompression(uncompressed_src, resource->texture);
}
}
}
free(rgba);
}
}
break;
case DataType::SOUND:
{
success = wi::audio::CreateSound(filedata, filesize, &resource->sound);
}
break;
case DataType::SCRIPT:
{
resource->script.resize(filesize);
std::memcpy(resource->script.data(), filedata, filesize);
resource->script_hash = wi::helper::string_hash(resource->script.c_str());
success = true;
}
break;
case DataType::VIDEO:
{
success = wi::video::CreateVideo(filedata, filesize, &resource->video);
}
break;
case DataType::FONTSTYLE:
{
resource->font_style = wi::font::AddFontStyle(name, filedata, filesize, true);
success = resource->font_style >= 0;
}
break;
};
if (!resource->filedata.empty() && !has_flag(flags, Flags::IMPORT_RETAIN_FILEDATA) && !has_flag(flags, Flags::IMPORT_DELAY))
{
// file data can be discarded:
resource->filedata.clear();
resource->filedata.shrink_to_fit();
}
return success;
}
Resource Load(
const std::string& name,
Flags flags,
const uint8_t* filedata,
size_t filesize,
const std::string& container_filename,
size_t container_fileoffset
)
{
locker.lock();
std::weak_ptr<ResourceInternal>& weak_resource = resources[name];
std::shared_ptr<ResourceInternal> resource = weak_resource.lock();
static bool basis_init = false; // within lock!
if (!basis_init)
{
basis_init = true;
basist::basisu_transcoder_init();
}
uint64_t timestamp = 0;
if(!container_filename.empty())
{
timestamp = wi::helper::FileTimestamp(container_filename);
}
else
{
timestamp = wi::helper::FileTimestamp(name);
}
if (resource == nullptr || resource->timestamp < timestamp)
{
resource = std::make_shared<ResourceInternal>();
resources[name] = resource;
resource->filename = name;
// Rememeber the streaming file parameters, which is either the resource filename,
// or it can be a specific filename and offset in the case when the file contained multiple resources
if (container_filename.empty())
{
resource->container_filename = name;
}
else
{
resource->container_filename = container_filename;
}
resource->container_filesize = filesize;
resource->container_fileoffset = container_fileoffset;
if (filedata != nullptr && resource->filedata.empty() && (has_flag(flags, Flags::IMPORT_RETAIN_FILEDATA) || has_flag(flags, Flags::IMPORT_DELAY)))
{
// resource was loaded with external filedata, and we want to retain filedata
// this must also happen when using IMPORT_DELAY!
resource->filedata.resize(filesize);
std::memcpy(resource->filedata.data(), filedata, filesize);
}
}
else
{
if (!has_flag(flags, Flags::IMPORT_DELAY) && has_flag(resource->flags, Flags::IMPORT_DELAY))
{
// If this is not an IMPORT_DELAY load, but this resource load was incomplete, using IMPORT_DELAY,
// then continue loading it as normal from existing file data and remove IMPORT_DELAY flag from it
resource->flags &= ~Flags::IMPORT_DELAY;
}
else
{
Resource retVal;
retVal.internal_state = resource;
locker.unlock();
return retVal;
}
}
locker.unlock();
if (filedata == nullptr || filesize == 0)
{
if (resource->filedata.empty())
{
if (!wi::helper::FileRead(resource->container_filename, resource->filedata, resource->container_filesize, resource->container_fileoffset))
{
resource.reset();
return Resource();
}
}
filedata = resource->filedata.data();
filesize = resource->filedata.size();
}
flags |= resource->flags;
bool success = false;
if (has_flag(flags, Flags::IMPORT_DELAY))
{
success = true;
}
else
{
success = LoadResourceDirectly(name, flags, filedata, filesize, resource.get());
}
if (success)
{
resource->flags = flags;
resource->timestamp = timestamp;
Resource retVal;
retVal.internal_state = resource;
return retVal;
}
return Resource();
}
bool Contains(const std::string& name)
{
bool result = false;
locker.lock();
auto it = resources.find(name);
if (it != resources.end())
{
auto resource = it->second.lock();
result = resource != nullptr;
}
locker.unlock();
return result;
}
void Clear()
{
locker.lock();
resources.clear();
locker.unlock();
}
wi::jobsystem::context streaming_ctx;
wi::vector<std::shared_ptr<ResourceInternal>> streaming_texture_jobs;
struct StreamingTextureReplace
{
std::shared_ptr<ResourceInternal> resource;
Texture texture;
int srgb_subresource = -1;
};
std::mutex streaming_replacement_mutex;
wi::vector<StreamingTextureReplace> streaming_texture_replacements;
float streaming_threshold = 0.8f;
float streaming_fade_speed = 4;
void SetStreamingMemoryThreshold(float value)
{
streaming_threshold = value;
}
float GetStreamingMemoryThreshold()
{
return streaming_threshold;
}
void UpdateStreamingResources(float dt)
{
// If any streaming replacement requests arrived, replace the resources here (main thread):
streaming_replacement_mutex.lock(); // streaming_replacement_mutex is not a long lock, it can only be held by the single streaming thread, so we don't need to try_lock
for (auto& replace : streaming_texture_replacements)
{
replace.resource->texture = replace.texture;
replace.resource->srgb_subresource = replace.srgb_subresource;
}
streaming_texture_replacements.clear();
streaming_replacement_mutex.unlock();
// Update resource min lod clamps smoothly:
GraphicsDevice* device = GetDevice();
if (!locker.try_lock()) // Use try lock as this is on the main thread which shouldn't hitch on long locking!
return; // Streaming is not that important, we can abandon it if some resource loading is holding the lock
for (auto& x : resources)
{
std::weak_ptr<ResourceInternal>& weak_resource = x.second;
std::shared_ptr<ResourceInternal> resource = weak_resource.lock();
if (resource != nullptr && resource->texture.IsValid() && has_flag(resource->flags, Flags::STREAMING))
{
const TextureDesc& desc = resource->texture.desc;
const float mip_offset = float(resource->streaming_texture.mip_count - desc.mip_levels);
float min_lod_clamp_absolute_next = resource->streaming_texture.min_lod_clamp_absolute - dt * streaming_fade_speed;
min_lod_clamp_absolute_next = std::max(mip_offset, min_lod_clamp_absolute_next);
if (wi::math::float_equal(min_lod_clamp_absolute_next, resource->streaming_texture.min_lod_clamp_absolute))
continue;
resource->streaming_texture.min_lod_clamp_absolute = min_lod_clamp_absolute_next;
const float min_lod_clamp_relative = min_lod_clamp_absolute_next - mip_offset;
device->DeleteSubresources(&resource->texture);
device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
nullptr,
nullptr,
nullptr,
min_lod_clamp_relative
);
resource->srgb_subresource = -1;
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
resource->srgb_subresource = device->CreateSubresource(
&resource->texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format,
nullptr,
nullptr,
min_lod_clamp_relative
);
}
}
}
// If previous streaming jobs were not finished, we cancel this until next frame:
if (wi::jobsystem::IsBusy(streaming_ctx))
{
locker.unlock();
return;
}
streaming_texture_jobs.clear();
// Gather the streaming jobs:
for (auto& x : resources)
{
std::weak_ptr<ResourceInternal>& weak_resource = x.second;
std::shared_ptr<ResourceInternal> resource = weak_resource.lock();
if (resource != nullptr && resource->texture.IsValid() && resource->streaming_texture.mip_count > 1)
{
streaming_texture_jobs.push_back(resource);
}
}
locker.unlock();
if (streaming_texture_jobs.empty())
return;
// One low priority thread will be responsible for streaming, to not cause any hitching while rendering:
streaming_ctx.priority = wi::jobsystem::Priority::Streaming;
wi::jobsystem::Execute(streaming_ctx, [](wi::jobsystem::JobArgs args) {
for(auto& resource : streaming_texture_jobs)
{
TextureDesc desc = resource->texture.desc;
uint32_t requested_resolution = resource->streaming_resolution.fetch_and(0); // set to zero while returning prev value
if (requested_resolution > 0)
{
requested_resolution = 1ul << (31ul - firstbithigh((unsigned long)requested_resolution)); // largest power of two
}
GraphicsDevice* device = GetDevice();
const GraphicsDevice::MemoryUsage memory_usage = device->GetMemoryUsage();
const float memory_percent = float(double(memory_usage.usage) / double(memory_usage.budget));
const bool memory_shortage = memory_percent > streaming_threshold;
const bool stream_in = requested_resolution >= std::min(desc.width, desc.height);
int mip_offset = int(resource->streaming_texture.mip_count - desc.mip_levels);
if (stream_in)
{
resource->streaming_unload_delay = 0; // unloading will be immediately halted
if (mip_offset == 0)
continue; // There aren't any more mip levels, cancel
// Mip level streaming IN:
desc.width <<= 1;
desc.height <<= 1;
if (requested_resolution < std::min(desc.width, desc.height))
continue; // Increased resolution would be too much, cancel
desc.mip_levels++;
mip_offset--;
}
else
{
resource->streaming_unload_delay++; // one more frame that this wants to unload
if (!memory_shortage && resource->streaming_unload_delay < 255)
continue; // only unload mips if it's been wanting to unload for a couple frames, or there is memory shortage
if (ComputeTextureMemorySizeInBytes(desc) <= streaming_texture_min_size)
continue; // Don't reduce the texture below, because of 4KB alignment, this would not reduce memory usage further
// Mip level streaming OUT:
desc.width >>= 1;
desc.height >>= 1;
desc.mip_levels--;
mip_offset++;
}
if (desc.mip_levels <= resource->streaming_texture.mip_count)
{
// memory offset of the first mip level in current streaming range:
const size_t mip_data_offset = resource->streaming_texture.streaming_data[mip_offset].data_offset;
const uint8_t* firstmipdata = resource->filedata.data();
static wi::vector<uint8_t> streaming_file; // make this static to not reallocate for each file loading
if (firstmipdata == nullptr)
{
// If file data is not available, then open the file partially with the streaming file parameters:
size_t filesize = resource->container_filesize - mip_data_offset;
size_t fileoffset = resource->container_fileoffset + mip_data_offset;
if (!wi::helper::FileRead(
resource->container_filename,
streaming_file,
filesize,
fileoffset
))
{
continue;
}
firstmipdata = streaming_file.data();
}
else
{
// If file data is available, we can use that for streaming:
firstmipdata += mip_data_offset;
}
// Convert relative to absolute GPU initialization data
SubresourceData initdata[16] = {};
for (uint32_t mip = 0; mip < desc.mip_levels; ++mip)
{
auto& streaming_data = resource->streaming_texture.streaming_data[mip_offset + mip];
initdata[mip].data_ptr = firstmipdata + streaming_data.data_offset - mip_data_offset;
initdata[mip].row_pitch = streaming_data.row_pitch;
initdata[mip].slice_pitch = streaming_data.slice_pitch;
}
// The replacement struct will store the newly created texture until replacement can be made later:
StreamingTextureReplace replace;
replace.resource = resource;
replace.srgb_subresource = -1;
bool success = device->CreateTexture(&desc, initdata, &replace.texture);
assert(success);
device->SetName(&replace.texture, resource->filename.c_str());
Format srgb_format = GetFormatSRGB(desc.format);
if (srgb_format != Format::UNKNOWN && srgb_format != desc.format)
{
replace.srgb_subresource = device->CreateSubresource(
&replace.texture,
SubresourceType::SRV,
0, -1,
0, -1,
&srgb_format
);
}
streaming_replacement_mutex.lock();
streaming_texture_replacements.push_back(replace);
streaming_replacement_mutex.unlock();
}
}
});
}
bool CheckResourcesOutdated()
{
std::scoped_lock lck(locker);
for (auto& x : resources)
{
const std::string& name = x.first;
auto resourceinternal = x.second.lock();
if (resourceinternal == nullptr)
continue;
uint64_t timestamp = wi::helper::FileTimestamp(resourceinternal->filename);
if (resourceinternal->timestamp < timestamp)
return true;
}
return false;
}
void ReloadOutdatedResources()
{
std::scoped_lock lck(locker);
for (auto& x : resources)
{
auto resourceinternal = x.second.lock();
if (resourceinternal == nullptr)
continue;
uint64_t timestamp = wi::helper::FileTimestamp(resourceinternal->filename);
if (resourceinternal->timestamp < timestamp)
{
wi::vector<uint8_t> filedata;
if (wi::helper::FileRead(resourceinternal->filename, filedata))
{
if (resourceinternal->streaming_texture.mip_count > 1)
wi::jobsystem::Wait(streaming_ctx); // reloading a resource that is potentially streaming needs to wait for current streaming job to end
if (LoadResourceDirectly(resourceinternal->filename, resourceinternal->flags, filedata.data(), filedata.size(), resourceinternal.get()))
{
resourceinternal->timestamp = timestamp;
resourceinternal->container_filename = resourceinternal->filename;
resourceinternal->container_fileoffset = 0;
resourceinternal->container_filesize = ~0ull;
wi::backlog::post("[resourcemanager] reload success: " + resourceinternal->filename);
}
else
{
wi::backlog::post("[resourcemanager] reload failure - LoadResourceDirectly returned false: " + resourceinternal->filename, wi::backlog::LogLevel::Error);
}
}
else
{
wi::backlog::post("[resourcemanager] reload failure - file data could not be read: " + resourceinternal->filename, wi::backlog::LogLevel::Error);
}
}
}
}
void Serialize_READ(wi::Archive& archive, ResourceSerializer& seri)
{
assert(archive.IsReadMode());
size_t serializable_count = 0;
archive >> serializable_count;
struct TempResource
{
std::string name;
const uint8_t* filedata = nullptr;
size_t filesize = 0;
};
wi::vector<TempResource> temp_resources;
temp_resources.resize(serializable_count);
wi::jobsystem::context ctx;
ctx.priority = wi::jobsystem::Priority::Low;
for (size_t i = 0; i < serializable_count; ++i)
{
auto& resource = temp_resources[i];
archive >> resource.name;
uint32_t flags_temp;
archive >> flags_temp;
// Note: flags not applied here, but they must be read
// We don't apply the flags, because they will be requested later by for example materials
// If we would apply flags here, then flags from previous session would be applied, that maybe we no longer want (for example RETAIN_FILEDATA)
// We don't read the file data from archive into a vector like usual, instead map the vector,
// this is much faster and we don't need to retain this data after archive lifetime
archive.MapVector(resource.filedata, resource.filesize);
size_t file_offset = archive.GetPos() - resource.filesize;
resource.name = archive.GetSourceDirectory() + resource.name;
if (Contains(resource.name))
continue;
// "Loading" the resource can happen asynchronously to serialization of file data, to improve performance
wi::jobsystem::Execute(ctx, [i, &temp_resources, &seri, &archive, file_offset](wi::jobsystem::JobArgs args) {
auto& tmp_resource = temp_resources[i];
auto res = Load(
tmp_resource.name,
Flags::IMPORT_DELAY,
tmp_resource.filedata,
tmp_resource.filesize,
archive.GetSourceFileName(),
file_offset
);
static std::mutex seri_locker;
seri_locker.lock();
seri.resources.push_back(res);
seri_locker.unlock();
});
}
wi::jobsystem::Wait(ctx);
}
void Serialize_WRITE(wi::Archive& archive, const wi::unordered_set<std::string>& resource_names)
{
assert(!archive.IsReadMode());
locker.lock();
size_t serializable_count = 0;
if (mode == Mode::NO_EMBEDDING)
{
// Simply not serialize any embedded resources
serializable_count = 0;
archive << serializable_count;
}
else
{
// Count embedded resources:
for (auto& name : resource_names)
{
auto it = resources.find(name);
if (it == resources.end())
continue;
std::shared_ptr<ResourceInternal> resource = it->second.lock();
if (resource != nullptr)
{
serializable_count++;
}
}
// Write all embedded resources:
archive << serializable_count;
for (auto& name : resource_names)
{
auto it = resources.find(name);
if (it == resources.end())
continue;
std::shared_ptr<ResourceInternal> resource = it->second.lock();
if (resource != nullptr)
{
std::string name = it->first;
wi::helper::MakePathRelative(archive.GetSourceDirectory(), name);
if (resource->filedata.empty())
{
wi::helper::FileRead(
resource->container_filename,
resource->filedata,
resource->container_filesize,
resource->container_fileoffset
);
}
archive << name;
archive << (uint32_t)resource->flags;
archive << resource->filedata;
if (!archive.GetSourceFileName().empty())
{
// Refresh the container file properties to the current file:
// The old file offsets could get stale otherwise if it's overwritten
resource->container_filename = archive.GetSourceFileName();
resource->container_fileoffset = archive.GetPos() - resource->filedata.size();
resource->container_filesize = resource->filedata.size();
if (!has_flag(resource->flags, Flags::IMPORT_RETAIN_FILEDATA))
{
resource->filedata.clear();
resource->filedata.shrink_to_fit();
}
}
}
}
}
locker.unlock();
}
}
}