diff options
author | Roxie Linden <roxie@lindenlab.com> | 2024-06-13 14:59:28 -0700 |
---|---|---|
committer | Roxie Linden <roxie@lindenlab.com> | 2024-06-13 14:59:28 -0700 |
commit | 5e60392c273f0c9c5efa765a05414c618381780a (patch) | |
tree | d1eedbb1dfa86e66532a6d8746b7a81e5a444d3a /indra/newview/gltf | |
parent | 0f3c3563b0861e8ea82b201aab8343d99f993bbc (diff) | |
parent | 100ebbab2437de7f5d124a0d7b8279a7a7b57656 (diff) |
Merge branch 'develop' of github.com:secondlife/viewer into roxie/webrtc-voice
Diffstat (limited to 'indra/newview/gltf')
-rw-r--r-- | indra/newview/gltf/README.md | 156 | ||||
-rw-r--r-- | indra/newview/gltf/accessor.cpp | 274 | ||||
-rw-r--r-- | indra/newview/gltf/accessor.h | 83 | ||||
-rw-r--r-- | indra/newview/gltf/animation.cpp | 282 | ||||
-rw-r--r-- | indra/newview/gltf/animation.h | 75 | ||||
-rw-r--r-- | indra/newview/gltf/asset.cpp | 1106 | ||||
-rw-r--r-- | indra/newview/gltf/asset.h | 290 | ||||
-rw-r--r-- | indra/newview/gltf/buffer_util.h | 817 | ||||
-rw-r--r-- | indra/newview/gltf/common.h | 83 | ||||
-rw-r--r-- | indra/newview/gltf/primitive.cpp | 604 | ||||
-rw-r--r-- | indra/newview/gltf/primitive.h | 48 |
11 files changed, 3107 insertions, 711 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..8b85eba3e5 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,21 @@ 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; + } } + + return true; } void Animation::update(Asset& asset, F32 dt) @@ -84,8 +97,7 @@ void Animation::apply(Asset& asset, float time) } }; - -void Animation::Sampler::allocateGLResources(Asset& asset) +bool Animation::Sampler::prep(Asset& asset) { Accessor& accessor = asset.mAccessors[mInput]; mMinTime = accessor.mMin[0]; @@ -95,10 +107,79 @@ 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; + if (time < mMinTime) { frameIndex = 0; @@ -108,17 +189,16 @@ void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F if (mFrameTimes.size() > 1) { + llassert(mFrameTimes.size() <= size_t(U32_MAX)); + frameIndex = U32(mFrameTimes.size()) - 2; + t = 1.f; + if (time > mMaxTime) { - frameIndex = mFrameTimes.size() - 2; - t = 1.0f; return; } - frameIndex = mFrameTimes.size() - 2; - t = 1.f; - - for (U32 i = 0; i < mFrameTimes.size() - 1; i++) + for (U32 i = 0; i < (U32)mFrameTimes.size() - 1; i++) { if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1]) { @@ -135,11 +215,13 @@ void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F } } -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) @@ -158,21 +240,21 @@ void Animation::RotationChannel::apply(Asset& asset, Sampler& sampler, F32 time) else { // 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) @@ -191,20 +273,22 @@ void Animation::TranslationChannel::apply(Asset& asset, Sampler& sampler, F32 ti else { // 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) @@ -223,65 +307,153 @@ void Animation::ScaleChannel::apply(Asset& asset, Sampler& sampler, F32 time) else { // 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()); + + write(channels, "channels", obj); +} - for (U32 i = 0; i < src.channels.size(); ++i) +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 + + 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..d5426fd4ce 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,10 @@ namespace LL S32 mOutput = INVALID_INDEX; std::string mInterpolation; - void allocateGLResources(Asset& asset); - - 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 +70,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 +104,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 +120,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 +139,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 +147,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..21be69aae2 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -30,13 +30,67 @@ #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" using namespace LL::GLTF; +using namespace boost::json; + + +namespace LL +{ + namespace GLTF + { + 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::updateTransforms(Asset& asset) { - LLMatrix4a identity; - identity.setIdentity(); + mat4 identity = glm::identity<mat4>(); + for (auto& nodeIndex : mNodes) { Node& node = asset.mNodes[nodeIndex]; @@ -44,7 +98,7 @@ void Scene::updateTransforms(Asset& asset) } } -void Scene::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview) +void Scene::updateRenderTransforms(Asset& asset, const mat4& modelview) { for (auto& nodeIndex : mNodes) { @@ -53,9 +107,9 @@ void Scene::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview) } } -void Node::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview) +void Node::updateRenderTransforms(Asset& asset, const mat4& modelview) { - matMul(mMatrix, modelview, mRenderMatrix); + mRenderMatrix = modelview * mMatrix; for (auto& childIndex : mChildren) { @@ -64,14 +118,13 @@ void Node::updateRenderTransforms(Asset& asset, const LLMatrix4a& modelview) } } -LLMatrix4a inverse(const LLMatrix4a& mat); - -void Node::updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix) +void Node::updateTransforms(Asset& asset, const mat4& parentMatrix) { makeMatrixValid(); - matMul(mMatrix, parentMatrix, mAssetMatrix); - mAssetMatrixInv = inverse(mAssetMatrix); - + mAssetMatrix = parentMatrix * mMatrix; + + mAssetMatrixInv = glm::inverse(mAssetMatrix); + S32 my_index = this - &asset.mNodes[0]; for (auto& childIndex : mChildren) @@ -90,26 +143,13 @@ void Asset::updateTransforms() } } -void Asset::updateRenderTransforms(const LLMatrix4a& modelview) +void Asset::updateRenderTransforms(const mat4& modelview) { -#if 0 - // traverse hierarchy and update render transforms from scratch - for (auto& scene : mScenes) - { - scene.updateRenderTransforms(*this, modelview); - } -#else // use mAssetMatrix to update render transforms from node list for (auto& node : mNodes) { - //if (node.mMesh != INVALID_INDEX) - { - matMul(node.mAssetMatrix, modelview, node.mRenderMatrix); - } + node.mRenderMatrix = modelview * node.mAssetMatrix; } - -#endif - } S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, @@ -133,12 +173,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 +202,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 +215,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 +260,933 @@ 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() +{ + 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); + } } - else if (!src.rotation.empty() || !src.translation.empty() || !src.scale.empty()) +} + +bool Asset::prep() +{ + // 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 (src.translation.size() == 3) + if (unsupported) + { + return false; + } + + // 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; + return true; +} - return *this; +Asset::Asset(const Value& src) +{ + *this = src; } -void Asset::render(bool opaque, bool rigged) +bool Asset::load(std::string_view filename) { - if (rigged) - { - gGL.loadIdentity(); - } + mFilename = filename; + std::string ext = gDirUtilp->getExtension(mFilename); - for (auto& node : mNodes) + std::ifstream file(filename.data(), std::ios::binary); + if (file.is_open()) { - if (node.mSkin != INVALID_INDEX) + std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); + file.close(); + + if (ext == "gltf") { - if (rigged) - { - Skin& skin = mSkins[node.mSkin]; - skin.uploadMatrixPalette(*this, node); - } - else - { - //skip static nodes if we're rendering rigged - continue; - } + Value val = parse(str); + *this = val; + return prep(); } - else if (rigged) + else if (ext == "glb") { - // skip rigged nodes if we're not rendering rigged - continue; + 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; +} - if (node.mMesh != INVALID_INDEX) +bool Asset::loadBinary(const std::string& data) +{ + // load from binary gltf + const U8* ptr = (const U8*)data.data(); + const U8* end = ptr + data.size(); + + if (end - ptr < 12) + { + LL_WARNS("GLTF") << "GLB file too short" << LL_ENDL; + return false; + } + + U32 magic = *(U32*)ptr; + ptr += 4; + + if (magic != 0x46546C67) + { + LL_WARNS("GLTF") << "Invalid GLB magic" << LL_ENDL; + return false; + } + + U32 version = *(U32*)ptr; + ptr += 4; + + if (version != 2) + { + LL_WARNS("GLTF") << "Unsupported GLB version" << LL_ENDL; + return false; + } + + U32 length = *(U32*)ptr; + ptr += 4; + + if (length != data.size()) + { + LL_WARNS("GLTF") << "GLB length mismatch" << LL_ENDL; + return false; + } + + U32 chunkLength = *(U32*)ptr; + ptr += 4; + + if (end - ptr < chunkLength + 8) + { + LL_WARNS("GLTF") << "GLB chunk too short" << LL_ENDL; + return false; + } + + U32 chunkType = *(U32*)ptr; + ptr += 4; + + if (chunkType != 0x4E4F534A) + { + LL_WARNS("GLTF") << "Invalid GLB chunk type" << LL_ENDL; + return false; + } + + Value val = parse(std::string_view((const char*)ptr, chunkLength)); + *this = val; + + if (mBuffers.size() > 0 && mBuffers[0].mUri.empty()) + { + // load binary chunk + ptr += chunkLength; + + if (end - ptr < 8) { - Mesh& mesh = mMeshes[node.mMesh]; - for (auto& primitive : mesh.mPrimitives) - { - if (!rigged) - { - gGL.loadMatrix((F32*)node.mRenderMatrix.mMatrix); - } - bool cull = true; - if (primitive.mMaterial != INVALID_INDEX) - { - Material& material = mMaterials[primitive.mMaterial]; + LL_WARNS("GLTF") << "GLB chunk too short" << LL_ENDL; + return false; + } - if ((material.mMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) == opaque) - { - continue; - } - material.mMaterial->bind(); - cull = !material.mMaterial->mDoubleSided; - } - else - { - if (!opaque) - { - continue; - } - LLFetchedGLTFMaterial::sDefault.bind(); - } + chunkLength = *(U32*)ptr; + ptr += 4; - LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0); + chunkType = *(U32*)ptr; + ptr += 4; - 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()); - } + 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; } } -} -void Asset::renderOpaque() -{ - render(true); + return prep(); } -void Asset::renderTransparent() +const Asset& Asset::operator=(const Value& src) { - render(false); -} + if (src.is_object()) + { + const object& obj = src.as_object(); -void Asset::update() -{ - F32 dt = gFrameTimeSeconds - mLastUpdateTime; + const auto it = obj.find("asset"); - if (dt > 0.f) - { - mLastUpdateTime = gFrameTimeSeconds; - if (mAnimations.size() > 0) + if (it != obj.end()) { - static LLCachedControl<U32> anim_idx(gSavedSettings, "GLTFAnimationIndex", 0); - static LLCachedControl<F32> anim_speed(gSavedSettings, "GLTFAnimationSpeed", 1.f); + const Value& asset = it->value(); - U32 idx = llclamp(anim_idx(), 0U, mAnimations.size() - 1); - mAnimations[idx].update(*this, dt*anim_speed); + copy(asset, "version", mVersion); + copy(asset, "minVersion", mMinVersion); + copy(asset, "generator", mGenerator); + copy(asset, "copyright", mCopyright); + copy(asset, "extras", mExtras); } - updateTransforms(); + 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); } + + 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); } -void Asset::allocateGLResources(const std::string& filename, const tinygltf::Model& model) +bool Asset::save(const std::string& filename) { - // do images first as materials may depend on images + // get folder path + std::string folder = gDirUtilp->getDirName(filename); + + // save images for (auto& image : mImages) { - image.allocateGLResources(); + if (!image.save(*this, folder)) + { + return false; + } } - // do materials before meshes as meshes may depend on materials - for (U32 i = 0; i < mMaterials.size(); ++i) + // save buffers + // NOTE: save buffers after saving images as saving images + // may remove image data from buffers + for (auto& buffer : mBuffers) { - mMaterials[i].allocateGLResources(*this); - LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true); + if (!buffer.save(*this, folder)) + { + return false; + } } - for (auto& mesh : mMeshes) - { - mesh.allocateGLResources(*this); - } + // 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()); - for (auto& animation : mAnimations) + return true; +} + +void Asset::eraseBufferView(S32 bufferView) +{ + mBufferViews.erase(mBufferViews.begin() + bufferView); + + for (auto& accessor : mAccessors) { - animation.allocateGLResources(*this); + if (accessor.mBufferView > bufferView) + { + accessor.mBufferView--; + } } - for (auto& skin : mSkins) + for (auto& image : mImages) { - skin.allocateGLResources(*this); + if (image.mBufferView > bufferView) + { + image.mBufferView--; + } } + } -const Asset& Asset::operator=(const tinygltf::Model& src) +LLViewerFetchedTexture* fetch_texture(const LLUUID& id); + +bool Image::prep(Asset& asset) { - mScenes.resize(src.scenes.size()); - for (U32 i = 0; i < src.scenes.size(); ++i) - { - mScenes[i] = src.scenes[i]; + 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); } - - mNodes.resize(src.nodes.size()); - for (U32 i = 0; i < src.nodes.size(); ++i) - { - mNodes[i] = src.nodes[i]; + 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]; - mMeshes.resize(src.meshes.size()); - for (U32 i = 0; i < src.meshes.size(); ++i) - { - mMeshes[i] = src.meshes[i]; - } + U8* data = buffer.mData.data() + bufferView.mByteOffset; - mMaterials.resize(src.materials.size()); - for (U32 i = 0; i < src.materials.size(); ++i) - { - mMaterials[i] = src.materials[i]; + 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; - mBuffers.resize(src.buffers.size()); - for (U32 i = 0; i < src.buffers.size(); ++i) + return false; + } + } + else { - mBuffers[i] = src.buffers[i]; + LL_WARNS("GLTF") << "Failed to load image: " << mName << LL_ENDL; + return false; } - mBufferViews.resize(src.bufferViews.size()); - for (U32 i = 0; i < src.bufferViews.size(); ++i) + return true; +} + + +void Image::clearData(Asset& asset) +{ + if (mBufferView != INVALID_INDEX) { - mBufferViews[i] = src.bufferViews[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); } - mTextures.resize(src.textures.size()); - for (U32 i = 0; i < src.textures.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()) { - mTextures[i] = src.textures[i]; + S32 idx = this - asset.mImages.data(); + name = llformat("image_%d", idx); } - mSamplers.resize(src.samplers.size()); - for (U32 i = 0; i < src.samplers.size(); ++i) + if (mBufferView != INVALID_INDEX) { - mSamplers[i] = src.samplers[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; - mImages.resize(src.images.size()); - for (U32 i = 0; i < src.images.size(); ++i) + // 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()) { - mImages[i] = src.images[i]; + 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; + + 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."; + } } - mAccessors.resize(src.accessors.size()); - for (U32 i = 0; i < src.accessors.size(); ++i) + 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 Material::TextureInfo::serialize(object& dst) const +{ + write(mIndex, "index", dst, INVALID_INDEX); + write(mTexCoord, "texCoord", dst, 0); + write_extensions(dst, &mTextureTransform, "KHR_texture_transform"); +} + +S32 Material::TextureInfo::getTexCoord() const +{ + if (mTextureTransform.mPresent && mTextureTransform.mTexCoord != INVALID_INDEX) { - mAnimations[i] = src.animations[i]; + return mTextureTransform.mTexCoord; } + return mTexCoord; +} + +bool Material::isMultiUV() const +{ + return mPbrMetallicRoughness.mBaseColorTexture.getTexCoord() != 0 || + mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord() != 0 || + mNormalTexture.getTexCoord() != 0 || + mOcclusionTexture.getTexCoord() != 0 || + mEmissiveTexture.getTexCoord() != 0; +} - mSkins.resize(src.skins.size()); - for (U32 i = 0; i < src.skins.size(); ++i) +const Material::TextureInfo& Material::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 Material::TextureInfo::operator==(const Material::TextureInfo& rhs) const { - mName = src.name; - return *this; + return mIndex == rhs.mIndex && mTexCoord == rhs.mTexCoord; +} + +bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const +{ + return !(*this == rhs); } -void Material::allocateGLResources(Asset& asset) +void Material::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 Material::OcclusionTextureInfo& Material::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 Material::NormalTextureInfo::serialize(object& dst) const { - for (auto& primitive : mPrimitives) + TextureInfo::serialize(dst); + write(mScale, "scale", dst, 1.f); +} + +const Material::NormalTextureInfo& Material::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); +} - return *this; +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; } -const Sampler& Sampler::operator=(const tinygltf::Sampler& src) +bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRoughness& rhs) const { - mMagFilter = src.magFilter; - mMinFilter = src.minFilter; - mWrapS = src.wrapS; - mWrapT = src.wrapT; - mName = src.name; + return !(*this == rhs); +} +const Material::Unlit& Material::Unlit::operator=(const Value& src) +{ + mPresent = true; return *this; } -void Skin::uploadMatrixPalette(Asset& asset, Node& node) +void Material::Unlit::serialize(object& dst) const { - // prepare matrix palette + // no members and object has already been created, nothing to do +} - // modelview will be applied by the shader, so assume matrix palette is in asset space - std::vector<glh::matrix4f> t_mp; +void TextureTransform::getPacked(F32* packed) const +{ + packed[0] = mScale.x; + packed[1] = mScale.y; + packed[2] = mRotation; + packed[3] = mOffset.x; + packed[4] = mOffset.y; + + packed[5] = packed[6] = packed[7] = 0.f; +} - t_mp.resize(mJoints.size()); - for (U32 i = 0; i < mJoints.size(); ++i) +const TextureTransform& TextureTransform::operator=(const Value& src) +{ + mPresent = true; + 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, "offset", mOffset); + copy(src, "rotation", mRotation); + copy(src, "scale", mScale); + copy(src, "texCoord", mTexCoord); + } + + return *this; +} + +void TextureTransform::serialize(object& dst) const +{ + 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); +} - //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 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"); +} +const Material& Material::operator=(const Value& src) +{ + if (src.is_object()) + { + 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; +} - std::vector<F32> glmp; - glmp.resize(mJoints.size() * 12); +void Mesh::serialize(object& dst) const +{ + write(mPrimitives, "primitives", dst); + write(mWeights, "weights", dst); + write(mName, "name", dst); +} - F32* mp = glmp.data(); +const Mesh& Mesh::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "primitives", mPrimitives); + copy(src, "weights", mWeights); + copy(src, "name", mName); + } - for (U32 i = 0; i < mJoints.size(); ++i) + return *this; +} + +bool Mesh::prep(Asset& asset) +{ + for (auto& primitive : mPrimitives) { - F32* m = (F32*)t_mp[i].m; + if (!primitive.prep(asset)) + { + return false; + } + } + + return true; +} + +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..ea3f7d480a 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -28,13 +28,20 @@ #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" extern F32SecondsImplicit gFrameTimeSeconds; +// wingdi defines OPAQUE, which conflicts with our enum +#if defined(OPAQUE) +#undef OPAQUE +#endif + // LL GLTF Implementation namespace LL { @@ -42,18 +49,117 @@ 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 floats + // dst MUST point to at least 8 floats + void getPacked(F32* dst) const; + + const TextureTransform& 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 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 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 +169,23 @@ 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 mRenderMatrix; //transform for rendering + 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 +203,15 @@ namespace LL std::string mName; - const Node& operator=(const tinygltf::Node& src); + const Node& operator=(const Value& src); + void serialize(boost::json::object& dst) const; // 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); + void updateRenderTransforms(Asset& asset, const mat4& modelview); // 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 +221,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 +258,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 +272,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 +295,39 @@ 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() - { - // allocate texture + 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); }; // 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 +340,28 @@ 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); + + // prepare for first time use + bool prep(); // Called periodically (typically once per frame) // Any ongoing work (such as animations) should be handled here @@ -241,11 +374,7 @@ namespace LL void updateTransforms(); // update node render transforms - void updateRenderTransforms(const LLMatrix4a& modelview); - - void render(bool opaque, bool rigged = false); - void renderOpaque(); - void renderTransparent(); + void updateRenderTransforms(const mat4& modelview); // 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 +386,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..4f660d7cfc --- /dev/null +++ b/indra/newview/gltf/common.h @@ -0,0 +1,83 @@ +#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 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; + } +} + diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index b57a0af18d..2280c7004e 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -9,7 +9,7 @@ * 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. + * 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 @@ -28,12 +28,295 @@ #include "asset.h" #include "buffer_util.h" +#include "../llviewershadermgr.h" + +#include "mikktspace/mikktspace.hh" + +#include "meshoptimizer/meshoptimizer.h" -#include "../lltinygltfhelper.h" using namespace LL::GLTF; +using namespace boost::json; + + +// 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]; + } +} -void Primitive::allocateGLResources(Asset& asset) +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 +349,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,96 +370,205 @@ void Primitive::allocateGLResources(Asset& asset) { Accessor& accessor = asset.mAccessors[mIndices]; copy(asset, accessor, mIndexArray); - } - U32 mask = ATTRIBUTE_MASK; - - if (!mWeights.empty()) - { - mask |= LLVertexBuffer::MAP_WEIGHT4; + for (auto& idx : mIndexArray) + { + if (idx >= mPositions.size()) + { + LL_WARNS("GLTF") << "Invalid index array" << LL_ENDL; + return false; + } + } } - 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()); + U32 mask = LLVertexBuffer::MAP_VERTEX; - if (!mIndexArray.empty()) + if (!mWeights.empty()) { - mVertexBuffer->setIndexData(mIndexArray.data()); + mask |= LLVertexBuffer::MAP_WEIGHT4; + mask |= LLVertexBuffer::MAP_JOINT; } - if (mTexCoords.empty()) + if (mTexCoords0.empty()) { - mTexCoords.resize(mPositions.size()); + mTexCoords0.resize(mPositions.size()); } - // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) - for (auto& tc : mTexCoords) - { - tc[1] = 1.f - tc[1]; - } - mVertexBuffer->setTexCoordData(mTexCoords.data()); + mask |= LLVertexBuffer::MAP_TEXCOORD0; - for (auto& tc : mTexCoords) + if (!mTexCoords1.empty()) { - tc[1] = 1.f - tc[1]; + mask |= LLVertexBuffer::MAP_TEXCOORD1; } if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } - + + mShaderVariant = 0; + + // TODO: support colorless vertex buffers + 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; + } } - mVertexBuffer->setColorData(mColors.data()); + 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 (mNormals.empty()) + 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); + } + } + + if (!mNormals.empty()) { - mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); + mask |= LLVertexBuffer::MAP_NORMAL; } - - mVertexBuffer->setNormalData(mNormals.data()); - if (mTangents.empty()) + if (!mTangents.empty()) { - // TODO: generate tangents if needed - mTangents.resize(mPositions.size(), LLVector4a(1, 0, 0, 1)); + mask |= LLVertexBuffer::MAP_TANGENT; } - mVertexBuffer->setTangentData(mTangents.data()); + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); + } + + mVertexBuffer = new LLVertexBuffer(mask); + // we store these buffer sizes as S32 elsewhere + llassert(mPositions.size() <= size_t(S32_MAX)); + llassert(mIndexArray.size() <= size_t(S32_MAX / 2)); + mVertexBuffer->allocateBuffer(U32(mPositions.size()), U32(mIndexArray.size() * 2)); // double the size of the index buffer for 32-bit indices + + mVertexBuffer->setBuffer(); + mVertexBuffer->setPositionData(mPositions.data()); + mVertexBuffer->setColorData(mColors.data()); + + if (!mNormals.empty()) + { + mVertexBuffer->setNormalData(mNormals.data()); + } + if (!mTangents.empty()) + { + mVertexBuffer->setTangentData(mTangents.data()); + } if (!mWeights.empty()) { - std::vector<LLVector4a> weight_data; - weight_data.resize(mWeights.size()); + mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED; + mVertexBuffer->setWeight4Data(mWeights.data()); + mVertexBuffer->setJointData(mJoints.data()); + } + + // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) + vertical_flip(mTexCoords0); + mVertexBuffer->setTexCoord0Data(mTexCoords0.data()); + vertical_flip(mTexCoords0); + + if (!mTexCoords1.empty()) + { + vertical_flip(mTexCoords1); + mVertexBuffer->setTexCoord1Data(mTexCoords1.data()); + vertical_flip(mTexCoords1); + } - 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]); - }; - mVertexBuffer->setWeight4Data(weight_data.data()); + if (!mIndexArray.empty()) + { + mVertexBuffer->setIndexData(mIndexArray.data()); } - + createOctree(); - + mVertexBuffer->unbind(); + + if (mMaterial != INVALID_INDEX) + { + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } + } + + return true; } void initOctreeTriangle(LLVolumeTriangle* tri, F32 scaler, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2) @@ -218,7 +614,7 @@ void Primitive::createOctree() F32 scaler = 0.25f; - if (mMode == TINYGLTF_MODE_TRIANGLES) + if (mMode == Mode::TRIANGLES) { const U32 num_triangles = mVertexBuffer->getNumIndices() / 3; // Initialize all the triangles we need @@ -235,14 +631,14 @@ void Primitive::createOctree() const LLVector4a& v0 = mPositions[i0]; const LLVector4a& v1 = mPositions[i1]; const LLVector4a& v2 = mPositions[i2]; - + initOctreeTriangle(tri, scaler, i0, i1, i2, v0, v1, v2); - + //insert mOctree->insert(tri); } } - else if (mMode == TINYGLTF_MODE_TRIANGLE_STRIP) + else if (mMode == Mode::TRIANGLE_STRIP) { const U32 num_triangles = mVertexBuffer->getNumIndices() - 2; // Initialize all the triangles we need @@ -266,7 +662,7 @@ 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; // Initialize all the triangles we need @@ -290,14 +686,14 @@ 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? } - + else { LL_ERRS() << "Unsupported Primitive mode" << LL_ENDL; @@ -327,13 +723,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 +747,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..7cc05cf831 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -28,35 +28,42 @@ #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: + enum class Mode : U8 + { + POINTS, + LINES, + LINE_LOOP, + LINE_STRIP, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLE_FAN + }; + ~Primitive(); // GPU copy of mesh data LLPointer<LLVertexBuffer> mVertexBuffer; - // CPU copy of mesh data - std::vector<LLVector2> mTexCoords; + // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code + 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 +71,22 @@ 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; + + 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 +95,11 @@ 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); }; } } |