From 9413387431048d1d9293fb733944a41e8cb46a76 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Wed, 10 Jun 2026 00:13:18 +0800 Subject: [PATCH] GLTFLoader: support MSFT DDS texture sources --- AssetLoader/src/GLTFLoader.cpp | 64 +++++++++- Tests/DiligentToolsTest/CMakeLists.txt | 1 + .../DiligentToolsTest/src/GLTFLoaderTest.cpp | 117 ++++++++++++++++++ 3 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 Tests/DiligentToolsTest/src/GLTFLoaderTest.cpp diff --git a/AssetLoader/src/GLTFLoader.cpp b/AssetLoader/src/GLTFLoader.cpp index 1624f7a9..64f60b5f 100644 --- a/AssetLoader/src/GLTFLoader.cpp +++ b/AssetLoader/src/GLTFLoader.cpp @@ -62,6 +62,20 @@ namespace Diligent namespace GLTF { +static std::string DecodeURI(const std::string& URI) +{ + std::string DecodedURI; + if (tinygltf::URIDecode(URI, &DecodedURI, nullptr)) + return DecodedURI; + + return URI; +} + +static std::string GetImagePath(const std::string& BaseDir, const std::string& URI) +{ + return !URI.empty() ? FileSystem::SimplifyPath((BaseDir + DecodeURI(URI)).c_str()) : ""; +} + InputLayoutDescX VertexAttributesToInputLayout(const VertexAttributeDesc* pAttributes, size_t NumAttributes) { VERIFY_EXPR(pAttributes != nullptr || NumAttributes == 0); @@ -1117,6 +1131,46 @@ void Model::InitMaterialTextureAddressingAttribs(Material& Mat, Uint32 TextureIn } } +namespace MSFTTextureDDS +{ + +constexpr const char* MSFTTextureDDSExtension = "MSFT_texture_dds"; + +bool IsValidImageSource(const tinygltf::Model& gltf_model, int Source) +{ + return Source >= 0 && Source < static_cast(gltf_model.images.size()); +} + +bool IsDDSImage(const tinygltf::Image& gltf_image) +{ + return (gltf_image.width < 0 && gltf_image.height < 0 && + static_cast(gltf_image.pixel_type) == IMAGE_FILE_FORMAT_DDS && + !gltf_image.image.empty()); +} + +int GetSource(const tinygltf::Texture& gltf_tex, + const tinygltf::Model& gltf_model) +{ + const auto ext_it = gltf_tex.extensions.find(MSFTTextureDDSExtension); + if (ext_it == gltf_tex.extensions.end() || !ext_it->second.IsObject()) + return -1; + + const tinygltf::Value& SourceValue = ext_it->second.Get("source"); + if (!SourceValue.IsInt()) + return -1; + + const int DDSSource = SourceValue.GetNumberAsInt(); + if (!IsValidImageSource(gltf_model, DDSSource)) + return -1; + + if (!IsDDSImage(gltf_model.images[DDSSource])) + return -1; + + return DDSSource; +} + +} // namespace MSFTTextureDDS + void Model::LoadTextures(IRenderDevice* pDevice, const tinygltf::Model& gltf_model, const std::string& BaseDir, @@ -1127,8 +1181,11 @@ void Model::LoadTextures(IRenderDevice* pDevice, Textures.reserve(gltf_model.textures.size()); for (const tinygltf::Texture& gltf_tex : gltf_model.textures) { - const tinygltf::Image& gltf_image = gltf_model.images[gltf_tex.source]; - const std::string CacheId = !gltf_image.uri.empty() ? FileSystem::SimplifyPath((BaseDir + gltf_image.uri).c_str()) : ""; + const int DDSSource = MSFTTextureDDS::GetSource(gltf_tex, gltf_model); + const int ImageSource = DDSSource >= 0 ? DDSSource : gltf_tex.source; + + const tinygltf::Image& gltf_image = gltf_model.images[ImageSource]; + const std::string CacheId = GetImagePath(BaseDir, gltf_image.uri); ImageData Image; Image.Width = gltf_image.width; @@ -1877,7 +1934,6 @@ struct LoaderData ModelCreateInfo::ReadWholeFileCallbackType ReadWholeFile = nullptr; }; - bool LoadImageData(tinygltf::Image* gltf_image, const int gltf_image_idx, std::string* error, @@ -1893,7 +1949,7 @@ bool LoadImageData(tinygltf::Image* gltf_image, LoaderData* pLoaderData = static_cast(user_data); if (pLoaderData != nullptr) { - const auto CacheId = !gltf_image->uri.empty() ? FileSystem::SimplifyPath((pLoaderData->BaseDir + gltf_image->uri).c_str()) : ""; + const auto CacheId = GetImagePath(pLoaderData->BaseDir, gltf_image->uri); if (pLoaderData->pResourceMgr != nullptr) { diff --git a/Tests/DiligentToolsTest/CMakeLists.txt b/Tests/DiligentToolsTest/CMakeLists.txt index 6f9dc642..f9ab7e26 100644 --- a/Tests/DiligentToolsTest/CMakeLists.txt +++ b/Tests/DiligentToolsTest/CMakeLists.txt @@ -26,6 +26,7 @@ target_link_libraries(DiligentToolsTest PRIVATE Diligent-BuildSettings Diligent-TargetPlatform + Diligent-AssetLoader Diligent-TextureLoader Diligent-Common Diligent-GraphicsEngine diff --git a/Tests/DiligentToolsTest/src/GLTFLoaderTest.cpp b/Tests/DiligentToolsTest/src/GLTFLoaderTest.cpp new file mode 100644 index 00000000..74087d5f --- /dev/null +++ b/Tests/DiligentToolsTest/src/GLTFLoaderTest.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2026 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#include "GLTFLoader.hpp" +#include "../../../ThirdParty/tinygltf/tiny_gltf.h" + +#include "gtest/gtest.h" + +#include "Image.h" + +namespace Diligent +{ + +namespace GLTF +{ + +namespace MSFTTextureDDS +{ + +int GetSource(const tinygltf::Texture& gltf_tex, + const tinygltf::Model& gltf_model); + +} // namespace MSFTTextureDDS + +} // namespace GLTF + +} // namespace Diligent + +using namespace Diligent; + +namespace +{ + +tinygltf::Texture CreateDDSTexture(int Source) +{ + tinygltf::Value::Object Extension; + Extension.emplace("source", tinygltf::Value{Source}); + + tinygltf::Texture Texture; + Texture.source = 0; + Texture.extensions.emplace("MSFT_texture_dds", tinygltf::Value{std::move(Extension)}); + return Texture; +} + +TEST(Tools_GLTFLoader, MSFTTextureDDSUsesRawDDSImageData) +{ + tinygltf::Image DDSImage; + DDSImage.uri = "texture.dds"; + DDSImage.pixel_type = IMAGE_FILE_FORMAT_DDS; + DDSImage.image = {'D', 'D', 'S', ' '}; + + tinygltf::Model Model; + Model.images.emplace_back(tinygltf::Image{}); + Model.images.emplace_back(std::move(DDSImage)); + + const tinygltf::Texture Texture = CreateDDSTexture(1); + + EXPECT_EQ(GLTF::MSFTTextureDDS::GetSource(Texture, Model), 1); +} + +TEST(Tools_GLTFLoader, MSFTTextureDDSRejectsLoadedImageMetadata) +{ + tinygltf::Image DDSImage; + DDSImage.uri = "cached.dds"; + DDSImage.width = 4; + DDSImage.height = 4; + DDSImage.component = 4; + DDSImage.bits = 8; + DDSImage.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; + + tinygltf::Model Model; + Model.images.emplace_back(tinygltf::Image{}); + Model.images.emplace_back(std::move(DDSImage)); + + const tinygltf::Texture Texture = CreateDDSTexture(1); + + EXPECT_EQ(GLTF::MSFTTextureDDS::GetSource(Texture, Model), -1); +} + +TEST(Tools_GLTFLoader, MSFTTextureDDSRejectsUriOnlyImage) +{ + tinygltf::Image DDSImage; + DDSImage.uri = "encoded%20texture.dds"; + + tinygltf::Model Model; + Model.images.emplace_back(tinygltf::Image{}); + Model.images.emplace_back(std::move(DDSImage)); + + const tinygltf::Texture Texture = CreateDDSTexture(1); + + EXPECT_EQ(GLTF::MSFTTextureDDS::GetSource(Texture, Model), -1); +} + +} // namespace