diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt
index 76d261ab3e4e6a67f6d74454d3088da5619264cf..2bd1edaacce377eca4b6b15b8448be21b29a2e20 100644
--- a/indra/llprimitive/CMakeLists.txt
+++ b/indra/llprimitive/CMakeLists.txt
@@ -34,6 +34,7 @@ set(llprimitive_HEADER_FILES
     lldaeloader.h
     llgltfloader.h
     llgltfmaterial.h
+    llgltfmaterial_templates.h
     legacy_object_types.h
     llmaterial.h
     llmaterialid.h
diff --git a/indra/llprimitive/llgltfmaterial.cpp b/indra/llprimitive/llgltfmaterial.cpp
index 19b7413934251f66957d34babbb3870653b92021..f42c11ee210f44e66d0b9acf67174942867356cf 100644
--- a/indra/llprimitive/llgltfmaterial.cpp
+++ b/indra/llprimitive/llgltfmaterial.cpp
@@ -24,25 +24,28 @@
  * $/LicenseInfo$
  */
 
+
 #include "linden_common.h"
 
 #include "llgltfmaterial.h"
+
 #include "llsdserialize.h"
 
 // NOTE -- this should be the one and only place tiny_gltf.h is included
 #include "tinygltf/tiny_gltf.h"
+#include "llgltfmaterial_templates.h"
 
 const char* const LLGLTFMaterial::ASSET_VERSION = "1.1";
 const char* const LLGLTFMaterial::ASSET_TYPE = "GLTF 2.0";
 const std::array<std::string, 2> LLGLTFMaterial::ACCEPTED_ASSET_VERSIONS = { "1.0", "1.1" };
 
-const char* const GLTF_FILE_EXTENSION_TRANSFORM = "KHR_texture_transform";
-const char* const GLTF_FILE_EXTENSION_TRANSFORM_SCALE = "scale";
-const char* const GLTF_FILE_EXTENSION_TRANSFORM_OFFSET = "offset";
-const char* const GLTF_FILE_EXTENSION_TRANSFORM_ROTATION = "rotation";
+const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM = "KHR_texture_transform";
+const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_SCALE = "scale";
+const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_OFFSET = "offset";
+const char* const LLGLTFMaterial::GLTF_FILE_EXTENSION_TRANSFORM_ROTATION = "rotation";
 
 // special UUID that indicates a null UUID in override data
-static const LLUUID GLTF_OVERRIDE_NULL_UUID = LLUUID("ffffffff-ffff-ffff-ffff-ffffffffffff");
+const LLUUID LLGLTFMaterial::GLTF_OVERRIDE_NULL_UUID = LLUUID("ffffffff-ffff-ffff-ffff-ffffffffffff");
 
 void LLGLTFMaterial::TextureTransform::getPacked(F32 (&packed)[8]) const
 {
@@ -68,14 +71,14 @@ LLGLTFMaterial::LLGLTFMaterial(const LLGLTFMaterial& rhs)
 
 LLGLTFMaterial& LLGLTFMaterial::operator=(const LLGLTFMaterial& rhs)
 {
-    //have to do a manual operator= because of LLRefCount 
+    //have to do a manual operator= because of LLRefCount
     mTextureId = rhs.mTextureId;
 
     mTextureTransform = rhs.mTextureTransform;
 
     mBaseColor = rhs.mBaseColor;
     mEmissiveColor = rhs.mEmissiveColor;
-    
+
     mMetallicFactor = rhs.mMetallicFactor;
     mRoughnessFactor = rhs.mRoughnessFactor;
     mAlphaCutoff = rhs.mAlphaCutoff;
@@ -97,7 +100,7 @@ bool LLGLTFMaterial::operator==(const LLGLTFMaterial& rhs) const
 
         mBaseColor == rhs.mBaseColor &&
         mEmissiveColor == rhs.mEmissiveColor &&
-        
+
         mMetallicFactor == rhs.mMetallicFactor &&
         mRoughnessFactor == rhs.mRoughnessFactor &&
         mAlphaCutoff == rhs.mAlphaCutoff &&
@@ -122,6 +125,7 @@ bool LLGLTFMaterial::fromJSON(const std::string& json, std::string& warn_msg, st
 
         return true;
     }
+
     return false;
 }
 
@@ -190,7 +194,8 @@ void LLGLTFMaterial::setFromModel(const tinygltf::Model& model, S32 mat_index)
     }
 }
 
-LLVector2 vec2_from_json(const tinygltf::Value::Object& object, const char* key, const LLVector2& default_value)
+// static
+LLVector2 LLGLTFMaterial::vec2FromJson(const tinygltf::Value::Object& object, const char* key, const LLVector2& default_value)
 {
     const auto it = object.find(key);
     if (it == object.end())
@@ -215,7 +220,8 @@ LLVector2 vec2_from_json(const tinygltf::Value::Object& object, const char* key,
     return value;
 }
 
-F32 float_from_json(const tinygltf::Value::Object& object, const char* key, const F32 default_value)
+// static
+F32 LLGLTFMaterial::floatFromJson(const tinygltf::Value::Object& object, const char* key, const F32 default_value)
 {
     const auto it = object.find(key);
     if (it == object.end())
@@ -230,52 +236,6 @@ F32 float_from_json(const tinygltf::Value::Object& object, const char* key, cons
     return (F32)real_json.GetNumberAsDouble();
 }
 
-template<typename T>
-std::string gltf_get_texture_image(const tinygltf::Model& model, const T& texture_info)
-{
-    const S32 texture_idx = texture_info.index;
-    if (texture_idx < 0 || texture_idx >= model.textures.size())
-    {
-        return "";
-    }
-    const tinygltf::Texture& texture = model.textures[texture_idx];
-
-    // Ignore texture.sampler for now
-
-    const S32 image_idx = texture.source;
-    if (image_idx < 0 || image_idx >= model.images.size())
-    {
-        return "";
-    }
-    const tinygltf::Image& image = model.images[image_idx];
-
-    return image.uri;
-}
-
-// *NOTE: Use template here as workaround for the different similar texture info classes
-template<typename T>
-void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id)
-{
-    LL_PROFILE_ZONE_SCOPED;
-    const std::string uri = gltf_get_texture_image(model, texture_info);
-    mTextureId[texture_info_id].set(uri);
-
-    const tinygltf::Value::Object& extensions_object = texture_info.extensions;
-    const auto transform_it = extensions_object.find(GLTF_FILE_EXTENSION_TRANSFORM);
-    if (transform_it != extensions_object.end())
-    {
-        const tinygltf::Value& transform_json = std::get<1>(*transform_it);
-        if (transform_json.IsObject())
-        {
-            const tinygltf::Value::Object& transform_object = transform_json.Get<tinygltf::Value::Object>();
-            TextureTransform& transform = mTextureTransform[texture_info_id];
-            transform.mOffset = vec2_from_json(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_OFFSET, getDefaultTextureOffset());
-            transform.mScale = vec2_from_json(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_SCALE, getDefaultTextureScale());
-            transform.mRotation = float_from_json(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_ROTATION, getDefaultTextureRotation());
-        }
-    }
-}
-
 void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const
 {
     LL_PROFILE_ZONE_SCOPED;
@@ -302,7 +262,7 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const
 
     material_out.alphaMode = getAlphaMode();
     material_out.alphaCutoff = mAlphaCutoff;
-    
+
     mBaseColor.write(material_out.pbrMetallicRoughness.baseColorFactor);
 
     if (mEmissiveColor != LLGLTFMaterial::getDefaultEmissiveColor())
@@ -320,7 +280,7 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const
     tinygltf::Value::Object extras;
     bool write_extras = false;
     if (mOverrideAlphaMode && mAlphaMode == getDefaultAlphaMode())
-    { 
+    {
         extras["override_alpha_mode"] = tinygltf::Value(mOverrideAlphaMode);
         write_extras = true;
     }
@@ -339,57 +299,6 @@ void LLGLTFMaterial::writeToModel(tinygltf::Model& model, S32 mat_index) const
     model.asset.version = "2.0";
 }
 
-template<typename T>
-void gltf_allocate_texture_image(tinygltf::Model& model, T& texture_info, const std::string& uri)
-{
-    const S32 image_idx = model.images.size();
-    model.images.emplace_back();
-    model.images[image_idx].uri = uri;
-
-    // The texture, not to be confused with the texture info
-    const S32 texture_idx = model.textures.size();
-    model.textures.emplace_back();
-    tinygltf::Texture& texture = model.textures[texture_idx];
-    texture.source = image_idx;
-
-    texture_info.index = texture_idx;
-}
-
-template<typename T>
-void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write) const
-{
-    LL_PROFILE_ZONE_SCOPED;
-    const LLUUID& texture_id = mTextureId[texture_info_id];
-    const TextureTransform& transform = mTextureTransform[texture_info_id];
-    const bool is_blank_transform = transform == sDefault.mTextureTransform[0];
-    // Check if this material matches all the fallback values, and if so, then
-    // skip including it to reduce material size
-    if (!force_write && texture_id.isNull() && is_blank_transform)
-    {
-        return;
-    }
-
-    // tinygltf will discard this texture info if there is no valid texture,
-    // causing potential loss of information for overrides, so ensure one is
-    // defined. -Cosmic,2023-01-30
-    gltf_allocate_texture_image(model, texture_info, texture_id.asString());
-
-    if (!is_blank_transform)
-    {
-        tinygltf::Value::Object transform_map;
-        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_OFFSET] = tinygltf::Value(tinygltf::Value::Array({
-            tinygltf::Value(transform.mOffset.mV[VX]),
-            tinygltf::Value(transform.mOffset.mV[VY])
-        }));
-        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_SCALE] = tinygltf::Value(tinygltf::Value::Array({
-            tinygltf::Value(transform.mScale.mV[VX]),
-            tinygltf::Value(transform.mScale.mV[VY])
-        }));
-        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_ROTATION] = tinygltf::Value(transform.mRotation);
-        texture_info.extensions[GLTF_FILE_EXTENSION_TRANSFORM] = tinygltf::Value(transform_map);
-    }
-}
-
 void LLGLTFMaterial::sanitizeAssetMaterial()
 {
     mTextureTransform = sDefault.mTextureTransform;
@@ -403,19 +312,19 @@ bool LLGLTFMaterial::setBaseMaterial()
     return *this != old_override;
 }
 
-bool LLGLTFMaterial::isClearedForBaseMaterial()
-{
-    LLGLTFMaterial cleared_override = sDefault;
-    cleared_override.setBaseMaterial(*this);
-    return *this == cleared_override;
-}
-
 // For material overrides only. Copies transforms from the old override.
 void LLGLTFMaterial::setBaseMaterial(const LLGLTFMaterial& old_override_mat)
 {
     mTextureTransform = old_override_mat.mTextureTransform;
 }
 
+bool LLGLTFMaterial::isClearedForBaseMaterial() const
+{
+    LLGLTFMaterial cleared_override = sDefault;
+    cleared_override.setBaseMaterial(*this);
+    return *this == cleared_override;
+}
+
 
 // static
 void LLGLTFMaterial::hackOverrideUUID(LLUUID& id)
@@ -516,7 +425,7 @@ void LLGLTFMaterial::setAlphaMode(const std::string& mode, bool for_override)
     {
         m = ALPHA_MODE_BLEND;
     }
-    
+
     setAlphaMode(m, for_override);
 }
 
@@ -709,7 +618,6 @@ void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& d
         {
             data["tex"][i] = LLSD::UUID(override_texture_id);
         }
-
     }
 
     if (override_mat.mBaseColor != getDefaultBaseColor())
@@ -764,23 +672,6 @@ void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& d
             data["ti"][i]["r"] = override_mat.mTextureTransform[i].mRotation;
         }
     }
-
-#if 0
-    {
-        std::ostringstream ostr;
-        LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_NOTATION);
-        std::string param_str(ostr.str());
-        LL_INFOS() << param_str << LL_ENDL;
-        LL_INFOS() << "Notation size: " << param_str.size() << LL_ENDL;
-    }
-
-    {
-        std::ostringstream ostr;
-        LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_BINARY);
-        std::string param_str(ostr.str());
-        LL_INFOS() << "Binary size: " << param_str.size() << LL_ENDL;
-    }
-#endif
 }
 
 
diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h
index ca2750770783678925d26a7abdfe0e4ea70785c8..a078a530a4bf656d8a3c022381faca5035032282 100644
--- a/indra/llprimitive/llgltfmaterial.h
+++ b/indra/llprimitive/llgltfmaterial.h
@@ -35,10 +35,13 @@
 #include "hbxxh.h"
 
 #include <string>
+#include <map>
 
 namespace tinygltf
 {
     class Model;
+    struct TextureInfo;
+    class Value;
 }
 
 class LLTextureEntry;
@@ -52,6 +55,9 @@ class LLGLTFMaterial : public LLRefCount
 
     static const char* const ASSET_VERSION;
     static const char* const ASSET_TYPE;
+    // Max allowed size of a GLTF material asset or override, when serialized
+    // as a minified JSON string
+    static constexpr size_t MAX_ASSET_LENGTH = 2048;
     static const std::array<std::string, 2> ACCEPTED_ASSET_VERSIONS;
     static bool isAcceptedVersion(const std::string& version) { return std::find(ACCEPTED_ASSET_VERSIONS.cbegin(), ACCEPTED_ASSET_VERSIONS.cend(), version) != ACCEPTED_ASSET_VERSIONS.cend(); }
 
@@ -64,6 +70,7 @@ class LLGLTFMaterial : public LLRefCount
         void getPacked(F32 (&packed)[8]) const;
 
         bool operator==(const TextureTransform& other) const;
+        bool operator!=(const TextureTransform& other) const { return !(*this == other); }
     };
 
     enum AlphaMode
@@ -96,8 +103,13 @@ class LLGLTFMaterial : public LLRefCount
         GLTF_TEXTURE_INFO_COUNT
     };
 
-    std::array<LLUUID, GLTF_TEXTURE_INFO_COUNT> mTextureId;
+    static const char* const GLTF_FILE_EXTENSION_TRANSFORM;
+    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_SCALE;
+    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_OFFSET;
+    static const char* const GLTF_FILE_EXTENSION_TRANSFORM_ROTATION;
+    static const LLUUID GLTF_OVERRIDE_NULL_UUID;
 
+    std::array<LLUUID, GLTF_TEXTURE_INFO_COUNT> mTextureId;
     std::array<TextureTransform, GLTF_TEXTURE_INFO_COUNT> mTextureTransform;
 
     // NOTE: initialize values to defaults according to the GLTF spec
@@ -137,7 +149,7 @@ class LLGLTFMaterial : public LLRefCount
     void setAlphaMode(S32 mode, bool for_override = false);
     void setDoubleSided(bool double_sided, bool for_override = false);
 
-    //NOTE: texture offsets only exist in overrides, so "for_override" is not needed
+    // *NOTE: texture offsets only exist in overrides, so "for_override" is not needed
 
     void setTextureOffset(TextureInfo texture_info, const LLVector2& offset);
     void setTextureScale(TextureInfo texture_info, const LLVector2& scale);
@@ -155,7 +167,6 @@ class LLGLTFMaterial : public LLRefCount
     static LLVector2 getDefaultTextureScale();
     static F32 getDefaultTextureRotation();
 
-
     static void hackOverrideUUID(LLUUID& id);
     static void applyOverrideUUID(LLUUID& dst_id, const LLUUID& override_id);
 
@@ -164,7 +175,7 @@ class LLGLTFMaterial : public LLRefCount
     void setAlphaMode(const std::string& mode, bool for_override = false);
 
     const char* getAlphaMode() const;
-    
+
     // set the contents of this LLGLTFMaterial from the given json
     // returns true if successful
     // if unsuccessful, the contents of this LLGLTFMaterial should be left unchanged and false is returned
@@ -176,7 +187,6 @@ class LLGLTFMaterial : public LLRefCount
     // get the contents of this LLGLTFMaterial as a json string
     std::string asJSON(bool prettyprint = false) const;
 
-
     // initialize from given tinygltf::Model
     // model - the model to reference
     // mat_index - index of material in model's material array
@@ -202,21 +212,29 @@ class LLGLTFMaterial : public LLRefCount
     // For material overrides only. Clears most properties to
     // default/fallthrough, but preserves the transforms.
     bool setBaseMaterial();
+    void setBaseMaterial(const LLGLTFMaterial& old_override_mat);
     // True if setBaseMaterial() was just called
-    bool isClearedForBaseMaterial();
+    bool isClearedForBaseMaterial() const;
 
     // For local materials, they have to keep track of where
     // they are assigned to for full updates
     virtual void addTextureEntry(LLTextureEntry* te) {};
     virtual void removeTextureEntry(LLTextureEntry* te) {};
 
-private:
+protected:
+    static LLVector2 vec2FromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const LLVector2& default_value);
+    static F32 floatFromJson(const std::map<std::string, tinygltf::Value>& object, const char* key, const F32 default_value);
+
+    template<typename T>
+    static void allocateTextureImage(tinygltf::Model& model, T& texture_info, const std::string& uri);
+
     template<typename T>
     void setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id);
+    template<typename T>
+    static void setFromTexture(const tinygltf::Model& model, const T& texture_info, LLUUID& texture_id, TextureTransform& transform);
 
     template<typename T>
     void writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write = false) const;
-
-    void setBaseMaterial(const LLGLTFMaterial& old_override_mat);
+    template<typename T>
+    static void writeToTexture(tinygltf::Model& model, T& texture_info, const LLUUID& texture_id, const TextureTransform& transform, bool force_write = false);
 };
-
diff --git a/indra/llprimitive/llgltfmaterial_templates.h b/indra/llprimitive/llgltfmaterial_templates.h
new file mode 100644
index 0000000000000000000000000000000000000000..f607dfe96749f3702fa7c0ad081b0915289d8468
--- /dev/null
+++ b/indra/llprimitive/llgltfmaterial_templates.h
@@ -0,0 +1,142 @@
+/**
+ * @file llgltfmaterial_templates.h
+ * @brief Material template definition
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ * 
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+#include "llgltfmaterial.h"
+
+// Use templates here as workaround for the different similar texture info classes in tinygltf
+// Includer must first include tiny_gltf.h with the desired flags
+
+template<typename T>
+std::string gltf_get_texture_image(const tinygltf::Model& model, const T& texture_info)
+{
+    const S32 texture_idx = texture_info.index;
+    if (texture_idx < 0 || texture_idx >= model.textures.size())
+    {
+        return "";
+    }
+    const tinygltf::Texture& texture = model.textures[texture_idx];
+
+    // Ignore texture.sampler for now
+
+    const S32 image_idx = texture.source;
+    if (image_idx < 0 || image_idx >= model.images.size())
+    {
+        return "";
+    }
+    const tinygltf::Image& image = model.images[image_idx];
+
+    return image.uri;
+}
+
+template<typename T>
+void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, TextureInfo texture_info_id)
+{
+    setFromTexture(model, texture_info, mTextureId[texture_info_id], mTextureTransform[texture_info_id]);
+    const std::string uri = gltf_get_texture_image(model, texture_info);
+}
+
+// static
+template<typename T>
+void LLGLTFMaterial::setFromTexture(const tinygltf::Model& model, const T& texture_info, LLUUID& texture_id, TextureTransform& transform)
+{
+    LL_PROFILE_ZONE_SCOPED;
+    const std::string uri = gltf_get_texture_image(model, texture_info);
+    texture_id.set(uri);
+
+    const tinygltf::Value::Object& extensions_object = texture_info.extensions;
+    const auto transform_it = extensions_object.find(GLTF_FILE_EXTENSION_TRANSFORM);
+    if (transform_it != extensions_object.end())
+    {
+        const tinygltf::Value& transform_json = std::get<1>(*transform_it);
+        if (transform_json.IsObject())
+        {
+            const tinygltf::Value::Object& transform_object = transform_json.Get<tinygltf::Value::Object>();
+            transform.mOffset = vec2FromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_OFFSET, getDefaultTextureOffset());
+            transform.mScale = vec2FromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_SCALE, getDefaultTextureScale());
+            transform.mRotation = floatFromJson(transform_object, GLTF_FILE_EXTENSION_TRANSFORM_ROTATION, getDefaultTextureRotation());
+        }
+    }
+}
+
+// static
+template<typename T>
+void LLGLTFMaterial::allocateTextureImage(tinygltf::Model& model, T& texture_info, const std::string& uri)
+{
+    const S32 image_idx = model.images.size();
+    model.images.emplace_back();
+    model.images[image_idx].uri = uri;
+
+    // The texture, not to be confused with the texture info
+    const S32 texture_idx = model.textures.size();
+    model.textures.emplace_back();
+    tinygltf::Texture& texture = model.textures[texture_idx];
+    texture.source = image_idx;
+
+    texture_info.index = texture_idx;
+}
+
+// static
+template<typename T>
+void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, TextureInfo texture_info_id, bool force_write) const
+{
+    writeToTexture(model, texture_info, mTextureId[texture_info_id], mTextureTransform[texture_info_id], force_write);
+}
+
+// static
+template<typename T>
+void LLGLTFMaterial::writeToTexture(tinygltf::Model& model, T& texture_info, const LLUUID& texture_id, const TextureTransform& transform, bool force_write)
+{
+    LL_PROFILE_ZONE_SCOPED;
+    const bool is_blank_transform = transform == sDefault.mTextureTransform[0];
+    // Check if this material matches all the fallback values, and if so, then
+    // skip including it to reduce material size
+    if (!force_write && texture_id.isNull() && is_blank_transform)
+    {
+        return;
+    }
+
+    // tinygltf will discard this texture info if there is no valid texture,
+    // causing potential loss of information for overrides, so ensure one is
+    // defined. -Cosmic,2023-01-30
+    allocateTextureImage(model, texture_info, texture_id.asString());
+
+    if (!is_blank_transform)
+    {
+        tinygltf::Value::Object transform_map;
+        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_OFFSET] = tinygltf::Value(tinygltf::Value::Array({
+            tinygltf::Value(transform.mOffset.mV[VX]),
+            tinygltf::Value(transform.mOffset.mV[VY])
+        }));
+        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_SCALE] = tinygltf::Value(tinygltf::Value::Array({
+            tinygltf::Value(transform.mScale.mV[VX]),
+            tinygltf::Value(transform.mScale.mV[VY])
+        }));
+        transform_map[GLTF_FILE_EXTENSION_TRANSFORM_ROTATION] = tinygltf::Value(transform.mRotation);
+        texture_info.extensions[GLTF_FILE_EXTENSION_TRANSFORM] = tinygltf::Value(transform_map);
+    }
+}