summaryrefslogtreecommitdiff
path: root/indra/newview/gltf
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2024-07-27 15:17:57 +0800
committerErik Kundiman <erik@megapahit.org>2024-07-28 08:24:59 +0800
commit96a81b5ecbe3bffb582ded930752c0523df5e80a (patch)
treea87a0bd09fd980562e88150dbfea3819d28d9f12 /indra/newview/gltf
parent06e8f0c443c1ba7858d000c6d695b7e988e02053 (diff)
parented73208eb96b862b97fa285036edea1e792ca3c6 (diff)
Merge remote-tracking branch 'secondlife/release/2024.06-atlasaurus' into 2024.06-atlasaurus
Diffstat (limited to 'indra/newview/gltf')
-rw-r--r--indra/newview/gltf/README.md156
-rw-r--r--indra/newview/gltf/accessor.cpp274
-rw-r--r--indra/newview/gltf/accessor.h83
-rw-r--r--indra/newview/gltf/animation.cpp368
-rw-r--r--indra/newview/gltf/animation.h76
-rw-r--r--indra/newview/gltf/asset.cpp1311
-rw-r--r--indra/newview/gltf/asset.h329
-rw-r--r--indra/newview/gltf/buffer_util.h817
-rw-r--r--indra/newview/gltf/common.h97
-rw-r--r--indra/newview/gltf/primitive.cpp620
-rw-r--r--indra/newview/gltf/primitive.h65
11 files changed, 3461 insertions, 735 deletions
diff --git a/indra/newview/gltf/README.md b/indra/newview/gltf/README.md
new file mode 100644
index 0000000000..a2d43be1d6
--- /dev/null
+++ b/indra/newview/gltf/README.md
@@ -0,0 +1,156 @@
+# Linden Lab GLTF Implementation
+
+Currently in prototype stage. Much functionality is missing (blend shapes,
+multiple texture coordinates, etc).
+
+GLTF Specification can be found here: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html.
+If this implementation disagrees with the GLTF Specification, the specification is correct.
+
+Class structure and naming should match the GLTF Specification as closely as possible while
+conforming to the LL coding standards. All code in headers should be contained in the
+LL::GLTF namespace.
+
+The implementation serves both the client and the server.
+
+## Design Principles
+
+- The implementation MUST be capable of round-trip serialization with no data loss beyond F64 to F32 conversions.
+- The implementation MUST use the same indexing scheme as the GLTF specification. Do not store pointers where the
+- GLTF specification stores indices, store indices.
+- Limit dependencies on llcommon as much as possible. Prefer std::, boost::, and (soon) glm:: over LL facsimiles.
+- Usage of LLSD is forbidden in the LL::GLTF namespace.
+- Use "using namespace" liberally in .cpp files, but never in .h files.
+- "using Foo = Bar" is permissible in .h files within the LL::GLTF namespace.
+
+## Loading, Copying, and Serialization
+Each class should provide two functions (Primitive shown for example):
+
+```
+// Serialize to the provided json object.
+// "obj" should be "this" in json form on return
+// Do not serialize default values
+void serialize(boost::json::object& obj) const;
+
+// Initialize from a provided json value
+const Primitive& operator=(const Value& src);
+```
+
+"serialize" implementations should use "write":
+
+```
+void Primitive::serialize(boost::json::object& dst) const
+{
+ write(mMaterial, "material", dst, -1);
+ write(mMode, "mode", dst, TINYGLTF_MODE_TRIANGLES);
+ write(mIndices, "indices", dst, INVALID_INDEX);
+ write(mAttributes, "attributes", dst);
+}
+```
+
+And operator= implementations should use "copy":
+
+```
+const Primitive& Primitive::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "material", mMaterial);
+ copy(src, "mode", mMode);
+ copy(src, "indices", mIndices);
+ copy(src, "attributes", mAttributes);
+
+ mGLMode = gltf_mode_to_gl_mode(mMode);
+ }
+ return *this;
+}
+```
+
+Parameters to "write" and "copy" MUST be ordered "src" before "dst"
+so the code reads as "write src to dst" and "copy src to dst".
+
+When reading string constants from GLTF json (i.e. "OPAQUE", "TRIANGLES"), these
+strings should be converted to enums inside operator=. It is permissible to
+store the original strings during prototyping to aid in development, but eventually
+we'll purge these strings from the implementation. However, implementations MUST
+preserve any and all "name" members.
+
+"write" and "copy" implementations MUST be stored in buffer_util.h.
+As implementers encounter new data types, you'll see compiler errors
+pointing at templates in buffer_util.h. See vec3 as a known good
+example of how to add support for a new type (there are bad examples, so beware):
+
+```
+// vec3
+template<>
+inline bool copy(const Value& src, vec3& dst)
+{
+ if (src.is_array())
+ {
+ const boost::json::array& arr = src.as_array();
+ if (arr.size() == 3)
+ {
+ if (arr[0].is_double() &&
+ arr[1].is_double() &&
+ arr[2].is_double())
+ {
+ dst = vec3(arr[0].get_double(), arr[1].get_double(), arr[2].get_double());
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+template<>
+inline bool write(const vec3& src, Value& dst)
+{
+ dst = boost::json::array();
+ boost::json::array& arr = dst.as_array();
+ arr.resize(3);
+ arr[0] = src.x;
+ arr[1] = src.y;
+ arr[2] = src.z;
+ return true;
+}
+
+```
+
+"write" MUST return true if ANY data was written
+"copy" MUST return true if ANY data was copied
+
+Speed is important, but so is safety. In writers, try to avoid redundant copies
+(prefer resize over push_back, convert dst to an empty array and fill it, don't
+make an array on the stack and copy it into dst).
+
+boost::json WILL throw exceptions if you call as_foo() on a mismatched type but
+WILL NOT throw exceptions on get_foo with a mismatched type. ALWAYS check is_foo
+before calling as_foo or get_foo. DO NOT add exception handlers. If boost throws
+an exception in serialization, the fix is to add type checks. If we see a large
+number of crash reports from boost::json exceptions, each of those reports
+indicates a place where we're missing "is_foo" checks. They are gold. Do not
+bury them with an exception handler.
+
+DO NOT rely on existing type conversion tools in the LL codebase -- LL data models
+conflict with the GLTF specification so we MUST provide conversions independent of
+our existing implementations.
+
+### JSON Serialization ###
+
+
+
+NEVER include buffer_util.h from a header.
+
+Loading from and saving to disk (import/export) is currently done using tinygltf, but this is not a long term
+solution. Eventually the implementation should rely solely on boost::json for reading and writing .gltf
+files and should handle .bin files natively.
+
+When serializing Images and Buffers to the server, clients MUST store a single UUID "uri" field and nothing else.
+The server MUST reject any data that violates this requirement.
+
+Clients MUST remove any Images from Buffers prior to upload to the server.
+Servers MAY reject Assets that contain Buffers with unreferenced data.
+
+... to be continued.
+
+
+
diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp
index 55d36b7a32..2ef9237f2d 100644
--- a/indra/newview/gltf/accessor.cpp
+++ b/indra/newview/gltf/accessor.cpp
@@ -27,40 +27,272 @@
#include "../llviewerprecompiledheaders.h"
#include "asset.h"
+#include "buffer_util.h"
+#include "llfilesystem.h"
using namespace LL::GLTF;
+using namespace boost::json;
-const Buffer& Buffer::operator=(const tinygltf::Buffer& src)
+namespace LL
{
- mData = src.data;
- mName = src.name;
- mUri = src.uri;
+ namespace GLTF
+ {
+ Accessor::Type gltf_type_to_enum(const std::string& type)
+ {
+ if (type == "SCALAR")
+ {
+ return Accessor::Type::SCALAR;
+ }
+ else if (type == "VEC2")
+ {
+ return Accessor::Type::VEC2;
+ }
+ else if (type == "VEC3")
+ {
+ return Accessor::Type::VEC3;
+ }
+ else if (type == "VEC4")
+ {
+ return Accessor::Type::VEC4;
+ }
+ else if (type == "MAT2")
+ {
+ return Accessor::Type::MAT2;
+ }
+ else if (type == "MAT3")
+ {
+ return Accessor::Type::MAT3;
+ }
+ else if (type == "MAT4")
+ {
+ return Accessor::Type::MAT4;
+ }
+
+ LL_WARNS("GLTF") << "Unknown accessor type: " << type << LL_ENDL;
+ llassert(false);
+
+ return Accessor::Type::SCALAR;
+ }
+
+ std::string enum_to_gltf_type(Accessor::Type type)
+ {
+ switch (type)
+ {
+ case Accessor::Type::SCALAR:
+ return "SCALAR";
+ case Accessor::Type::VEC2:
+ return "VEC2";
+ case Accessor::Type::VEC3:
+ return "VEC3";
+ case Accessor::Type::VEC4:
+ return "VEC4";
+ case Accessor::Type::MAT2:
+ return "MAT2";
+ case Accessor::Type::MAT3:
+ return "MAT3";
+ case Accessor::Type::MAT4:
+ return "MAT4";
+ }
+
+ LL_WARNS("GLTF") << "Unknown accessor type: " << (S32)type << LL_ENDL;
+ llassert(false);
+
+ return "SCALAR";
+ }
+ }
+}
+
+void Buffer::erase(Asset& asset, S32 offset, S32 length)
+{
+ S32 idx = this - &asset.mBuffers[0];
+
+ mData.erase(mData.begin() + offset, mData.begin() + offset + length);
+
+ llassert(mData.size() <= size_t(INT_MAX));
+ mByteLength = S32(mData.size());
+
+ for (BufferView& view : asset.mBufferViews)
+ {
+ if (view.mBuffer == idx)
+ {
+ if (view.mByteOffset >= offset)
+ {
+ view.mByteOffset -= length;
+ }
+ }
+ }
+}
+
+bool Buffer::prep(Asset& asset)
+{
+ if (mByteLength == 0)
+ {
+ return false;
+ }
+
+ LLUUID id;
+ if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())
+ { // loaded from an asset, fetch the buffer data from the asset store
+ LLFileSystem file(id, LLAssetType::AT_GLTF_BIN, LLFileSystem::READ);
+
+ if (mByteLength > file.getSize())
+ {
+ LL_WARNS("GLTF") << "Unexpected glbin size: " << id << " is " << file.getSize() << " bytes, expected " << mByteLength << LL_ENDL;
+ return false;
+ }
+
+ mData.resize(mByteLength);
+ if (!file.read((U8*)mData.data(), mByteLength))
+ {
+ LL_WARNS("GLTF") << "Failed to load buffer data from asset: " << id << LL_ENDL;
+ return false;
+ }
+ }
+ else if (mUri.find("data:") == 0)
+ { // loaded from a data URI, load the texture from the data
+ LL_WARNS() << "Data URIs not yet supported" << LL_ENDL;
+ return false;
+ }
+ else if (!asset.mFilename.empty() &&
+ !mUri.empty()) // <-- uri could be empty if we're loading from .glb
+ {
+ std::string dir = gDirUtilp->getDirName(asset.mFilename);
+ std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri;
+
+ std::ifstream file(bin_file, std::ios::binary);
+ if (!file.is_open())
+ {
+ LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL;
+ return false;
+ }
+
+ file.seekg(0, std::ios::end);
+ if (mByteLength > file.tellg())
+ {
+ LL_WARNS("GLTF") << "Unexpected file size: " << bin_file << " is " << file.tellg() << " bytes, expected " << mByteLength << LL_ENDL;
+ return false;
+ }
+ file.seekg(0, std::ios::beg);
+
+ mData.resize(mByteLength);
+ file.read((char*)mData.data(), mData.size());
+ }
+
+ // POSTCONDITION: on success, mData.size == mByteLength
+ llassert(mData.size() == mByteLength);
+ return true;
+}
+
+bool Buffer::save(Asset& asset, const std::string& folder)
+{
+ if (mUri.substr(0, 5) == "data:")
+ {
+ LL_WARNS("GLTF") << "Data URIs not yet supported" << LL_ENDL;
+ return false;
+ }
+
+ std::string bin_file = folder + gDirUtilp->getDirDelimiter();
+
+ if (mUri.empty())
+ {
+ if (mName.empty())
+ {
+ S32 idx = this - &asset.mBuffers[0];
+ mUri = llformat("buffer_%d.bin", idx);
+ }
+ else
+ {
+ mUri = mName + ".bin";
+ }
+ }
+
+ bin_file += mUri;
+
+ std::ofstream file(bin_file, std::ios::binary);
+ if (!file.is_open())
+ {
+ LL_WARNS("GLTF") << "Failed to open file: " << bin_file << LL_ENDL;
+ return false;
+ }
+
+ file.write((char*)mData.data(), mData.size());
+
+ return true;
+}
+
+void Buffer::serialize(object& dst) const
+{
+ write(mName, "name", dst);
+ write(mUri, "uri", dst);
+ write_always(mByteLength, "byteLength", dst);
+};
+
+const Buffer& Buffer::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "name", mName);
+ copy(src, "uri", mUri);
+ copy(src, "byteLength", mByteLength);
+
+ // NOTE: DO NOT attempt to handle the uri here.
+ // The uri is a reference to a file that is not loaded until
+ // after the json document is parsed
+ }
return *this;
}
-const BufferView& BufferView::operator=(const tinygltf::BufferView& src)
+void BufferView::serialize(object& dst) const
{
- mBuffer = src.buffer;
- mByteLength = src.byteLength;
- mByteOffset = src.byteOffset;
- mByteStride = src.byteStride;
- mTarget = src.target;
- mName = src.name;
+ write_always(mBuffer, "buffer", dst);
+ write_always(mByteLength, "byteLength", dst);
+ write(mByteOffset, "byteOffset", dst, 0);
+ write(mByteStride, "byteStride", dst, 0);
+ write(mTarget, "target", dst, -1);
+ write(mName, "name", dst);
+}
+
+const BufferView& BufferView::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "buffer", mBuffer);
+ copy(src, "byteLength", mByteLength);
+ copy(src, "byteOffset", mByteOffset);
+ copy(src, "byteStride", mByteStride);
+ copy(src, "target", mTarget);
+ copy(src, "name", mName);
+ }
return *this;
}
-const Accessor& Accessor::operator=(const tinygltf::Accessor& src)
+void Accessor::serialize(object& dst) const
{
- mBufferView = src.bufferView;
- mByteOffset = src.byteOffset;
- mComponentType = src.componentType;
- mCount = src.count;
- mType = src.type;
- mNormalized = src.normalized;
- mName = src.name;
- mMax = src.maxValues;
- mMin = src.minValues;
+ write(mName, "name", dst);
+ write(mBufferView, "bufferView", dst, INVALID_INDEX);
+ write(mByteOffset, "byteOffset", dst, 0);
+ write_always(mComponentType, "componentType", dst);
+ write_always(mCount, "count", dst);
+ write_always(enum_to_gltf_type(mType), "type", dst);
+ write(mNormalized, "normalized", dst, false);
+ write(mMax, "max", dst);
+ write(mMin, "min", dst);
+}
+const Accessor& Accessor::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "name", mName);
+ copy(src, "bufferView", mBufferView);
+ copy(src, "byteOffset", mByteOffset);
+ copy(src, "componentType", mComponentType);
+ copy(src, "count", mCount);
+ copy(src, "type", mType);
+ copy(src, "normalized", mNormalized);
+ copy(src, "max", mMax);
+ copy(src, "min", mMin);
+ }
return *this;
}
diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h
index 9b8265d8da..ec68c5f624 100644
--- a/indra/newview/gltf/accessor.h
+++ b/indra/newview/gltf/accessor.h
@@ -26,16 +26,16 @@
* $/LicenseInfo$
*/
-#include "../lltinygltfhelper.h"
#include "llstrider.h"
+#include "boost/json.hpp"
+
+#include "common.h"
// LL GLTF Implementation
namespace LL
{
namespace GLTF
{
- class Asset;
-
constexpr S32 INVALID_INDEX = -1;
class Buffer
@@ -44,52 +44,77 @@ namespace LL
std::vector<U8> mData;
std::string mName;
std::string mUri;
+ S32 mByteLength = 0;
+
+ // erase the given range from this buffer.
+ // also updates all buffer views in given asset that reference this buffer
+ void erase(Asset& asset, S32 offset, S32 length);
+
+ bool prep(Asset& asset);
- const Buffer& operator=(const tinygltf::Buffer& src);
+ void serialize(boost::json::object& obj) const;
+ const Buffer& operator=(const Value& value);
+
+ bool save(Asset& asset, const std::string& folder);
};
class BufferView
{
public:
S32 mBuffer = INVALID_INDEX;
- S32 mByteLength;
- S32 mByteOffset;
- S32 mByteStride;
- S32 mTarget;
- S32 mComponentType;
+ S32 mByteLength = 0;
+ S32 mByteOffset = 0;
+ S32 mByteStride = 0;
+ S32 mTarget = -1;
std::string mName;
- const BufferView& operator=(const tinygltf::BufferView& src);
-
+ void serialize(boost::json::object& obj) const;
+ const BufferView& operator=(const Value& value);
};
-
+
class Accessor
{
public:
- S32 mBufferView = INVALID_INDEX;
- S32 mByteOffset;
- S32 mComponentType;
- S32 mCount;
- std::vector<double> mMax;
- std::vector<double> mMin;
+ enum class Type : U8
+ {
+ SCALAR,
+ VEC2,
+ VEC3,
+ VEC4,
+ MAT2,
+ MAT3,
+ MAT4
+ };
- enum class Type : S32
+ enum class ComponentType : U32
{
- SCALAR = TINYGLTF_TYPE_SCALAR,
- VEC2 = TINYGLTF_TYPE_VEC2,
- VEC3 = TINYGLTF_TYPE_VEC3,
- VEC4 = TINYGLTF_TYPE_VEC4,
- MAT2 = TINYGLTF_TYPE_MAT2,
- MAT3 = TINYGLTF_TYPE_MAT3,
- MAT4 = TINYGLTF_TYPE_MAT4
+ BYTE = 5120,
+ UNSIGNED_BYTE = 5121,
+ SHORT = 5122,
+ UNSIGNED_SHORT = 5123,
+ UNSIGNED_INT = 5125,
+ FLOAT = 5126
};
- S32 mType;
- bool mNormalized;
+ std::vector<double> mMax;
+ std::vector<double> mMin;
std::string mName;
+ S32 mBufferView = INVALID_INDEX;
+ S32 mByteOffset = 0;
+ ComponentType mComponentType = ComponentType::BYTE;
+ S32 mCount = 0;
+ Type mType = Type::SCALAR;
+ bool mNormalized = false;
- const Accessor& operator=(const tinygltf::Accessor& src);
+ void serialize(boost::json::object& obj) const;
+ const Accessor& operator=(const Value& value);
};
+
+ // convert from "SCALAR", "VEC2", etc to Accessor::Type
+ Accessor::Type gltf_type_to_enum(const std::string& type);
+
+ // convert from Accessor::Type to "SCALAR", "VEC2", etc
+ std::string enum_to_gltf_type(Accessor::Type type);
}
}
diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp
index da6d02b356..3dff67d746 100644
--- a/indra/newview/gltf/animation.cpp
+++ b/indra/newview/gltf/animation.cpp
@@ -28,10 +28,12 @@
#include "asset.h"
#include "buffer_util.h"
+#include "../llskinningutil.h"
using namespace LL::GLTF;
+using namespace boost::json;
-void Animation::allocateGLResources(Asset& asset)
+bool Animation::prep(Asset& asset)
{
if (!mSamplers.empty())
{
@@ -39,7 +41,10 @@ void Animation::allocateGLResources(Asset& asset)
mMaxTime = -FLT_MAX;
for (auto& sampler : mSamplers)
{
- sampler.allocateGLResources(asset);
+ if (!sampler.prep(asset))
+ {
+ return false;
+ }
mMinTime = llmin(sampler.mMinTime, mMinTime);
mMaxTime = llmax(sampler.mMaxTime, mMaxTime);
}
@@ -51,13 +56,29 @@ void Animation::allocateGLResources(Asset& asset)
for (auto& channel : mRotationChannels)
{
- channel.allocateGLResources(asset, mSamplers[channel.mSampler]);
+ if (!channel.prep(asset, mSamplers[channel.mSampler]))
+ {
+ return false;
+ }
}
for (auto& channel : mTranslationChannels)
{
- channel.allocateGLResources(asset, mSamplers[channel.mSampler]);
+ if (!channel.prep(asset, mSamplers[channel.mSampler]))
+ {
+ return false;
+ }
+ }
+
+ for (auto& channel : mScaleChannels)
+ {
+ if (!channel.prep(asset, mSamplers[channel.mSampler]))
+ {
+ return false;
+ }
}
+
+ return true;
}
void Animation::update(Asset& asset, F32 dt)
@@ -69,23 +90,41 @@ void Animation::update(Asset& asset, F32 dt)
void Animation::apply(Asset& asset, float time)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+
// convert time to animation loop time
time = fmod(time, mMaxTime - mMinTime) + mMinTime;
// apply each channel
- for (auto& channel : mRotationChannels)
{
- channel.apply(asset, mSamplers[channel.mSampler], time);
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - rotation");
+
+ for (auto& channel : mRotationChannels)
+ {
+ channel.apply(asset, mSamplers[channel.mSampler], time);
+ }
}
- for (auto& channel : mTranslationChannels)
{
- channel.apply(asset, mSamplers[channel.mSampler], time);
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - translation");
+
+ for (auto& channel : mTranslationChannels)
+ {
+ channel.apply(asset, mSamplers[channel.mSampler], time);
+ }
}
-};
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - scale");
+
+ for (auto& channel : mScaleChannels)
+ {
+ channel.apply(asset, mSamplers[channel.mSampler], time);
+ }
+ }
+};
-void Animation::Sampler::allocateGLResources(Asset& asset)
+bool Animation::Sampler::prep(Asset& asset)
{
Accessor& accessor = asset.mAccessors[mInput];
mMinTime = accessor.mMin[0];
@@ -95,10 +134,80 @@ void Animation::Sampler::allocateGLResources(Asset& asset)
LLStrider<F32> frame_times = mFrameTimes.data();
copy(asset, accessor, frame_times);
+
+ return true;
+}
+
+
+void Animation::Sampler::serialize(object& obj) const
+{
+ write(mInput, "input", obj, INVALID_INDEX);
+ write(mOutput, "output", obj, INVALID_INDEX);
+ write(mInterpolation, "interpolation", obj, std::string("LINEAR"));
+ write(mMinTime, "min_time", obj);
+ write(mMaxTime, "max_time", obj);
+}
+
+const Animation::Sampler& Animation::Sampler::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "input", mInput);
+ copy(src, "output", mOutput);
+ copy(src, "interpolation", mInterpolation);
+ copy(src, "min_time", mMinTime);
+ copy(src, "max_time", mMaxTime);
+ }
+ return *this;
+}
+
+bool Animation::Channel::Target::operator==(const Channel::Target& rhs) const
+{
+ return mNode == rhs.mNode && mPath == rhs.mPath;
+}
+
+bool Animation::Channel::Target::operator!=(const Channel::Target& rhs) const
+{
+ return !(*this == rhs);
+}
+
+void Animation::Channel::Target::serialize(object& obj) const
+{
+ write(mNode, "node", obj, INVALID_INDEX);
+ write(mPath, "path", obj);
+}
+
+const Animation::Channel::Target& Animation::Channel::Target::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "node", mNode);
+ copy(src, "path", mPath);
+ }
+ return *this;
+}
+
+void Animation::Channel::serialize(object& obj) const
+{
+ write(mSampler, "sampler", obj, INVALID_INDEX);
+ write(mTarget, "target", obj);
+}
+
+const Animation::Channel& Animation::Channel::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "sampler", mSampler);
+ copy(src, "target", mTarget);
+ }
+ return *this;
}
void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F32& t)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ llassert(mFrameTimes.size() > 1); // if there is only one frame, there is no need to interpolate
+
if (time < mMinTime)
{
frameIndex = 0;
@@ -106,40 +215,42 @@ void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F
return;
}
- if (mFrameTimes.size() > 1)
+ frameIndex = U32(mFrameTimes.size()) - 2;
+ t = 1.f;
+
+ if (time > mMaxTime)
{
- if (time > mMaxTime)
- {
- frameIndex = mFrameTimes.size() - 2;
- t = 1.0f;
- return;
- }
+ return;
+ }
+
+ if (time < mLastFrameTime)
+ {
+ mLastFrameIndex = 0;
+ }
+
+ mLastFrameTime = time;
- frameIndex = mFrameTimes.size() - 2;
- t = 1.f;
+ U32 idx = mLastFrameIndex;
- for (U32 i = 0; i < mFrameTimes.size() - 1; i++)
+ for (U32 i = idx; i < (U32)mFrameTimes.size() - 1; i++)
+ {
+ if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1])
{
- if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1])
- {
- frameIndex = i;
- t = (time - mFrameTimes[i]) / (mFrameTimes[i + 1] - mFrameTimes[i]);
- return;
- }
+ frameIndex = i;
+ t = (time - mFrameTimes[i]) / (mFrameTimes[i + 1] - mFrameTimes[i]);
+ mLastFrameIndex = frameIndex;
+ return;
}
}
- else
- {
- frameIndex = 0;
- t = 0.0f;
- }
}
-void Animation::RotationChannel::allocateGLResources(Asset& asset, Animation::Sampler& sampler)
+bool Animation::RotationChannel::prep(Asset& asset, Animation::Sampler& sampler)
{
Accessor& accessor = asset.mAccessors[sampler.mOutput];
copy(asset, accessor, mRotations);
+
+ return true;
}
void Animation::RotationChannel::apply(Asset& asset, Sampler& sampler, F32 time)
@@ -149,30 +260,30 @@ void Animation::RotationChannel::apply(Asset& asset, Sampler& sampler, F32 time)
Node& node = asset.mNodes[mTarget.mNode];
- sampler.getFrameInfo(asset, time, frameIndex, t);
-
- if (sampler.mFrameTimes.size() == 1)
+ if (sampler.mFrameTimes.size() < 2)
{
node.setRotation(mRotations[0]);
}
else
{
+ sampler.getFrameInfo(asset, time, frameIndex, t);
+
// interpolate
- LLQuaternion q0(mRotations[frameIndex].get_value());
- LLQuaternion q1(mRotations[frameIndex + 1].get_value());
+ quat qf = glm::slerp(mRotations[frameIndex], mRotations[frameIndex + 1], t);
- LLQuaternion qf = slerp(t, q0, q1);
+ qf = glm::normalize(qf);
- qf.normalize();
- node.setRotation(glh::quaternionf(qf.mQ));
+ node.setRotation(qf);
}
}
-void Animation::TranslationChannel::allocateGLResources(Asset& asset, Animation::Sampler& sampler)
+bool Animation::TranslationChannel::prep(Asset& asset, Animation::Sampler& sampler)
{
Accessor& accessor = asset.mAccessors[sampler.mOutput];
copy(asset, accessor, mTranslations);
+
+ return true;
}
void Animation::TranslationChannel::apply(Asset& asset, Sampler& sampler, F32 time)
@@ -182,29 +293,31 @@ void Animation::TranslationChannel::apply(Asset& asset, Sampler& sampler, F32 ti
Node& node = asset.mNodes[mTarget.mNode];
- sampler.getFrameInfo(asset, time, frameIndex, t);
-
- if (sampler.mFrameTimes.size() == 1)
+ if (sampler.mFrameTimes.size() < 2)
{
node.setTranslation(mTranslations[0]);
}
else
{
+ sampler.getFrameInfo(asset, time, frameIndex, t);
+
// interpolate
- const glh::vec3f& v0 = mTranslations[frameIndex];
- const glh::vec3f& v1 = mTranslations[frameIndex + 1];
+ const vec3& v0 = mTranslations[frameIndex];
+ const vec3& v1 = mTranslations[frameIndex + 1];
- glh::vec3f vf = v0 + t * (v1 - v0);
+ vec3 vf = v0 + t * (v1 - v0);
node.setTranslation(vf);
}
}
-void Animation::ScaleChannel::allocateGLResources(Asset& asset, Animation::Sampler& sampler)
+bool Animation::ScaleChannel::prep(Asset& asset, Animation::Sampler& sampler)
{
Accessor& accessor = asset.mAccessors[sampler.mOutput];
copy(asset, accessor, mScales);
+
+ return true;
}
void Animation::ScaleChannel::apply(Asset& asset, Sampler& sampler, F32 time)
@@ -214,74 +327,163 @@ void Animation::ScaleChannel::apply(Asset& asset, Sampler& sampler, F32 time)
Node& node = asset.mNodes[mTarget.mNode];
- sampler.getFrameInfo(asset, time, frameIndex, t);
-
- if (sampler.mFrameTimes.size() == 1)
+ if (sampler.mFrameTimes.size() < 2)
{
node.setScale(mScales[0]);
}
else
{
+ sampler.getFrameInfo(asset, time, frameIndex, t);
+
// interpolate
- const glh::vec3f& v0 = mScales[frameIndex];
- const glh::vec3f& v1 = mScales[frameIndex + 1];
+ const vec3& v0 = mScales[frameIndex];
+ const vec3& v1 = mScales[frameIndex + 1];
- glh::vec3f vf = v0 + t * (v1 - v0);
+ vec3 vf = v0 + t * (v1 - v0);
node.setScale(vf);
}
}
-const Animation& Animation::operator=(const tinygltf::Animation& src)
+void Animation::serialize(object& obj) const
{
- mName = src.name;
+ write(mName, "name", obj);
+ write(mSamplers, "samplers", obj);
- mSamplers.resize(src.samplers.size());
- for (U32 i = 0; i < src.samplers.size(); ++i)
- {
- mSamplers[i] = src.samplers[i];
- }
+ std::vector<Channel> channels;
+ channels.insert(channels.end(), mRotationChannels.begin(), mRotationChannels.end());
+ channels.insert(channels.end(), mTranslationChannels.begin(), mTranslationChannels.end());
+ channels.insert(channels.end(), mScaleChannels.begin(), mScaleChannels.end());
- for (U32 i = 0; i < src.channels.size(); ++i)
+ write(channels, "channels", obj);
+}
+
+const Animation& Animation::operator=(const Value& src)
+{
+ if (src.is_object())
{
- if (src.channels[i].target_path == "rotation")
- {
- mRotationChannels.push_back(RotationChannel());
- mRotationChannels.back() = src.channels[i];
- }
+ const object& obj = src.as_object();
- if (src.channels[i].target_path == "translation")
- {
- mTranslationChannels.push_back(TranslationChannel());
- mTranslationChannels.back() = src.channels[i];
- }
+ copy(obj, "name", mName);
+ copy(obj, "samplers", mSamplers);
+
+ // make a temporory copy of generic channels
+ std::vector<Channel> channels;
+ copy(obj, "channels", channels);
- if (src.channels[i].target_path == "scale")
+ // break up into channel specific implementations
+ for (auto& channel: channels)
{
- mScaleChannels.push_back(ScaleChannel());
- mScaleChannels.back() = src.channels[i];
+ if (channel.mTarget.mPath == "rotation")
+ {
+ mRotationChannels.push_back(channel);
+ }
+ else if (channel.mTarget.mPath == "translation")
+ {
+ mTranslationChannels.push_back(channel);
+ }
+ else if (channel.mTarget.mPath == "scale")
+ {
+ mScaleChannels.push_back(channel);
+ }
}
}
-
return *this;
}
-void Skin::allocateGLResources(Asset& asset)
+Skin::~Skin()
+{
+ if (mUBO)
+ {
+ glDeleteBuffers(1, &mUBO);
+ }
+}
+
+void Skin::uploadMatrixPalette(Asset& asset)
+{
+ // prepare matrix palette
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+
+ U32 max_joints = LLSkinningUtil::getMaxGLTFJointCount();
+
+ if (mUBO == 0)
+ {
+ glGenBuffers(1, &mUBO);
+ }
+
+ size_t joint_count = llmin<size_t>(max_joints, mJoints.size());
+
+ std::vector<mat4> t_mp;
+
+ t_mp.resize(joint_count);
+
+ for (U32 i = 0; i < joint_count; ++i)
+ {
+ Node& joint = asset.mNodes[mJoints[i]];
+ // build matrix palette in asset space
+ t_mp[i] = joint.mAssetMatrix * mInverseBindMatricesData[i];
+ }
+
+ std::vector<F32> glmp;
+
+ glmp.resize(joint_count * 12);
+
+ F32* mp = glmp.data();
+
+ for (U32 i = 0; i < joint_count; ++i)
+ {
+ F32* m = glm::value_ptr(t_mp[i]);
+
+ U32 idx = i * 12;
+
+ mp[idx + 0] = m[0];
+ mp[idx + 1] = m[1];
+ mp[idx + 2] = m[2];
+ mp[idx + 3] = m[12];
+
+ mp[idx + 4] = m[4];
+ mp[idx + 5] = m[5];
+ mp[idx + 6] = m[6];
+ mp[idx + 7] = m[13];
+
+ mp[idx + 8] = m[8];
+ mp[idx + 9] = m[9];
+ mp[idx + 10] = m[10];
+ mp[idx + 11] = m[14];
+ }
+
+ glBindBuffer(GL_UNIFORM_BUFFER, mUBO);
+ glBufferData(GL_UNIFORM_BUFFER, glmp.size() * sizeof(F32), glmp.data(), GL_STREAM_DRAW);
+ glBindBuffer(GL_UNIFORM_BUFFER, 0);
+}
+
+bool Skin::prep(Asset& asset)
{
if (mInverseBindMatrices != INVALID_INDEX)
{
Accessor& accessor = asset.mAccessors[mInverseBindMatrices];
copy(asset, accessor, mInverseBindMatricesData);
}
+
+ return true;
}
-const Skin& Skin::operator=(const tinygltf::Skin& src)
+const Skin& Skin::operator=(const Value& src)
{
- mName = src.name;
- mSkeleton = src.skeleton;
- mInverseBindMatrices = src.inverseBindMatrices;
- mJoints = src.joints;
-
+ if (src.is_object())
+ {
+ copy(src, "name", mName);
+ copy(src, "skeleton", mSkeleton);
+ copy(src, "inverseBindMatrices", mInverseBindMatrices);
+ copy(src, "joints", mJoints);
+ }
return *this;
}
+void Skin::serialize(object& obj) const
+{
+ write(mInverseBindMatrices, "inverseBindMatrices", obj, INVALID_INDEX);
+ write(mJoints, "joints", obj);
+ write(mName, "name", obj);
+ write(mSkeleton, "skeleton", obj, INVALID_INDEX);
+}
diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h
index 869eae963a..ab8839470a 100644
--- a/indra/newview/gltf/animation.h
+++ b/indra/newview/gltf/animation.h
@@ -27,7 +27,6 @@
*/
#include "accessor.h"
-
// LL GLTF Implementation
namespace LL
{
@@ -50,16 +49,13 @@ namespace LL
S32 mOutput = INVALID_INDEX;
std::string mInterpolation;
- void allocateGLResources(Asset& asset);
+ F32 mLastFrameTime = 0.f;
+ U32 mLastFrameIndex = 0;
- const Sampler& operator=(const tinygltf::AnimationSampler& src)
- {
- mInput = src.input;
- mOutput = src.output;
- mInterpolation = src.interpolation;
+ bool prep(Asset& asset);
- return *this;
- }
+ void serialize(boost::json::object& dst) const;
+ const Sampler& operator=(const Value& value);
// get the frame index and time for the specified time
// asset -- the asset to reference for Accessors
@@ -77,40 +73,33 @@ namespace LL
public:
S32 mNode = INVALID_INDEX;
std::string mPath;
+
+ bool operator==(const Target& other) const;
+ bool operator!=(const Target& other) const;
+
+ void serialize(boost::json::object& dst) const;
+ const Target& operator=(const Value& value);
};
S32 mSampler = INVALID_INDEX;
Target mTarget;
- std::string mTargetPath;
- std::string mName;
-
- const Channel& operator=(const tinygltf::AnimationChannel& src)
- {
- mSampler = src.sampler;
-
- mTarget.mNode = src.target_node;
- mTarget.mPath = src.target_path;
-
- return *this;
- }
+ void serialize(boost::json::object& dst) const;
+ const Channel& operator=(const Value& value);
};
class RotationChannel : public Channel
{
public:
- std::vector<glh::quaternionf> mRotations;
+ RotationChannel() = default;
+ RotationChannel(const Channel& channel) : Channel(channel) {}
- const RotationChannel& operator=(const tinygltf::AnimationChannel& src)
- {
- Channel::operator=(src);
- return *this;
- }
+ std::vector<quat> mRotations;
// prepare data needed for rendering
// asset -- asset to reference for Accessors
// sampler -- Sampler associated with this channel
- void allocateGLResources(Asset& asset, Sampler& sampler);
+ bool prep(Asset& asset, Sampler& sampler);
void apply(Asset& asset, Sampler& sampler, F32 time);
};
@@ -118,18 +107,15 @@ namespace LL
class TranslationChannel : public Channel
{
public:
- std::vector<glh::vec3f> mTranslations;
+ TranslationChannel() = default;
+ TranslationChannel(const Channel& channel) : Channel(channel) {}
- const TranslationChannel& operator=(const tinygltf::AnimationChannel& src)
- {
- Channel::operator=(src);
- return *this;
- }
+ std::vector<vec3> mTranslations;
// prepare data needed for rendering
// asset -- asset to reference for Accessors
// sampler -- Sampler associated with this channel
- void allocateGLResources(Asset& asset, Sampler& sampler);
+ bool prep(Asset& asset, Sampler& sampler);
void apply(Asset& asset, Sampler& sampler, F32 time);
};
@@ -137,18 +123,15 @@ namespace LL
class ScaleChannel : public Channel
{
public:
- std::vector<glh::vec3f> mScales;
+ ScaleChannel() = default;
+ ScaleChannel(const Channel& channel) : Channel(channel) {}
- const ScaleChannel& operator=(const tinygltf::AnimationChannel& src)
- {
- Channel::operator=(src);
- return *this;
- }
+ std::vector<vec3> mScales;
// prepare data needed for rendering
// asset -- asset to reference for Accessors
// sampler -- Sampler associated with this channel
- void allocateGLResources(Asset& asset, Sampler& sampler);
+ bool prep(Asset& asset, Sampler& sampler);
void apply(Asset& asset, Sampler& sampler, F32 time);
};
@@ -159,7 +142,7 @@ namespace LL
// min/max time values for all samplers combined
F32 mMinTime = 0.f;
F32 mMaxTime = 0.f;
-
+
// current time of the animation
F32 mTime = 0.f;
@@ -167,9 +150,10 @@ namespace LL
std::vector<TranslationChannel> mTranslationChannels;
std::vector<ScaleChannel> mScaleChannels;
- const Animation& operator=(const tinygltf::Animation& src);
-
- void allocateGLResources(Asset& asset);
+ void serialize(boost::json::object& dst) const;
+ const Animation& operator=(const Value& value);
+
+ bool prep(Asset& asset);
void update(Asset& asset, float dt);
diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp
index 313e82bf01..a454e68a92 100644
--- a/indra/newview/gltf/asset.cpp
+++ b/indra/newview/gltf/asset.cpp
@@ -30,48 +30,81 @@
#include "llvolumeoctree.h"
#include "../llviewershadermgr.h"
#include "../llviewercontrol.h"
+#include "../llviewertexturelist.h"
+#include "../pipeline.h"
+#include "buffer_util.h"
+#include <boost/url.hpp>
+#include "llimagejpeg.h"
+#include "../llskinningutil.h"
using namespace LL::GLTF;
+using namespace boost::json;
-void Scene::updateTransforms(Asset& asset)
+
+namespace LL
{
- LLMatrix4a identity;
- identity.setIdentity();
- for (auto& nodeIndex : mNodes)
+ namespace GLTF
{
- Node& node = asset.mNodes[nodeIndex];
- node.updateTransforms(asset, identity);
+ static std::unordered_set<std::string> ExtensionsSupported = {
+ "KHR_materials_unlit",
+ "KHR_texture_transform"
+ };
+
+ Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode)
+ {
+ if (alpha_mode == "OPAQUE")
+ {
+ return Material::AlphaMode::OPAQUE;
+ }
+ else if (alpha_mode == "MASK")
+ {
+ return Material::AlphaMode::MASK;
+ }
+ else if (alpha_mode == "BLEND")
+ {
+ return Material::AlphaMode::BLEND;
+ }
+ else
+ {
+ return Material::AlphaMode::OPAQUE;
+ }
+ }
+
+ std::string enum_to_gltf_alpha_mode(Material::AlphaMode alpha_mode)
+ {
+ switch (alpha_mode)
+ {
+ case Material::AlphaMode::OPAQUE:
+ return "OPAQUE";
+ case Material::AlphaMode::MASK:
+ return "MASK";
+ case Material::AlphaMode::BLEND:
+ return "BLEND";
+ default:
+ return "OPAQUE";
+ }
+ }
}
}
-void Scene::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview)
+void Scene::updateTransforms(Asset& asset)
{
+ mat4 identity = glm::identity<mat4>();
+
for (auto& nodeIndex : mNodes)
{
Node& node = asset.mNodes[nodeIndex];
- node.updateRenderTransforms(asset, modelview);
+ node.updateTransforms(asset, identity);
}
}
-void Node::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview)
+void Node::updateTransforms(Asset& asset, const mat4& parentMatrix)
{
- matMul(mMatrix, modelview, mRenderMatrix);
-
- for (auto& childIndex : mChildren)
- {
- Node& child = asset.mNodes[childIndex];
- child.updateRenderTransforms(asset, mRenderMatrix);
- }
-}
+ makeMatrixValid();
+ mAssetMatrix = parentMatrix * mMatrix;
-LLMatrix4a inverse(const LLMatrix4a& mat);
+ mAssetMatrixInv = glm::inverse(mAssetMatrix);
-void Node::updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix)
-{
- makeMatrixValid();
- matMul(mMatrix, parentMatrix, mAssetMatrix);
- mAssetMatrixInv = inverse(mAssetMatrix);
-
S32 my_index = this - &asset.mNodes[0];
for (auto& childIndex : mChildren)
@@ -84,32 +117,119 @@ void Node::updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix)
void Asset::updateTransforms()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
for (auto& scene : mScenes)
{
scene.updateTransforms(*this);
}
+
+ uploadTransforms();
}
-void Asset::updateRenderTransforms(const LLMatrix4a& modelview)
+void Asset::uploadTransforms()
{
-#if 0
- // traverse hierarchy and update render transforms from scratch
- for (auto& scene : mScenes)
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ // prepare matrix palette
+ U32 max_nodes = LLSkinningUtil::getMaxGLTFJointCount();
+
+ size_t node_count = llmin<size_t>(max_nodes, mNodes.size());
+
+ std::vector<mat4> t_mp;
+
+ t_mp.resize(node_count);
+
+ for (U32 i = 0; i < node_count; ++i)
{
- scene.updateRenderTransforms(*this, modelview);
+ Node& node = mNodes[i];
+ // build matrix palette in asset space
+ t_mp[i] = node.mAssetMatrix;
}
-#else
- // use mAssetMatrix to update render transforms from node list
- for (auto& node : mNodes)
+
+ std::vector<F32> glmp;
+
+ glmp.resize(node_count * 12);
+
+ F32* mp = glmp.data();
+
+ for (U32 i = 0; i < node_count; ++i)
{
- //if (node.mMesh != INVALID_INDEX)
- {
- matMul(node.mAssetMatrix, modelview, node.mRenderMatrix);
- }
+ F32* m = glm::value_ptr(t_mp[i]);
+
+ U32 idx = i * 12;
+
+ mp[idx + 0] = m[0];
+ mp[idx + 1] = m[1];
+ mp[idx + 2] = m[2];
+ mp[idx + 3] = m[12];
+
+ mp[idx + 4] = m[4];
+ mp[idx + 5] = m[5];
+ mp[idx + 6] = m[6];
+ mp[idx + 7] = m[13];
+
+ mp[idx + 8] = m[8];
+ mp[idx + 9] = m[9];
+ mp[idx + 10] = m[10];
+ mp[idx + 11] = m[14];
}
-#endif
+ if (mNodesUBO == 0)
+ {
+ glGenBuffers(1, &mNodesUBO);
+ }
+ glBindBuffer(GL_UNIFORM_BUFFER, mNodesUBO);
+ glBufferData(GL_UNIFORM_BUFFER, glmp.size() * sizeof(F32), glmp.data(), GL_STREAM_DRAW);
+ glBindBuffer(GL_UNIFORM_BUFFER, 0);
+}
+
+void Asset::uploadMaterials()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ // see pbrmetallicroughnessV.glsl for the layout of the material UBO
+ std::vector<vec4> md;
+
+ U32 material_size = sizeof(vec4) * 12;
+ U32 max_materials = gGLManager.mMaxUniformBlockSize / material_size;
+
+ U32 mat_count = (U32)mMaterials.size();
+ mat_count = llmin(mat_count, max_materials);
+
+ md.resize(mat_count * 12);
+
+ for (U32 i = 0; i < mat_count*12; i += 12)
+ {
+ Material& material = mMaterials[i/12];
+
+ // add texture transforms and UV indices
+ material.mPbrMetallicRoughness.mBaseColorTexture.mTextureTransform.getPacked(&md[i+0]);
+ md[i + 1].g = (F32)material.mPbrMetallicRoughness.mBaseColorTexture.getTexCoord();
+ material.mNormalTexture.mTextureTransform.getPacked(&md[i + 2]);
+ md[i + 3].g = (F32)material.mNormalTexture.getTexCoord();
+ material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTextureTransform.getPacked(&md[i+4]);
+ md[i + 5].g = (F32)material.mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord();
+ material.mEmissiveTexture.mTextureTransform.getPacked(&md[i + 6]);
+ md[i + 7].g = (F32)material.mEmissiveTexture.getTexCoord();
+ material.mOcclusionTexture.mTextureTransform.getPacked(&md[i + 8]);
+ md[i + 9].g = (F32)material.mOcclusionTexture.getTexCoord();
+
+ // add material properties
+ F32 min_alpha = material.mAlphaMode == Material::AlphaMode::MASK ? material.mAlphaCutoff : -1.0f;
+ md[i + 10] = vec4(material.mEmissiveFactor, 1.f);
+ md[i + 11] = vec4(0.f,
+ material.mPbrMetallicRoughness.mRoughnessFactor,
+ material.mPbrMetallicRoughness.mMetallicFactor,
+ min_alpha);
+ }
+
+ if (mMaterialsUBO == 0)
+ {
+ glGenBuffers(1, &mMaterialsUBO);
+ }
+
+ glBindBuffer(GL_UNIFORM_BUFFER, mMaterialsUBO);
+ glBufferData(GL_UNIFORM_BUFFER, md.size() * sizeof(vec4), md.data(), GL_STREAM_DRAW);
+ glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
@@ -133,12 +253,13 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
{
if (node.mMesh != INVALID_INDEX)
{
-
bool newHit = false;
+ LLMatrix4a ami;
+ ami.loadu(glm::value_ptr(node.mAssetMatrixInv));
// transform start and end to this node's local space
- node.mAssetMatrixInv.affineTransform(start, local_start);
- node.mAssetMatrixInv.affineTransform(asset_end, local_end);
+ ami.affineTransform(start, local_start);
+ ami.affineTransform(asset_end, local_end);
Mesh& mesh = mMeshes[node.mMesh];
for (auto& primitive : mesh.mPrimitives)
@@ -161,8 +282,10 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
if (newHit)
{
+ LLMatrix4a am;
+ am.loadu(glm::value_ptr(node.mAssetMatrix));
// shorten line segment on hit
- node.mAssetMatrix.affineTransform(p, asset_end);
+ am.affineTransform(p, asset_end);
// transform results back to asset space
if (intersection)
@@ -172,12 +295,10 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
if (normal || tangent)
{
- LLMatrix4 normalMatrix(node.mAssetMatrixInv.getF32ptr());
-
- normalMatrix.transpose();
+ mat4 normalMatrix = glm::transpose(node.mAssetMatrixInv);
LLMatrix4a norm_mat;
- norm_mat.loadu((F32*)normalMatrix.mMatrix);
+ norm_mat.loadu(glm::value_ptr(normalMatrix));
if (normal)
{
@@ -219,446 +340,1066 @@ void Node::makeMatrixValid()
{
if (!mMatrixValid && mTRSValid)
{
- glh::matrix4f rot;
- mRotation.get_value(rot);
-
- glh::matrix4f trans;
- trans.set_translate(mTranslation);
-
- glh::matrix4f sc;
- sc.set_scale(mScale);
-
- glh::matrix4f t;
- //t = sc * rot * trans;
- //t = trans * rot * sc; // best so far, still wrong on negative scale
- //t = sc * trans * rot;
- t = trans * sc * rot;
-
- mMatrix.loadu(t.m);
+ mMatrix = glm::recompose(mScale, mRotation, mTranslation, vec3(0,0,0), vec4(0,0,0,1));
mMatrixValid = true;
}
+
+ llassert(mMatrixValid);
}
void Node::makeTRSValid()
{
if (!mTRSValid && mMatrixValid)
{
- glh::matrix4f t(mMatrix.getF32ptr());
-
- glh::vec4f p = t.get_column(3);
- mTranslation.set_value(p.v[0], p.v[1], p.v[2]);
+ vec3 skew;
+ vec4 perspective;
+ glm::decompose(mMatrix, mScale, mRotation, mTranslation, skew, perspective);
- mScale.set_value(t.get_column(0).length(), t.get_column(1).length(), t.get_column(2).length());
- mRotation.set_value(t);
mTRSValid = true;
}
+
+ llassert(mTRSValid);
}
-void Node::setRotation(const glh::quaternionf& q)
+void Node::setRotation(const quat& q)
{
makeTRSValid();
mRotation = q;
mMatrixValid = false;
}
-void Node::setTranslation(const glh::vec3f& t)
+void Node::setTranslation(const vec3& t)
{
makeTRSValid();
mTranslation = t;
mMatrixValid = false;
}
-void Node::setScale(const glh::vec3f& s)
+void Node::setScale(const vec3& s)
{
makeTRSValid();
mScale = s;
mMatrixValid = false;
}
-const Node& Node::operator=(const tinygltf::Node& src)
+void Node::serialize(object& dst) const
{
- F32* dstMatrix = mMatrix.getF32ptr();
+ write(mName, "name", dst);
+ write(mMatrix, "matrix", dst, glm::identity<mat4>());
+ write(mRotation, "rotation", dst, glm::identity<quat>());
+ write(mTranslation, "translation", dst, glm::vec3(0.f, 0.f, 0.f));
+ write(mScale, "scale", dst, vec3(1.f,1.f,1.f));
+ write(mChildren, "children", dst);
+ write(mMesh, "mesh", dst, INVALID_INDEX);
+ write(mSkin, "skin", dst, INVALID_INDEX);
+}
- if (src.matrix.size() == 16)
+const Node& Node::operator=(const Value& src)
+{
+ copy(src, "name", mName);
+ mMatrixValid = copy(src, "matrix", mMatrix);
+ copy(src, "rotation", mRotation);
+ copy(src, "translation", mTranslation);
+ copy(src, "scale", mScale);
+ copy(src, "children", mChildren);
+ copy(src, "mesh", mMesh);
+ copy(src, "skin", mSkin);
+
+ if (!mMatrixValid)
{
- // Node has a transformation matrix, just copy it
- for (U32 i = 0; i < 16; ++i)
+ mTRSValid = true;
+ }
+
+ return *this;
+}
+
+void Image::serialize(object& dst) const
+{
+ write(mUri, "uri", dst);
+ write(mMimeType, "mimeType", dst);
+ write(mBufferView, "bufferView", dst, INVALID_INDEX);
+ write(mName, "name", dst);
+ write(mWidth, "width", dst, -1);
+ write(mHeight, "height", dst, -1);
+ write(mComponent, "component", dst, -1);
+ write(mBits, "bits", dst, -1);
+ write(mPixelType, "pixelType", dst, -1);
+}
+
+const Image& Image::operator=(const Value& src)
+{
+ copy(src, "uri", mUri);
+ copy(src, "mimeType", mMimeType);
+ copy(src, "bufferView", mBufferView);
+ copy(src, "name", mName);
+ copy(src, "width", mWidth);
+ copy(src, "height", mHeight);
+ copy(src, "component", mComponent);
+ copy(src, "bits", mBits);
+ copy(src, "pixelType", mPixelType);
+
+ return *this;
+}
+
+void Asset::update()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ F32 dt = gFrameTimeSeconds - mLastUpdateTime;
+
+ if (dt > 0.f)
+ {
+ mLastUpdateTime = gFrameTimeSeconds;
+ if (mAnimations.size() > 0)
{
- dstMatrix[i] = (F32)src.matrix[i];
+ static LLCachedControl<U32> anim_idx(gSavedSettings, "GLTFAnimationIndex", 0);
+ static LLCachedControl<F32> anim_speed(gSavedSettings, "GLTFAnimationSpeed", 1.f);
+
+ U32 idx = llclamp(anim_idx(), 0U, mAnimations.size() - 1);
+ mAnimations[idx].update(*this, dt*anim_speed);
}
- mMatrixValid = true;
+ updateTransforms();
+
+ for (auto& skin : mSkins)
+ {
+ skin.uploadMatrixPalette(*this);
+ }
+
+ uploadMaterials();
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltf - addTextureStats");
+
+ for (auto& image : mImages)
+ {
+ if (image.mTexture.notNull())
+ { // HACK - force texture to be loaded full rez
+ // TODO: calculate actual vsize
+ image.mTexture->addTextureStats(2048.f * 2048.f);
+ }
+ }
+ }
}
- else if (!src.rotation.empty() || !src.translation.empty() || !src.scale.empty())
+}
+
+bool Asset::prep()
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ // check required extensions and fail if not supported
+ bool unsupported = false;
+ for (auto& extension : mExtensionsRequired)
{
- // node has rotation/translation/scale, convert to matrix
- if (src.rotation.size() == 4)
+ if (ExtensionsSupported.find(extension) == ExtensionsSupported.end())
{
- mRotation = glh::quaternionf((F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2], (F32)src.rotation[3]);
+ LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL;
+ unsupported = true;
}
+ }
+
+ if (unsupported)
+ {
+ return false;
+ }
- if (src.translation.size() == 3)
+ // do buffers first as other resources depend on them
+ for (auto& buffer : mBuffers)
+ {
+ if (!buffer.prep(*this))
{
- mTranslation = glh::vec3f((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]);
+ return false;
}
+ }
- glh::vec3f scale;
- if (src.scale.size() == 3)
+ for (auto& image : mImages)
+ {
+ if (!image.prep(*this))
{
- mScale = glh::vec3f((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]);
+ return false;
}
- else
+ }
+
+ for (auto& mesh : mMeshes)
+ {
+ if (!mesh.prep(*this))
{
- mScale.set_value(1.f, 1.f, 1.f);
+ return false;
}
+ }
- mTRSValid = true;
+ for (auto& animation : mAnimations)
+ {
+ if (!animation.prep(*this))
+ {
+ return false;
+ }
}
- else
+
+ for (auto& skin : mSkins)
{
- // node specifies no transformation, set to identity
- mMatrix.setIdentity();
+ if (!skin.prep(*this))
+ {
+ return false;
+ }
}
- mChildren = src.children;
- mMesh = src.mesh;
- mSkin = src.skin;
- mName = src.name;
+ // prepare vertex buffers
- return *this;
-}
+ // material count is number of materials + 1 for default material
+ U32 mat_count = (U32) mMaterials.size() + 1;
-void Asset::render(bool opaque, bool rigged)
-{
- if (rigged)
- {
- gGL.loadIdentity();
+ if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
+ { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
+ gDebugProgram.bind();
}
- for (auto& node : mNodes)
+ for (S32 double_sided = 0; double_sided < 2; ++double_sided)
{
- if (node.mSkin != INVALID_INDEX)
+ RenderData& rd = mRenderData[double_sided];
+ for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i)
{
- if (rigged)
- {
- Skin& skin = mSkins[node.mSkin];
- skin.uploadMatrixPalette(*this, node);
- }
- else
- {
- //skip static nodes if we're rendering rigged
- continue;
- }
+ rd.mBatches[i].resize(mat_count);
}
- else if (rigged)
+
+ // for each material
+ for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id)
{
- // skip rigged nodes if we're not rendering rigged
- continue;
- }
+ // for each shader variant
+ U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
+ U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 };
+ S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided;
+ if (ds_mat != double_sided)
+ {
+ continue;
+ }
- if (node.mMesh != INVALID_INDEX)
- {
- Mesh& mesh = mMeshes[node.mMesh];
- for (auto& primitive : mesh.mPrimitives)
+ for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant)
{
- if (!rigged)
- {
- gGL.loadMatrix((F32*)node.mRenderMatrix.mMatrix);
- }
- bool cull = true;
- if (primitive.mMaterial != INVALID_INDEX)
+ U32 attribute_mask = 0;
+ // for each mesh
+ for (auto& mesh : mMeshes)
{
- Material& material = mMaterials[primitive.mMaterial];
-
- if ((material.mMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) == opaque)
+ // for each primitive
+ for (auto& primitive : mesh.mPrimitives)
{
- continue;
+ if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ {
+ // accumulate vertex and index counts
+ primitive.mVertexOffset = vertex_count[variant];
+ primitive.mIndexOffset = index_count[variant];
+
+ vertex_count[variant] += primitive.getVertexCount();
+ index_count[variant] += primitive.getIndexCount();
+
+ // all primitives of a given variant and material should all have the same attribute mask
+ llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask);
+ attribute_mask |= primitive.mAttributeMask;
+ }
}
- material.mMaterial->bind();
- cull = !material.mMaterial->mDoubleSided;
}
- else
+
+ // allocate vertex buffer and pack it
+ if (vertex_count[variant] > 0)
{
- if (!opaque)
+ U32 mat_idx = mat_id + 1;
+ LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask);
+
+ rd.mBatches[variant][mat_idx].mVertexBuffer = vb;
+ vb->allocateBuffer(vertex_count[variant],
+ index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
+ vb->setBuffer();
+
+ for (auto& mesh : mMeshes)
{
- continue;
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant)
+ {
+ primitive.upload(vb);
+ }
+ }
}
- LLFetchedGLTFMaterial::sDefault.bind();
- }
- LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0);
+ vb->unmapBuffer();
- primitive.mVertexBuffer->setBuffer();
- if (primitive.mVertexBuffer->getNumIndices() > 0)
- {
- primitive.mVertexBuffer->draw(primitive.mGLMode, primitive.mVertexBuffer->getNumIndices(), 0);
- }
- else
- {
- primitive.mVertexBuffer->drawArrays(primitive.mGLMode, 0, primitive.mVertexBuffer->getNumVerts());
+ vb->unbind();
}
+ }
+ }
+ }
+
+ // sanity check that all primitives have a vertex buffer
+ for (auto& mesh : mMeshes)
+ {
+ for (auto& primitive : mesh.mPrimitives)
+ {
+ llassert(primitive.mVertexBuffer.notNull());
+ }
+ }
+ // build render batches
+ for (S32 node_id = 0; node_id < mNodes.size(); ++node_id)
+ {
+ Node& node = mNodes[node_id];
+
+ if (node.mMesh != INVALID_INDEX)
+ {
+ auto& mesh = mMeshes[node.mMesh];
+
+ S32 mat_idx = mesh.mPrimitives[0].mMaterial + 1;
+
+ S32 double_sided = mat_idx == 0 ? 0 : mMaterials[mat_idx - 1].mDoubleSided;
+
+ for (S32 j = 0; j < mesh.mPrimitives.size(); ++j)
+ {
+ auto& primitive = mesh.mPrimitives[j];
+
+ S32 variant = primitive.mShaderVariant;
+
+ RenderData& rd = mRenderData[double_sided];
+ RenderBatch& rb = rd.mBatches[variant][mat_idx];
+
+ rb.mPrimitives.push_back({ j, node_id });
}
}
}
+ return true;
}
-void Asset::renderOpaque()
+Asset::Asset(const Value& src)
{
- render(true);
+ *this = src;
}
-void Asset::renderTransparent()
+bool Asset::load(std::string_view filename)
{
- render(false);
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+ mFilename = filename;
+ std::string ext = gDirUtilp->getExtension(mFilename);
+
+ std::ifstream file(filename.data(), std::ios::binary);
+ if (file.is_open())
+ {
+ std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
+ file.close();
+
+ if (ext == "gltf")
+ {
+ Value val = parse(str);
+ *this = val;
+ return prep();
+ }
+ else if (ext == "glb")
+ {
+ return loadBinary(str);
+ }
+ else
+ {
+ LL_WARNS() << "Unsupported file type: " << ext << LL_ENDL;
+ return false;
+ }
+ }
+ else
+ {
+ LL_WARNS() << "Failed to open file: " << filename << LL_ENDL;
+ return false;
+ }
+
+ return false;
}
-void Asset::update()
+bool Asset::loadBinary(const std::string& data)
{
- F32 dt = gFrameTimeSeconds - mLastUpdateTime;
+ // load from binary gltf
+ const U8* ptr = (const U8*)data.data();
+ const U8* end = ptr + data.size();
- if (dt > 0.f)
+ if (end - ptr < 12)
{
- mLastUpdateTime = gFrameTimeSeconds;
- if (mAnimations.size() > 0)
- {
- static LLCachedControl<U32> anim_idx(gSavedSettings, "GLTFAnimationIndex", 0);
- static LLCachedControl<F32> anim_speed(gSavedSettings, "GLTFAnimationSpeed", 1.f);
+ LL_WARNS("GLTF") << "GLB file too short" << LL_ENDL;
+ return false;
+ }
- U32 idx = llclamp(anim_idx(), 0U, mAnimations.size() - 1);
- mAnimations[idx].update(*this, dt*anim_speed);
- }
+ U32 magic = *(U32*)ptr;
+ ptr += 4;
- updateTransforms();
+ if (magic != 0x46546C67)
+ {
+ LL_WARNS("GLTF") << "Invalid GLB magic" << LL_ENDL;
+ return false;
}
-}
-void Asset::allocateGLResources(const std::string& filename, const tinygltf::Model& model)
-{
- // do images first as materials may depend on images
- for (auto& image : mImages)
+ U32 version = *(U32*)ptr;
+ ptr += 4;
+
+ if (version != 2)
{
- image.allocateGLResources();
+ LL_WARNS("GLTF") << "Unsupported GLB version" << LL_ENDL;
+ return false;
}
- // do materials before meshes as meshes may depend on materials
- for (U32 i = 0; i < mMaterials.size(); ++i)
+ U32 length = *(U32*)ptr;
+ ptr += 4;
+
+ if (length != data.size())
{
- mMaterials[i].allocateGLResources(*this);
- LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true);
+ LL_WARNS("GLTF") << "GLB length mismatch" << LL_ENDL;
+ return false;
}
- for (auto& mesh : mMeshes)
+ U32 chunkLength = *(U32*)ptr;
+ ptr += 4;
+
+ if (end - ptr < chunkLength + 8)
{
- mesh.allocateGLResources(*this);
+ LL_WARNS("GLTF") << "GLB chunk too short" << LL_ENDL;
+ return false;
}
- for (auto& animation : mAnimations)
+ U32 chunkType = *(U32*)ptr;
+ ptr += 4;
+
+ if (chunkType != 0x4E4F534A)
{
- animation.allocateGLResources(*this);
+ LL_WARNS("GLTF") << "Invalid GLB chunk type" << LL_ENDL;
+ return false;
}
- for (auto& skin : mSkins)
+ Value val = parse(std::string_view((const char*)ptr, chunkLength));
+ *this = val;
+
+ if (mBuffers.size() > 0 && mBuffers[0].mUri.empty())
{
- skin.allocateGLResources(*this);
+ // load binary chunk
+ ptr += chunkLength;
+
+ if (end - ptr < 8)
+ {
+ LL_WARNS("GLTF") << "GLB chunk too short" << LL_ENDL;
+ return false;
+ }
+
+ chunkLength = *(U32*)ptr;
+ ptr += 4;
+
+ chunkType = *(U32*)ptr;
+ ptr += 4;
+
+ if (chunkType != 0x004E4942)
+ {
+ LL_WARNS("GLTF") << "Invalid GLB chunk type" << LL_ENDL;
+ return false;
+ }
+
+ auto& buffer = mBuffers[0];
+
+ if (ptr + buffer.mByteLength <= end)
+ {
+ buffer.mData.resize(buffer.mByteLength);
+ memcpy(buffer.mData.data(), ptr, buffer.mByteLength);
+ ptr += buffer.mByteLength;
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Buffer too short" << LL_ENDL;
+ return false;
+ }
}
+
+ return prep();
}
-const Asset& Asset::operator=(const tinygltf::Model& src)
+const Asset& Asset::operator=(const Value& src)
{
- mScenes.resize(src.scenes.size());
- for (U32 i = 0; i < src.scenes.size(); ++i)
+ if (src.is_object())
{
- mScenes[i] = src.scenes[i];
+ const object& obj = src.as_object();
+
+ const auto it = obj.find("asset");
+
+ if (it != obj.end())
+ {
+ const Value& asset = it->value();
+
+ copy(asset, "version", mVersion);
+ copy(asset, "minVersion", mMinVersion);
+ copy(asset, "generator", mGenerator);
+ copy(asset, "copyright", mCopyright);
+ copy(asset, "extras", mExtras);
+ }
+
+ copy(obj, "scene", mScene);
+ copy(obj, "scenes", mScenes);
+ copy(obj, "nodes", mNodes);
+ copy(obj, "meshes", mMeshes);
+ copy(obj, "materials", mMaterials);
+ copy(obj, "buffers", mBuffers);
+ copy(obj, "bufferViews", mBufferViews);
+ copy(obj, "textures", mTextures);
+ copy(obj, "samplers", mSamplers);
+ copy(obj, "images", mImages);
+ copy(obj, "accessors", mAccessors);
+ copy(obj, "animations", mAnimations);
+ copy(obj, "skins", mSkins);
+ copy(obj, "extensionsUsed", mExtensionsUsed);
+ copy(obj, "extensionsRequired", mExtensionsRequired);
}
- mNodes.resize(src.nodes.size());
- for (U32 i = 0; i < src.nodes.size(); ++i)
+ return *this;
+}
+
+void Asset::serialize(object& dst) const
+{
+ static const std::string sGenerator = "Linden Lab GLTF Prototype v0.1";
+
+ dst["asset"] = object{};
+ object& asset = dst["asset"].get_object();
+
+ write(mVersion, "version", asset);
+ write(mMinVersion, "minVersion", asset, std::string());
+ write(sGenerator, "generator", asset);
+ write(mScene, "scene", dst, INVALID_INDEX);
+ write(mScenes, "scenes", dst);
+ write(mNodes, "nodes", dst);
+ write(mMeshes, "meshes", dst);
+ write(mMaterials, "materials", dst);
+ write(mBuffers, "buffers", dst);
+ write(mBufferViews, "bufferViews", dst);
+ write(mTextures, "textures", dst);
+ write(mSamplers, "samplers", dst);
+ write(mImages, "images", dst);
+ write(mAccessors, "accessors", dst);
+ write(mAnimations, "animations", dst);
+ write(mSkins, "skins", dst);
+ write(mExtensionsUsed, "extensionsUsed", dst);
+ write(mExtensionsRequired, "extensionsRequired", dst);
+}
+
+bool Asset::save(const std::string& filename)
+{
+ // get folder path
+ std::string folder = gDirUtilp->getDirName(filename);
+
+ // save images
+ for (auto& image : mImages)
{
- mNodes[i] = src.nodes[i];
+ if (!image.save(*this, folder))
+ {
+ return false;
+ }
}
- mMeshes.resize(src.meshes.size());
- for (U32 i = 0; i < src.meshes.size(); ++i)
+ // save buffers
+ // NOTE: save buffers after saving images as saving images
+ // may remove image data from buffers
+ for (auto& buffer : mBuffers)
{
- mMeshes[i] = src.meshes[i];
+ if (!buffer.save(*this, folder))
+ {
+ return false;
+ }
}
- mMaterials.resize(src.materials.size());
- for (U32 i = 0; i < src.materials.size(); ++i)
+ // save .gltf
+ object obj;
+ serialize(obj);
+ std::string buffer = boost::json::serialize(obj, {});
+ std::ofstream file(filename, std::ios::binary);
+ file.write(buffer.c_str(), buffer.size());
+
+ return true;
+}
+
+void Asset::eraseBufferView(S32 bufferView)
+{
+ mBufferViews.erase(mBufferViews.begin() + bufferView);
+
+ for (auto& accessor : mAccessors)
{
- mMaterials[i] = src.materials[i];
+ if (accessor.mBufferView > bufferView)
+ {
+ accessor.mBufferView--;
+ }
}
- mBuffers.resize(src.buffers.size());
- for (U32 i = 0; i < src.buffers.size(); ++i)
+ for (auto& image : mImages)
{
- mBuffers[i] = src.buffers[i];
+ if (image.mBufferView > bufferView)
+ {
+ image.mBufferView--;
+ }
}
- mBufferViews.resize(src.bufferViews.size());
- for (U32 i = 0; i < src.bufferViews.size(); ++i)
+}
+
+LLViewerFetchedTexture* fetch_texture(const LLUUID& id);
+
+bool Image::prep(Asset& asset)
+{
+ LLUUID id;
+ if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull())
+ { // loaded from an asset, fetch the texture from the asset system
+ mTexture = fetch_texture(id);
+ }
+ else if (mUri.find("data:") == 0)
+ { // embedded in a data URI, load the texture from the URI
+ LL_WARNS() << "Data URIs not yet supported" << LL_ENDL;
+ return false;
+ }
+ else if (mBufferView != INVALID_INDEX)
+ { // embedded in a buffer, load the texture from the buffer
+ BufferView& bufferView = asset.mBufferViews[mBufferView];
+ Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
+
+ U8* data = buffer.mData.data() + bufferView.mByteOffset;
+
+ mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType);
+
+ if (mTexture.isNull())
+ {
+ LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL;
+ LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
+ LL_WARNS("GLTF") << " mimeType: " << mMimeType << LL_ENDL;
+
+ return false;
+ }
+ }
+ else if (!asset.mFilename.empty() && !mUri.empty())
+ { // loaded locally and not embedded, load the texture as a local preview
+ std::string dir = gDirUtilp->getDirName(asset.mFilename);
+ std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri;
+
+ LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file);
+ if (tracking_id.notNull())
+ {
+ LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id);
+ mTexture = LLViewerTextureManager::getFetchedTexture(world_id);
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL;
+ LL_WARNS("GLTF") << " image: " << mName << LL_ENDL;
+ LL_WARNS("GLTF") << " file: " << img_file << LL_ENDL;
+
+ return false;
+ }
+ }
+ else
{
- mBufferViews[i] = src.bufferViews[i];
+ LL_WARNS("GLTF") << "Failed to load image: " << mName << LL_ENDL;
+ return false;
}
- mTextures.resize(src.textures.size());
- for (U32 i = 0; i < src.textures.size(); ++i)
+ return true;
+}
+
+
+void Image::clearData(Asset& asset)
+{
+ if (mBufferView != INVALID_INDEX)
{
- mTextures[i] = src.textures[i];
+ // remove data from buffer
+ BufferView& bufferView = asset.mBufferViews[mBufferView];
+ Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
+
+ buffer.erase(asset, bufferView.mByteOffset, bufferView.mByteLength);
+
+ asset.eraseBufferView(mBufferView);
}
- mSamplers.resize(src.samplers.size());
- for (U32 i = 0; i < src.samplers.size(); ++i)
+ mBufferView = INVALID_INDEX;
+ mWidth = -1;
+ mHeight = -1;
+ mComponent = -1;
+ mBits = -1;
+ mPixelType = -1;
+ mMimeType = "";
+}
+
+bool Image::save(Asset& asset, const std::string& folder)
+{
+ // NOTE: this *MUST* be a lossless save
+ // Artists use this to save their work repeatedly, so
+ // adding any compression artifacts here will degrade
+ // images over time.
+ std::string name = mName;
+ std::string error;
+ const std::string& delim = gDirUtilp->getDirDelimiter();
+ if (name.empty())
{
- mSamplers[i] = src.samplers[i];
+ S32 idx = this - asset.mImages.data();
+ name = llformat("image_%d", idx);
}
- mImages.resize(src.images.size());
- for (U32 i = 0; i < src.images.size(); ++i)
+ if (mBufferView != INVALID_INDEX)
{
- mImages[i] = src.images[i];
+ // we have the bytes of the original image, save that out in its
+ // original format
+ BufferView& bufferView = asset.mBufferViews[mBufferView];
+ Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
+
+ std::string extension;
+
+ if (mMimeType == "image/jpeg")
+ {
+ extension = ".jpg";
+ }
+ else if (mMimeType == "image/png")
+ {
+ extension = ".png";
+ }
+ else
+ {
+ error = "Unknown mime type, saved as .bin";
+ extension = ".bin";
+ }
+
+ std::string filename = folder + delim + name + extension;
+
+ // set URI to non-j2c file for now, but later we'll want to reference the j2c hash
+ mUri = name + extension;
+
+ std::ofstream file(filename, std::ios::binary);
+ file.write((const char*)buffer.mData.data() + bufferView.mByteOffset, bufferView.mByteLength);
}
+ else if (mTexture.notNull())
+ {
+ auto bitmapmgr = LLLocalBitmapMgr::getInstance();
+ if (bitmapmgr->isLocal(mTexture->getID()))
+ {
+ LLUUID tracking_id = bitmapmgr->getTrackingID(mTexture->getID());
+ if (tracking_id.notNull())
+ { // copy original file to destination folder
+ std::string source = bitmapmgr->getFilename(tracking_id);
+ if (gDirUtilp->fileExists(source))
+ {
+ std::string filename = gDirUtilp->getBaseFileName(source);
+ std::string dest = folder + delim + filename;
- mAccessors.resize(src.accessors.size());
- for (U32 i = 0; i < src.accessors.size(); ++i)
+ LLFile::copy(source, dest);
+ mUri = filename;
+ }
+ else
+ {
+ error = "File not found: " + source;
+ }
+ }
+ else
+ {
+ error = "Local image missing.";
+ }
+ }
+ else if (!mUri.empty())
+ {
+ std::string from_dir = gDirUtilp->getDirName(asset.mFilename);
+ std::string base_filename = gDirUtilp->getBaseFileName(mUri);
+ std::string filename = from_dir + delim + base_filename;
+ if (gDirUtilp->fileExists(filename))
+ {
+ std::string dest = folder + delim + base_filename;
+ LLFile::copy(filename, dest);
+ mUri = base_filename;
+ }
+ else
+ {
+ error = "Original image file not found: " + filename;
+ }
+ }
+ else
+ {
+ error = "Image is not a local image and has no uri, cannot save.";
+ }
+ }
+
+ if (!error.empty())
{
- mAccessors[i] = src.accessors[i];
+ LL_WARNS("GLTF") << "Failed to save " << name << ": " << error << LL_ENDL;
+ return false;
}
- mAnimations.resize(src.animations.size());
- for (U32 i = 0; i < src.animations.size(); ++i)
+ clearData(asset);
+
+ return true;
+}
+
+void TextureInfo::serialize(object& dst) const
+{
+ write(mIndex, "index", dst, INVALID_INDEX);
+ write(mTexCoord, "texCoord", dst, 0);
+ write_extensions(dst, &mTextureTransform, "KHR_texture_transform");
+}
+
+S32 TextureInfo::getTexCoord() const
+{
+ if (mTextureTransform.mPresent && mTextureTransform.mTexCoord != INVALID_INDEX)
{
- mAnimations[i] = src.animations[i];
+ return mTextureTransform.mTexCoord;
}
+ return mTexCoord;
+}
- mSkins.resize(src.skins.size());
- for (U32 i = 0; i < src.skins.size(); ++i)
+bool Material::isMultiUV() const
+{
+ return mPbrMetallicRoughness.mBaseColorTexture.getTexCoord() != 0 ||
+ mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord() != 0 ||
+ mNormalTexture.getTexCoord() != 0 ||
+ mOcclusionTexture.getTexCoord() != 0 ||
+ mEmissiveTexture.getTexCoord() != 0;
+}
+
+const TextureInfo& TextureInfo::operator=(const Value& src)
+{
+ if (src.is_object())
{
- mSkins[i] = src.skins[i];
+ copy(src, "index", mIndex);
+ copy(src, "texCoord", mTexCoord);
+ copy_extensions(src, "KHR_texture_transform", &mTextureTransform);
}
-
+
return *this;
}
-const Material& Material::operator=(const tinygltf::Material& src)
+bool TextureInfo::operator==(const TextureInfo& rhs) const
{
- mName = src.name;
- return *this;
+ return mIndex == rhs.mIndex && mTexCoord == rhs.mTexCoord;
+}
+
+bool TextureInfo::operator!=(const TextureInfo& rhs) const
+{
+ return !(*this == rhs);
}
-void Material::allocateGLResources(Asset& asset)
+void OcclusionTextureInfo::serialize(object& dst) const
{
- // allocate material
- mMaterial = new LLFetchedGLTFMaterial();
+ TextureInfo::serialize(dst);
+ write(mStrength, "strength", dst, 1.f);
}
-const Mesh& Mesh::operator=(const tinygltf::Mesh& src)
+const OcclusionTextureInfo& OcclusionTextureInfo::operator=(const Value& src)
{
- mPrimitives.resize(src.primitives.size());
- for (U32 i = 0; i < src.primitives.size(); ++i)
+ TextureInfo::operator=(src);
+
+ if (src.is_object())
{
- mPrimitives[i] = src.primitives[i];
+ copy(src, "strength", mStrength);
}
- mWeights = src.weights;
- mName = src.name;
-
return *this;
}
-void Mesh::allocateGLResources(Asset& asset)
+void NormalTextureInfo::serialize(object& dst) const
{
- for (auto& primitive : mPrimitives)
+ TextureInfo::serialize(dst);
+ write(mScale, "scale", dst, 1.f);
+}
+
+const NormalTextureInfo& NormalTextureInfo::operator=(const Value& src)
+{
+ TextureInfo::operator=(src);
+ if (src.is_object())
{
- primitive.allocateGLResources(asset);
+ copy(src, "index", mIndex);
+ copy(src, "texCoord", mTexCoord);
+ copy(src, "scale", mScale);
}
+
+ return *this;
}
-const Scene& Scene::operator=(const tinygltf::Scene& src)
+const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const Value& src)
{
- mNodes = src.nodes;
- mName = src.name;
+ if (src.is_object())
+ {
+ copy(src, "baseColorFactor", mBaseColorFactor);
+ copy(src, "baseColorTexture", mBaseColorTexture);
+ copy(src, "metallicFactor", mMetallicFactor);
+ copy(src, "roughnessFactor", mRoughnessFactor);
+ copy(src, "metallicRoughnessTexture", mMetallicRoughnessTexture);
+ }
return *this;
}
-const Texture& Texture::operator=(const tinygltf::Texture& src)
+void Material::PbrMetallicRoughness::serialize(object& dst) const
{
- mSampler = src.sampler;
- mSource = src.source;
- mName = src.name;
+ write(mBaseColorFactor, "baseColorFactor", dst, vec4(1.f, 1.f, 1.f, 1.f));
+ write(mBaseColorTexture, "baseColorTexture", dst);
+ write(mMetallicFactor, "metallicFactor", dst, 1.f);
+ write(mRoughnessFactor, "roughnessFactor", dst, 1.f);
+ write(mMetallicRoughnessTexture, "metallicRoughnessTexture", dst);
+}
+bool Material::PbrMetallicRoughness::operator==(const Material::PbrMetallicRoughness& rhs) const
+{
+ return mBaseColorFactor == rhs.mBaseColorFactor &&
+ mBaseColorTexture == rhs.mBaseColorTexture &&
+ mMetallicFactor == rhs.mMetallicFactor &&
+ mRoughnessFactor == rhs.mRoughnessFactor &&
+ mMetallicRoughnessTexture == rhs.mMetallicRoughnessTexture;
+}
+
+bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRoughness& rhs) const
+{
+ return !(*this == rhs);
+}
+
+const Material::Unlit& Material::Unlit::operator=(const Value& src)
+{
+ mPresent = true;
return *this;
}
-const Sampler& Sampler::operator=(const tinygltf::Sampler& src)
+void Material::Unlit::serialize(object& dst) const
{
- mMagFilter = src.magFilter;
- mMinFilter = src.minFilter;
- mWrapS = src.wrapS;
- mWrapT = src.wrapT;
- mName = src.name;
+ // no members and object has already been created, nothing to do
+}
+
+void TextureTransform::getPacked(vec4* packed) const
+{
+ packed[0] = vec4(mScale.x, mScale.y, mRotation, mOffset.x);
+ packed[1] = vec4(mOffset.y, 0.f, 0.f, 0.f);
+}
+
+const TextureTransform& TextureTransform::operator=(const Value& src)
+{
+ mPresent = true;
+ if (src.is_object())
+ {
+ copy(src, "offset", mOffset);
+ copy(src, "rotation", mRotation);
+ copy(src, "scale", mScale);
+ copy(src, "texCoord", mTexCoord);
+ }
return *this;
}
-void Skin::uploadMatrixPalette(Asset& asset, Node& node)
+void TextureTransform::serialize(object& dst) const
{
- // prepare matrix palette
+ write(mOffset, "offset", dst, vec2(0.f, 0.f));
+ write(mRotation, "rotation", dst, 0.f);
+ write(mScale, "scale", dst, vec2(1.f, 1.f));
+ write(mTexCoord, "texCoord", dst, -1);
+}
- // modelview will be applied by the shader, so assume matrix palette is in asset space
- std::vector<glh::matrix4f> t_mp;
- t_mp.resize(mJoints.size());
+void Material::serialize(object& dst) const
+{
+ write(mName, "name", dst);
+ write(mEmissiveFactor, "emissiveFactor", dst, vec3(0.f, 0.f, 0.f));
+ write(mPbrMetallicRoughness, "pbrMetallicRoughness", dst);
+ write(mNormalTexture, "normalTexture", dst);
+ write(mOcclusionTexture, "occlusionTexture", dst);
+ write(mEmissiveTexture, "emissiveTexture", dst);
+ write(mAlphaMode, "alphaMode", dst, Material::AlphaMode::OPAQUE);
+ write(mAlphaCutoff, "alphaCutoff", dst, 0.5f);
+ write(mDoubleSided, "doubleSided", dst, false);
+ write_extensions(dst, &mUnlit, "KHR_materials_unlit");
+}
- for (U32 i = 0; i < mJoints.size(); ++i)
+const Material& Material::operator=(const Value& src)
+{
+ if (src.is_object())
{
- Node& joint = asset.mNodes[mJoints[i]];
-
- //t_mp[i].set_value(joint.mRenderMatrix.getF32ptr());
- //t_mp[i] = t_mp[i] * mInverseBindMatricesData[i];
+ copy(src, "name", mName);
+ copy(src, "emissiveFactor", mEmissiveFactor);
+ copy(src, "pbrMetallicRoughness", mPbrMetallicRoughness);
+ copy(src, "normalTexture", mNormalTexture);
+ copy(src, "occlusionTexture", mOcclusionTexture);
+ copy(src, "emissiveTexture", mEmissiveTexture);
+ copy(src, "alphaMode", mAlphaMode);
+ copy(src, "alphaCutoff", mAlphaCutoff);
+ copy(src, "doubleSided", mDoubleSided);
+ copy_extensions(src,
+ "KHR_materials_unlit", &mUnlit );
+ }
+ return *this;
+}
- //t_mp[i].set_value(joint.mRenderMatrix.getF32ptr());
- //t_mp[i] = mInverseBindMatricesData[i] * t_mp[i];
- t_mp[i].set_value(joint.mRenderMatrix.getF32ptr());
- t_mp[i] = t_mp[i] * mInverseBindMatricesData[i];
+void Mesh::serialize(object& dst) const
+{
+ write(mPrimitives, "primitives", dst);
+ write(mWeights, "weights", dst);
+ write(mName, "name", dst);
+}
+const Mesh& Mesh::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "primitives", mPrimitives);
+ copy(src, "weights", mWeights);
+ copy(src, "name", mName);
}
- std::vector<F32> glmp;
+ return *this;
+}
- glmp.resize(mJoints.size() * 12);
+bool Mesh::prep(Asset& asset)
+{
+ for (auto& primitive : mPrimitives)
+ {
+ if (!primitive.prep(asset))
+ {
+ return false;
+ }
+ }
- F32* mp = glmp.data();
+ return true;
+}
- for (U32 i = 0; i < mJoints.size(); ++i)
- {
- F32* m = (F32*)t_mp[i].m;
+void Scene::serialize(object& dst) const
+{
+ write(mNodes, "nodes", dst);
+ write(mName, "name", dst);
+}
- U32 idx = i * 12;
+const Scene& Scene::operator=(const Value& src)
+{
+ copy(src, "nodes", mNodes);
+ copy(src, "name", mName);
- mp[idx + 0] = m[0];
- mp[idx + 1] = m[1];
- mp[idx + 2] = m[2];
- mp[idx + 3] = m[12];
+ return *this;
+}
- mp[idx + 4] = m[4];
- mp[idx + 5] = m[5];
- mp[idx + 6] = m[6];
- mp[idx + 7] = m[13];
+void Texture::serialize(object& dst) const
+{
+ write(mSampler, "sampler", dst, INVALID_INDEX);
+ write(mSource, "source", dst, INVALID_INDEX);
+ write(mName, "name", dst);
+}
- mp[idx + 8] = m[8];
- mp[idx + 9] = m[9];
- mp[idx + 10] = m[10];
- mp[idx + 11] = m[14];
+const Texture& Texture::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "sampler", mSampler);
+ copy(src, "source", mSource);
+ copy(src, "name", mName);
}
- LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX,
- mJoints.size(),
- FALSE,
- (GLfloat*)glmp.data());
+ return *this;
+}
+
+void Sampler::serialize(object& dst) const
+{
+ write(mMagFilter, "magFilter", dst, LINEAR);
+ write(mMinFilter, "minFilter", dst, LINEAR_MIPMAP_LINEAR);
+ write(mWrapS, "wrapS", dst, REPEAT);
+ write(mWrapT, "wrapT", dst, REPEAT);
+ write(mName, "name", dst);
}
+const Sampler& Sampler::operator=(const Value& src)
+{
+ copy(src, "magFilter", mMagFilter);
+ copy(src, "minFilter", mMinFilter);
+ copy(src, "wrapS", mWrapS);
+ copy(src, "wrapT", mWrapT);
+ copy(src, "name", mName);
+
+ return *this;
+}
+
+
diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h
index 5ceac74a8a..27821659db 100644
--- a/indra/newview/gltf/asset.h
+++ b/indra/newview/gltf/asset.h
@@ -28,13 +28,21 @@
#include "llvertexbuffer.h"
#include "llvolumeoctree.h"
-#include "../lltinygltfhelper.h"
#include "accessor.h"
#include "primitive.h"
#include "animation.h"
+#include "boost/json.hpp"
+#include "common.h"
+#include "../llviewertexture.h"
+#include "llglslshader.h"
extern F32SecondsImplicit gFrameTimeSeconds;
+// wingdi defines OPAQUE, which conflicts with our enum
+#if defined(OPAQUE)
+#undef OPAQUE
+#endif
+
// LL GLTF Implementation
namespace LL
{
@@ -42,18 +50,118 @@ namespace LL
{
class Asset;
+ class Extension
+ {
+ public:
+ // true if this extension is present in the gltf file
+ // otherwise false
+ bool mPresent = false;
+ };
+
+ class TextureTransform : public Extension // KHR_texture_transform implementation
+ {
+ public:
+ vec2 mOffset = vec2(0.f, 0.f);
+ F32 mRotation = 0.f;
+ vec2 mScale = vec2(1.f, 1.f);
+ S32 mTexCoord = INVALID_INDEX;
+
+ // get the texture transform as a packed array of vec4's
+ // dst MUST point to at least 2 vec4's
+ void getPacked(vec4* dst) const;
+
+ const TextureTransform& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+ class TextureInfo
+ {
+ public:
+ S32 mIndex = INVALID_INDEX;
+ S32 mTexCoord = 0;
+
+ TextureTransform mTextureTransform;
+
+ bool operator==(const TextureInfo& rhs) const;
+ bool operator!=(const TextureInfo& rhs) const;
+
+ // get the UV channel that should be used for sampling this texture
+ // returns mTextureTransform.mTexCoord if present and valid, otherwise mTexCoord
+ S32 getTexCoord() const;
+
+ const TextureInfo& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+ class NormalTextureInfo : public TextureInfo
+ {
+ public:
+ F32 mScale = 1.0f;
+
+ const NormalTextureInfo& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+ class OcclusionTextureInfo : public TextureInfo
+ {
+ public:
+ F32 mStrength = 1.0f;
+
+ const OcclusionTextureInfo& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
class Material
{
public:
- // use LLFetchedGLTFMaterial for now, but eventually we'll want to use
- // a more flexible GLTF material implementation instead of the fixed packing
- // version we use for sharable GLTF material assets
- LLPointer<LLFetchedGLTFMaterial> mMaterial;
+
+ class Unlit : public Extension // KHR_materials_unlit implementation
+ {
+ public:
+ const Unlit& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+ enum class AlphaMode
+ {
+ OPAQUE,
+ MASK,
+ BLEND
+ };
+
+
+ class PbrMetallicRoughness
+ {
+ public:
+ vec4 mBaseColorFactor = vec4(1.f,1.f,1.f,1.f);
+ TextureInfo mBaseColorTexture;
+ F32 mMetallicFactor = 1.0f;
+ F32 mRoughnessFactor = 1.0f;
+ TextureInfo mMetallicRoughnessTexture;
+
+ bool operator==(const PbrMetallicRoughness& rhs) const;
+ bool operator!=(const PbrMetallicRoughness& rhs) const;
+ const PbrMetallicRoughness& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+ };
+
+
+ PbrMetallicRoughness mPbrMetallicRoughness;
+ NormalTextureInfo mNormalTexture;
+ OcclusionTextureInfo mOcclusionTexture;
+ TextureInfo mEmissiveTexture;
+
std::string mName;
+ vec3 mEmissiveFactor = vec3(0.f, 0.f, 0.f);
+ AlphaMode mAlphaMode = AlphaMode::OPAQUE;
+ F32 mAlphaCutoff = 0.5f;
+ bool mDoubleSided = false;
+ Unlit mUnlit;
- const Material& operator=(const tinygltf::Material& src);
+ bool isMultiUV() const;
- void allocateGLResources(Asset& asset);
+ const Material& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
};
class Mesh
@@ -63,22 +171,22 @@ namespace LL
std::vector<double> mWeights;
std::string mName;
- const Mesh& operator=(const tinygltf::Mesh& src);
+ const Mesh& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
- void allocateGLResources(Asset& asset);
+ bool prep(Asset& asset);
};
class Node
{
public:
- LLMatrix4a mMatrix; //local transform
- LLMatrix4a mRenderMatrix; //transform for rendering
- LLMatrix4a mAssetMatrix; //transform from local to asset space
- LLMatrix4a mAssetMatrixInv; //transform from asset to local space
+ mat4 mMatrix = glm::identity<mat4>(); //local transform
+ mat4 mAssetMatrix; //transform from local to asset space
+ mat4 mAssetMatrixInv; //transform from asset to local space
- glh::vec3f mTranslation;
- glh::quaternionf mRotation;
- glh::vec3f mScale;
+ vec3 mTranslation = vec3(0,0,0);
+ quat mRotation = glm::identity<quat>();
+ vec3 mScale = vec3(1.f,1.f,1.f);
// if true, mMatrix is valid and up to date
bool mMatrixValid = false;
@@ -96,14 +204,11 @@ namespace LL
std::string mName;
- const Node& operator=(const tinygltf::Node& src);
-
- // Set mRenderMatrix to a transform that can be used for the current render pass
- // modelview -- parent's render matrix
- void updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview);
+ const Node& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
// update mAssetMatrix and mAssetMatrixInv
- void updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix);
+ void updateTransforms(Asset& asset, const mat4& parentMatrix);
// ensure mMatrix is valid -- if mMatrixValid is false and mTRSValid is true, will update mMatrix to match Translation/Rotation/Scale
void makeMatrixValid();
@@ -113,30 +218,35 @@ namespace LL
// Set rotation of this node
// SIDE EFFECT: invalidates mMatrix
- void setRotation(const glh::quaternionf& rotation);
+ void setRotation(const quat& rotation);
// Set translation of this node
// SIDE EFFECT: invalidates mMatrix
- void setTranslation(const glh::vec3f& translation);
+ void setTranslation(const vec3& translation);
// Set scale of this node
// SIDE EFFECT: invalidates mMatrix
- void setScale(const glh::vec3f& scale);
+ void setScale(const vec3& scale);
};
class Skin
{
public:
+ ~Skin();
+
S32 mInverseBindMatrices = INVALID_INDEX;
S32 mSkeleton = INVALID_INDEX;
+
+ U32 mUBO = 0;
std::vector<S32> mJoints;
std::string mName;
- std::vector<glh::matrix4f> mInverseBindMatricesData;
+ std::vector<mat4> mInverseBindMatricesData;
- void allocateGLResources(Asset& asset);
- void uploadMatrixPalette(Asset& asset, Node& node);
+ bool prep(Asset& asset);
+ void uploadMatrixPalette(Asset& asset);
- const Skin& operator=(const tinygltf::Skin& src);
+ const Skin& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
};
class Scene
@@ -145,10 +255,11 @@ namespace LL
std::vector<S32> mNodes;
std::string mName;
- const Scene& operator=(const tinygltf::Scene& src);
+ const Scene& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
void updateTransforms(Asset& asset);
- void updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview);
+ void updateRenderTransforms(Asset& asset, const mat4& modelview);
};
class Texture
@@ -158,19 +269,21 @@ namespace LL
S32 mSource = INVALID_INDEX;
std::string mName;
- const Texture& operator=(const tinygltf::Texture& src);
+ const Texture& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
};
class Sampler
{
public:
- S32 mMagFilter;
- S32 mMinFilter;
- S32 mWrapS;
- S32 mWrapT;
+ S32 mMagFilter = LINEAR;
+ S32 mMinFilter = LINEAR_MIPMAP_LINEAR;
+ S32 mWrapS = REPEAT;
+ S32 mWrapT = REPEAT;
std::string mName;
- const Sampler& operator=(const tinygltf::Sampler& src);
+ const Sampler& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
};
class Image
@@ -179,38 +292,64 @@ namespace LL
std::string mName;
std::string mUri;
std::string mMimeType;
- std::vector<U8> mData;
- S32 mWidth;
- S32 mHeight;
- S32 mComponent;
- S32 mBits;
+
+ S32 mBufferView = INVALID_INDEX;
+
+ S32 mWidth = -1;
+ S32 mHeight = -1;
+ S32 mComponent = -1;
+ S32 mBits = -1;
+ S32 mPixelType = -1;
+
LLPointer<LLViewerFetchedTexture> mTexture;
- const Image& operator=(const tinygltf::Image& src)
- {
- mName = src.name;
- mUri = src.uri;
- mMimeType = src.mimeType;
- mData = src.image;
- mWidth = src.width;
- mHeight = src.height;
- mComponent = src.component;
- mBits = src.bits;
-
- return *this;
- }
-
- void allocateGLResources()
+ const Image& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+
+ // save image to disk
+ // may remove image data from bufferviews and convert to
+ // file uri if necessary
+ bool save(Asset& asset, const std::string& filename);
+
+ // erase the buffer view associated with this image
+ // free any associated GLTF resources
+ // preserve only uri and name
+ void clearData(Asset& asset);
+
+ bool prep(Asset& asset);
+ };
+
+ // Render Batch -- vertex buffer and list of primitives to render using
+ // said vertex buffer
+ class RenderBatch
+ {
+ public:
+ struct PrimitiveData
{
- // allocate texture
+ S32 mPrimitiveIndex = INVALID_INDEX;
+ S32 mNodeIndex = INVALID_INDEX;
+ };
+
+ LLPointer<LLVertexBuffer> mVertexBuffer;
+ std::vector<PrimitiveData> mPrimitives;
+ };
- }
+ class RenderData
+ {
+ public:
+ // list of render batches
+ // indexed by [material index + 1](0 is reserved for default material)
+ // there should be exactly one render batch per material per variant
+ std::vector<RenderBatch> mBatches[LLGLSLShader::NUM_GLTF_VARIANTS];
};
+
// C++ representation of a GLTF Asset
- class Asset : public LLRefCount
+ class Asset
{
public:
+
+ static const std::string minVersion_default;
std::vector<Scene> mScenes;
std::vector<Node> mNodes;
std::vector<Mesh> mMeshes;
@@ -223,12 +362,38 @@ namespace LL
std::vector<Accessor> mAccessors;
std::vector<Animation> mAnimations;
std::vector<Skin> mSkins;
+ std::vector<std::string> mExtensionsUsed;
+ std::vector<std::string> mExtensionsRequired;
+
+ std::string mVersion;
+ std::string mGenerator;
+ std::string mMinVersion;
+ std::string mCopyright;
+
+ S32 mScene = INVALID_INDEX;
+ Value mExtras;
+
+ U32 mPendingBuffers = 0;
+
+ // local file this asset was loaded from (if any)
+ std::string mFilename;
// the last time update() was called according to gFrameTimeSeconds
F32 mLastUpdateTime = gFrameTimeSeconds;
- // prepare the asset for rendering
- void allocateGLResources(const std::string& filename, const tinygltf::Model& model);
+ // data used for rendering
+ // 0 - single sided
+ // 1 - double sided
+ RenderData mRenderData[2];
+
+ // UBO for storing node transforms
+ U32 mNodesUBO = 0;
+
+ // UBO for storing material data
+ U32 mMaterialsUBO = 0;
+
+ // prepare for first time use
+ bool prep();
// Called periodically (typically once per frame)
// Any ongoing work (such as animations) should be handled here
@@ -240,12 +405,11 @@ namespace LL
// update asset-to-node and node-to-asset transforms
void updateTransforms();
- // update node render transforms
- void updateRenderTransforms(const LLMatrix4a& modelview);
+ // upload matrices to UBO
+ void uploadTransforms();
- void render(bool opaque, bool rigged = false);
- void renderOpaque();
- void renderTransparent();
+ // upload materils to UBO
+ void uploadMaterials();
// return the index of the node that the line segment intersects with, or -1 if no hit
// input and output values must be in this asset's local coordinate frame
@@ -257,8 +421,37 @@ namespace LL
S32* primitive_hitp = nullptr // return the index of the primitive that was hit
);
- const Asset& operator=(const tinygltf::Model& src);
+ Asset() = default;
+ Asset(const Value& src);
+
+ // load from given file
+ // accepts .gltf and .glb files
+ // Any existing data will be lost
+ // returns result of prep() on success
+ bool load(std::string_view filename);
+
+ // load .glb contents from memory
+ // data - binary contents of .glb file
+ // returns result of prep() on success
+ bool loadBinary(const std::string& data);
+ const Asset& operator=(const Value& src);
+ void serialize(boost::json::object& dst) const;
+
+ // save the asset to the given .gltf file
+ // saves images and bins alongside the gltf file
+ bool save(const std::string& filename);
+
+ // remove the bufferview at the given index
+ // updates all bufferview indices in this Asset as needed
+ void eraseBufferView(S32 bufferView);
+
+ // return true if this Asset has been loaded as a local preview
+ // Local previews may be uploaded or exported to disk
+ bool isLocalPreview() { return !mFilename.empty(); }
};
+
+ Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode);
+ std::string enum_to_gltf_alpha_mode(Material::AlphaMode alpha_mode);
}
}
diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h
index 4e6f5901e7..40f9448aaf 100644
--- a/indra/newview/gltf/buffer_util.h
+++ b/indra/newview/gltf/buffer_util.h
@@ -31,60 +31,65 @@
// whenever we add support for more types
#ifdef _MSC_VER
-#define LL_FUNCSIG __FUNCSIG__
+#define LL_FUNCSIG __FUNCSIG__
#else
#define LL_FUNCSIG __PRETTY_FUNCTION__
#endif
+#include "accessor.h"
+
namespace LL
{
namespace GLTF
{
+
+ using string_view = boost::json::string_view;
+
// copy one Scalar from src to dst
template<class S, class T>
- static void copyScalar(S* src, T& dst)
+ inline void copyScalar(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
// copy one vec2 from src to dst
template<class S, class T>
- static void copyVec2(S* src, T& dst)
+ inline void copyVec2(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
// copy one vec3 from src to dst
template<class S, class T>
- static void copyVec3(S* src, T& dst)
+ inline void copyVec3(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
// copy one vec4 from src to dst
template<class S, class T>
- static void copyVec4(S* src, T& dst)
+ inline void copyVec4(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
- // copy one vec2 from src to dst
+ // copy one mat2 from src to dst
template<class S, class T>
- static void copyMat2(S* src, T& dst)
+ inline void copyMat2(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
- // copy one vec3 from src to dst
+ // copy one mat3 from src to dst
template<class S, class T>
- static void copyMat3(S* src, T& dst)
+ inline void copyMat3(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
- // copy one vec4 from src to dst
+ // copy one mat4 from src to dst
template<class S, class T>
- static void copyMat4(S* src, T& dst)
+ inline void copyMat4(S* src, T& dst)
{
LL_ERRS() << "TODO: implement " << LL_FUNCSIG << LL_ENDL;
}
@@ -93,135 +98,138 @@ namespace LL
// concrete implementations for different types of source and destination
//=========================================================================================================
-// suppress unused function warning -- clang complains here but these specializations are definitely used
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-function"
-#endif
-
template<>
- void copyScalar<F32, F32>(F32* src, F32& dst)
+ inline void copyScalar<F32, F32>(F32* src, F32& dst)
{
dst = *src;
}
template<>
- void copyScalar<U32, U32>(U32* src, U32& dst)
+ inline void copyScalar<U32, U32>(U32* src, U32& dst)
{
dst = *src;
}
template<>
- void copyScalar<U32, U16>(U32* src, U16& dst)
+ inline void copyScalar<U32, U16>(U32* src, U16& dst)
{
dst = *src;
}
template<>
- void copyScalar<U16, U16>(U16* src, U16& dst)
+ inline void copyScalar<U16, U16>(U16* src, U16& dst)
{
dst = *src;
}
template<>
- void copyScalar<U16, U32>(U16* src, U32& dst)
+ inline void copyScalar<U16, U32>(U16* src, U32& dst)
{
dst = *src;
}
template<>
- void copyScalar<U8, U16>(U8* src, U16& dst)
+ inline void copyScalar<U8, U16>(U8* src, U16& dst)
{
dst = *src;
}
template<>
- void copyScalar<U8, U32>(U8* src, U32& dst)
+ inline void copyScalar<U8, U32>(U8* src, U32& dst)
{
dst = *src;
}
template<>
- void copyVec2<F32, LLVector2>(F32* src, LLVector2& dst)
+ inline void copyVec2<F32, LLVector2>(F32* src, LLVector2& dst)
{
dst.set(src[0], src[1]);
}
template<>
- void copyVec3<F32, glh::vec3f>(F32* src, glh::vec3f& dst)
+ inline void copyVec3<F32, vec3>(F32* src, vec3& dst)
{
- dst.set_value(src[0], src[1], src[2]);
+ dst = vec3(src[0], src[1], src[2]);
}
template<>
- void copyVec3<F32, LLVector4a>(F32* src, LLVector4a& dst)
+ inline void copyVec3<F32, LLVector4a>(F32* src, LLVector4a& dst)
{
dst.load3(src);
}
template<>
- void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
+ inline void copyVec3<U16, LLColor4U>(U16* src, LLColor4U& dst)
{
dst.set(src[0], src[1], src[2], 255);
}
template<>
- void copyVec4<U8, LLColor4U>(U8* src, LLColor4U& dst)
+ inline void copyVec4<U8, LLColor4U>(U8* src, LLColor4U& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
}
template<>
- void copyVec4<U16, LLColor4U>(U16* src, LLColor4U& dst)
+ inline void copyVec4<U16, U64>(U16* src, U64& dst)
+ {
+ U16* data = (U16*)&dst;
+ data[0] = src[0];
+ data[1] = src[1];
+ data[2] = src[2];
+ data[3] = src[3];
+ }
+
+ template<>
+ inline void copyVec4<U16, LLColor4U>(U16* src, LLColor4U& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
}
template<>
- void copyVec4<F32, LLColor4U>(F32* src, LLColor4U& dst)
+ inline void copyVec4<F32, LLColor4U>(F32* src, LLColor4U& dst)
{
dst.set(src[0]*255, src[1]*255, src[2]*255, src[3]*255);
}
template<>
- void copyVec4<F32, LLVector4a>(F32* src, LLVector4a& dst)
+ inline void copyVec4<F32, LLVector4a>(F32* src, LLVector4a& dst)
{
dst.loadua(src);
}
template<>
- void copyVec4<U16, LLVector4a>(U16* src, LLVector4a& dst)
+ inline void copyVec4<U16, LLVector4a>(U16* src, LLVector4a& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
}
template<>
- void copyVec4<U8, LLVector4a>(U8* src, LLVector4a& dst)
+ inline void copyVec4<U8, LLVector4a>(U8* src, LLVector4a& dst)
{
dst.set(src[0], src[1], src[2], src[3]);
}
template<>
- void copyVec4<F32, glh::quaternionf>(F32* src, glh::quaternionf& dst)
+ inline void copyVec4<F32, quat>(F32* src, quat& dst)
{
- dst.set_value(src);
+ dst.x = src[0];
+ dst.y = src[1];
+ dst.z = src[2];
+ dst.w = src[3];
}
template<>
- void copyMat4<F32, glh::matrix4f>(F32* src, glh::matrix4f& dst)
+ inline void copyMat4<F32, mat4>(F32* src, mat4& dst)
{
- dst.set_value(src);
+ dst = glm::make_mat4(src);
}
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#endif
-
//=========================================================================================================
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyScalar(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyScalar(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -233,7 +241,7 @@ namespace LL
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyVec2(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyVec2(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -245,7 +253,7 @@ namespace LL
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyVec3(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyVec3(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -257,7 +265,7 @@ namespace LL
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyVec4(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyVec4(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -269,7 +277,7 @@ namespace LL
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyMat2(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyMat2(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -281,7 +289,7 @@ namespace LL
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyMat3(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyMat3(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -293,7 +301,7 @@ namespace LL
// copy from src to dst, stride is the number of bytes between each element in src, count is number of elements to copy
template<class S, class T>
- static void copyMat4(S* src, LLStrider<T> dst, S32 stride, S32 count)
+ inline void copyMat4(S* src, LLStrider<T> dst, S32 stride, S32 count)
{
for (S32 i = 0; i < count; ++i)
{
@@ -304,39 +312,39 @@ namespace LL
}
template<class S, class T>
- static void copy(Asset& asset, Accessor& accessor, const S* src, LLStrider<T>& dst, S32 byteStride)
+ inline void copy(Asset& asset, Accessor& accessor, const S* src, LLStrider<T>& dst, S32 byteStride)
{
- if (accessor.mType == (S32)Accessor::Type::SCALAR)
+ if (accessor.mType == Accessor::Type::SCALAR)
{
S32 stride = byteStride == 0 ? sizeof(S) * 1 : byteStride;
copyScalar((S*)src, dst, stride, accessor.mCount);
}
- else if (accessor.mType == (S32)Accessor::Type::VEC2)
+ else if (accessor.mType == Accessor::Type::VEC2)
{
S32 stride = byteStride == 0 ? sizeof(S) * 2 : byteStride;
copyVec2((S*)src, dst, stride, accessor.mCount);
}
- else if (accessor.mType == (S32)Accessor::Type::VEC3)
+ else if (accessor.mType == Accessor::Type::VEC3)
{
S32 stride = byteStride == 0 ? sizeof(S) * 3 : byteStride;
copyVec3((S*)src, dst, stride, accessor.mCount);
}
- else if (accessor.mType == (S32)Accessor::Type::VEC4)
+ else if (accessor.mType == Accessor::Type::VEC4)
{
S32 stride = byteStride == 0 ? sizeof(S) * 4 : byteStride;
copyVec4((S*)src, dst, stride, accessor.mCount);
}
- else if (accessor.mType == (S32)Accessor::Type::MAT2)
+ else if (accessor.mType == Accessor::Type::MAT2)
{
S32 stride = byteStride == 0 ? sizeof(S) * 4 : byteStride;
copyMat2((S*)src, dst, stride, accessor.mCount);
}
- else if (accessor.mType == (S32)Accessor::Type::MAT3)
+ else if (accessor.mType == Accessor::Type::MAT3)
{
S32 stride = byteStride == 0 ? sizeof(S) * 9 : byteStride;
copyMat3((S*)src, dst, stride, accessor.mCount);
}
- else if (accessor.mType == (S32)Accessor::Type::MAT4)
+ else if (accessor.mType == Accessor::Type::MAT4)
{
S32 stride = byteStride == 0 ? sizeof(S) * 16 : byteStride;
copyMat4((S*)src, dst, stride, accessor.mCount);
@@ -349,54 +357,701 @@ namespace LL
// copy data from accessor to strider
template<class T>
- static void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)
+ inline void copy(Asset& asset, Accessor& accessor, LLStrider<T>& dst)
{
const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView];
const Buffer& buffer = asset.mBuffers[bufferView.mBuffer];
const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset;
- if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_FLOAT)
+ switch (accessor.mComponentType)
{
- LL::GLTF::copy(asset, accessor, (const F32*)src, dst, bufferView.mByteStride);
+ case Accessor::ComponentType::FLOAT:
+ copy(asset, accessor, (const F32*)src, dst, bufferView.mByteStride);
+ break;
+ case Accessor::ComponentType::UNSIGNED_INT:
+ copy(asset, accessor, (const U32*)src, dst, bufferView.mByteStride);
+ break;
+ case Accessor::ComponentType::SHORT:
+ copy(asset, accessor, (const S16*)src, dst, bufferView.mByteStride);
+ break;
+ case Accessor::ComponentType::UNSIGNED_SHORT:
+ copy(asset, accessor, (const U16*)src, dst, bufferView.mByteStride);
+ break;
+ case Accessor::ComponentType::BYTE:
+ copy(asset, accessor, (const S8*)src, dst, bufferView.mByteStride);
+ break;
+ case Accessor::ComponentType::UNSIGNED_BYTE:
+ copy(asset, accessor, (const U8*)src, dst, bufferView.mByteStride);
+ break;
+ default:
+ LL_ERRS("GLTF") << "Invalid component type" << LL_ENDL;
+ break;
}
- else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
+ }
+
+ // copy data from accessor to vector
+ template<class T>
+ inline void copy(Asset& asset, Accessor& accessor, std::vector<T>& dst)
+ {
+ dst.resize(accessor.mCount);
+ LLStrider<T> strider = dst.data();
+ copy(asset, accessor, strider);
+ }
+
+
+ //=========================================================================================================
+ // boost::json copying utilities
+ // ========================================================================================================
+
+ //====================== unspecialized base template, single value ===========================
+
+ // to/from Value
+ template<typename T>
+ inline bool copy(const Value& src, T& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+ template<typename T>
+ inline bool write(const T& src, Value& dst)
+ {
+ dst = boost::json::object();
+ src.serialize(dst.as_object());
+ return true;
+ }
+
+ template<typename T>
+ inline bool copy(const Value& src, std::unordered_map<std::string, T>& dst)
+ {
+ if (src.is_object())
{
- LL::GLTF::copy(asset, accessor, (const U16*)src, dst, bufferView.mByteStride);
+ const boost::json::object& obj = src.as_object();
+ for (const auto& [key, value] : obj)
+ {
+ copy<T>(value, dst[key]);
+ }
+ return true;
}
- else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
+ return false;
+ }
+
+ template<typename T>
+ inline bool write(const std::unordered_map<std::string, T>& src, Value& dst)
+ {
+ boost::json::object obj;
+ for (const auto& [key, value] : src)
{
- LL::GLTF::copy(asset, accessor, (const U32*)src, dst, bufferView.mByteStride);
+ Value v;
+ if (write<T>(value, v))
+ {
+ obj[key] = v;
+ }
+ else
+ {
+ return false;
+ }
}
- else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE)
+ dst = obj;
+ return true;
+ }
+
+ // to/from array
+ template<typename T>
+ inline bool copy(const Value& src, std::vector<T>& dst)
+ {
+ if (src.is_array())
{
- LL::GLTF::copy(asset, accessor, (const U8*)src, dst, bufferView.mByteStride);
+ const boost::json::array& arr = src.get_array();
+ dst.resize(arr.size());
+ for (size_t i = 0; i < arr.size(); ++i)
+ {
+ copy(arr[i], dst[i]);
+ }
+ return true;
}
- else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_SHORT)
+
+ return false;
+ }
+
+ template<typename T>
+ inline bool write(const std::vector<T>& src, Value& dst)
+ {
+ boost::json::array arr;
+ for (const T& t : src)
{
- LL::GLTF::copy(asset, accessor, (const S16*)src, dst, bufferView.mByteStride);
+ Value v;
+ if (write(t, v))
+ {
+ arr.push_back(v);
+ }
+ else
+ {
+ return false;
+ }
}
- else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_BYTE)
+ dst = arr;
+ return true;
+ }
+
+ // to/from object member
+ template<typename T>
+ inline bool copy(const boost::json::object& src, string_view member, T& dst)
+ {
+ auto it = src.find(member);
+ if (it != src.end())
{
- LL::GLTF::copy(asset, accessor, (const S8*)src, dst, bufferView.mByteStride);
+ return copy(it->value(), dst);
}
- else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_DOUBLE)
+ return false;
+ }
+
+ // always write a member to an object without checking default
+ template<typename T>
+ inline bool write_always(const T& src, string_view member, boost::json::object& dst)
+ {
+ Value& v = dst[member];
+ if (!write(src, v))
{
- LL::GLTF::copy(asset, accessor, (const F64*)src, dst, bufferView.mByteStride);
+ dst.erase(member);
+ return false;
}
- else
+ return true;
+ }
+
+
+ // to/from extension
+
+ // for internal use only, use copy_extensions instead
+ template<typename T>
+ inline bool _copy_extension(const boost::json::object& extensions, std::string_view member, T* dst)
+ {
+ if (extensions.contains(member))
{
- LL_ERRS("GLTF") << "Unsupported component type" << LL_ENDL;
+ return copy(extensions.at(member), *dst);
}
+
+ return false;
}
- // copy data from accessor to vector
- template<class T>
- static void copy(Asset& asset, Accessor& accessor, std::vector<T>& dst)
+ // Copy all extensions from src.extensions to provided destinations
+ // Usage:
+ // copy_extensions(src,
+ // "KHR_materials_unlit", &mUnlit,
+ // "KHR_materials_pbrSpecularGlossiness", &mPbrSpecularGlossiness);
+ // returns true if any of the extensions are copied
+ template<class... Types>
+ inline bool copy_extensions(const boost::json::value& src, Types... args)
{
- dst.resize(accessor.mCount);
- LLStrider<T> strider = dst.data();
- copy(asset, accessor, strider);
+ // extract the extensions object (don't assume it exists and verify that it is an object)
+ if (src.is_object())
+ {
+ boost::json::object obj = src.get_object();
+ if (obj.contains("extensions"))
+ {
+ const boost::json::value& extensions = obj.at("extensions");
+ if (extensions.is_object())
+ {
+ const boost::json::object& ext_obj = extensions.as_object();
+ bool success = false;
+ // copy each extension, return true if any of them succeed, do not short circuit on success
+ U32 count = sizeof...(args);
+ for (U32 i = 0; i < count; i += 2)
+ {
+ if (_copy_extension(ext_obj, args...))
+ {
+ success = true;
+ }
+ }
+ return success;
+ }
+ }
+ }
+
+ return false;
}
+
+ // internal use aonly, use write_extensions instead
+ template<typename T>
+ inline bool _write_extension(boost::json::object& extensions, const T* src, string_view member)
+ {
+ if (src->mPresent)
+ {
+ Value v;
+ if (write(*src, v))
+ {
+ extensions[member] = v;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Write all extensions to dst.extensions
+ // Usage:
+ // write_extensions(dst,
+ // mUnlit, "KHR_materials_unlit",
+ // mPbrSpecularGlossiness, "KHR_materials_pbrSpecularGlossiness");
+ // returns true if any of the extensions are written
+ template<class... Types>
+ inline bool write_extensions(boost::json::object& dst, Types... args)
+ {
+ bool success = false;
+
+ boost::json::object extensions;
+ U32 count = sizeof...(args) - 1;
+
+ for (U32 i = 0; i < count; i += 2)
+ {
+ if (_write_extension(extensions, args...))
+ {
+ success = true;
+ }
+ }
+
+ if (success)
+ {
+ dst["extensions"] = extensions;
+ }
+
+ return success;
+ }
+
+ // conditionally write a member to an object if the member
+ // is not the default value
+ template<typename T>
+ inline bool write(const T& src, string_view member, boost::json::object& dst, const T& default_value = T())
+ {
+ if (src != default_value)
+ {
+ return write_always(src, member, dst);
+ }
+ return false;
+ }
+
+ template<typename T>
+ inline bool write(const std::unordered_map<std::string, T>& src, string_view member, boost::json::object& dst, const std::unordered_map<std::string, T>& default_value = std::unordered_map<std::string, T>())
+ {
+ if (!src.empty())
+ {
+ Value v;
+ if (write<T>(src, v))
+ {
+ dst[member] = v;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<typename T>
+ inline bool write(const std::vector<T>& src, string_view member, boost::json::object& dst, const std::vector<T>& deafault_value = std::vector<T>())
+ {
+ if (!src.empty())
+ {
+ Value v;
+ if (write(src, v))
+ {
+ dst[member] = v;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<typename T>
+ inline bool copy(const Value& src, string_view member, T& dst)
+ {
+ if (src.is_object())
+ {
+ const boost::json::object& obj = src.as_object();
+ return copy(obj, member, dst);
+ }
+
+ return false;
+ }
+
+ // Accessor::ComponentType
+ template<>
+ inline bool copy(const Value& src, Accessor::ComponentType& dst)
+ {
+ if (src.is_int64())
+ {
+ dst = (Accessor::ComponentType)src.get_int64();
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const Accessor::ComponentType& src, Value& dst)
+ {
+ dst = (S32)src;
+ return true;
+ }
+
+ //Primitive::Mode
+ template<>
+ inline bool copy(const Value& src, Primitive::Mode& dst)
+ {
+ if (src.is_int64())
+ {
+ dst = (Primitive::Mode)src.get_int64();
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const Primitive::Mode& src, Value& dst)
+ {
+ dst = (S32)src;
+ return true;
+ }
+
+ // vec4
+ template<>
+ inline bool copy(const Value& src, vec4& dst)
+ {
+ if (src.is_array())
+ {
+ const boost::json::array& arr = src.as_array();
+ if (arr.size() == 4)
+ {
+ vec4 v;
+ std::error_code ec;
+
+ v.x = arr[0].to_number<F32>(ec); if (ec) return false;
+ v.y = arr[1].to_number<F32>(ec); if (ec) return false;
+ v.z = arr[2].to_number<F32>(ec); if (ec) return false;
+ v.w = arr[3].to_number<F32>(ec); if (ec) return false;
+
+ dst = v;
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const vec4& src, Value& dst)
+ {
+ dst = boost::json::array();
+ boost::json::array& arr = dst.get_array();
+ arr.resize(4);
+ arr[0] = src.x;
+ arr[1] = src.y;
+ arr[2] = src.z;
+ arr[3] = src.w;
+ return true;
+ }
+
+ // quat
+ template<>
+ inline bool copy(const Value& src, quat& dst)
+ {
+ if (src.is_array())
+ {
+ const boost::json::array& arr = src.as_array();
+ if (arr.size() == 4)
+ {
+ std::error_code ec;
+ dst.x = arr[0].to_number<F32>(ec); if (ec) return false;
+ dst.y = arr[1].to_number<F32>(ec); if (ec) return false;
+ dst.z = arr[2].to_number<F32>(ec); if (ec) return false;
+ dst.w = arr[3].to_number<F32>(ec); if (ec) return false;
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const quat& src, Value& dst)
+ {
+ dst = boost::json::array();
+ boost::json::array& arr = dst.get_array();
+ arr.resize(4);
+ arr[0] = src.x;
+ arr[1] = src.y;
+ arr[2] = src.z;
+ arr[3] = src.w;
+ return true;
+ }
+
+
+ // vec3
+ template<>
+ inline bool copy(const Value& src, vec3& dst)
+ {
+ if (src.is_array())
+ {
+ const boost::json::array& arr = src.as_array();
+ if (arr.size() == 3)
+ {
+ std::error_code ec;
+ vec3 t;
+ t.x = arr[0].to_number<F32>(ec); if (ec) return false;
+ t.y = arr[1].to_number<F32>(ec); if (ec) return false;
+ t.z = arr[2].to_number<F32>(ec); if (ec) return false;
+
+ dst = t;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const vec3& src, Value& dst)
+ {
+ dst = boost::json::array();
+ boost::json::array& arr = dst.as_array();
+ arr.resize(3);
+ arr[0] = src.x;
+ arr[1] = src.y;
+ arr[2] = src.z;
+ return true;
+ }
+
+ // vec2
+ template<>
+ inline bool copy(const Value& src, vec2& dst)
+ {
+ if (src.is_array())
+ {
+ const boost::json::array& arr = src.as_array();
+ if (arr.size() == 2)
+ {
+ std::error_code ec;
+ vec2 t;
+ t.x = arr[0].to_number<F32>(ec); if (ec) return false;
+ t.y = arr[1].to_number<F32>(ec); if (ec) return false;
+
+ dst = t;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const vec2& src, Value& dst)
+ {
+ dst = boost::json::array();
+ boost::json::array& arr = dst.as_array();
+ arr.resize(2);
+ arr[0] = src.x;
+ arr[1] = src.y;
+
+ return true;
+ }
+
+ // bool
+ template<>
+ inline bool copy(const Value& src, bool& dst)
+ {
+ if (src.is_bool())
+ {
+ dst = src.get_bool();
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const bool& src, Value& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+ // F32
+ template<>
+ inline bool copy(const Value& src, F32& dst)
+ {
+ std::error_code ec;
+ F32 t = src.to_number<F32>(ec); if (ec) return false;
+ dst = t;
+ return true;
+ }
+
+ template<>
+ inline bool write(const F32& src, Value& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+
+ // U32
+ template<>
+ inline bool copy(const Value& src, U32& dst)
+ {
+ if (src.is_int64())
+ {
+ dst = src.get_int64();
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const U32& src, Value& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+ // F64
+ template<>
+ inline bool copy(const Value& src, F64& dst)
+ {
+ std::error_code ec;
+ F64 t = src.to_number<F64>(ec); if (ec) return false;
+ dst = t;
+ return true;
+ }
+
+ template<>
+ inline bool write(const F64& src, Value& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+ // Accessor::Type
+ template<>
+ inline bool copy(const Value& src, Accessor::Type& dst)
+ {
+ if (src.is_string())
+ {
+ dst = gltf_type_to_enum(src.get_string().c_str());
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const Accessor::Type& src, Value& dst)
+ {
+ dst = enum_to_gltf_type(src);
+ return true;
+ }
+
+ // S32
+ template<>
+ inline bool copy(const Value& src, S32& dst)
+ {
+ if (src.is_int64())
+ {
+ dst = src.get_int64();
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const S32& src, Value& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+
+ // std::string
+ template<>
+ inline bool copy(const Value& src, std::string& dst)
+ {
+ if (src.is_string())
+ {
+ dst = src.get_string().c_str();
+ return true;
+ }
+ return false;
+ }
+
+ template<>
+ inline bool write(const std::string& src, Value& dst)
+ {
+ dst = src;
+ return true;
+ }
+
+ // mat4
+ template<>
+ inline bool copy(const Value& src, mat4& dst)
+ {
+ if (src.is_array())
+ {
+ const boost::json::array& arr = src.get_array();
+ if (arr.size() == 16)
+ {
+ // populate a temporary local in case
+ // we hit an error in the middle of the array
+ // (don't partially write a matrix)
+ mat4 t;
+ F32* p = glm::value_ptr(t);
+
+ for (U32 i = 0; i < arr.size(); ++i)
+ {
+ std::error_code ec;
+ p[i] = arr[i].to_number<F32>(ec);
+ if (ec)
+ {
+ return false;
+ }
+ }
+
+ dst = t;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ template<>
+ inline bool write(const mat4& src, Value& dst)
+ {
+ dst = boost::json::array();
+ boost::json::array& arr = dst.get_array();
+ arr.resize(16);
+ const F32* p = glm::value_ptr(src);
+ for (U32 i = 0; i < 16; ++i)
+ {
+ arr[i] = p[i];
+ }
+ return true;
+ }
+
+ // Material::AlphaMode
+ template<>
+ inline bool copy(const Value& src, Material::AlphaMode& dst)
+ {
+ if (src.is_string())
+ {
+ dst = gltf_alpha_mode_to_enum(src.get_string().c_str());
+ return true;
+ }
+ return true;
+ }
+
+ template<>
+ inline bool write(const Material::AlphaMode& src, Value& dst)
+ {
+ dst = enum_to_gltf_alpha_mode(src);
+ return true;
+ }
+
+ //
+ // ========================================================================================================
+
}
}
+
+
+
diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h
new file mode 100644
index 0000000000..b9698d4017
--- /dev/null
+++ b/indra/newview/gltf/common.h
@@ -0,0 +1,97 @@
+#pragma once
+
+/**
+ * @file common.h
+ * @brief LL GLTF Implementation
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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$
+ */
+
+#define GLM_ENABLE_EXPERIMENTAL 1
+
+#include "glm/vec2.hpp"
+#include "glm/vec3.hpp"
+#include "glm/vec4.hpp"
+#include "glm/mat4x4.hpp"
+#include "glm/gtc/type_ptr.hpp"
+#include "glm/ext/quaternion_float.hpp"
+#include "glm/gtx/quaternion.hpp"
+#include "glm/gtx/matrix_decompose.hpp"
+#include <boost/json.hpp>
+
+// Common types and constants used in the GLTF implementation
+namespace LL
+{
+ namespace GLTF
+ {
+ using Value = boost::json::value;
+
+ using mat4 = glm::mat4;
+ using vec4 = glm::vec4;
+ using vec3 = glm::vec3;
+ using vec2 = glm::vec2;
+ using quat = glm::quat;
+
+ constexpr S32 LINEAR = 9729;
+ constexpr S32 NEAREST = 9728;
+ constexpr S32 NEAREST_MIPMAP_NEAREST = 9984;
+ constexpr S32 LINEAR_MIPMAP_NEAREST = 9985;
+ constexpr S32 NEAREST_MIPMAP_LINEAR = 9986;
+ constexpr S32 LINEAR_MIPMAP_LINEAR = 9987;
+ constexpr S32 CLAMP_TO_EDGE = 33071;
+ constexpr S32 MIRRORED_REPEAT = 33648;
+ constexpr S32 REPEAT = 10497;
+
+
+ class Asset;
+ class Material;
+ class TextureInfo;
+ class NormalTextureInfo;
+ class OcclusionTextureInfo;
+ class Mesh;
+ class Node;
+ class Scene;
+ class Texture;
+ class Sampler;
+ class Image;
+ class Animation;
+ class Skin;
+ class Camera;
+ class Light;
+ class Primitive;
+ class Accessor;
+ class BufferView;
+ class Buffer;
+
+ enum class TextureType : U8
+ {
+ BASE_COLOR = 0,
+ NORMAL,
+ METALLIC_ROUGHNESS,
+ OCCLUSION,
+ EMISSIVE
+ };
+
+ constexpr U32 TEXTURE_TYPE_COUNT = 5;
+ }
+}
+
diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp
index 46af87ebbb..7613d81af4 100644
--- a/indra/newview/gltf/primitive.cpp
+++ b/indra/newview/gltf/primitive.cpp
@@ -28,12 +28,299 @@
#include "asset.h"
#include "buffer_util.h"
+#include "../llviewershadermgr.h"
+
+#include "mikktspace/mikktspace.hh"
+
+#if LL_USESYSTEMLIBS
+#include <meshoptimizer.h>
+#else
+#include "meshoptimizer/meshoptimizer.h"
+#endif
-#include "../lltinygltfhelper.h"
using namespace LL::GLTF;
+using namespace boost::json;
+
-void Primitive::allocateGLResources(Asset& asset)
+// Mesh data useful for Mikktspace tangent generation (and flat normal generation)
+struct MikktMesh
+{
+ std::vector<LLVector3> p; //positions
+ std::vector<LLVector3> n; //normals
+ std::vector<LLVector4> t; //tangents
+ std::vector<LLVector2> tc0; //texcoords 0
+ std::vector<LLVector2> tc1; //texcoords 1
+ std::vector<LLColor4U> c; //colors
+ std::vector<LLVector4> w; //weights
+ std::vector<U64> j; //joints
+
+ // initialize from src primitive and make an unrolled triangle list
+ // returns false if the Primitive cannot be converted to a triangle list
+ bool copy(const Primitive* prim)
+ {
+ bool indexed = !prim->mIndexArray.empty();
+ size_t vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size();
+
+ size_t triangle_count = 0;
+
+ if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP ||
+ prim->mMode == Primitive::Mode::TRIANGLE_FAN)
+ {
+ triangle_count = vert_count - 2;
+ }
+ else if (prim->mMode == Primitive::Mode::TRIANGLES)
+ {
+ triangle_count = vert_count / 3;
+ }
+ else
+ {
+ LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32)prim->mMode << LL_ENDL;
+ return false;
+ }
+
+ vert_count = triangle_count * 3;
+ llassert(vert_count <= size_t(U32_MAX)); // triangle_count will also naturally be under the limit
+
+ p.resize(vert_count);
+ n.resize(vert_count);
+ tc0.resize(vert_count);
+ c.resize(vert_count);
+
+ bool has_normals = !prim->mNormals.empty();
+ if (has_normals)
+ {
+ n.resize(vert_count);
+ }
+ bool has_tangents = !prim->mTangents.empty();
+ if (has_tangents)
+ {
+ t.resize(vert_count);
+ }
+
+ bool rigged = !prim->mWeights.empty();
+ if (rigged)
+ {
+ w.resize(vert_count);
+ j.resize(vert_count);
+ }
+
+ bool multi_uv = !prim->mTexCoords1.empty();
+ if (multi_uv)
+ {
+ tc1.resize(vert_count);
+ }
+
+ for (U32 tri_idx = 0; tri_idx < U32(triangle_count); ++tri_idx)
+ {
+ U32 idx[3];
+
+ if (prim->mMode == Primitive::Mode::TRIANGLES)
+ {
+ idx[0] = tri_idx * 3;
+ idx[1] = tri_idx * 3 + 1;
+ idx[2] = tri_idx * 3 + 2;
+ }
+ else if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP)
+ {
+ idx[0] = tri_idx;
+ idx[1] = tri_idx + 1;
+ idx[2] = tri_idx + 2;
+
+ if (tri_idx % 2 != 0)
+ {
+ std::swap(idx[1], idx[2]);
+ }
+ }
+ else if (prim->mMode == Primitive::Mode::TRIANGLE_FAN)
+ {
+ idx[0] = 0;
+ idx[1] = tri_idx + 1;
+ idx[2] = tri_idx + 2;
+ }
+
+ if (indexed)
+ {
+ idx[0] = prim->mIndexArray[idx[0]];
+ idx[1] = prim->mIndexArray[idx[1]];
+ idx[2] = prim->mIndexArray[idx[2]];
+ }
+
+ for (U32 v = 0; v < 3; ++v)
+ {
+ U32 i = tri_idx * 3 + v;
+ p[i].set(prim->mPositions[idx[v]].getF32ptr());
+ tc0[i].set(prim->mTexCoords0[idx[v]]);
+ c[i] = prim->mColors[idx[v]];
+
+ if (multi_uv)
+ {
+ tc1[i].set(prim->mTexCoords1[idx[v]]);
+ }
+
+ if (has_normals)
+ {
+ n[i].set(prim->mNormals[idx[v]].getF32ptr());
+ }
+
+ if (rigged)
+ {
+ w[i].set(prim->mWeights[idx[v]].getF32ptr());
+ j[i] = prim->mJoints[idx[v]];
+ }
+ }
+ }
+
+ return true;
+ }
+
+ void genNormals()
+ {
+ size_t tri_count = p.size() / 3;
+ for (size_t i = 0; i < tri_count; ++i)
+ {
+ LLVector3 v0 = p[i * 3];
+ LLVector3 v1 = p[i * 3 + 1];
+ LLVector3 v2 = p[i * 3 + 2];
+
+ LLVector3 normal = (v1 - v0) % (v2 - v0);
+ normal.normalize();
+
+ n[i * 3] = normal;
+ n[i * 3 + 1] = normal;
+ n[i * 3 + 2] = normal;
+ }
+ }
+
+ void genTangents()
+ {
+ t.resize(p.size());
+ mikk::Mikktspace ctx(*this);
+ ctx.genTangSpace();
+ }
+
+ // write to target primitive as an indexed triangle list
+ // Only modifies runtime data, does not modify the original GLTF data
+ void write(Primitive* prim) const
+ {
+ //re-weld
+ std::vector<meshopt_Stream> mos =
+ {
+ { &p[0], sizeof(LLVector3), sizeof(LLVector3) },
+ { &n[0], sizeof(LLVector3), sizeof(LLVector3) },
+ { &t[0], sizeof(LLVector4), sizeof(LLVector4) },
+ { &tc0[0], sizeof(LLVector2), sizeof(LLVector2) },
+ { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) }
+ };
+
+ if (!w.empty())
+ {
+ mos.push_back({ &w[0], sizeof(LLVector4), sizeof(LLVector4) });
+ mos.push_back({ &j[0], sizeof(U64), sizeof(U64) });
+ }
+
+ if (!tc1.empty())
+ {
+ mos.push_back({ &tc1[0], sizeof(LLVector2), sizeof(LLVector2) });
+ }
+
+ std::vector<U32> remap;
+ remap.resize(p.size());
+
+ size_t stream_count = mos.size();
+
+ size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos.data(), stream_count);
+
+ prim->mTexCoords0.resize(vert_count);
+ prim->mNormals.resize(vert_count);
+ prim->mTangents.resize(vert_count);
+ prim->mPositions.resize(vert_count);
+ prim->mColors.resize(vert_count);
+ if (!w.empty())
+ {
+ prim->mWeights.resize(vert_count);
+ prim->mJoints.resize(vert_count);
+ }
+ if (!tc1.empty())
+ {
+ prim->mTexCoords1.resize(vert_count);
+ }
+
+ prim->mIndexArray.resize(remap.size());
+
+ for (int i = 0; i < remap.size(); ++i)
+ {
+ U32 src_idx = i;
+ U32 dst_idx = remap[i];
+
+ prim->mIndexArray[i] = dst_idx;
+
+ prim->mPositions[dst_idx].load3(p[src_idx].mV);
+ prim->mNormals[dst_idx].load3(n[src_idx].mV);
+ prim->mTexCoords0[dst_idx] = tc0[src_idx];
+ prim->mTangents[dst_idx].loadua(t[src_idx].mV);
+ prim->mColors[dst_idx] = c[src_idx];
+
+ if (!w.empty())
+ {
+ prim->mWeights[dst_idx].loadua(w[src_idx].mV);
+ prim->mJoints[dst_idx] = j[src_idx];
+ }
+
+ if (!tc1.empty())
+ {
+ prim->mTexCoords1[dst_idx] = tc1[src_idx];
+ }
+ }
+
+ prim->mGLMode = LLRender::TRIANGLES;
+ }
+
+ uint32_t GetNumFaces()
+ {
+ return uint32_t(p.size()/3);
+ }
+
+ uint32_t GetNumVerticesOfFace(const uint32_t face_num)
+ {
+ return 3;
+ }
+
+ mikk::float3 GetPosition(const uint32_t face_num, const uint32_t vert_num)
+ {
+ F32* v = p[face_num * 3 + vert_num].mV;
+ return mikk::float3(v);
+ }
+
+ mikk::float3 GetTexCoord(const uint32_t face_num, const uint32_t vert_num)
+ {
+ F32* uv = tc0[face_num * 3 + vert_num].mV;
+ return mikk::float3(uv[0], 1.f-uv[1], 1.0f);
+ }
+
+ mikk::float3 GetNormal(const uint32_t face_num, const uint32_t vert_num)
+ {
+ F32* normal = n[face_num * 3 + vert_num].mV;
+ return mikk::float3(normal);
+ }
+
+ void SetTangentSpace(const uint32_t face_num, const uint32_t vert_num, mikk::float3 T, bool orientation)
+ {
+ S32 i = face_num * 3 + vert_num;
+ t[i].set(T.x, T.y, T.z, orientation ? 1.0f : -1.0f);
+ }
+};
+
+
+static void vertical_flip(std::vector<LLVector2>& texcoords)
+{
+ for (auto& tc : texcoords)
+ {
+ tc[1] = 1.f - tc[1];
+ }
+}
+
+bool Primitive::prep(Asset& asset)
{
// allocate vertex buffer
// We diverge from the intent of the GLTF format here to work with our existing render pipeline
@@ -66,7 +353,11 @@ void Primitive::allocateGLResources(Asset& asset)
}
else if (attribName == "TEXCOORD_0")
{
- copy(asset, accessor, mTexCoords);
+ copy(asset, accessor, mTexCoords0);
+ }
+ else if (attribName == "TEXCOORD_1")
+ {
+ copy(asset, accessor, mTexCoords1);
}
else if (attribName == "JOINTS_0")
{
@@ -83,41 +374,46 @@ void Primitive::allocateGLResources(Asset& asset)
{
Accessor& accessor = asset.mAccessors[mIndices];
copy(asset, accessor, mIndexArray);
+
+ for (auto& idx : mIndexArray)
+ {
+ if (idx >= mPositions.size())
+ {
+ LL_WARNS("GLTF") << "Invalid index array" << LL_ENDL;
+ return false;
+ }
+ }
+ }
+ else
+ { //everything must be indexed at runtime
+ mIndexArray.resize(mPositions.size());
+ for (U32 i = 0; i < mPositions.size(); ++i)
+ {
+ mIndexArray[i] = i;
+ }
}
- U32 mask = ATTRIBUTE_MASK;
+ U32 mask = LLVertexBuffer::MAP_VERTEX;
+
+ mShaderVariant = 0;
if (!mWeights.empty())
{
+ mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED;
mask |= LLVertexBuffer::MAP_WEIGHT4;
+ mask |= LLVertexBuffer::MAP_JOINT;
}
- mVertexBuffer = new LLVertexBuffer(mask);
- mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size()*2); // double the size of the index buffer for 32-bit indices
-
- mVertexBuffer->setBuffer();
- mVertexBuffer->setPositionData(mPositions.data());
-
- if (!mIndexArray.empty())
+ if (mTexCoords0.empty())
{
- mVertexBuffer->setIndexData(mIndexArray.data());
+ mTexCoords0.resize(mPositions.size());
}
- if (mTexCoords.empty())
- {
- mTexCoords.resize(mPositions.size());
- }
+ mask |= LLVertexBuffer::MAP_TEXCOORD0;
- // flip texcoord y, upload, then flip back (keep the off-spec data in vram only)
- for (auto& tc : mTexCoords)
+ if (!mTexCoords1.empty())
{
- tc[1] = 1.f - tc[1];
- }
- mVertexBuffer->setTexCoord0Data(mTexCoords.data());
-
- for (auto& tc : mTexCoords)
- {
- tc[1] = 1.f - tc[1];
+ mask |= LLVertexBuffer::MAP_TEXCOORD1;
}
if (mColors.empty())
@@ -125,54 +421,176 @@ void Primitive::allocateGLResources(Asset& asset)
mColors.resize(mPositions.size(), LLColor4U::white);
}
+ mask |= LLVertexBuffer::MAP_COLOR;
+
+ bool unlit = false;
+
// bake material basecolor into color array
if (mMaterial != INVALID_INDEX)
{
const Material& material = asset.mMaterials[mMaterial];
- LLColor4 baseColor = material.mMaterial->mBaseColor;
+ LLColor4 baseColor(glm::value_ptr(material.mPbrMetallicRoughness.mBaseColorFactor));
for (auto& dst : mColors)
{
dst = LLColor4U(baseColor * LLColor4(dst));
}
+
+ if (material.mUnlit.mPresent)
+ { // material uses KHR_materials_unlit
+ mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT;
+ unlit = true;
+ }
+
+ if (material.isMultiUV())
+ {
+ mShaderVariant |= LLGLSLShader::GLTFVariant::MULTI_UV;
+ }
+ }
+
+ if (mNormals.empty() && !unlit)
+ {
+ mTangents.clear();
+
+ if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP)
+ { //no normals and no surfaces, this primitive is unlit
+ mTangents.clear();
+ mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT;
+ unlit = true;
+ }
+ else
+ {
+ // unroll into non-indexed array of flat shaded triangles
+ MikktMesh data;
+ if (!data.copy(this))
+ {
+ return false;
+ }
+
+ data.genNormals();
+ data.genTangents();
+ data.write(this);
+ }
+ }
+
+ if (mTangents.empty() && !unlit)
+ { // NOTE: must be done last because tangent generation rewrites the other arrays
+ // adapted from usage of Mikktspace in llvolume.cpp
+ if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP)
+ {
+ // for points and lines, just make sure tangent is perpendicular to normal
+ mTangents.resize(mNormals.size());
+ LLVector4a up(0.f, 0.f, 1.f, 0.f);
+ LLVector4a left(1.f, 0.f, 0.f, 0.f);
+ for (U32 i = 0; i < mNormals.size(); ++i)
+ {
+ if (fabsf(mNormals[i].getF32ptr()[2]) < 0.999f)
+ {
+ mTangents[i] = up.cross3(mNormals[i]);
+ }
+ else
+ {
+ mTangents[i] = left.cross3(mNormals[i]);
+ }
+
+ mTangents[i].getF32ptr()[3] = 1.f;
+ }
+ }
+ else
+ {
+ MikktMesh data;
+ if (!data.copy(this))
+ {
+ return false;
+ }
+
+ data.genTangents();
+ data.write(this);
+ }
}
- mVertexBuffer->setColorData(mColors.data());
+ if (!mNormals.empty())
+ {
+ mask |= LLVertexBuffer::MAP_NORMAL;
+ }
- if (mNormals.empty())
+ if (!mTangents.empty())
{
- mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0));
+ mask |= LLVertexBuffer::MAP_TANGENT;
}
- mVertexBuffer->setNormalData(mNormals.data());
+ mAttributeMask = mask;
- if (mTangents.empty())
+ if (mMaterial != INVALID_INDEX)
{
- // TODO: generate tangents if needed
- mTangents.resize(mPositions.size(), LLVector4a(1, 0, 0, 1));
+ Material& material = asset.mMaterials[mMaterial];
+ if (material.mAlphaMode == Material::AlphaMode::BLEND)
+ {
+ mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND;
+ }
}
- mVertexBuffer->setTangentData(mTangents.data());
+ createOctree();
+
+ return true;
+}
+
+void Primitive::upload(LLVertexBuffer* buffer)
+{
+ mVertexBuffer = buffer;
+ // we store these buffer sizes as S32 elsewhere
+ llassert(mPositions.size() <= size_t(S32_MAX));
+ llassert(mIndexArray.size() <= size_t(S32_MAX / 2));
+
+ llassert(mVertexBuffer != nullptr);
+
+ // assert that buffer can hold this primitive
+ llassert(mVertexBuffer->getNumVerts() >= mPositions.size() + mVertexOffset);
+ llassert(mVertexBuffer->getNumIndices() >= mIndexArray.size() + mIndexOffset);
+ llassert(mVertexBuffer->getTypeMask() == mAttributeMask);
+
+ U32 offset = mVertexOffset;
+ U32 count = getVertexCount();
+
+ mVertexBuffer->setPositionData(mPositions.data(), offset, count);
+ mVertexBuffer->setColorData(mColors.data(), offset, count);
+
+ if (!mNormals.empty())
+ {
+ mVertexBuffer->setNormalData(mNormals.data(), offset, count);
+ }
+ if (!mTangents.empty())
+ {
+ mVertexBuffer->setTangentData(mTangents.data(), offset, count);
+ }
if (!mWeights.empty())
{
- std::vector<LLVector4a> weight_data;
- weight_data.resize(mWeights.size());
+ mVertexBuffer->setWeight4Data(mWeights.data(), offset, count);
+ mVertexBuffer->setJointData(mJoints.data(), offset, count);
+ }
- F32 max_weight = 1.f - FLT_EPSILON*100.f;
- LLVector4a maxw(max_weight, max_weight, max_weight, max_weight);
- for (U32 i = 0; i < mWeights.size(); ++i)
- {
- LLVector4a& w = weight_data[i];
- w.setMin(mWeights[i], maxw);
- w.add(mJoints[i]);
- };
+ // flip texcoord y, upload, then flip back (keep the off-spec data in vram only)
+ vertical_flip(mTexCoords0);
+ mVertexBuffer->setTexCoord0Data(mTexCoords0.data(), offset, count);
+ vertical_flip(mTexCoords0);
- mVertexBuffer->setWeight4Data(weight_data.data());
+ if (!mTexCoords1.empty())
+ {
+ vertical_flip(mTexCoords1);
+ mVertexBuffer->setTexCoord1Data(mTexCoords1.data(), offset, count);
+ vertical_flip(mTexCoords1);
}
- createOctree();
-
- mVertexBuffer->unbind();
+ if (!mIndexArray.empty())
+ {
+ std::vector<U32> index_array;
+ index_array.resize(mIndexArray.size());
+ for (U32 i = 0; i < mIndexArray.size(); ++i)
+ {
+ index_array[i] = mIndexArray[i] + mVertexOffset;
+ }
+ mVertexBuffer->setIndexData(index_array.data(), mIndexOffset, getIndexCount());
+ }
}
void initOctreeTriangle(LLVolumeTriangle* tri, F32 scaler, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2)
@@ -218,9 +636,9 @@ void Primitive::createOctree()
F32 scaler = 0.25f;
- if (mMode == TINYGLTF_MODE_TRIANGLES)
+ if (mMode == Mode::TRIANGLES)
{
- const U32 num_triangles = mVertexBuffer->getNumIndices() / 3;
+ const U32 num_triangles = getIndexCount() / 3;
// Initialize all the triangles we need
mOctreeTriangles.resize(num_triangles);
@@ -242,9 +660,9 @@ void Primitive::createOctree()
mOctree->insert(tri);
}
}
- else if (mMode == TINYGLTF_MODE_TRIANGLE_STRIP)
+ else if (mMode == Mode::TRIANGLE_STRIP)
{
- const U32 num_triangles = mVertexBuffer->getNumIndices() - 2;
+ const U32 num_triangles = getIndexCount() - 2;
// Initialize all the triangles we need
mOctreeTriangles.resize(num_triangles);
@@ -266,9 +684,9 @@ void Primitive::createOctree()
mOctree->insert(tri);
}
}
- else if (mMode == TINYGLTF_MODE_TRIANGLE_FAN)
+ else if (mMode == Mode::TRIANGLE_FAN)
{
- const U32 num_triangles = mVertexBuffer->getNumIndices() - 2;
+ const U32 num_triangles = getIndexCount() - 2;
// Initialize all the triangles we need
mOctreeTriangles.resize(num_triangles);
@@ -290,10 +708,10 @@ void Primitive::createOctree()
mOctree->insert(tri);
}
}
- else if (mMode == TINYGLTF_MODE_POINTS ||
- mMode == TINYGLTF_MODE_LINE ||
- mMode == TINYGLTF_MODE_LINE_LOOP ||
- mMode == TINYGLTF_MODE_LINE_STRIP)
+ else if (mMode == Mode::POINTS ||
+ mMode == Mode::LINES ||
+ mMode == Mode::LINE_LOOP ||
+ mMode == Mode::LINE_STRIP)
{
// nothing to do, no volume... maybe add some collision geometry around these primitive types?
}
@@ -327,13 +745,13 @@ const LLVolumeTriangle* Primitive::lineSegmentIntersect(const LLVector4a& start,
//create a proxy LLVolumeFace for the raycast
LLVolumeFace face;
face.mPositions = mPositions.data();
- face.mTexCoords = mTexCoords.data();
+ face.mTexCoords = mTexCoords0.data();
face.mNormals = mNormals.data();
face.mTangents = mTangents.data();
face.mIndices = nullptr; // unreferenced
- face.mNumIndices = mIndexArray.size();
- face.mNumVertices = mPositions.size();
+ face.mNumIndices = S32(mIndexArray.size());
+ face.mNumVertices = S32(mPositions.size());
LLOctreeTriangleRayIntersect intersect(start, dir, &face, &closest_t, intersection, tex_coord, normal, tangent_out);
intersect.traverse(mOctree);
@@ -351,50 +769,48 @@ Primitive::~Primitive()
mOctree = nullptr;
}
-
-const Primitive& Primitive::operator=(const tinygltf::Primitive& src)
+LLRender::eGeomModes gltf_mode_to_gl_mode(Primitive::Mode mode)
{
- // load material
- mMaterial = src.material;
-
- // load mode
- mMode = src.mode;
-
- // load indices
- mIndices = src.indices;
-
- // load attributes
- for (auto& it : src.attributes)
- {
- mAttributes[it.first] = it.second;
- }
-
- switch (mMode)
- {
- case TINYGLTF_MODE_POINTS:
- mGLMode = LLRender::POINTS;
- break;
- case TINYGLTF_MODE_LINE:
- mGLMode = LLRender::LINES;
- break;
- case TINYGLTF_MODE_LINE_LOOP:
- mGLMode = LLRender::LINE_LOOP;
- break;
- case TINYGLTF_MODE_LINE_STRIP:
- mGLMode = LLRender::LINE_STRIP;
- break;
- case TINYGLTF_MODE_TRIANGLES:
- mGLMode = LLRender::TRIANGLES;
- break;
- case TINYGLTF_MODE_TRIANGLE_STRIP:
- mGLMode = LLRender::TRIANGLE_STRIP;
- break;
- case TINYGLTF_MODE_TRIANGLE_FAN:
- mGLMode = LLRender::TRIANGLE_FAN;
- break;
+ switch (mode)
+ {
+ case Primitive::Mode::POINTS:
+ return LLRender::POINTS;
+ case Primitive::Mode::LINES:
+ return LLRender::LINES;
+ case Primitive::Mode::LINE_LOOP:
+ return LLRender::LINE_LOOP;
+ case Primitive::Mode::LINE_STRIP:
+ return LLRender::LINE_STRIP;
+ case Primitive::Mode::TRIANGLES:
+ return LLRender::TRIANGLES;
+ case Primitive::Mode::TRIANGLE_STRIP:
+ return LLRender::TRIANGLE_STRIP;
+ case Primitive::Mode::TRIANGLE_FAN:
+ return LLRender::TRIANGLE_FAN;
default:
- mGLMode = GL_TRIANGLES;
+ return LLRender::TRIANGLES;
}
+}
+
+void Primitive::serialize(boost::json::object& dst) const
+{
+ write(mMaterial, "material", dst, -1);
+ write(mMode, "mode", dst, Primitive::Mode::TRIANGLES);
+ write(mIndices, "indices", dst, INVALID_INDEX);
+ write(mAttributes, "attributes", dst);
+}
+const Primitive& Primitive::operator=(const Value& src)
+{
+ if (src.is_object())
+ {
+ copy(src, "material", mMaterial);
+ copy(src, "mode", mMode);
+ copy(src, "indices", mIndices);
+ copy(src, "attributes", mAttributes);
+
+ mGLMode = gltf_mode_to_gl_mode(mMode);
+ }
return *this;
}
+
diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h
index 07e8e7deb2..304eb26432 100644
--- a/indra/newview/gltf/primitive.h
+++ b/indra/newview/gltf/primitive.h
@@ -28,35 +28,39 @@
#include "llvertexbuffer.h"
#include "llvolumeoctree.h"
+#include "boost/json.hpp"
// LL GLTF Implementation
namespace LL
{
namespace GLTF
{
+ using Value = boost::json::value;
class Asset;
- constexpr U32 ATTRIBUTE_MASK =
- LLVertexBuffer::MAP_VERTEX |
- LLVertexBuffer::MAP_NORMAL |
- LLVertexBuffer::MAP_TEXCOORD0 |
- LLVertexBuffer::MAP_TANGENT |
- LLVertexBuffer::MAP_COLOR;
-
class Primitive
{
public:
- ~Primitive();
+ enum class Mode : U8
+ {
+ POINTS,
+ LINES,
+ LINE_LOOP,
+ LINE_STRIP,
+ TRIANGLES,
+ TRIANGLE_STRIP,
+ TRIANGLE_FAN
+ };
- // GPU copy of mesh data
- LLPointer<LLVertexBuffer> mVertexBuffer;
+ ~Primitive();
// CPU copy of mesh data
- std::vector<LLVector2> mTexCoords;
+ std::vector<LLVector2> mTexCoords0;
+ std::vector<LLVector2> mTexCoords1;
std::vector<LLVector4a> mNormals;
std::vector<LLVector4a> mTangents;
std::vector<LLVector4a> mPositions;
- std::vector<LLVector4a> mJoints;
+ std::vector<U64> mJoints;
std::vector<LLVector4a> mWeights;
std::vector<LLColor4U> mColors;
std::vector<U32> mIndexArray;
@@ -64,18 +68,33 @@ namespace LL
// raycast acceleration structure
LLPointer<LLVolumeOctree> mOctree;
std::vector<LLVolumeTriangle> mOctreeTriangles;
-
+
S32 mMaterial = -1;
- U32 mMode = TINYGLTF_MODE_TRIANGLES; // default to triangles
- U32 mGLMode = LLRender::TRIANGLES;
+ Mode mMode = Mode::TRIANGLES; // default to triangles
+ LLRender::eGeomModes mGLMode = LLRender::TRIANGLES; // for use with LLRender
S32 mIndices = -1;
- std::unordered_map<std::string, int> mAttributes;
+
+ // shader variant according to LLGLSLShader::GLTFVariant flags
+ U8 mShaderVariant = 0;
+
+ // vertex attribute mask
+ U32 mAttributeMask = 0;
+
+ // backpointer to vertex buffer (owned by Asset)
+ LLPointer<LLVertexBuffer> mVertexBuffer;
+ U32 mVertexOffset = 0;
+ U32 mIndexOffset = 0;
+
+ U32 getVertexCount() const { return (U32) mPositions.size(); }
+ U32 getIndexCount() const { return (U32) mIndexArray.size(); }
+
+ std::unordered_map<std::string, S32> mAttributes;
// create octree based on vertex buffer
// must be called before buffer is unmapped and after buffer is populated with good data
void createOctree();
- //get the LLVolumeTriangle that intersects with the given line segment at the point
+ //get the LLVolumeTriangle that intersects with the given line segment at the point
//closest to start. Moves end to the point of intersection. Returns nullptr if no intersection.
//Line segment must be in the same coordinate frame as this Primitive
const LLVolumeTriangle* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end,
@@ -84,10 +103,16 @@ namespace LL
LLVector4a* normal = NULL, // return the surface normal at the intersection point
LLVector4a* tangent = NULL // return the surface tangent at the intersection point
);
-
- const Primitive& operator=(const tinygltf::Primitive& src);
- void allocateGLResources(Asset& asset);
+ void serialize(boost::json::object& obj) const;
+ const Primitive& operator=(const Value& src);
+
+ bool prep(Asset& asset);
+
+ // upload geometry to given vertex buffer
+ // asserts that buffer is bound
+ // asserts that buffer is valid for this primitive
+ void upload(LLVertexBuffer* buffer);
};
}
}