From 3eb4c6cc37a362ae2fb6f7e6ebef2c0fe0c4e167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tur=C3=A1nszki=20J=C3=A1nos?= Date: Wed, 19 Jun 2024 07:28:00 +0200 Subject: [PATCH] resource manager hot reload (#864) --- .../ScriptingAPI-Documentation.md | 2 + Editor/Editor.cpp | 44 + Editor/Editor.h | 2 + Editor/main_SDL2.cpp | 19 +- Editor/main_Windows.cpp | 24 +- WickedEngine/wiHelper.cpp | 2 + WickedEngine/wiPrimitive_BindLua.cpp | 15 + WickedEngine/wiResourceManager.cpp | 1837 +++++++++-------- WickedEngine/wiResourceManager.h | 8 +- WickedEngine/wiScene.cpp | 4 +- WickedEngine/wiScene_Components.h | 1 + WickedEngine/wiVersion.cpp | 4 +- 12 files changed, 1045 insertions(+), 917 deletions(-) diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md index 74eb3409f..5796527a1 100644 --- a/Content/Documentation/ScriptingAPI-Documentation.md +++ b/Content/Documentation/ScriptingAPI-Documentation.md @@ -1616,6 +1616,7 @@ Axis Aligned Bounding Box. Can be intersected with other primitives. - Intersects(AABB aabb) : bool result - Intersects(Sphere sphere) : bool result - Intersects(Ray ray) : bool result +- Intersects(Vector point) : bool result - GetMin() : Vector result - GetMax() : Vector result - SetMin(Vector vector) @@ -1637,6 +1638,7 @@ Sphere defined by center Vector and radius. Can be intersected with other primit - Intersects(Sphere sphere) : bool result - Intersects(Capsule capsule) : bool result - Intersects(Ray ray) : bool result +- Intersects(Vector point) : bool result - GetCenter() : Vector result - GetRadius() : float result - SetCenter(Vector value) diff --git a/Editor/Editor.cpp b/Editor/Editor.cpp index dbb6b66bd..c6dd09f8b 100644 --- a/Editor/Editor.cpp +++ b/Editor/Editor.cpp @@ -97,6 +97,50 @@ void Editor::Initialize() renderComponent.Load(); ActivatePath(&renderComponent, 0.2f); } +void Editor::HotReload() +{ + if (!wi::initializer::IsInitializeFinished()) + return; + + static wi::jobsystem::context hotreload_ctx; + wi::jobsystem::Wait(hotreload_ctx); + hotreload_ctx.priority = wi::jobsystem::Priority::Low; + + if (wi::shadercompiler::GetRegisteredShaderCount() > 0) + { + wi::jobsystem::Execute(hotreload_ctx, [](wi::jobsystem::JobArgs args) { + wi::backlog::post("[Shader check] Started checking " + std::to_string(wi::shadercompiler::GetRegisteredShaderCount()) + " registered shaders for changes..."); + if (wi::shadercompiler::CheckRegisteredShadersOutdated()) + { + wi::backlog::post("[Shader check] Changes detected, initiating reload..."); + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [](uint64_t userdata) { + wi::renderer::ReloadShaders(); + }); + } + else + { + wi::backlog::post("[Shader check] All up to date"); + } + }); + } + + wi::jobsystem::Execute(hotreload_ctx, [](wi::jobsystem::JobArgs args) { + wi::backlog::post("[Resource check] Started checking resource manager for changes..."); + if (wi::resourcemanager::CheckResourcesOutdated()) + { + wi::backlog::post("[Resource check] Changes detected, initiating reload..."); + wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [](uint64_t userdata) { + wi::resourcemanager::ReloadOutdatedResources(); + }); + } + else + { + wi::backlog::post("[Resource check] All up to date"); + } + }); + + renderComponent.ReloadLanguage(); +} void EditorComponent::ResizeBuffers() { diff --git a/Editor/Editor.h b/Editor/Editor.h index a7914e28f..8e69b9a48 100644 --- a/Editor/Editor.h +++ b/Editor/Editor.h @@ -229,6 +229,8 @@ public: void Initialize() override; + void HotReload(); + ~Editor() override { config.Commit(); diff --git a/Editor/main_SDL2.cpp b/Editor/main_SDL2.cpp index eeead85e8..e3bb00328 100644 --- a/Editor/main_SDL2.cpp +++ b/Editor/main_SDL2.cpp @@ -35,24 +35,7 @@ int sdl_loop(Editor &editor) break; case SDL_WINDOWEVENT_FOCUS_GAINED: editor.is_window_active = true; - if (wi::shadercompiler::GetRegisteredShaderCount() > 0) - { - std::thread([] { - wi::backlog::post("[Shader check] Started checking " + std::to_string(wi::shadercompiler::GetRegisteredShaderCount()) + " registered shaders for changes..."); - if (wi::shadercompiler::CheckRegisteredShadersOutdated()) - { - wi::backlog::post("[Shader check] Changes detected, initiating reload..."); - wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [](uint64_t userdata) { - wi::renderer::ReloadShaders(); - }); - } - else - { - wi::backlog::post("[Shader check] All up to date"); - } - }).detach(); - } - editor.renderComponent.ReloadLanguage(); + editor.HotReload(); break; default: break; diff --git a/Editor/main_Windows.cpp b/Editor/main_Windows.cpp index 94f0abce6..8d896b1b3 100644 --- a/Editor/main_Windows.cpp +++ b/Editor/main_Windows.cpp @@ -276,29 +276,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_SETFOCUS: editor.is_window_active = true; - if (wi::shadercompiler::GetRegisteredShaderCount() > 0) - { - static wi::jobsystem::context shader_check_ctx; - if (!wi::jobsystem::IsBusy(shader_check_ctx)) - { - shader_check_ctx.priority = wi::jobsystem::Priority::Low; - wi::jobsystem::Execute(shader_check_ctx, [](wi::jobsystem::JobArgs args) { - wi::backlog::post("[Shader check] Started checking " + std::to_string(wi::shadercompiler::GetRegisteredShaderCount()) + " registered shaders for changes..."); - if (wi::shadercompiler::CheckRegisteredShadersOutdated()) - { - wi::backlog::post("[Shader check] Changes detected, initiating reload..."); - wi::eventhandler::Subscribe_Once(wi::eventhandler::EVENT_THREAD_SAFE_POINT, [](uint64_t userdata) { - wi::renderer::ReloadShaders(); - }); - } - else - { - wi::backlog::post("[Shader check] All up to date"); - } - }); - } - } - editor.renderComponent.ReloadLanguage(); + editor.HotReload(); break; case WM_PAINT: { diff --git a/WickedEngine/wiHelper.cpp b/WickedEngine/wiHelper.cpp index 9f3ed8162..8bf097659 100644 --- a/WickedEngine/wiHelper.cpp +++ b/WickedEngine/wiHelper.cpp @@ -1064,6 +1064,8 @@ namespace wi::helper uint64_t FileTimestamp(const std::string& fileName) { + if (!FileExists(fileName)) + return 0; auto tim = std::filesystem::last_write_time(ToNativeString(fileName)); return std::chrono::duration_cast>(tim.time_since_epoch()).count(); } diff --git a/WickedEngine/wiPrimitive_BindLua.cpp b/WickedEngine/wiPrimitive_BindLua.cpp index 37f70d074..f436d3786 100644 --- a/WickedEngine/wiPrimitive_BindLua.cpp +++ b/WickedEngine/wiPrimitive_BindLua.cpp @@ -260,6 +260,13 @@ namespace wi::lua::primitive int argc = wi::lua::SGetArgCount(L); if (argc > 0) { + Vector_BindLua* _vec = Luna::lightcheck(L, 1); + if (_vec) + { + wi::lua::SSetBool(L, aabb.intersects(_vec->GetFloat3())); + return 1; + } + AABB_BindLua* _aabb = Luna::lightcheck(L, 1); if (_aabb) { @@ -446,6 +453,14 @@ namespace wi::lua::primitive int argc = wi::lua::SGetArgCount(L); if (argc > 0) { + Vector_BindLua* _vec = Luna::lightcheck(L, 1); + if (_vec) + { + bool intersects = sphere.intersects(_vec->GetFloat3()); + wi::lua::SSetBool(L, intersects); + return 1; + } + AABB_BindLua* _aabb = Luna::lightcheck(L, 1); if (_aabb) { diff --git a/WickedEngine/wiResourceManager.cpp b/WickedEngine/wiResourceManager.cpp index 172f03b3c..6b8afa737 100644 --- a/WickedEngine/wiResourceManager.cpp +++ b/WickedEngine/wiResourceManager.cpp @@ -41,6 +41,7 @@ namespace wi int srgb_subresource = -1; wi::audio::Sound sound; std::string script; + size_t script_hash = 0; wi::video::Video video; wi::vector filedata; int font_style = -1; @@ -53,6 +54,7 @@ namespace wi std::string container_filename; size_t container_filesize = ~0ull; size_t container_fileoffset = 0; + uint64_t timestamp = 0; // Streaming parameters: StreamingTexture streaming_texture; @@ -80,6 +82,13 @@ namespace wi 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(); @@ -159,7 +168,7 @@ namespace wi internal_state = std::make_shared(); } ResourceInternal* resourceinternal = (ResourceInternal*)internal_state.get(); - resourceinternal->flags |= resourcemanager::Flags::IMPORT_DELAY; // this will cause resource to be recreated, but let using old file data like delayed loading + resourceinternal->timestamp = 0; } void Resource::StreamingRequestResolution(uint32_t resolution) @@ -273,6 +282,890 @@ namespace wi 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 transcoded_data(transcoded_data_size); + + wi::vector 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 transcoded_data(transcoded_data_size); + + wi::vector 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 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, @@ -293,7 +1186,17 @@ namespace wi basist::basisu_transcoder_init(); } - if (resource == nullptr) + 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(); resources[name] = resource; @@ -362,880 +1265,13 @@ namespace wi } else { - 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 Resource(); - } - } - - 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 transcoded_data(transcoded_data_size); - - wi::vector 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 transcoded_data(transcoded_data_size); - - wi::vector 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 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); - 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(); + success = LoadResourceDirectly(name, flags, filedata, filesize, resource.get()); } if (success) { resource->flags = flags; + resource->timestamp = timestamp; Resource retVal; retVal.internal_state = resource; @@ -1366,7 +1402,7 @@ namespace wi { std::weak_ptr& weak_resource = x.second; std::shared_ptr resource = weak_resource.lock(); - if (resource != nullptr && resource->texture.IsValid() && has_flag(resource->flags, Flags::STREAMING)) + if (resource != nullptr && resource->texture.IsValid() && resource->streaming_texture.mip_count > 1) { streaming_texture_jobs.push_back(resource); } @@ -1487,6 +1523,63 @@ namespace wi }); } + 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 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()); diff --git a/WickedEngine/wiResourceManager.h b/WickedEngine/wiResourceManager.h index ce84314e0..178b631f8 100644 --- a/WickedEngine/wiResourceManager.h +++ b/WickedEngine/wiResourceManager.h @@ -24,6 +24,7 @@ namespace wi const wi::graphics::Texture& GetTexture() const; const wi::audio::Sound& GetSound() const; const std::string& GetScript() const; + size_t GetScriptHash() const; const wi::video::Video& GetVideo() const; int GetTextureSRGBSubresource() const; int GetFontStyle() const; @@ -38,7 +39,6 @@ namespace wi void SetVideo(const wi::video::Video& script); // Resource marked for recreate on resourcemanager::Load() - // It keeps embedded file data if exists void SetOutdated(); // Let the streaming system know the required resolution of this resource @@ -107,6 +107,12 @@ namespace wi // Launching or finalizing background streaming jobs is attempted here void UpdateStreamingResources(float dt); + // Returns true if any of the loaded resources are outdated compared to their files + bool CheckResourcesOutdated(); + + // Reload all resources that are outdated + void ReloadOutdatedResources(); + struct ResourceSerializer { wi::vector resources; diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index c90875e66..5643b87e7 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -4966,8 +4966,10 @@ namespace wi::scene if (script.IsPlaying()) { - if (script.script.empty() && script.resource.IsValid()) + if (script.resource.IsValid() && (script.script.empty() || script.script_hash != script.resource.GetScriptHash())) { + script.script.clear(); + script.script_hash = script.resource.GetScriptHash(); std::string str = script.resource.GetScript(); wi::lua::AttachScriptParameters(str, script.filename, wi::lua::GeneratePID(), "local function GetEntity() return " + std::to_string(entity) + "; end;", ""); wi::lua::CompileText(str, script.script); diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index 7fc48a4a4..71a7a68ee 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -1716,6 +1716,7 @@ namespace wi::scene // Non-serialized attributes: wi::vector script; // compiled script binary data wi::Resource resource; + size_t script_hash = 0; inline void Play() { _flags |= PLAYING; } inline void SetPlayOnce(bool once = true) { if (once) { _flags |= PLAY_ONCE; } else { _flags &= ~PLAY_ONCE; } } diff --git a/WickedEngine/wiVersion.cpp b/WickedEngine/wiVersion.cpp index 7b59cce7c..4a86b3064 100644 --- a/WickedEngine/wiVersion.cpp +++ b/WickedEngine/wiVersion.cpp @@ -9,7 +9,7 @@ namespace wi::version // minor features, major updates, breaking compatibility changes const int minor = 71; // minor bug fixes, alterations, refactors, updates - const int revision = 488; + const int revision = 489; const std::string version_string = std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(revision); @@ -50,7 +50,7 @@ All contributors: https://github.com/turanszkij/WickedEngine/graphs/contributors Patreon supporters --------------------------- -Nemerle, James Webb, Quifeng Jin, TheGameCreators, Joseph Goldin, Yuri, Sergey K, Yukawa Kanta, Dragon Josh, John, LurkingNinja, Bernardo Del Castillo, Invictus, Scott Hunt, Yazan Altaki, Tuan NV, Robert MacGregor, cybernescence, Alexander Dahlin, blueapples, Delhills, NI NI, Sherief, ktopoet, Justin Macklin, Cédric Fabre, TogetherTeam, Bartosz Boczula, Arne Koenig, Ivan Trajchev, nathants, Fahd Ahmed, Gabriel Jadderson, SAS_Controller, Dominik Madarász, Segfault, Mike amanfo, Dennis Brakhane, rookie, Peter Moore, therealjtgill, Nicolas Embleton, Desuuc, radino1977, Anthony Curtis, manni heck, Matthias Hölzl, Phyffer, Lucas Pinheiro, Tapkaara, gpman, Anthony Python, Gnowos, Klaus, slaughternaut, Paul Brain, Connor Greaves, Alexandr, Lee Bamber, MCAlarm MC2, Titoutan, Willow, Aldo, lokimx, K. Osterman, Nomad, ykl, Alex Krokos, Timmy, Avaflow, mat, Hexegonel Samael Michael, Joe Spataro, soru, GeniokV, Mammoth, Ignacio, datae +Nemerle, James Webb, Quifeng Jin, TheGameCreators, Joseph Goldin, Yuri, Sergey K, Yukawa Kanta, Dragon Josh, John, LurkingNinja, Bernardo Del Castillo, Invictus, Scott Hunt, Yazan Altaki, Tuan NV, Robert MacGregor, cybernescence, Alexander Dahlin, blueapples, Delhills, NI NI, Sherief, ktopoet, Justin Macklin, Cédric Fabre, TogetherTeam, Bartosz Boczula, Arne Koenig, Ivan Trajchev, nathants, Fahd Ahmed, Gabriel Jadderson, SAS_Controller, Dominik Madarász, Segfault, Mike amanfo, Dennis Brakhane, rookie, Peter Moore, therealjtgill, Nicolas Embleton, Desuuc, radino1977, Anthony Curtis, manni heck, Matthias Hölzl, Phyffer, Lucas Pinheiro, Tapkaara, gpman, Anthony Python, Gnowos, Klaus, slaughternaut, Paul Brain, Connor Greaves, Alexandr, Lee Bamber, MCAlarm MC2, Titoutan, Willow, Aldo, lokimx, K. Osterman, Nomad, ykl, Alex Krokos, Timmy, Avaflow, mat, Hexegonel Samael Michael, Joe Spataro, soru, GeniokV, Mammoth, Ignacio, datae, Jason Rice )"; return credits;