From 5e2bac01cb6e8d3de3cc0e496d94a729e4740247 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 30 Apr 2024 17:26:48 -0500 Subject: #1357 GLTF Export Prototype --- indra/newview/gltf/animation.h | 2 - indra/newview/gltf/asset.cpp | 332 +++++++++++++++++++++++++++++++++++++++++ indra/newview/gltf/asset.h | 66 +++++++- 3 files changed, 397 insertions(+), 3 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h index 869eae963a..cc2045ebb2 100644 --- a/indra/newview/gltf/animation.h +++ b/indra/newview/gltf/animation.h @@ -81,8 +81,6 @@ namespace LL S32 mSampler = INVALID_INDEX; Target mTarget; - std::string mTargetPath; - std::string mName; const Channel& operator=(const tinygltf::AnimationChannel& src) { diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 313e82bf01..233faac545 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -33,6 +33,267 @@ using namespace LL::GLTF; + +namespace LL +{ + namespace GLTF + { + template + void copy(const std::vector& src, std::vector& dst) + { + dst.resize(src.size()); + for (U32 i = 0; i < src.size(); ++i) + { + copy(src[i], dst[i]); + } + } + + void copy(const Node& src, tinygltf::Node& dst) + { + if (src.mMatrixValid) + { + if (!src.mMatrix.asMatrix4().isIdentity()) + { + dst.matrix.resize(16); + for (U32 i = 0; i < 16; ++i) + { + dst.matrix[i] = src.mMatrix.getF32ptr()[i]; + } + } + } + else if (src.mTRSValid) + { + if (!src.mRotation.equals(glh::quaternionf::identity(), FLT_EPSILON)) + { + dst.rotation.resize(4); + for (U32 i = 0; i < 4; ++i) + { + dst.rotation[i] = src.mRotation.get_value()[i]; + } + } + + if (src.mTranslation != glh::vec3f(0.f, 0.f, 0.f)) + { + dst.translation.resize(3); + for (U32 i = 0; i < 3; ++i) + { + dst.translation[i] = src.mTranslation.v[i]; + } + } + + if (src.mScale != glh::vec3f(1.f, 1.f, 1.f)) + { + dst.scale.resize(3); + for (U32 i = 0; i < 3; ++i) + { + dst.scale[i] = src.mScale.v[i]; + } + } + } + + dst.children = src.mChildren; + dst.mesh = src.mMesh; + dst.skin = src.mSkin; + dst.name = src.mName; + } + + void copy(const Scene& src, tinygltf::Scene& dst) + { + dst.nodes = src.mNodes; + dst.name = src.mName; + } + + void copy(const Primitive& src, tinygltf::Primitive& dst) + { + for (auto& attrib : src.mAttributes) + { + dst.attributes[attrib.first] = attrib.second; + } + dst.indices = src.mIndices; + dst.material = src.mMaterial; + dst.mode = src.mMode; + } + + void copy(const Mesh& src, tinygltf::Mesh& mesh) + { + copy(src.mPrimitives, mesh.primitives); + mesh.weights = src.mWeights; + mesh.name = src.mName; + } + + void copy(const Material::TextureInfo& src, tinygltf::TextureInfo& dst) + { + dst.index = src.mIndex; + dst.texCoord = src.mTexCoord; + } + + void copy(const Material::OcclusionTextureInfo& src, tinygltf::OcclusionTextureInfo& dst) + { + dst.index = src.mIndex; + dst.texCoord = src.mTexCoord; + dst.strength = src.mStrength; + } + + void copy(const Material::NormalTextureInfo& src, tinygltf::NormalTextureInfo& dst) + { + dst.index = src.mIndex; + dst.texCoord = src.mTexCoord; + dst.scale = src.mScale; + } + + void copy(const Material::PbrMetallicRoughness& src, tinygltf::PbrMetallicRoughness& dst) + { + dst.baseColorFactor = { src.mBaseColorFactor.v[0], src.mBaseColorFactor.v[1], src.mBaseColorFactor.v[2], src.mBaseColorFactor.v[3] }; + copy(src.mBaseColorTexture, dst.baseColorTexture); + dst.metallicFactor = src.mMetallicFactor; + dst.roughnessFactor = src.mRoughnessFactor; + copy(src.mMetallicRoughnessTexture, dst.metallicRoughnessTexture); + } + + void copy(const Material& src, tinygltf::Material& material) + { + material.name = src.mName; + + material.emissiveFactor = { src.mEmissiveFactor.v[0], src.mEmissiveFactor.v[1], src.mEmissiveFactor.v[2] }; + copy(src.mPbrMetallicRoughness, material.pbrMetallicRoughness); + copy(src.mNormalTexture, material.normalTexture); + copy(src.mEmissiveTexture, material.emissiveTexture); + } + + void copy(const Texture& src, tinygltf::Texture& texture) + { + texture.sampler = src.mSampler; + texture.source = src.mSource; + texture.name = src.mName; + } + + void copy(const Sampler& src, tinygltf::Sampler& sampler) + { + sampler.magFilter = src.mMagFilter; + sampler.minFilter = src.mMinFilter; + sampler.wrapS = src.mWrapS; + sampler.wrapT = src.mWrapT; + sampler.name = src.mName; + } + + void copy(const Skin& src, tinygltf::Skin& skin) + { + skin.joints = src.mJoints; + skin.inverseBindMatrices = src.mInverseBindMatrices; + skin.skeleton = src.mSkeleton; + skin.name = src.mName; + } + + void copy(const Accessor& src, tinygltf::Accessor& accessor) + { + accessor.bufferView = src.mBufferView; + accessor.byteOffset = src.mByteOffset; + accessor.componentType = src.mComponentType; + accessor.minValues = src.mMin; + accessor.maxValues = src.mMax; + + accessor.count = src.mCount; + accessor.type = src.mType; + accessor.normalized = src.mNormalized; + accessor.name = src.mName; + } + + void copy(const Animation::Sampler& src, tinygltf::AnimationSampler& sampler) + { + sampler.input = src.mInput; + sampler.output = src.mOutput; + sampler.interpolation = src.mInterpolation; + } + + void copy(const Animation::Channel& src, tinygltf::AnimationChannel& channel) + { + channel.sampler = src.mSampler; + channel.target_node = src.mTarget.mNode; + channel.target_path = src.mTarget.mPath; + } + + void copy(const Animation& src, tinygltf::Animation& animation) + { + animation.name = src.mName; + + copy(src.mSamplers, animation.samplers); + + U32 channel_count = src.mRotationChannels.size() + src.mTranslationChannels.size() + src.mScaleChannels.size(); + + animation.channels.resize(channel_count); + + U32 idx = 0; + for (U32 i = 0; i < src.mTranslationChannels.size(); ++i) + { + copy(src.mTranslationChannels[i], animation.channels[idx++]); + } + + for (U32 i = 0; i < src.mRotationChannels.size(); ++i) + { + copy(src.mRotationChannels[i], animation.channels[idx++]); + } + + for (U32 i = 0; i < src.mScaleChannels.size(); ++i) + { + copy(src.mScaleChannels[i], animation.channels[idx++]); + } + } + + void copy(const Buffer& src, tinygltf::Buffer& buffer) + { + buffer.uri = src.mUri; + buffer.data = src.mData; + buffer.name = src.mName; + } + + void copy(const BufferView& src, tinygltf::BufferView& bufferView) + { + bufferView.buffer = src.mBuffer; + bufferView.byteOffset = src.mByteOffset; + bufferView.byteLength = src.mByteLength; + bufferView.byteStride = src.mByteStride; + bufferView.target = src.mTarget; + bufferView.name = src.mName; + } + + void copy(const Image& src, tinygltf::Image& image) + { + image.name = src.mName; + image.width = src.mWidth; + image.height = src.mHeight; + image.component = src.mComponent; + image.bits = src.mBits; + image.pixel_type = src.mPixelType; + + image.image = src.mData; + image.bufferView = src.mBufferView; + image.mimeType = src.mMimeType; + image.uri = src.mUri; + } + + void copy(const Asset & src, tinygltf::Model& dst) + { + dst.defaultScene = src.mDefaultScene; + dst.asset.copyright = src.mCopyright; + dst.asset.version = src.mVersion; + dst.asset.minVersion = src.mMinVersion; + dst.asset.generator = "Linden Lab Experimental GLTF Export"; + + copy(src.mScenes, dst.scenes); + copy(src.mNodes, dst.nodes); + copy(src.mMeshes, dst.meshes); + copy(src.mMaterials, dst.materials); + copy(src.mBuffers, dst.buffers); + copy(src.mBufferViews, dst.bufferViews); + copy(src.mTextures, dst.textures); + copy(src.mSamplers, dst.samplers); + copy(src.mImages, dst.images); + copy(src.mAccessors, dst.accessors); + copy(src.mAnimations, dst.animations); + copy(src.mSkins, dst.skins); + } + } +} void Scene::updateTransforms(Asset& asset) { LLMatrix4a identity; @@ -237,6 +498,8 @@ void Node::makeMatrixValid() mMatrix.loadu(t.m); mMatrixValid = true; } + + llassert(mMatrixValid); } void Node::makeTRSValid() @@ -252,6 +515,8 @@ void Node::makeTRSValid() mRotation.set_value(t); mTRSValid = true; } + + llassert(mTRSValid); } void Node::setRotation(const glh::quaternionf& q) @@ -318,6 +583,7 @@ const Node& Node::operator=(const tinygltf::Node& src) { // node specifies no transformation, set to identity mMatrix.setIdentity(); + mMatrixValid = true; } mChildren = src.children; @@ -467,6 +733,14 @@ void Asset::allocateGLResources(const std::string& filename, const tinygltf::Mod const Asset& Asset::operator=(const tinygltf::Model& src) { + mVersion = src.asset.version; + mMinVersion = src.asset.minVersion; + mGenerator = src.asset.generator; + mCopyright = src.asset.copyright; + + mDefaultScene = src.defaultScene; + + mScenes.resize(src.scenes.size()); for (U32 i = 0; i < src.scenes.size(); ++i) { @@ -542,9 +816,67 @@ const Asset& Asset::operator=(const tinygltf::Model& src) return *this; } +void Asset::save(tinygltf::Model& dst) +{ + LL::GLTF::copy(*this, dst); +} + + +const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) +{ + mIndex = src.index; + mTexCoord = src.texCoord; + return *this; +} + +const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const tinygltf::OcclusionTextureInfo& src) +{ + mIndex = src.index; + mTexCoord = src.texCoord; + mStrength = src.strength; + return *this; +} + +const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const tinygltf::NormalTextureInfo& src) +{ + mIndex = src.index; + mTexCoord = src.texCoord; + mScale = src.scale; + return *this; +} + +const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const tinygltf::PbrMetallicRoughness& src) +{ + if (src.baseColorFactor.size() == 4) + { + mBaseColorFactor.set_value(src.baseColorFactor[0], src.baseColorFactor[1], src.baseColorFactor[2], src.baseColorFactor[3]); + } + + mBaseColorTexture = src.baseColorTexture; + mMetallicFactor = src.metallicFactor; + mRoughnessFactor = src.roughnessFactor; + mMetallicRoughnessTexture = src.metallicRoughnessTexture; + + return *this; +} const Material& Material::operator=(const tinygltf::Material& src) { mName = src.name; + + if (src.emissiveFactor.size() == 3) + { + mEmissiveFactor.set_value(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]); + } + + mPbrMetallicRoughness = src.pbrMetallicRoughness; + mNormalTexture = src.normalTexture; + mOcclusionTexture = src.occlusionTexture; + mEmissiveTexture = src.emissiveTexture; + + mAlphaMode = src.alphaMode; + mAlphaCutoff = src.alphaCutoff; + mDoubleSided = src.doubleSided; + return *this; } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 6e576a1ffe..b8300c2d8a 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -45,11 +45,59 @@ namespace LL class Material { public: + class TextureInfo + { + public: + S32 mIndex = INVALID_INDEX; + S32 mTexCoord = 0; + + const TextureInfo& operator=(const tinygltf::TextureInfo& src); + }; + + class NormalTextureInfo : public TextureInfo + { + public: + F32 mScale = 1.0f; + + const NormalTextureInfo& operator=(const tinygltf::NormalTextureInfo& src); + }; + + class OcclusionTextureInfo : public TextureInfo + { + public: + F32 mStrength = 1.0f; + + const OcclusionTextureInfo& operator=(const tinygltf::OcclusionTextureInfo& src); + }; + + class PbrMetallicRoughness + { + public: + glh::vec4f mBaseColorFactor = glh::vec4f(1.f,1.f,1.f,1.f); + TextureInfo mBaseColorTexture; + F32 mMetallicFactor = 1.0f; + F32 mRoughnessFactor = 1.0f; + TextureInfo mMetallicRoughnessTexture; + const PbrMetallicRoughness& operator=(const tinygltf::PbrMetallicRoughness& src); + }; + + // 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 mMaterial; + PbrMetallicRoughness mPbrMetallicRoughness; + NormalTextureInfo mNormalTexture; + OcclusionTextureInfo mOcclusionTexture; + TextureInfo mEmissiveTexture; + + std::string mName; + glh::vec3f mEmissiveFactor = glh::vec3f(0.f, 0.f, 0.f); + std::string mAlphaMode = "OPAQUE"; + F32 mAlphaCutoff = 0.5f; + bool mDoubleSided = false; + const Material& operator=(const tinygltf::Material& src); @@ -179,11 +227,16 @@ namespace LL std::string mName; std::string mUri; std::string mMimeType; + + S32 mBufferView = INVALID_INDEX; + std::vector mData; S32 mWidth; S32 mHeight; S32 mComponent; S32 mBits; + S32 mPixelType; + LLPointer mTexture; const Image& operator=(const tinygltf::Image& src) @@ -196,7 +249,8 @@ namespace LL mHeight = src.height; mComponent = src.component; mBits = src.bits; - + mBufferView = src.bufferView; + mPixelType = src.pixel_type; return *this; } @@ -224,6 +278,13 @@ namespace LL std::vector mAnimations; std::vector mSkins; + std::string mVersion; + std::string mGenerator; + std::string mMinVersion; + std::string mCopyright; + + S32 mDefaultScene = INVALID_INDEX; + // the last time update() was called according to gFrameTimeSeconds F32 mLastUpdateTime = gFrameTimeSeconds; @@ -258,6 +319,9 @@ namespace LL ); const Asset& operator=(const tinygltf::Model& src); + + // save the asset to a tinygltf model + void save(tinygltf::Model& dst); }; } -- cgit v1.2.3 From 170765fd3505410dced83b342f87030fd9151e35 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 30 Apr 2024 21:57:42 -0500 Subject: #1357 Proof of concept on decomposing a GLTF scene into its component parts --- indra/newview/gltf/accessor.cpp | 18 +++++++ indra/newview/gltf/accessor.h | 4 ++ indra/newview/gltf/asset.cpp | 102 ++++++++++++++++++++++++++++++++++++++++ indra/newview/gltf/asset.h | 11 ++++- 4 files changed, 134 insertions(+), 1 deletion(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 55d36b7a32..9bfdc2afa6 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -30,6 +30,24 @@ using namespace LL::GLTF; +void Buffer::erase(Asset& asset, S32 offset, S32 length) +{ + S32 idx = this - &asset.mBuffers[0]; + + mData.erase(mData.begin() + offset, mData.begin() + offset + length); + + for (BufferView& view : asset.mBufferViews) + { + if (view.mBuffer == idx) + { + if (view.mByteOffset >= offset) + { + view.mByteOffset -= length; + } + } + } +} + const Buffer& Buffer::operator=(const tinygltf::Buffer& src) { mData = src.data; diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 9b8265d8da..6849cd8609 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -45,6 +45,10 @@ namespace LL std::string mName; std::string mUri; + // 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); + const Buffer& operator=(const tinygltf::Buffer& src); }; diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 233faac545..475cbcb6e5 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -30,9 +30,11 @@ #include "llvolumeoctree.h" #include "../llviewershadermgr.h" #include "../llviewercontrol.h" +#include "../llviewertexturelist.h" using namespace LL::GLTF; +#pragma optimize("", off) namespace LL { @@ -821,6 +823,106 @@ void Asset::save(tinygltf::Model& dst) LL::GLTF::copy(*this, dst); } +void Asset::decompose(const std::string& filename) +{ + // get folder path + std::string folder = gDirUtilp->getDirName(filename); + + // decompose images + for (auto& image : mImages) + { + image.decompose(*this, folder); + } +} + +void Asset::eraseBufferView(S32 bufferView) +{ + mBufferViews.erase(mBufferViews.begin() + bufferView); + + for (auto& accessor : mAccessors) + { + if (accessor.mBufferView > bufferView) + { + accessor.mBufferView--; + } + } + + for (auto& image : mImages) + { + if (image.mBufferView > bufferView) + { + image.mBufferView--; + } + } + +} + +void Image::decompose(Asset& asset, const std::string& folder) +{ + std::string name = mName; + if (name.empty()) + { + S32 idx = this - asset.mImages.data(); + name = llformat("image_%d", idx); + } + + if (mBufferView != INVALID_INDEX) + { + // save original image + 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 + { + extension = ".bin"; + } + + std::string filename = folder + "/" + name + "." + extension; + + // set URI to non-j2c file for now, but later we'll want to reference the j2c hash + mUri = name + "." + extension; + + std::ofstream file(filename, std::ios::binary); + file.write((const char*)buffer.mData.data() + bufferView.mByteOffset, bufferView.mByteLength); + + buffer.erase(asset, bufferView.mByteOffset, bufferView.mByteLength); + + asset.eraseBufferView(mBufferView); + } + + if (!mData.empty()) + { + // save j2c image + std::string filename = folder + "/" + name + ".j2c"; + + LLPointer raw = new LLImageRaw(mWidth, mHeight, mComponent); + U8* data = raw->allocateData(); + llassert(mData.size() == raw->getDataSize()); + memcpy(data, mData.data(), mData.size()); + + LLViewerTextureList::createUploadFile(raw, filename, 4096); + + mData.clear(); + } + + mWidth = -1; + mHeight = -1; + mComponent = -1; + mBits = -1; + mPixelType = -1; + mMimeType = ""; + +} const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) { diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index b8300c2d8a..cb28c4a572 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -254,6 +254,9 @@ namespace LL return *this; } + // save image clear local data, and set uri + void decompose(Asset& asset, const std::string& filename); + void allocateGLResources() { // allocate texture @@ -322,7 +325,13 @@ namespace LL // save the asset to a tinygltf model void save(tinygltf::Model& dst); - + + // decompose the asset to the given .gltf file + void decompose(const std::string& filename); + + // remove the bufferview at the given index + // updates all bufferview indices in this Asset as needed + void eraseBufferView(S32 bufferView); }; } } -- cgit v1.2.3 From a701cce8e0959503156a010683f6d0d57beaae36 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Wed, 1 May 2024 09:51:32 -0500 Subject: #1357 Preserve asset "extras" and switch to std::shared_ptr for referencing Asset from LLViewerObject to cut down rebuild time --- indra/newview/gltf/asset.cpp | 6 +++--- indra/newview/gltf/asset.h | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 475cbcb6e5..42f064699c 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -34,8 +34,6 @@ using namespace LL::GLTF; -#pragma optimize("", off) - namespace LL { namespace GLTF @@ -280,6 +278,7 @@ namespace LL dst.asset.version = src.mVersion; dst.asset.minVersion = src.mMinVersion; dst.asset.generator = "Linden Lab Experimental GLTF Export"; + dst.asset.extras = src.mExtras; copy(src.mScenes, dst.scenes); copy(src.mNodes, dst.nodes); @@ -739,9 +738,10 @@ const Asset& Asset::operator=(const tinygltf::Model& src) mMinVersion = src.asset.minVersion; mGenerator = src.asset.generator; mCopyright = src.asset.copyright; + mExtras = src.asset.extras; mDefaultScene = src.defaultScene; - + mScenes.resize(src.scenes.size()); for (U32 i = 0; i < src.scenes.size(); ++i) diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index cb28c4a572..5a62313705 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -265,7 +265,7 @@ namespace LL }; // C++ representation of a GLTF Asset - class Asset : public LLRefCount + class Asset { public: std::vector mScenes; @@ -287,6 +287,8 @@ namespace LL std::string mCopyright; S32 mDefaultScene = INVALID_INDEX; + tinygltf::Value mExtras; + // the last time update() was called according to gFrameTimeSeconds F32 mLastUpdateTime = gFrameTimeSeconds; -- cgit v1.2.3 From 799ebf21624edb8b42ca16b8cf51c138643efd32 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 10 May 2024 15:16:06 +0200 Subject: Fix broken merge and BOOL/bool issues --- indra/newview/gltf/asset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 42f064699c..5f2217a075 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -1092,7 +1092,7 @@ void Skin::uploadMatrixPalette(Asset& asset, Node& node) LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, mJoints.size(), - FALSE, + GL_FALSE, (GLfloat*)glmp.data()); } -- cgit v1.2.3 From 03c4458bdcc6821a3047f93b729d412e274ab9af Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 20 May 2024 13:22:55 -0500 Subject: #1392 GLTF Upload (#1394) * #1392 WIP -- Functional texture upload, stubbed out .bin upload. * #1392 GLTF Upload WIP -- Emulates successful upload Successfully uploads texture Emulates successful .gltf and .bin upload by injecting into local asset cache. Emulates rez from inventory by setting sculpt ID of selected object Currently fails in tinygltf parsing due to missing .bin * Add missing notification * Build fix * #1392 Add boost::json .gltf reading support. * #1392 boost::json GLTF writing prototype * Create gltf/README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * #1392 Add ability to render directly from LL::GLTF::Material * Fix for mac build * Mac build fix * #1392 AssetType and Inventory Type plumbing * #1392 More sane error handling and scheduling of uploads. * #1392 Actually attempt to upload glbin * Mac build fix, upload nudge * Mac build fix * Fix glTF asset uploads to server * Mac build fix (inline not static) * More consistent inline * Add glm, mac nudge. * #1392 For consistency with spec, start using glm over glh:: and LLFoo * Another attempt at placating Mac builds * Another Mac nudge * Mac build take 23 * #1392 Prune LLMatrix4a from GLTF namespace. * #1392 Fix for orientation being off (glm::quat is wxyz, not xyzw) * #1392 WIP -- Actually send the sculpt type and id, nudge readme and alpha rendering * #1392 Working download! * #1394 Add support for GLTFEnabled SimulatorFeature * #1392 Review feedback --------- Co-authored-by: Pepper Linden <3782201+rohvani@users.noreply.github.com> --- indra/newview/gltf/README.md | 156 +++++++++ indra/newview/gltf/accessor.cpp | 175 +++++++++- indra/newview/gltf/accessor.h | 38 +- indra/newview/gltf/animation.cpp | 175 +++++++++- indra/newview/gltf/animation.h | 49 +-- indra/newview/gltf/asset.cpp | 725 ++++++++++++++++++++++++++++++++------- indra/newview/gltf/asset.h | 146 +++++--- indra/newview/gltf/buffer_util.h | 608 ++++++++++++++++++++++++++++---- indra/newview/gltf/common.h | 66 ++++ indra/newview/gltf/primitive.cpp | 79 +++-- indra/newview/gltf/primitive.h | 8 +- 11 files changed, 1914 insertions(+), 311 deletions(-) create mode 100644 indra/newview/gltf/README.md create mode 100644 indra/newview/gltf/common.h (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/README.md b/indra/newview/gltf/README.md new file mode 100644 index 0000000000..8e7df0a439 --- /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 9bfdc2afa6..369ff4f240 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -27,8 +27,79 @@ #include "../llviewerprecompiledheaders.h" #include "asset.h" +#include "buffer_util.h" using namespace LL::GLTF; +using namespace boost::json; + +namespace LL +{ + 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) { @@ -48,6 +119,27 @@ void Buffer::erase(Asset& asset, S32 offset, S32 length) } } +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); + + // 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 Buffer& Buffer::operator=(const tinygltf::Buffer& src) { mData = src.data; @@ -56,6 +148,31 @@ const Buffer& Buffer::operator=(const tinygltf::Buffer& src) return *this; } + +void BufferView::serialize(object& dst) const +{ + 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 BufferView& BufferView::operator=(const tinygltf::BufferView& src) { mBuffer = src.buffer; @@ -67,13 +184,69 @@ const BufferView& BufferView::operator=(const tinygltf::BufferView& src) return *this; } +Accessor::Type tinygltf_type_to_enum(S32 type) +{ + switch (type) + { + case TINYGLTF_TYPE_SCALAR: + return Accessor::Type::SCALAR; + case TINYGLTF_TYPE_VEC2: + return Accessor::Type::VEC2; + case TINYGLTF_TYPE_VEC3: + return Accessor::Type::VEC3; + case TINYGLTF_TYPE_VEC4: + return Accessor::Type::VEC4; + case TINYGLTF_TYPE_MAT2: + return Accessor::Type::MAT2; + case TINYGLTF_TYPE_MAT3: + return Accessor::Type::MAT3; + case TINYGLTF_TYPE_MAT4: + return Accessor::Type::MAT4; + } + + LL_WARNS("GLTF") << "Unknown tinygltf accessor type: " << type << LL_ENDL; + llassert(false); + + return Accessor::Type::SCALAR; +} + +void Accessor::serialize(object& dst) const +{ + 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; +} + const Accessor& Accessor::operator=(const tinygltf::Accessor& src) { mBufferView = src.bufferView; mByteOffset = src.byteOffset; mComponentType = src.componentType; mCount = src.count; - mType = src.type; + mType = tinygltf_type_to_enum(src.type); mNormalized = src.normalized; mName = src.name; mMax = src.maxValues; diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 6849cd8609..3bbc5216bd 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -28,14 +28,15 @@ #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,11 +45,14 @@ namespace LL std::vector 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); + void serialize(boost::json::object& obj) const; + const Buffer& operator=(const Value& value); const Buffer& operator=(const tinygltf::Buffer& src); }; @@ -56,25 +60,25 @@ namespace LL { 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; + void serialize(boost::json::object& obj) const; + const BufferView& operator=(const Value& value); const BufferView& operator=(const tinygltf::BufferView& src); - }; class Accessor { public: S32 mBufferView = INVALID_INDEX; - S32 mByteOffset; - S32 mComponentType; - S32 mCount; + S32 mByteOffset = 0; + S32 mComponentType = 0; + S32 mCount = 0; std::vector mMax; std::vector mMin; @@ -89,11 +93,19 @@ namespace LL MAT4 = TINYGLTF_TYPE_MAT4 }; - S32 mType; - bool mNormalized; + Type mType = Type::SCALAR; + bool mNormalized = false; std::string mName; + void serialize(boost::json::object& obj) const; + const Accessor& operator=(const Value& value); const Accessor& operator=(const tinygltf::Accessor& src); }; + + // 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..8a542fb315 100644 --- a/indra/newview/gltf/animation.cpp +++ b/indra/newview/gltf/animation.cpp @@ -30,6 +30,7 @@ #include "buffer_util.h" using namespace LL::GLTF; +using namespace boost::json; void Animation::allocateGLResources(Asset& asset) { @@ -84,7 +85,6 @@ void Animation::apply(Asset& asset, float time) } }; - void Animation::Sampler::allocateGLResources(Asset& asset) { Accessor& accessor = asset.mAccessors[mInput]; @@ -97,8 +97,96 @@ void Animation::Sampler::allocateGLResources(Asset& asset) copy(asset, accessor, frame_times); } + +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; +} + + +const Animation::Sampler& Animation::Sampler::operator=(const tinygltf::AnimationSampler& src) +{ + mInput = src.input; + mOutput = src.output; + mInterpolation = src.interpolation; + + 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; +} + +const Animation::Channel& Animation::Channel::operator=(const tinygltf::AnimationChannel& src) +{ + mSampler = src.sampler; + + mTarget.mNode = src.target_node; + mTarget.mPath = src.target_path; + + return *this; +} + + void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F32& t) { + LL_PROFILE_ZONE_SCOPED; + if (time < mMinTime) { frameIndex = 0; @@ -158,13 +246,11 @@ 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); } } @@ -191,10 +277,10 @@ 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); } @@ -223,15 +309,61 @@ 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); } } +void Animation::serialize(object& obj) const +{ + write(mName, "name", obj); + write(mSamplers, "samplers", obj); + + std::vector 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); +} + +const Animation& Animation::operator=(const Value& src) +{ + if (src.is_object()) + { + const object& obj = src.as_object(); + + copy(obj, "name", mName); + copy(obj, "samplers", mSamplers); + + // make a temporory copy of generic channels + std::vector channels; + copy(obj, "channels", channels); + + // break up into channel specific implementations + for (auto& channel: channels) + { + 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; +} + const Animation& Animation::operator=(const tinygltf::Animation& src) { mName = src.name; @@ -275,6 +407,18 @@ void Skin::allocateGLResources(Asset& asset) } } +const Skin& Skin::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "name", mName); + copy(src, "skeleton", mSkeleton); + copy(src, "inverseBindMatrices", mInverseBindMatrices); + copy(src, "joints", mJoints); + } + return *this; +} + const Skin& Skin::operator=(const tinygltf::Skin& src) { mName = src.name; @@ -285,3 +429,10 @@ const Skin& Skin::operator=(const tinygltf::Skin& src) 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 cc2045ebb2..53c11d4669 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 { @@ -52,14 +51,10 @@ namespace LL void allocateGLResources(Asset& asset); - const Sampler& operator=(const tinygltf::AnimationSampler& src) - { - mInput = src.input; - mOutput = src.output; - mInterpolation = src.interpolation; - - return *this; - } + void serialize(boost::json::object& dst) const; + const Sampler& operator=(const Value& value); + const Sampler& operator=(const tinygltf::AnimationSampler& src); + // get the frame index and time for the specified time // asset -- the asset to reference for Accessors @@ -77,27 +72,29 @@ 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; - 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); + const Channel& operator=(const tinygltf::AnimationChannel& src); }; class RotationChannel : public Channel { public: - std::vector mRotations; + RotationChannel() = default; + RotationChannel(const Channel& channel) : Channel(channel) {} + + std::vector mRotations; const RotationChannel& operator=(const tinygltf::AnimationChannel& src) { @@ -116,7 +113,10 @@ namespace LL class TranslationChannel : public Channel { public: - std::vector mTranslations; + TranslationChannel() = default; + TranslationChannel(const Channel& channel) : Channel(channel) {} + + std::vector mTranslations; const TranslationChannel& operator=(const tinygltf::AnimationChannel& src) { @@ -135,7 +135,10 @@ namespace LL class ScaleChannel : public Channel { public: - std::vector mScales; + ScaleChannel() = default; + ScaleChannel(const Channel& channel) : Channel(channel) {} + + std::vector mScales; const ScaleChannel& operator=(const tinygltf::AnimationChannel& src) { @@ -165,6 +168,8 @@ namespace LL std::vector mTranslationChannels; std::vector mScaleChannels; + void serialize(boost::json::object& dst) const; + const Animation& operator=(const Value& value); const Animation& operator=(const tinygltf::Animation& src); void allocateGLResources(Asset& asset); diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 5f2217a075..c64d48662c 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -31,13 +31,51 @@ #include "../llviewershadermgr.h" #include "../llviewercontrol.h" #include "../llviewertexturelist.h" +#include "../pipeline.h" +#include "buffer_util.h" using namespace LL::GLTF; +using namespace boost::json; namespace LL { namespace GLTF { + 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"; + } + } + template void copy(const std::vector& src, std::vector& dst) { @@ -45,49 +83,48 @@ namespace LL for (U32 i = 0; i < src.size(); ++i) { copy(src[i], dst[i]); - } + } } void copy(const Node& src, tinygltf::Node& dst) { if (src.mMatrixValid) { - if (!src.mMatrix.asMatrix4().isIdentity()) + if (src.mMatrix != glm::identity()) { dst.matrix.resize(16); + const F32* m = glm::value_ptr(src.mMatrix); for (U32 i = 0; i < 16; ++i) { - dst.matrix[i] = src.mMatrix.getF32ptr()[i]; + dst.matrix[i] = m[i]; } } } else if (src.mTRSValid) { - if (!src.mRotation.equals(glh::quaternionf::identity(), FLT_EPSILON)) + if (src.mRotation != glm::identity()) { dst.rotation.resize(4); - for (U32 i = 0; i < 4; ++i) - { - dst.rotation[i] = src.mRotation.get_value()[i]; - } - } + dst.rotation[0] = src.mRotation.x; + dst.rotation[1] = src.mRotation.y; + dst.rotation[2] = src.mRotation.z; + dst.rotation[3] = src.mRotation.w; + } - if (src.mTranslation != glh::vec3f(0.f, 0.f, 0.f)) + if (src.mTranslation != vec3(0.f, 0.f, 0.f)) { dst.translation.resize(3); - for (U32 i = 0; i < 3; ++i) - { - dst.translation[i] = src.mTranslation.v[i]; - } + dst.translation[0] = src.mTranslation.x; + dst.translation[1] = src.mTranslation.y; + dst.translation[2] = src.mTranslation.z; } - if (src.mScale != glh::vec3f(1.f, 1.f, 1.f)) + if (src.mScale != vec3(1.f, 1.f, 1.f)) { dst.scale.resize(3); - for (U32 i = 0; i < 3; ++i) - { - dst.scale[i] = src.mScale.v[i]; - } + dst.scale[0] = src.mScale.x; + dst.scale[1] = src.mScale.y; + dst.scale[2] = src.mScale.z; } } @@ -143,7 +180,7 @@ namespace LL void copy(const Material::PbrMetallicRoughness& src, tinygltf::PbrMetallicRoughness& dst) { - dst.baseColorFactor = { src.mBaseColorFactor.v[0], src.mBaseColorFactor.v[1], src.mBaseColorFactor.v[2], src.mBaseColorFactor.v[3] }; + dst.baseColorFactor = { src.mBaseColorFactor.r, src.mBaseColorFactor.g, src.mBaseColorFactor.b, src.mBaseColorFactor.a }; copy(src.mBaseColorTexture, dst.baseColorTexture); dst.metallicFactor = src.mMetallicFactor; dst.roughnessFactor = src.mRoughnessFactor; @@ -154,7 +191,7 @@ namespace LL { material.name = src.mName; - material.emissiveFactor = { src.mEmissiveFactor.v[0], src.mEmissiveFactor.v[1], src.mEmissiveFactor.v[2] }; + material.emissiveFactor = { src.mEmissiveFactor.r, src.mEmissiveFactor.g, src.mEmissiveFactor.b }; copy(src.mPbrMetallicRoughness, material.pbrMetallicRoughness); copy(src.mNormalTexture, material.normalTexture); copy(src.mEmissiveTexture, material.emissiveTexture); @@ -193,7 +230,7 @@ namespace LL accessor.maxValues = src.mMax; accessor.count = src.mCount; - accessor.type = src.mType; + accessor.type = (S32) src.mType; accessor.normalized = src.mNormalized; accessor.name = src.mName; } @@ -278,7 +315,8 @@ namespace LL dst.asset.version = src.mVersion; dst.asset.minVersion = src.mMinVersion; dst.asset.generator = "Linden Lab Experimental GLTF Export"; - dst.asset.extras = src.mExtras; + + // NOTE: extras are lost in the conversion for now copy(src.mScenes, dst.scenes); copy(src.mNodes, dst.nodes); @@ -297,8 +335,8 @@ namespace LL } void Scene::updateTransforms(Asset& asset) { - LLMatrix4a identity; - identity.setIdentity(); + mat4 identity = glm::identity(); + for (auto& nodeIndex : mNodes) { Node& node = asset.mNodes[nodeIndex]; @@ -306,7 +344,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) { @@ -315,9 +353,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) { @@ -326,13 +364,12 @@ 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]; @@ -352,26 +389,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, @@ -398,9 +422,11 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, 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) @@ -423,8 +449,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) @@ -434,12 +462,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) { @@ -481,22 +507,7 @@ 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; } @@ -507,43 +518,72 @@ 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]); - - mScale.set_value(t.get_column(0).length(), t.get_column(1).length(), t.get_column(2).length()); - mRotation.set_value(t); + vec3 skew; + vec4 perspective; + glm::decompose(mMatrix, mScale, mRotation, mTranslation, skew, perspective); + 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; } +void Node::serialize(object& dst) const +{ + write(mName, "name", dst); + write(mMatrix, "matrix", dst, glm::identity()); + write(mRotation, "rotation", dst); + write(mTranslation, "translation", dst); + 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); +} + +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) + { + mTRSValid = true; + } + + return *this; +} + const Node& Node::operator=(const tinygltf::Node& src) { - F32* dstMatrix = mMatrix.getF32ptr(); + F32* dstMatrix = glm::value_ptr(mMatrix); if (src.matrix.size() == 16) { @@ -560,22 +600,21 @@ const Node& Node::operator=(const tinygltf::Node& src) // node has rotation/translation/scale, convert to matrix if (src.rotation.size() == 4) { - mRotation = glh::quaternionf((F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2], (F32)src.rotation[3]); + mRotation = quat((F32)src.rotation[3], (F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2]); } if (src.translation.size() == 3) { - mTranslation = glh::vec3f((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]); + mTranslation = vec3((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]); } - glh::vec3f scale; if (src.scale.size() == 3) { - mScale = glh::vec3f((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]); + mScale = vec3((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]); } else { - mScale.set_value(1.f, 1.f, 1.f); + mScale = vec3(1.f, 1.f, 1.f); } mTRSValid = true; @@ -583,7 +622,7 @@ const Node& Node::operator=(const tinygltf::Node& src) else { // node specifies no transformation, set to identity - mMatrix.setIdentity(); + mMatrix = glm::identity(); mMatrixValid = true; } @@ -595,6 +634,50 @@ const Node& Node::operator=(const tinygltf::Node& src) 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; +} + +const Image& Image::operator=(const tinygltf::Image& src) +{ + mName = src.name; + mWidth = src.width; + mHeight = src.height; + mComponent = src.component; + mBits = src.bits; + mPixelType = src.pixel_type; + mUri = src.uri; + mBufferView = src.bufferView; + mMimeType = src.mimeType; + mData = src.image; + return *this; +} + + void Asset::render(bool opaque, bool rigged) { if (rigged) @@ -623,7 +706,6 @@ void Asset::render(bool opaque, bool rigged) continue; } - if (node.mMesh != INVALID_INDEX) { Mesh& mesh = mMeshes[node.mMesh]; @@ -631,19 +713,28 @@ void Asset::render(bool opaque, bool rigged) { if (!rigged) { - gGL.loadMatrix((F32*)node.mRenderMatrix.mMatrix); + gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix)); } bool cull = true; if (primitive.mMaterial != INVALID_INDEX) { Material& material = mMaterials[primitive.mMaterial]; + bool mat_opaque = material.mAlphaMode != Material::AlphaMode::BLEND; - if ((material.mMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) == opaque) + if (mat_opaque != opaque) { continue; } - material.mMaterial->bind(); - cull = !material.mMaterial->mDoubleSided; + + if (mMaterials[primitive.mMaterial].mMaterial.notNull()) + { + material.mMaterial->bind(); + } + else + { + material.bind(*this); + } + cull = !material.mDoubleSided; } else { @@ -709,11 +800,16 @@ void Asset::allocateGLResources(const std::string& filename, const tinygltf::Mod image.allocateGLResources(); } + // do materials before meshes as meshes may depend on materials - for (U32 i = 0; i < mMaterials.size(); ++i) + if (!filename.empty()) { - mMaterials[i].allocateGLResources(*this); - LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true); + for (U32 i = 0; i < mMaterials.size(); ++i) + { + // HACK: local preview mode, load material from model for now + mMaterials[i].allocateGLResources(*this); + LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true); + } } for (auto& mesh : mMeshes) @@ -732,16 +828,26 @@ void Asset::allocateGLResources(const std::string& filename, const tinygltf::Mod } } +Asset::Asset(const tinygltf::Model& src) +{ + *this = src; +} + +Asset::Asset(const Value& src) +{ + *this = src; +} + const Asset& Asset::operator=(const tinygltf::Model& src) { mVersion = src.asset.version; mMinVersion = src.asset.minVersion; mGenerator = src.asset.generator; mCopyright = src.asset.copyright; - mExtras = src.asset.extras; + + // note: extras are lost in the conversion for now mDefaultScene = src.defaultScene; - mScenes.resize(src.scenes.size()); for (U32 i = 0; i < src.scenes.size(); ++i) @@ -818,11 +924,69 @@ const Asset& Asset::operator=(const tinygltf::Model& src) return *this; } +const Asset& Asset::operator=(const Value& src) +{ + if (src.is_object()) + { + const object& obj = src.as_object(); + + const auto it = obj.find("asset"); + + if (it != obj.end()) + { + const Value& asset = it->value(); + + copy(asset, "version", mVersion); + copy(asset, "minVersion", mMinVersion); + copy(asset, "generator", mGenerator); + copy(asset, "copyright", mCopyright); + copy(asset, "extras", mExtras); + } + + copy(obj, "defaultScene", mDefaultScene); + 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); + } + + return *this; +} + void Asset::save(tinygltf::Model& dst) { LL::GLTF::copy(*this, dst); } +void Asset::serialize(object& dst) const +{ + write(mVersion, "version", dst); + write(mMinVersion, "minVersion", dst, std::string()); + write(mGenerator, "generator", dst); + write(mDefaultScene, "defaultScene", dst, 0); + + 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); +} + void Asset::decompose(const std::string& filename) { // get folder path @@ -857,6 +1021,41 @@ void Asset::eraseBufferView(S32 bufferView) } +LLViewerFetchedTexture* fetch_texture(const LLUUID& id); + +void Image::allocateGLResources() +{ + LLUUID id; + if (LLUUID::parseUUID(mUri, &id) && id.notNull()) + { + mTexture = fetch_texture(id); + } +} + + +void Image::clearData(Asset& asset) +{ + if (mBufferView != INVALID_INDEX) + { + // 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); + } + + mData.clear(); + mBufferView = INVALID_INDEX; + mWidth = -1; + mHeight = -1; + mComponent = -1; + mBits = -1; + mPixelType = -1; + mMimeType = ""; +} + void Image::decompose(Asset& asset, const std::string& folder) { std::string name = mName; @@ -894,12 +1093,9 @@ void Image::decompose(Asset& asset, const std::string& folder) std::ofstream file(filename, std::ios::binary); file.write((const char*)buffer.mData.data() + bufferView.mByteOffset, bufferView.mByteLength); - - buffer.erase(asset, bufferView.mByteOffset, bufferView.mByteLength); - - asset.eraseBufferView(mBufferView); } +#if 0 if (!mData.empty()) { // save j2c image @@ -907,21 +1103,44 @@ void Image::decompose(Asset& asset, const std::string& folder) LLPointer raw = new LLImageRaw(mWidth, mHeight, mComponent); U8* data = raw->allocateData(); - llassert(mData.size() == raw->getDataSize()); + llassert_always(mData.size() == raw->getDataSize()); memcpy(data, mData.data(), mData.size()); LLViewerTextureList::createUploadFile(raw, filename, 4096); mData.clear(); } +#endif - mWidth = -1; - mHeight = -1; - mComponent = -1; - mBits = -1; - mPixelType = -1; - mMimeType = ""; + clearData(asset); +} + +void Material::TextureInfo::serialize(object& dst) const +{ + write(mIndex, "index", dst, INVALID_INDEX); + write(mTexCoord, "texCoord", dst, 0); +} + +const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "index", mIndex); + copy(src, "texCoord", mTexCoord); + } + + return *this; +} + +bool Material::TextureInfo::operator==(const Material::TextureInfo& rhs) const +{ + return mIndex == rhs.mIndex && mTexCoord == rhs.mTexCoord; +} + +bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const +{ + return !(*this == rhs); } const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) @@ -931,6 +1150,25 @@ const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::Te return *this; } +void Material::OcclusionTextureInfo::serialize(object& dst) const +{ + write(mIndex, "index", dst, INVALID_INDEX); + write(mTexCoord, "texCoord", dst, 0); + write(mStrength, "strength", dst, 1.f); +} + +const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "index", mIndex); + copy(src, "texCoord", mTexCoord); + copy(src, "strength", mStrength); + } + + return *this; +} + const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const tinygltf::OcclusionTextureInfo& src) { mIndex = src.index; @@ -939,6 +1177,24 @@ const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=( return *this; } +void Material::NormalTextureInfo::serialize(object& dst) const +{ + write(mIndex, "index", dst, INVALID_INDEX); + write(mTexCoord, "texCoord", dst, 0); + write(mScale, "scale", dst, 1.f); +} + +const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "index", mIndex); + copy(src, "texCoord", mTexCoord); + copy(src, "scale", mScale); + } + + return *this; +} const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const tinygltf::NormalTextureInfo& src) { mIndex = src.index; @@ -947,11 +1203,48 @@ const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const return *this; } +const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const Value& src) +{ + 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; +} + +void Material::PbrMetallicRoughness::serialize(object& dst) const +{ + write(mBaseColorFactor, "baseColorFactor", dst, vec4(1.f, 1.f, 1.f, 1.f)); + write(mBaseColorTexture, "baseColorTexture", dst); + write(mMetallicFactor, "metallicFactor", dst, 1.f); + write(mRoughnessFactor, "roughnessFactor", dst, 1.f); + write(mMetallicRoughnessTexture, "metallicRoughnessTexture", dst); +} + +bool Material::PbrMetallicRoughness::operator==(const Material::PbrMetallicRoughness& rhs) const +{ + return mBaseColorFactor == rhs.mBaseColorFactor && + mBaseColorTexture == rhs.mBaseColorTexture && + mMetallicFactor == rhs.mMetallicFactor && + mRoughnessFactor == rhs.mRoughnessFactor && + mMetallicRoughnessTexture == rhs.mMetallicRoughnessTexture; +} + +bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRoughness& rhs) const +{ + return !(*this == rhs); +} + const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const tinygltf::PbrMetallicRoughness& src) { if (src.baseColorFactor.size() == 4) { - mBaseColorFactor.set_value(src.baseColorFactor[0], src.baseColorFactor[1], src.baseColorFactor[2], src.baseColorFactor[3]); + mBaseColorFactor = vec4(src.baseColorFactor[0], src.baseColorFactor[1], src.baseColorFactor[2], src.baseColorFactor[3]); } mBaseColorTexture = src.baseColorTexture; @@ -961,13 +1254,129 @@ const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=( return *this; } + +static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, LLViewerTexture* fallback) +{ + if (info.mIndex != INVALID_INDEX) + { + LLViewerTexture* tex = asset.mImages[asset.mTextures[info.mIndex].mSource].mTexture; + if (tex) + { + tex->addTextureStats(2048.f * 2048.f); + LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex); + } + else + { + LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback); + } + } + else + { + LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback); + } +} + +void Material::bind(Asset& asset) +{ + // bind for rendering (derived from LLFetchedGLTFMaterial::bind) + // glTF 2.0 Specification 3.9.4. Alpha Coverage + // mAlphaCutoff is only valid for LLGLTFMaterial::ALPHA_MODE_MASK + F32 min_alpha = -1.0; + + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + + if (!LLPipeline::sShadowRender || (mAlphaMode == Material::AlphaMode::BLEND)) + { + if (mAlphaMode == Material::AlphaMode::MASK) + { + // dividing the alpha cutoff by transparency here allows the shader to compare against + // the alpha value of the texture without needing the transparency value + if (mPbrMetallicRoughness.mBaseColorFactor.a > 0.f) + { + min_alpha = mAlphaCutoff / mPbrMetallicRoughness.mBaseColorFactor.a; + } + else + { + min_alpha = 1024.f; + } + } + shader->uniform1f(LLShaderMgr::MINIMUM_ALPHA, min_alpha); + } + + bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep); + + F32 base_color_packed[8]; + //mTextureTransform[GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed); + LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed); + shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, (F32*)base_color_packed); + + if (!LLPipeline::sShadowRender) + { + bindTexture(asset, LLShaderMgr::BUMP_MAP, mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep); + bindTexture(asset, LLShaderMgr::SPECULAR_MAP, mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep); + bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep); + + // NOTE: base color factor is baked into vertex stream + + shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, mPbrMetallicRoughness.mRoughnessFactor); + shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, mPbrMetallicRoughness.mMetallicFactor); + shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(mEmissiveFactor)); + + F32 normal_packed[8]; + //mTextureTransform[GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed); + LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed); + shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, (F32*)normal_packed); + + F32 metallic_roughness_packed[8]; + //mTextureTransform[GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed); + LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed); + shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, (F32*)metallic_roughness_packed); + + F32 emissive_packed[8]; + //mTextureTransform[GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed); + LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed); + shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, (F32*)emissive_packed); + } +} + +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); +} + +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); + } + return *this; +} + + const Material& Material::operator=(const tinygltf::Material& src) { mName = src.name; if (src.emissiveFactor.size() == 3) { - mEmissiveFactor.set_value(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]); + mEmissiveFactor = vec3(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]); } mPbrMetallicRoughness = src.pbrMetallicRoughness; @@ -975,7 +1384,7 @@ const Material& Material::operator=(const tinygltf::Material& src) mOcclusionTexture = src.occlusionTexture; mEmissiveTexture = src.emissiveTexture; - mAlphaMode = src.alphaMode; + mAlphaMode = gltf_alpha_mode_to_enum(src.alphaMode); mAlphaCutoff = src.alphaCutoff; mDoubleSided = src.doubleSided; @@ -984,10 +1393,31 @@ const Material& Material::operator=(const tinygltf::Material& src) void Material::allocateGLResources(Asset& asset) { - // allocate material + // HACK: allocate an LLFetchedGLTFMaterial for now + // later we'll render directly from the GLTF Images + // and BufferViews mMaterial = new LLFetchedGLTFMaterial(); } +void Mesh::serialize(object& dst) const +{ + write(mPrimitives, "primitives", dst); + write(mWeights, "weights", dst); + write(mName, "name", dst); +} + +const Mesh& Mesh::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "primitives", mPrimitives); + copy(src, "weights", mWeights); + copy(src, "name", mName); + } + + return *this; + +} const Mesh& Mesh::operator=(const tinygltf::Mesh& src) { mPrimitives.resize(src.primitives.size()); @@ -1010,6 +1440,20 @@ void Mesh::allocateGLResources(Asset& asset) } } +void Scene::serialize(object& dst) const +{ + write(mNodes, "nodes", dst); + write(mName, "name", dst); +} + +const Scene& Scene::operator=(const Value& src) +{ + copy(src, "nodes", mNodes); + copy(src, "name", mName); + + return *this; +} + const Scene& Scene::operator=(const tinygltf::Scene& src) { mNodes = src.nodes; @@ -1018,6 +1462,25 @@ const Scene& Scene::operator=(const tinygltf::Scene& src) return *this; } +void Texture::serialize(object& dst) const +{ + write(mSampler, "sampler", dst, INVALID_INDEX); + write(mSource, "source", dst, INVALID_INDEX); + write(mName, "name", dst); +} + +const Texture& Texture::operator=(const Value& src) +{ + if (src.is_object()) + { + copy(src, "sampler", mSampler); + copy(src, "source", mSource); + copy(src, "name", mName); + } + + return *this; +} + const Texture& Texture::operator=(const tinygltf::Texture& src) { mSampler = src.sampler; @@ -1027,6 +1490,27 @@ const Texture& Texture::operator=(const tinygltf::Texture& src) 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; +} + const Sampler& Sampler::operator=(const tinygltf::Sampler& src) { mMagFilter = src.magFilter; @@ -1043,23 +1527,14 @@ void Skin::uploadMatrixPalette(Asset& asset, Node& node) // prepare matrix palette // modelview will be applied by the shader, so assume matrix palette is in asset space - std::vector t_mp; + std::vector t_mp; t_mp.resize(mJoints.size()); for (U32 i = 0; i < mJoints.size(); ++i) { Node& joint = asset.mNodes[mJoints[i]]; - - //t_mp[i].set_value(joint.mRenderMatrix.getF32ptr()); - //t_mp[i] = t_mp[i] * mInverseBindMatricesData[i]; - - //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]; - + t_mp[i] = joint.mRenderMatrix * mInverseBindMatricesData[i]; } std::vector glmp; @@ -1070,7 +1545,7 @@ void Skin::uploadMatrixPalette(Asset& asset, Node& node) for (U32 i = 0; i < mJoints.size(); ++i) { - F32* m = (F32*)t_mp[i].m; + F32* m = glm::value_ptr(t_mp[i]); U32 idx = i * 12; diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 5a62313705..1d707cbeba 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -32,9 +32,16 @@ #include "accessor.h" #include "primitive.h" #include "animation.h" +#include "boost/json.hpp" +#include "common.h" extern F32SecondsImplicit gFrameTimeSeconds; +// wingdi defines OPAQUE, which conflicts with our enum +#if defined(OPAQUE) +#undef OPAQUE +#endif + // LL GLTF Implementation namespace LL { @@ -45,13 +52,26 @@ namespace LL class Material { public: + + enum class AlphaMode + { + OPAQUE, + MASK, + BLEND + }; + class TextureInfo { public: S32 mIndex = INVALID_INDEX; S32 mTexCoord = 0; + bool operator==(const TextureInfo& rhs) const; + bool operator!=(const TextureInfo& rhs) const; + const TextureInfo& operator=(const tinygltf::TextureInfo& src); + const TextureInfo& operator=(const Value& src); + void serialize(boost::json::object& dst) const; }; class NormalTextureInfo : public TextureInfo @@ -60,6 +80,8 @@ namespace LL F32 mScale = 1.0f; const NormalTextureInfo& operator=(const tinygltf::NormalTextureInfo& src); + const NormalTextureInfo& operator=(const Value& src); + void serialize(boost::json::object& dst) const; }; class OcclusionTextureInfo : public TextureInfo @@ -68,17 +90,24 @@ namespace LL F32 mStrength = 1.0f; const OcclusionTextureInfo& operator=(const tinygltf::OcclusionTextureInfo& src); + const OcclusionTextureInfo& operator=(const Value& src); + void serialize(boost::json::object& dst) const; }; class PbrMetallicRoughness { public: - glh::vec4f mBaseColorFactor = glh::vec4f(1.f,1.f,1.f,1.f); + 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 tinygltf::PbrMetallicRoughness& src); + const PbrMetallicRoughness& operator=(const Value& src); + void serialize(boost::json::object& dst) const; }; @@ -93,13 +122,16 @@ namespace LL std::string mName; - glh::vec3f mEmissiveFactor = glh::vec3f(0.f, 0.f, 0.f); - std::string mAlphaMode = "OPAQUE"; + vec3 mEmissiveFactor = vec3(0.f, 0.f, 0.f); + AlphaMode mAlphaMode = AlphaMode::OPAQUE; F32 mAlphaCutoff = 0.5f; bool mDoubleSided = false; - + // bind for rendering + void bind(Asset& asset); const Material& operator=(const tinygltf::Material& src); + const Material& operator=(const Value& src); + void serialize(boost::json::object& dst) const; void allocateGLResources(Asset& asset); }; @@ -112,6 +144,8 @@ namespace LL 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); }; @@ -119,14 +153,14 @@ namespace LL 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(); //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(); + vec3 mScale = vec3(1.f,1.f,1.f); // if true, mMatrix is valid and up to date bool mMatrixValid = false; @@ -145,13 +179,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(); @@ -161,15 +197,15 @@ 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 @@ -179,12 +215,14 @@ namespace LL S32 mSkeleton = INVALID_INDEX; std::vector mJoints; std::string mName; - std::vector mInverseBindMatricesData; + std::vector mInverseBindMatricesData; void allocateGLResources(Asset& asset); void uploadMatrixPalette(Asset& asset, Node& node); const Skin& operator=(const tinygltf::Skin& src); + const Skin& operator=(const Value& src); + void serialize(boost::json::object& dst) const; }; class Scene @@ -194,9 +232,11 @@ namespace LL 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 @@ -207,18 +247,22 @@ namespace LL 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 @@ -231,43 +275,35 @@ namespace LL S32 mBufferView = INVALID_INDEX; std::vector mData; - S32 mWidth; - S32 mHeight; - S32 mComponent; - S32 mBits; - S32 mPixelType; + S32 mWidth = -1; + S32 mHeight = -1; + S32 mComponent = -1; + S32 mBits = -1; + S32 mPixelType = -1; LLPointer 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; - mBufferView = src.bufferView; - mPixelType = src.pixel_type; - return *this; - } - + const Image& operator=(const tinygltf::Image& src); + const Image& operator=(const Value& src); + void serialize(boost::json::object& dst) const; + // save image clear local data, and set uri void decompose(Asset& asset, const std::string& filename); - void allocateGLResources() - { - // allocate texture + // erase the buffer view associated with this image + // free any associated resources + // preserve only uri and name + void clearData(Asset& asset); - } + void allocateGLResources(); }; // C++ representation of a GLTF Asset class Asset { public: + + static const std::string minVersion_default; std::vector mScenes; std::vector mNodes; std::vector mMeshes; @@ -287,14 +323,15 @@ namespace LL std::string mCopyright; S32 mDefaultScene = INVALID_INDEX; - tinygltf::Value mExtras; + Value mExtras; + U32 mPendingBuffers = 0; // 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); + void allocateGLResources(const std::string& filename = "", const tinygltf::Model& model = tinygltf::Model()); // Called periodically (typically once per frame) // Any ongoing work (such as animations) should be handled here @@ -307,7 +344,7 @@ namespace LL void updateTransforms(); // update node render transforms - void updateRenderTransforms(const LLMatrix4a& modelview); + void updateRenderTransforms(const mat4& modelview); void render(bool opaque, bool rigged = false); void renderOpaque(); @@ -323,7 +360,13 @@ namespace LL S32* primitive_hitp = nullptr // return the index of the primitive that was hit ); + Asset() = default; + Asset(const tinygltf::Model& src); + Asset(const Value& src); + const Asset& operator=(const tinygltf::Model& src); + const Asset& operator=(const Value& src); + void serialize(boost::json::object& dst) const; // save the asset to a tinygltf model void save(tinygltf::Model& dst); @@ -335,5 +378,8 @@ namespace LL // updates all bufferview indices in this Asset as needed void eraseBufferView(S32 bufferView); }; + + 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..b0fbc8524d 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -36,55 +36,60 @@ #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 - 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 - 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 - 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 - 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 - 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 - 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 - 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,128 @@ 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* src, F32& dst) + inline void copyScalar(F32* src, F32& dst) { dst = *src; } template<> - void copyScalar(U32* src, U32& dst) + inline void copyScalar(U32* src, U32& dst) { dst = *src; } template<> - void copyScalar(U32* src, U16& dst) + inline void copyScalar(U32* src, U16& dst) { dst = *src; } template<> - void copyScalar(U16* src, U16& dst) + inline void copyScalar(U16* src, U16& dst) { dst = *src; } template<> - void copyScalar(U16* src, U32& dst) + inline void copyScalar(U16* src, U32& dst) { dst = *src; } template<> - void copyScalar(U8* src, U16& dst) + inline void copyScalar(U8* src, U16& dst) { dst = *src; } template<> - void copyScalar(U8* src, U32& dst) + inline void copyScalar(U8* src, U32& dst) { dst = *src; } template<> - void copyVec2(F32* src, LLVector2& dst) + inline void copyVec2(F32* src, LLVector2& dst) { dst.set(src[0], src[1]); } template<> - void copyVec3(F32* src, glh::vec3f& dst) + inline void copyVec3(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* src, LLVector4a& dst) + inline void copyVec3(F32* src, LLVector4a& dst) { dst.load3(src); } template<> - void copyVec3(U16* src, LLColor4U& dst) + inline void copyVec3(U16* src, LLColor4U& dst) { dst.set(src[0], src[1], src[2], 255); } template<> - void copyVec4(U8* src, LLColor4U& dst) + inline void copyVec4(U8* src, LLColor4U& dst) { dst.set(src[0], src[1], src[2], src[3]); } template<> - void copyVec4(U16* src, LLColor4U& dst) + inline void copyVec4(U16* src, LLColor4U& dst) { dst.set(src[0], src[1], src[2], src[3]); } template<> - void copyVec4(F32* src, LLColor4U& dst) + inline void copyVec4(F32* src, LLColor4U& dst) { dst.set(src[0]*255, src[1]*255, src[2]*255, src[3]*255); } template<> - void copyVec4(F32* src, LLVector4a& dst) + inline void copyVec4(F32* src, LLVector4a& dst) { dst.loadua(src); } template<> - void copyVec4(U16* src, LLVector4a& dst) + inline void copyVec4(U16* src, LLVector4a& dst) { dst.set(src[0], src[1], src[2], src[3]); } template<> - void copyVec4(U8* src, LLVector4a& dst) + inline void copyVec4(U8* src, LLVector4a& dst) { dst.set(src[0], src[1], src[2], src[3]); } template<> - void copyVec4(F32* src, glh::quaternionf& dst) + inline void copyVec4(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* src, glh::matrix4f& dst) + inline void copyMat4(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 - static void copyScalar(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyScalar(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -233,7 +231,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 - static void copyVec2(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyVec2(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -245,7 +243,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 - static void copyVec3(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyVec3(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -257,7 +255,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 - static void copyVec4(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyVec4(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -269,7 +267,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 - static void copyMat2(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyMat2(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -281,7 +279,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 - static void copyMat3(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyMat3(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -293,7 +291,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 - static void copyMat4(S* src, LLStrider dst, S32 stride, S32 count) + inline void copyMat4(S* src, LLStrider dst, S32 stride, S32 count) { for (S32 i = 0; i < count; ++i) { @@ -304,39 +302,39 @@ namespace LL } template - static void copy(Asset& asset, Accessor& accessor, const S* src, LLStrider& dst, S32 byteStride) + inline void copy(Asset& asset, Accessor& accessor, const S* src, LLStrider& 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,7 +347,7 @@ namespace LL // copy data from accessor to strider template - static void copy(Asset& asset, Accessor& accessor, LLStrider& dst) + inline void copy(Asset& asset, Accessor& accessor, LLStrider& dst) { const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView]; const Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; @@ -391,12 +389,504 @@ namespace LL // copy data from accessor to vector template - static void copy(Asset& asset, Accessor& accessor, std::vector& dst) + inline void copy(Asset& asset, Accessor& accessor, std::vector& dst) { dst.resize(accessor.mCount); LLStrider strider = dst.data(); copy(asset, accessor, strider); } + + + //========================================================================================================= + // boost::json copying utilities + // ======================================================================================================== + + //====================== unspecialized base template, single value =========================== + + // to/from Value + template + inline bool copy(const Value& src, T& dst) + { + dst = src; + return true; + } + + template + inline bool write(const T& src, Value& dst) + { + dst = boost::json::object(); + src.serialize(dst.as_object()); + return true; + } + + template + inline bool copy(const Value& src, std::unordered_map& dst) + { + if (src.is_object()) + { + const boost::json::object& obj = src.as_object(); + for (const auto& [key, value] : obj) + { + copy(value, dst[key]); + } + return true; + } + return false; + } + + template + inline bool write(const std::unordered_map& src, Value& dst) + { + boost::json::object obj; + for (const auto& [key, value] : src) + { + Value v; + if (write(value, v)) + { + obj[key] = v; + } + else + { + return false; + } + } + dst = obj; + return true; + } + + // to/from array + template + inline bool copy(const Value& src, std::vector& dst) + { + if (src.is_array()) + { + 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; + } + + return false; + } + + template + inline bool write(const std::vector& src, Value& dst) + { + boost::json::array arr; + for (const T& t : src) + { + Value v; + if (write(t, v)) + { + arr.push_back(v); + } + else + { + return false; + } + } + dst = arr; + return true; + } + + // to/from object member + template + inline bool copy(const boost::json::object& src, string_view member, T& dst) + { + auto it = src.find(member); + if (it != src.end()) + { + return copy(it->value(), dst); + } + return false; + } + + // always write a member to an object without checking default + template + inline bool write_always(const T& src, string_view member, boost::json::object& dst) + { + Value& v = dst[member]; + if (!write(src, v)) + { + dst.erase(member); + return false; + } + return true; + } + + // conditionally write a member to an object if the member + // is not the default value + template + 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 + inline bool write(const std::unordered_map& src, string_view member, boost::json::object& dst, const std::unordered_map& default_value = std::unordered_map()) + { + if (!src.empty()) + { + Value v; + if (write(src, v)) + { + dst[member] = v; + return true; + } + } + return false; + } + + template + inline bool write(const std::vector& src, string_view member, boost::json::object& dst, const std::vector& deafault_value = std::vector()) + { + if (!src.empty()) + { + Value v; + if (write(src, v)) + { + dst[member] = v; + return true; + } + } + return false; + } + + template + 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; + } + + // 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) + { + if (arr[0].is_double() && + arr[1].is_double() && + arr[2].is_double() && + arr[3].is_double()) + { + dst = vec4(arr[0].get_double(), arr[1].get_double(), arr[2].get_double(), arr[3].get_double()); + 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) + { + if (arr[0].is_double() && + arr[1].is_double() && + arr[2].is_double() && + arr[3].is_double()) + { + dst.x = arr[0].get_double(); + dst.y = arr[1].get_double(); + dst.z = arr[2].get_double(); + dst.w = arr[3].get_double(); + 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) + { + 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; + } + + // 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) + { + if (src.is_double()) + { + dst = src.get_double(); + return true; + } + return false; + } + + 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) + { + if (src.is_double()) + { + dst = src.get_double(); + return true; + } + return false; + } + + 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) + { + if (arr[i].is_double()) + { + p[i] = arr[i].get_double(); + } + else + { + 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..859e202738 --- /dev/null +++ b/indra/newview/gltf/common.h @@ -0,0 +1,66 @@ +#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" + +// 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; + } +} + diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index b57a0af18d..1bde7327e6 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -28,10 +28,12 @@ #include "asset.h" #include "buffer_util.h" +#include "../llviewershadermgr.h" #include "../lltinygltfhelper.h" using namespace LL::GLTF; +using namespace boost::json; void Primitive::allocateGLResources(Asset& asset) { @@ -92,6 +94,10 @@ void Primitive::allocateGLResources(Asset& asset) mask |= LLVertexBuffer::MAP_WEIGHT4; } + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); + } mVertexBuffer = new LLVertexBuffer(mask); mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size()*2); // double the size of the index buffer for 32-bit indices @@ -129,7 +135,7 @@ void Primitive::allocateGLResources(Asset& asset) 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)); @@ -351,6 +357,50 @@ Primitive::~Primitive() mOctree = nullptr; } +U32 gltf_mode_to_gl_mode(U32 mode) +{ + switch (mode) + { + case TINYGLTF_MODE_POINTS: + return LLRender::POINTS; + case TINYGLTF_MODE_LINE: + return LLRender::LINES; + case TINYGLTF_MODE_LINE_LOOP: + return LLRender::LINE_LOOP; + case TINYGLTF_MODE_LINE_STRIP: + return LLRender::LINE_STRIP; + case TINYGLTF_MODE_TRIANGLES: + return LLRender::TRIANGLES; + case TINYGLTF_MODE_TRIANGLE_STRIP: + return LLRender::TRIANGLE_STRIP; + case TINYGLTF_MODE_TRIANGLE_FAN: + return LLRender::TRIANGLE_FAN; + default: + return LLRender::TRIANGLES; + } +} + +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); +} + +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; +} const Primitive& Primitive::operator=(const tinygltf::Primitive& src) { @@ -369,32 +419,7 @@ const Primitive& Primitive::operator=(const tinygltf::Primitive& src) 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; - default: - mGLMode = GL_TRIANGLES; - } + 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..18aadce808 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -28,12 +28,14 @@ #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 = @@ -66,10 +68,10 @@ namespace LL std::vector mOctreeTriangles; S32 mMaterial = -1; - U32 mMode = TINYGLTF_MODE_TRIANGLES; // default to triangles + S32 mMode = TINYGLTF_MODE_TRIANGLES; // default to triangles U32 mGLMode = LLRender::TRIANGLES; S32 mIndices = -1; - std::unordered_map mAttributes; + std::unordered_map mAttributes; // create octree based on vertex buffer // must be called before buffer is unmapped and after buffer is populated with good data @@ -85,6 +87,8 @@ namespace LL LLVector4a* tangent = NULL // return the surface tangent at the intersection point ); + void serialize(boost::json::object& obj) const; + const Primitive& operator=(const Value& src); const Primitive& operator=(const tinygltf::Primitive& src); void allocateGLResources(Asset& asset); -- cgit v1.2.3 From 2f4120038429c6aff865f153f708ceefb60d67f4 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 28 May 2024 09:45:40 -0500 Subject: Remove tinygltf dependency from LL::GLTF (#1541) * #1535 Image loading/saving support in boost::json driven GLTF parser * #1536 GLB Support in boost::json drvien GLTF parser --- indra/newview/gltf/README.md | 30 +- indra/newview/gltf/accessor.cpp | 158 ++++--- indra/newview/gltf/accessor.h | 49 ++- indra/newview/gltf/animation.cpp | 104 ++--- indra/newview/gltf/animation.h | 36 +- indra/newview/gltf/asset.cpp | 862 ++++++++++++++------------------------- indra/newview/gltf/asset.h | 67 ++- indra/newview/gltf/buffer_util.h | 100 +++-- indra/newview/gltf/common.h | 1 + indra/newview/gltf/primitive.cpp | 86 ++-- indra/newview/gltf/primitive.h | 24 +- 11 files changed, 628 insertions(+), 889 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/README.md b/indra/newview/gltf/README.md index 8e7df0a439..a2d43be1d6 100644 --- a/indra/newview/gltf/README.md +++ b/indra/newview/gltf/README.md @@ -1,13 +1,13 @@ # Linden Lab GLTF Implementation - -Currently in prototype stage. Much functionality is missing (blend shapes, + +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 +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. @@ -18,7 +18,7 @@ The implementation serves both the client and the server. - 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. +- 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. @@ -69,14 +69,14 @@ 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 +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 +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): ``` @@ -120,29 +120,29 @@ inline bool write(const vec3& src, Value& dst) 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). +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 +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 +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 +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. +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. +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. @@ -152,5 +152,5 @@ 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 369ff4f240..0619c617e2 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -28,6 +28,7 @@ #include "asset.h" #include "buffer_util.h" +#include "llfilesystem.h" using namespace LL::GLTF; using namespace boost::json; @@ -107,6 +108,8 @@ void Buffer::erase(Asset& asset, S32 offset, S32 length) mData.erase(mData.begin() + offset, mData.begin() + offset + length); + mByteLength = mData.size(); + for (BufferView& view : asset.mBufferViews) { if (view.mBuffer == idx) @@ -119,6 +122,95 @@ void Buffer::erase(Asset& asset, S32 offset, S32 length) } } +bool Buffer::prep(Asset& asset) +{ + // PRECONDITION: mByteLength must not be 0 + llassert(mByteLength != 0); + + 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); + + mData.resize(file.getSize()); + if (!file.read((U8*)mData.data(), mData.size())) + { + 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); @@ -132,23 +224,15 @@ const Buffer& Buffer::operator=(const Value& src) { copy(src, "name", mName); copy(src, "uri", mUri); - - // NOTE: DO NOT attempt to handle the uri here. + 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 Buffer& Buffer::operator=(const tinygltf::Buffer& src) -{ - mData = src.data; - mName = src.name; - mUri = src.uri; - return *this; -} - - void BufferView::serialize(object& dst) const { write_always(mBuffer, "buffer", dst); @@ -173,43 +257,6 @@ const BufferView& BufferView::operator=(const Value& src) return *this; } -const BufferView& BufferView::operator=(const tinygltf::BufferView& src) -{ - mBuffer = src.buffer; - mByteLength = src.byteLength; - mByteOffset = src.byteOffset; - mByteStride = src.byteStride; - mTarget = src.target; - mName = src.name; - return *this; -} - -Accessor::Type tinygltf_type_to_enum(S32 type) -{ - switch (type) - { - case TINYGLTF_TYPE_SCALAR: - return Accessor::Type::SCALAR; - case TINYGLTF_TYPE_VEC2: - return Accessor::Type::VEC2; - case TINYGLTF_TYPE_VEC3: - return Accessor::Type::VEC3; - case TINYGLTF_TYPE_VEC4: - return Accessor::Type::VEC4; - case TINYGLTF_TYPE_MAT2: - return Accessor::Type::MAT2; - case TINYGLTF_TYPE_MAT3: - return Accessor::Type::MAT3; - case TINYGLTF_TYPE_MAT4: - return Accessor::Type::MAT4; - } - - LL_WARNS("GLTF") << "Unknown tinygltf accessor type: " << type << LL_ENDL; - llassert(false); - - return Accessor::Type::SCALAR; -} - void Accessor::serialize(object& dst) const { write(mName, "name", dst); @@ -240,18 +287,3 @@ const Accessor& Accessor::operator=(const Value& src) return *this; } -const Accessor& Accessor::operator=(const tinygltf::Accessor& src) -{ - mBufferView = src.bufferView; - mByteOffset = src.byteOffset; - mComponentType = src.componentType; - mCount = src.count; - mType = tinygltf_type_to_enum(src.type); - mNormalized = src.normalized; - mName = src.name; - mMax = src.maxValues; - mMin = src.minValues; - - return *this; -} - diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 3bbc5216bd..ec68c5f624 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -26,7 +26,6 @@ * $/LicenseInfo$ */ -#include "../lltinygltfhelper.h" #include "llstrider.h" #include "boost/json.hpp" @@ -51,9 +50,12 @@ namespace LL // also updates all buffer views in given asset that reference this buffer void erase(Asset& asset, S32 offset, S32 length); + bool prep(Asset& asset); + void serialize(boost::json::object& obj) const; const Buffer& operator=(const Value& value); - const Buffer& operator=(const tinygltf::Buffer& src); + + bool save(Asset& asset, const std::string& folder); }; class BufferView @@ -69,37 +71,44 @@ namespace LL void serialize(boost::json::object& obj) const; const BufferView& operator=(const Value& value); - const BufferView& operator=(const tinygltf::BufferView& src); }; - + class Accessor { public: - S32 mBufferView = INVALID_INDEX; - S32 mByteOffset = 0; - S32 mComponentType = 0; - S32 mCount = 0; - std::vector mMax; - std::vector 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 }; + std::vector mMax; + std::vector 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; - std::string mName; void serialize(boost::json::object& obj) const; const Accessor& operator=(const Value& value); - const Accessor& operator=(const tinygltf::Accessor& src); }; // convert from "SCALAR", "VEC2", etc to Accessor::Type diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp index 8a542fb315..f18bba788c 100644 --- a/indra/newview/gltf/animation.cpp +++ b/indra/newview/gltf/animation.cpp @@ -32,7 +32,7 @@ using namespace LL::GLTF; using namespace boost::json; -void Animation::allocateGLResources(Asset& asset) +bool Animation::prep(Asset& asset) { if (!mSamplers.empty()) { @@ -40,7 +40,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); } @@ -52,13 +55,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) @@ -85,7 +96,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,6 +106,8 @@ void Animation::Sampler::allocateGLResources(Asset& asset) LLStrider frame_times = mFrameTimes.data(); copy(asset, accessor, frame_times); + + return true; } @@ -120,16 +133,6 @@ const Animation::Sampler& Animation::Sampler::operator=(const Value& src) return *this; } - -const Animation::Sampler& Animation::Sampler::operator=(const tinygltf::AnimationSampler& src) -{ - mInput = src.input; - mOutput = src.output; - mInterpolation = src.interpolation; - - return *this; -} - bool Animation::Channel::Target::operator==(const Channel::Target& rhs) const { return mNode == rhs.mNode && mPath == rhs.mPath; @@ -172,17 +175,6 @@ const Animation::Channel& Animation::Channel::operator=(const Value& src) return *this; } -const Animation::Channel& Animation::Channel::operator=(const tinygltf::AnimationChannel& src) -{ - mSampler = src.sampler; - - mTarget.mNode = src.target_node; - mTarget.mPath = src.target_path; - - return *this; -} - - void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F32& t) { LL_PROFILE_ZONE_SCOPED; @@ -223,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) @@ -254,11 +248,13 @@ void Animation::RotationChannel::apply(Asset& asset, Sampler& sampler, F32 time) } } -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) @@ -286,11 +282,13 @@ void Animation::TranslationChannel::apply(Asset& asset, Sampler& sampler, F32 ti } } -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) @@ -364,47 +362,15 @@ const Animation& Animation::operator=(const Value& src) return *this; } -const Animation& Animation::operator=(const tinygltf::Animation& src) -{ - mName = src.name; - - mSamplers.resize(src.samplers.size()); - for (U32 i = 0; i < src.samplers.size(); ++i) - { - mSamplers[i] = src.samplers[i]; - } - - for (U32 i = 0; i < src.channels.size(); ++i) - { - if (src.channels[i].target_path == "rotation") - { - mRotationChannels.push_back(RotationChannel()); - mRotationChannels.back() = src.channels[i]; - } - - if (src.channels[i].target_path == "translation") - { - mTranslationChannels.push_back(TranslationChannel()); - mTranslationChannels.back() = src.channels[i]; - } - - if (src.channels[i].target_path == "scale") - { - mScaleChannels.push_back(ScaleChannel()); - mScaleChannels.back() = src.channels[i]; - } - } - - return *this; -} - -void Skin::allocateGLResources(Asset& asset) +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 Value& src) @@ -419,16 +385,6 @@ const Skin& Skin::operator=(const Value& src) return *this; } -const Skin& Skin::operator=(const tinygltf::Skin& src) -{ - mName = src.name; - mSkeleton = src.skeleton; - mInverseBindMatrices = src.inverseBindMatrices; - mJoints = src.joints; - - return *this; -} - void Skin::serialize(object& obj) const { write(mInverseBindMatrices, "inverseBindMatrices", obj, INVALID_INDEX); diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h index 53c11d4669..d5426fd4ce 100644 --- a/indra/newview/gltf/animation.h +++ b/indra/newview/gltf/animation.h @@ -49,12 +49,10 @@ namespace LL S32 mOutput = INVALID_INDEX; std::string mInterpolation; - void allocateGLResources(Asset& asset); + bool prep(Asset& asset); void serialize(boost::json::object& dst) const; const Sampler& operator=(const Value& value); - const Sampler& operator=(const tinygltf::AnimationSampler& src); - // get the frame index and time for the specified time // asset -- the asset to reference for Accessors @@ -85,7 +83,6 @@ namespace LL void serialize(boost::json::object& dst) const; const Channel& operator=(const Value& value); - const Channel& operator=(const tinygltf::AnimationChannel& src); }; class RotationChannel : public Channel @@ -96,16 +93,10 @@ namespace LL std::vector mRotations; - const RotationChannel& operator=(const tinygltf::AnimationChannel& src) - { - Channel::operator=(src); - return *this; - } - // 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,16 +109,10 @@ namespace LL std::vector mTranslations; - const TranslationChannel& operator=(const tinygltf::AnimationChannel& src) - { - Channel::operator=(src); - return *this; - } - // 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); }; @@ -140,16 +125,10 @@ namespace LL std::vector mScales; - const ScaleChannel& operator=(const tinygltf::AnimationChannel& src) - { - Channel::operator=(src); - return *this; - } - // 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); }; @@ -160,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; @@ -170,9 +149,8 @@ namespace LL void serialize(boost::json::object& dst) const; const Animation& operator=(const Value& value); - const Animation& operator=(const tinygltf::Animation& src); - - void allocateGLResources(Asset& asset); + + 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 c64d48662c..485984fac1 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -33,6 +33,8 @@ #include "../llviewertexturelist.h" #include "../pipeline.h" #include "buffer_util.h" +#include +#include "llimagejpeg.h" using namespace LL::GLTF; using namespace boost::json; @@ -75,268 +77,14 @@ namespace LL return "OPAQUE"; } } - - template - void copy(const std::vector& src, std::vector& dst) - { - dst.resize(src.size()); - for (U32 i = 0; i < src.size(); ++i) - { - copy(src[i], dst[i]); - } - } - - void copy(const Node& src, tinygltf::Node& dst) - { - if (src.mMatrixValid) - { - if (src.mMatrix != glm::identity()) - { - dst.matrix.resize(16); - const F32* m = glm::value_ptr(src.mMatrix); - for (U32 i = 0; i < 16; ++i) - { - dst.matrix[i] = m[i]; - } - } - } - else if (src.mTRSValid) - { - if (src.mRotation != glm::identity()) - { - dst.rotation.resize(4); - dst.rotation[0] = src.mRotation.x; - dst.rotation[1] = src.mRotation.y; - dst.rotation[2] = src.mRotation.z; - dst.rotation[3] = src.mRotation.w; - } - - if (src.mTranslation != vec3(0.f, 0.f, 0.f)) - { - dst.translation.resize(3); - dst.translation[0] = src.mTranslation.x; - dst.translation[1] = src.mTranslation.y; - dst.translation[2] = src.mTranslation.z; - } - - if (src.mScale != vec3(1.f, 1.f, 1.f)) - { - dst.scale.resize(3); - dst.scale[0] = src.mScale.x; - dst.scale[1] = src.mScale.y; - dst.scale[2] = src.mScale.z; - } - } - - dst.children = src.mChildren; - dst.mesh = src.mMesh; - dst.skin = src.mSkin; - dst.name = src.mName; - } - - void copy(const Scene& src, tinygltf::Scene& dst) - { - dst.nodes = src.mNodes; - dst.name = src.mName; - } - - void copy(const Primitive& src, tinygltf::Primitive& dst) - { - for (auto& attrib : src.mAttributes) - { - dst.attributes[attrib.first] = attrib.second; - } - dst.indices = src.mIndices; - dst.material = src.mMaterial; - dst.mode = src.mMode; - } - - void copy(const Mesh& src, tinygltf::Mesh& mesh) - { - copy(src.mPrimitives, mesh.primitives); - mesh.weights = src.mWeights; - mesh.name = src.mName; - } - - void copy(const Material::TextureInfo& src, tinygltf::TextureInfo& dst) - { - dst.index = src.mIndex; - dst.texCoord = src.mTexCoord; - } - - void copy(const Material::OcclusionTextureInfo& src, tinygltf::OcclusionTextureInfo& dst) - { - dst.index = src.mIndex; - dst.texCoord = src.mTexCoord; - dst.strength = src.mStrength; - } - - void copy(const Material::NormalTextureInfo& src, tinygltf::NormalTextureInfo& dst) - { - dst.index = src.mIndex; - dst.texCoord = src.mTexCoord; - dst.scale = src.mScale; - } - - void copy(const Material::PbrMetallicRoughness& src, tinygltf::PbrMetallicRoughness& dst) - { - dst.baseColorFactor = { src.mBaseColorFactor.r, src.mBaseColorFactor.g, src.mBaseColorFactor.b, src.mBaseColorFactor.a }; - copy(src.mBaseColorTexture, dst.baseColorTexture); - dst.metallicFactor = src.mMetallicFactor; - dst.roughnessFactor = src.mRoughnessFactor; - copy(src.mMetallicRoughnessTexture, dst.metallicRoughnessTexture); - } - - void copy(const Material& src, tinygltf::Material& material) - { - material.name = src.mName; - - material.emissiveFactor = { src.mEmissiveFactor.r, src.mEmissiveFactor.g, src.mEmissiveFactor.b }; - copy(src.mPbrMetallicRoughness, material.pbrMetallicRoughness); - copy(src.mNormalTexture, material.normalTexture); - copy(src.mEmissiveTexture, material.emissiveTexture); - } - - void copy(const Texture& src, tinygltf::Texture& texture) - { - texture.sampler = src.mSampler; - texture.source = src.mSource; - texture.name = src.mName; - } - - void copy(const Sampler& src, tinygltf::Sampler& sampler) - { - sampler.magFilter = src.mMagFilter; - sampler.minFilter = src.mMinFilter; - sampler.wrapS = src.mWrapS; - sampler.wrapT = src.mWrapT; - sampler.name = src.mName; - } - - void copy(const Skin& src, tinygltf::Skin& skin) - { - skin.joints = src.mJoints; - skin.inverseBindMatrices = src.mInverseBindMatrices; - skin.skeleton = src.mSkeleton; - skin.name = src.mName; - } - - void copy(const Accessor& src, tinygltf::Accessor& accessor) - { - accessor.bufferView = src.mBufferView; - accessor.byteOffset = src.mByteOffset; - accessor.componentType = src.mComponentType; - accessor.minValues = src.mMin; - accessor.maxValues = src.mMax; - - accessor.count = src.mCount; - accessor.type = (S32) src.mType; - accessor.normalized = src.mNormalized; - accessor.name = src.mName; - } - - void copy(const Animation::Sampler& src, tinygltf::AnimationSampler& sampler) - { - sampler.input = src.mInput; - sampler.output = src.mOutput; - sampler.interpolation = src.mInterpolation; - } - - void copy(const Animation::Channel& src, tinygltf::AnimationChannel& channel) - { - channel.sampler = src.mSampler; - channel.target_node = src.mTarget.mNode; - channel.target_path = src.mTarget.mPath; - } - - void copy(const Animation& src, tinygltf::Animation& animation) - { - animation.name = src.mName; - - copy(src.mSamplers, animation.samplers); - - U32 channel_count = src.mRotationChannels.size() + src.mTranslationChannels.size() + src.mScaleChannels.size(); - - animation.channels.resize(channel_count); - - U32 idx = 0; - for (U32 i = 0; i < src.mTranslationChannels.size(); ++i) - { - copy(src.mTranslationChannels[i], animation.channels[idx++]); - } - - for (U32 i = 0; i < src.mRotationChannels.size(); ++i) - { - copy(src.mRotationChannels[i], animation.channels[idx++]); - } - - for (U32 i = 0; i < src.mScaleChannels.size(); ++i) - { - copy(src.mScaleChannels[i], animation.channels[idx++]); - } - } - - void copy(const Buffer& src, tinygltf::Buffer& buffer) - { - buffer.uri = src.mUri; - buffer.data = src.mData; - buffer.name = src.mName; - } - - void copy(const BufferView& src, tinygltf::BufferView& bufferView) - { - bufferView.buffer = src.mBuffer; - bufferView.byteOffset = src.mByteOffset; - bufferView.byteLength = src.mByteLength; - bufferView.byteStride = src.mByteStride; - bufferView.target = src.mTarget; - bufferView.name = src.mName; - } - - void copy(const Image& src, tinygltf::Image& image) - { - image.name = src.mName; - image.width = src.mWidth; - image.height = src.mHeight; - image.component = src.mComponent; - image.bits = src.mBits; - image.pixel_type = src.mPixelType; - - image.image = src.mData; - image.bufferView = src.mBufferView; - image.mimeType = src.mMimeType; - image.uri = src.mUri; - } - - void copy(const Asset & src, tinygltf::Model& dst) - { - dst.defaultScene = src.mDefaultScene; - dst.asset.copyright = src.mCopyright; - dst.asset.version = src.mVersion; - dst.asset.minVersion = src.mMinVersion; - dst.asset.generator = "Linden Lab Experimental GLTF Export"; - - // NOTE: extras are lost in the conversion for now - - copy(src.mScenes, dst.scenes); - copy(src.mNodes, dst.nodes); - copy(src.mMeshes, dst.meshes); - copy(src.mMaterials, dst.materials); - copy(src.mBuffers, dst.buffers); - copy(src.mBufferViews, dst.bufferViews); - copy(src.mTextures, dst.textures); - copy(src.mSamplers, dst.samplers); - copy(src.mImages, dst.images); - copy(src.mAccessors, dst.accessors); - copy(src.mAnimations, dst.animations); - copy(src.mSkins, dst.skins); - } } } + + void Scene::updateTransforms(Asset& asset) { mat4 identity = glm::identity(); - + for (auto& nodeIndex : mNodes) { Node& node = asset.mNodes[nodeIndex]; @@ -368,9 +116,9 @@ void Node::updateTransforms(Asset& asset, const mat4& parentMatrix) { makeMatrixValid(); mAssetMatrix = parentMatrix * mMatrix; - + mAssetMatrixInv = glm::inverse(mAssetMatrix); - + S32 my_index = this - &asset.mNodes[0]; for (auto& childIndex : mChildren) @@ -419,7 +167,6 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, { if (node.mMesh != INVALID_INDEX) { - bool newHit = false; LLMatrix4a ami; @@ -452,7 +199,7 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLMatrix4a am; am.loadu(glm::value_ptr(node.mAssetMatrix)); // shorten line segment on hit - am.affineTransform(p, asset_end); + am.affineTransform(p, asset_end); // transform results back to asset space if (intersection) @@ -521,7 +268,7 @@ void Node::makeTRSValid() vec3 skew; vec4 perspective; glm::decompose(mMatrix, mScale, mRotation, mTranslation, skew, perspective); - + mTRSValid = true; } @@ -577,59 +324,6 @@ const Node& Node::operator=(const Value& src) { mTRSValid = true; } - - return *this; -} - -const Node& Node::operator=(const tinygltf::Node& src) -{ - F32* dstMatrix = glm::value_ptr(mMatrix); - - if (src.matrix.size() == 16) - { - // Node has a transformation matrix, just copy it - for (U32 i = 0; i < 16; ++i) - { - dstMatrix[i] = (F32)src.matrix[i]; - } - - mMatrixValid = true; - } - else if (!src.rotation.empty() || !src.translation.empty() || !src.scale.empty()) - { - // node has rotation/translation/scale, convert to matrix - if (src.rotation.size() == 4) - { - mRotation = quat((F32)src.rotation[3], (F32)src.rotation[0], (F32)src.rotation[1], (F32)src.rotation[2]); - } - - if (src.translation.size() == 3) - { - mTranslation = vec3((F32)src.translation[0], (F32)src.translation[1], (F32)src.translation[2]); - } - - if (src.scale.size() == 3) - { - mScale = vec3((F32)src.scale[0], (F32)src.scale[1], (F32)src.scale[2]); - } - else - { - mScale = vec3(1.f, 1.f, 1.f); - } - - mTRSValid = true; - } - else - { - // node specifies no transformation, set to identity - mMatrix = glm::identity(); - mMatrixValid = true; - } - - mChildren = src.children; - mMesh = src.mesh; - mSkin = src.skin; - mName = src.name; return *this; } @@ -662,22 +356,6 @@ const Image& Image::operator=(const Value& src) return *this; } -const Image& Image::operator=(const tinygltf::Image& src) -{ - mName = src.name; - mWidth = src.width; - mHeight = src.height; - mComponent = src.component; - mBits = src.bits; - mPixelType = src.pixel_type; - mUri = src.uri; - mBufferView = src.bufferView; - mMimeType = src.mimeType; - mData = src.image; - return *this; -} - - void Asset::render(bool opaque, bool rigged) { if (rigged) @@ -726,14 +404,8 @@ void Asset::render(bool opaque, bool rigged) continue; } - if (mMaterials[primitive.mMaterial].mMaterial.notNull()) - { - material.mMaterial->bind(); - } - else - { - material.bind(*this); - } + material.bind(*this); + cull = !material.mDoubleSided; } else @@ -792,45 +464,50 @@ void Asset::update() } } -void Asset::allocateGLResources(const std::string& filename, const tinygltf::Model& model) +bool Asset::prep() { - // do images first as materials may depend on images - for (auto& image : mImages) + // do buffers first as other resources depend on them + for (auto& buffer : mBuffers) { - image.allocateGLResources(); + if (!buffer.prep(*this)) + { + return false; + } } - - // do materials before meshes as meshes may depend on materials - if (!filename.empty()) + for (auto& image : mImages) { - for (U32 i = 0; i < mMaterials.size(); ++i) + if (!image.prep(*this)) { - // HACK: local preview mode, load material from model for now - mMaterials[i].allocateGLResources(*this); - LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, mMaterials[i].mMaterial, mMaterials[i].mName, true); + return false; } } for (auto& mesh : mMeshes) { - mesh.allocateGLResources(*this); + if (!mesh.prep(*this)) + { + return false; + } } for (auto& animation : mAnimations) { - animation.allocateGLResources(*this); + if (!animation.prep(*this)) + { + return false; + } } for (auto& skin : mSkins) { - skin.allocateGLResources(*this); + if (!skin.prep(*this)) + { + return false; + } } -} -Asset::Asset(const tinygltf::Model& src) -{ - *this = src; + return true; } Asset::Asset(const Value& src) @@ -838,90 +515,141 @@ Asset::Asset(const Value& src) *this = src; } -const Asset& Asset::operator=(const tinygltf::Model& src) +bool Asset::load(std::string_view filename) { - mVersion = src.asset.version; - mMinVersion = src.asset.minVersion; - mGenerator = src.asset.generator; - mCopyright = src.asset.copyright; + mFilename = filename; + std::string ext = gDirUtilp->getExtension(mFilename); - // note: extras are lost in the conversion for now - - mDefaultScene = src.defaultScene; - - mScenes.resize(src.scenes.size()); - for (U32 i = 0; i < src.scenes.size(); ++i) + std::ifstream file(filename.data(), std::ios::binary); + if (file.is_open()) { - mScenes[i] = src.scenes[i]; - } + std::string str((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); - mNodes.resize(src.nodes.size()); - for (U32 i = 0; i < src.nodes.size(); ++i) - { - mNodes[i] = src.nodes[i]; + if (ext == "gltf") + { + Value val = parse(str); + *this = val; + return prep(); + } + else if (ext == "glb") + { + return loadBinary(str); + } + else + { + LL_WARNS() << "Unsupported file type: " << ext << LL_ENDL; + return false; + } } - - mMeshes.resize(src.meshes.size()); - for (U32 i = 0; i < src.meshes.size(); ++i) + else { - mMeshes[i] = src.meshes[i]; + LL_WARNS() << "Failed to open file: " << filename << LL_ENDL; + return false; } - mMaterials.resize(src.materials.size()); - for (U32 i = 0; i < src.materials.size(); ++i) - { - mMaterials[i] = src.materials[i]; - } + return false; +} - mBuffers.resize(src.buffers.size()); - for (U32 i = 0; i < src.buffers.size(); ++i) - { - mBuffers[i] = src.buffers[i]; - } +bool Asset::loadBinary(const std::string& data) +{ + // load from binary gltf + const U8* ptr = (const U8*)data.data(); + const U8* end = ptr + data.size(); - mBufferViews.resize(src.bufferViews.size()); - for (U32 i = 0; i < src.bufferViews.size(); ++i) + if (end - ptr < 12) { - mBufferViews[i] = src.bufferViews[i]; + LL_WARNS("GLTF") << "GLB file too short" << LL_ENDL; + return false; } - mTextures.resize(src.textures.size()); - for (U32 i = 0; i < src.textures.size(); ++i) + U32 magic = *(U32*)ptr; + ptr += 4; + + if (magic != 0x46546C67) { - mTextures[i] = src.textures[i]; + LL_WARNS("GLTF") << "Invalid GLB magic" << LL_ENDL; + return false; } - mSamplers.resize(src.samplers.size()); - for (U32 i = 0; i < src.samplers.size(); ++i) + U32 version = *(U32*)ptr; + ptr += 4; + + if (version != 2) { - mSamplers[i] = src.samplers[i]; + LL_WARNS("GLTF") << "Unsupported GLB version" << LL_ENDL; + return false; } - mImages.resize(src.images.size()); - for (U32 i = 0; i < src.images.size(); ++i) + U32 length = *(U32*)ptr; + ptr += 4; + + if (length != data.size()) { - mImages[i] = src.images[i]; + LL_WARNS("GLTF") << "GLB length mismatch" << LL_ENDL; + return false; } - mAccessors.resize(src.accessors.size()); - for (U32 i = 0; i < src.accessors.size(); ++i) + U32 chunkLength = *(U32*)ptr; + ptr += 4; + + if (end - ptr < chunkLength + 8) { - mAccessors[i] = src.accessors[i]; + LL_WARNS("GLTF") << "GLB chunk too short" << LL_ENDL; + return false; } - mAnimations.resize(src.animations.size()); - for (U32 i = 0; i < src.animations.size(); ++i) + U32 chunkType = *(U32*)ptr; + ptr += 4; + + if (chunkType != 0x4E4F534A) { - mAnimations[i] = src.animations[i]; + LL_WARNS("GLTF") << "Invalid GLB chunk type" << LL_ENDL; + return false; } - mSkins.resize(src.skins.size()); - for (U32 i = 0; i < src.skins.size(); ++i) + Value val = parse(std::string_view((const char*)ptr, chunkLength)); + *this = val; + + if (mBuffers.size() > 0 && mBuffers[0].mUri.empty()) { - mSkins[i] = src.skins[i]; + // load binary chunk + ptr += chunkLength; + + if (end - ptr < 8) + { + LL_WARNS("GLTF") << "GLB chunk too short" << LL_ENDL; + return false; + } + + chunkLength = *(U32*)ptr; + ptr += 4; + + chunkType = *(U32*)ptr; + ptr += 4; + + if (chunkType != 0x004E4942) + { + LL_WARNS("GLTF") << "Invalid GLB chunk type" << LL_ENDL; + return false; + } + + auto& buffer = mBuffers[0]; + + if (ptr + buffer.mByteLength <= end) + { + buffer.mData.resize(buffer.mByteLength); + memcpy(buffer.mData.data(), ptr, buffer.mByteLength); + ptr += buffer.mByteLength; + } + else + { + LL_WARNS("GLTF") << "Buffer too short" << LL_ENDL; + return false; + } } - - return *this; + + return prep(); } const Asset& Asset::operator=(const Value& src) @@ -943,7 +671,7 @@ const Asset& Asset::operator=(const Value& src) copy(asset, "extras", mExtras); } - copy(obj, "defaultScene", mDefaultScene); + copy(obj, "scene", mScene); copy(obj, "scenes", mScenes); copy(obj, "nodes", mNodes); copy(obj, "meshes", mMeshes); @@ -961,18 +689,17 @@ const Asset& Asset::operator=(const Value& src) return *this; } -void Asset::save(tinygltf::Model& dst) -{ - LL::GLTF::copy(*this, dst); -} - void Asset::serialize(object& dst) const { - write(mVersion, "version", dst); - write(mMinVersion, "minVersion", dst, std::string()); - write(mGenerator, "generator", dst); - write(mDefaultScene, "defaultScene", dst, 0); - + 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); @@ -987,16 +714,39 @@ void Asset::serialize(object& dst) const write(mSkins, "skins", dst); } -void Asset::decompose(const std::string& filename) +bool Asset::save(const std::string& filename) { // get folder path std::string folder = gDirUtilp->getDirName(filename); - // decompose images + // save images for (auto& image : mImages) { - image.decompose(*this, folder); + if (!image.save(*this, folder)) + { + return false; + } + } + + // save buffers + // NOTE: save buffers after saving images as saving images + // may remove image data from buffers + for (auto& buffer : mBuffers) + { + if (!buffer.save(*this, folder)) + { + return false; + } } + + // save .gltf + object obj; + serialize(obj); + std::string buffer = boost::json::serialize(obj, {}); + std::ofstream file(filename, std::ios::binary); + file.write(buffer.c_str(), buffer.size()); + + return true; } void Asset::eraseBufferView(S32 bufferView) @@ -1023,13 +773,63 @@ void Asset::eraseBufferView(S32 bufferView) LLViewerFetchedTexture* fetch_texture(const LLUUID& id); -void Image::allocateGLResources() +bool Image::prep(Asset& asset) { LLUUID id; - if (LLUUID::parseUUID(mUri, &id) && id.notNull()) - { + if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull()) + { // loaded from an asset, fetch the texture from the asset system mTexture = fetch_texture(id); } + else if (mUri.find("data:") == 0) + { // embedded in a data URI, load the texture from the URI + LL_WARNS() << "Data URIs not yet supported" << LL_ENDL; + return false; + } + else if (mBufferView != INVALID_INDEX) + { // embedded in a buffer, load the texture from the buffer + BufferView& bufferView = asset.mBufferViews[mBufferView]; + Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; + + U8* data = buffer.mData.data() + bufferView.mByteOffset; + + mTexture = LLViewerTextureManager::getFetchedTextureFromMemory(data, bufferView.mByteLength, mMimeType); + + if (mTexture.isNull()) + { + LL_WARNS("GLTF") << "Failed to load image from buffer:" << LL_ENDL; + LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; + LL_WARNS("GLTF") << " mimeType: " << mMimeType << LL_ENDL; + + return false; + } + } + else if (!asset.mFilename.empty() && !mUri.empty()) + { // loaded locally and not embedded, load the texture as a local preview + std::string dir = gDirUtilp->getDirName(asset.mFilename); + std::string img_file = dir + gDirUtilp->getDirDelimiter() + mUri; + + LLUUID tracking_id = LLLocalBitmapMgr::getInstance()->addUnit(img_file); + if (tracking_id.notNull()) + { + LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); + mTexture = LLViewerTextureManager::getFetchedTexture(world_id); + } + else + { + LL_WARNS("GLTF") << "Failed to load image from file:" << LL_ENDL; + LL_WARNS("GLTF") << " image: " << mName << LL_ENDL; + LL_WARNS("GLTF") << " file: " << img_file << LL_ENDL; + + return false; + } + } + else + { + LL_WARNS("GLTF") << "Failed to load image: " << mName << LL_ENDL; + return false; + } + + return true; } @@ -1046,7 +846,6 @@ void Image::clearData(Asset& asset) asset.eraseBufferView(mBufferView); } - mData.clear(); mBufferView = INVALID_INDEX; mWidth = -1; mHeight = -1; @@ -1056,9 +855,15 @@ void Image::clearData(Asset& asset) mMimeType = ""; } -void Image::decompose(Asset& asset, const std::string& folder) +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()) { S32 idx = this - asset.mImages.data(); @@ -1067,10 +872,11 @@ void Image::decompose(Asset& asset, const std::string& folder) if (mBufferView != INVALID_INDEX) { - // save original image + // 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") @@ -1083,37 +889,76 @@ void Image::decompose(Asset& asset, const std::string& folder) } else { + error = "Unknown mime type, saved as .bin"; extension = ".bin"; } - std::string filename = folder + "/" + name + "." + extension; + std::string filename = folder + delim + name + extension; // set URI to non-j2c file for now, but later we'll want to reference the j2c hash - mUri = name + "." + extension; + mUri = name + extension; std::ofstream file(filename, std::ios::binary); file.write((const char*)buffer.mData.data() + bufferView.mByteOffset, bufferView.mByteLength); } - -#if 0 - if (!mData.empty()) + else if (mTexture.notNull()) { - // save j2c image - std::string filename = folder + "/" + name + ".j2c"; - - LLPointer raw = new LLImageRaw(mWidth, mHeight, mComponent); - U8* data = raw->allocateData(); - llassert_always(mData.size() == raw->getDataSize()); - memcpy(data, mData.data(), mData.size()); - - LLViewerTextureList::createUploadFile(raw, filename, 4096); + 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; - mData.clear(); + 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."; + } } -#endif + if (!error.empty()) + { + LL_WARNS("GLTF") << "Failed to save " << name << ": " << error << LL_ENDL; + return false; + } clearData(asset); + + return true; } void Material::TextureInfo::serialize(object& dst) const @@ -1143,13 +988,6 @@ bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const return !(*this == rhs); } -const Material::TextureInfo& Material::TextureInfo::operator=(const tinygltf::TextureInfo& src) -{ - mIndex = src.index; - mTexCoord = src.texCoord; - return *this; -} - void Material::OcclusionTextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); @@ -1169,14 +1007,6 @@ const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=( return *this; } -const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const tinygltf::OcclusionTextureInfo& src) -{ - mIndex = src.index; - mTexCoord = src.texCoord; - mStrength = src.strength; - return *this; -} - void Material::NormalTextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); @@ -1195,13 +1025,6 @@ const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const return *this; } -const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const tinygltf::NormalTextureInfo& src) -{ - mIndex = src.index; - mTexCoord = src.texCoord; - mScale = src.scale; - return *this; -} const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const Value& src) { @@ -1240,21 +1063,6 @@ bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRough return !(*this == rhs); } -const Material::PbrMetallicRoughness& Material::PbrMetallicRoughness::operator=(const tinygltf::PbrMetallicRoughness& src) -{ - if (src.baseColorFactor.size() == 4) - { - mBaseColorFactor = vec4(src.baseColorFactor[0], src.baseColorFactor[1], src.baseColorFactor[2], src.baseColorFactor[3]); - } - - mBaseColorTexture = src.baseColorTexture; - mMetallicFactor = src.metallicFactor; - mRoughnessFactor = src.roughnessFactor; - mMetallicRoughnessTexture = src.metallicRoughnessTexture; - - return *this; -} - static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, LLViewerTexture* fallback) { if (info.mIndex != INVALID_INDEX) @@ -1312,10 +1120,10 @@ void Material::bind(Asset& asset) if (!LLPipeline::sShadowRender) { - bindTexture(asset, LLShaderMgr::BUMP_MAP, mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep); + bindTexture(asset, LLShaderMgr::BUMP_MAP, mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep); bindTexture(asset, LLShaderMgr::SPECULAR_MAP, mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep); bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep); - + // NOTE: base color factor is baked into vertex stream shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, mPbrMetallicRoughness.mRoughnessFactor); @@ -1370,35 +1178,6 @@ const Material& Material::operator=(const Value& src) } -const Material& Material::operator=(const tinygltf::Material& src) -{ - mName = src.name; - - if (src.emissiveFactor.size() == 3) - { - mEmissiveFactor = vec3(src.emissiveFactor[0], src.emissiveFactor[1], src.emissiveFactor[2]); - } - - mPbrMetallicRoughness = src.pbrMetallicRoughness; - mNormalTexture = src.normalTexture; - mOcclusionTexture = src.occlusionTexture; - mEmissiveTexture = src.emissiveTexture; - - mAlphaMode = gltf_alpha_mode_to_enum(src.alphaMode); - mAlphaCutoff = src.alphaCutoff; - mDoubleSided = src.doubleSided; - - return *this; -} - -void Material::allocateGLResources(Asset& asset) -{ - // HACK: allocate an LLFetchedGLTFMaterial for now - // later we'll render directly from the GLTF Images - // and BufferViews - mMaterial = new LLFetchedGLTFMaterial(); -} - void Mesh::serialize(object& dst) const { write(mPrimitives, "primitives", dst); @@ -1418,26 +1197,18 @@ const Mesh& Mesh::operator=(const Value& src) return *this; } -const Mesh& Mesh::operator=(const tinygltf::Mesh& src) -{ - mPrimitives.resize(src.primitives.size()); - for (U32 i = 0; i < src.primitives.size(); ++i) - { - mPrimitives[i] = src.primitives[i]; - } - - mWeights = src.weights; - mName = src.name; - return *this; -} - -void Mesh::allocateGLResources(Asset& asset) +bool Mesh::prep(Asset& asset) { for (auto& primitive : mPrimitives) { - primitive.allocateGLResources(asset); + if (!primitive.prep(asset)) + { + return false; + } } + + return true; } void Scene::serialize(object& dst) const @@ -1450,14 +1221,6 @@ const Scene& Scene::operator=(const Value& src) { copy(src, "nodes", mNodes); copy(src, "name", mName); - - return *this; -} - -const Scene& Scene::operator=(const tinygltf::Scene& src) -{ - mNodes = src.nodes; - mName = src.name; return *this; } @@ -1481,16 +1244,6 @@ const Texture& Texture::operator=(const Value& src) return *this; } -const Texture& Texture::operator=(const tinygltf::Texture& src) -{ - mSampler = src.sampler; - mSource = src.source; - mName = src.name; - - return *this; -} - - void Sampler::serialize(object& dst) const { write(mMagFilter, "magFilter", dst, LINEAR); @@ -1511,17 +1264,6 @@ const Sampler& Sampler::operator=(const Value& src) return *this; } -const Sampler& Sampler::operator=(const tinygltf::Sampler& src) -{ - mMagFilter = src.magFilter; - mMinFilter = src.minFilter; - mWrapS = src.wrapS; - mWrapT = src.wrapT; - mName = src.name; - - return *this; -} - void Skin::uploadMatrixPalette(Asset& asset, Node& node) { // prepare matrix palette diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 990e1e41a9..761e746aa1 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -28,12 +28,12 @@ #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; @@ -69,7 +69,6 @@ namespace LL bool operator==(const TextureInfo& rhs) const; bool operator!=(const TextureInfo& rhs) const; - const TextureInfo& operator=(const tinygltf::TextureInfo& src); const TextureInfo& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; @@ -79,7 +78,6 @@ namespace LL public: F32 mScale = 1.0f; - const NormalTextureInfo& operator=(const tinygltf::NormalTextureInfo& src); const NormalTextureInfo& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; @@ -89,7 +87,6 @@ namespace LL public: F32 mStrength = 1.0f; - const OcclusionTextureInfo& operator=(const tinygltf::OcclusionTextureInfo& src); const OcclusionTextureInfo& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; @@ -105,22 +102,16 @@ namespace LL bool operator==(const PbrMetallicRoughness& rhs) const; bool operator!=(const PbrMetallicRoughness& rhs) const; - const PbrMetallicRoughness& operator=(const tinygltf::PbrMetallicRoughness& src); const PbrMetallicRoughness& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; - // 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 mMaterial; PbrMetallicRoughness mPbrMetallicRoughness; NormalTextureInfo mNormalTexture; OcclusionTextureInfo mOcclusionTexture; TextureInfo mEmissiveTexture; - std::string mName; vec3 mEmissiveFactor = vec3(0.f, 0.f, 0.f); AlphaMode mAlphaMode = AlphaMode::OPAQUE; @@ -129,11 +120,8 @@ namespace LL // bind for rendering void bind(Asset& asset); - const Material& operator=(const tinygltf::Material& src); const Material& operator=(const Value& src); void serialize(boost::json::object& dst) const; - - void allocateGLResources(Asset& asset); }; class Mesh @@ -143,11 +131,10 @@ namespace LL std::vector 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 @@ -178,7 +165,6 @@ 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; @@ -217,10 +203,9 @@ namespace LL std::string mName; std::vector mInverseBindMatricesData; - void allocateGLResources(Asset& asset); + bool prep(Asset& asset); void uploadMatrixPalette(Asset& asset, Node& node); - const Skin& operator=(const tinygltf::Skin& src); const Skin& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; @@ -231,7 +216,6 @@ namespace LL std::vector mNodes; std::string mName; - const Scene& operator=(const tinygltf::Scene& src); const Scene& operator=(const Value& src); void serialize(boost::json::object& dst) const; @@ -246,7 +230,6 @@ 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; }; @@ -260,7 +243,6 @@ namespace LL 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; }; @@ -274,7 +256,6 @@ namespace LL S32 mBufferView = INVALID_INDEX; - std::vector mData; S32 mWidth = -1; S32 mHeight = -1; S32 mComponent = -1; @@ -283,19 +264,20 @@ namespace LL LLPointer mTexture; - const Image& operator=(const tinygltf::Image& src); const Image& operator=(const Value& src); void serialize(boost::json::object& dst) const; - // save image clear local data, and set uri - void decompose(Asset& asset, const std::string& filename); + // 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 resources + // free any associated GLTF resources // preserve only uri and name void clearData(Asset& asset); - void allocateGLResources(); + bool prep(Asset& asset); }; // C++ representation of a GLTF Asset @@ -322,16 +304,20 @@ namespace LL std::string mMinVersion; std::string mCopyright; - S32 mDefaultScene = INVALID_INDEX; + 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 = tinygltf::Model()); + + // prepare for first time use + bool prep(); // Called periodically (typically once per frame) // Any ongoing work (such as animations) should be handled here @@ -361,18 +347,25 @@ namespace LL ); Asset() = default; - Asset(const tinygltf::Model& src); Asset(const Value& src); - const Asset& operator=(const tinygltf::Model& 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 a tinygltf model - void save(tinygltf::Model& dst); - - // decompose the asset to the given .gltf file - void decompose(const std::string& filename); + // 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 diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index b0fbc8524d..72e69c9599 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -31,7 +31,7 @@ // 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 @@ -353,37 +353,29 @@ namespace LL const Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset; - if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_FLOAT) - { - LL::GLTF::copy(asset, accessor, (const F32*)src, dst, bufferView.mByteStride); - } - else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) - { - LL::GLTF::copy(asset, accessor, (const U16*)src, dst, bufferView.mByteStride); - } - else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) - { - LL::GLTF::copy(asset, accessor, (const U32*)src, dst, bufferView.mByteStride); - } - else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) - { - LL::GLTF::copy(asset, accessor, (const U8*)src, dst, bufferView.mByteStride); - } - else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_SHORT) - { - LL::GLTF::copy(asset, accessor, (const S16*)src, dst, bufferView.mByteStride); - } - else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_BYTE) - { - LL::GLTF::copy(asset, accessor, (const S8*)src, dst, bufferView.mByteStride); - } - else if (accessor.mComponentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) - { - LL::GLTF::copy(asset, accessor, (const F64*)src, dst, bufferView.mByteStride); - } - else - { - LL_ERRS("GLTF") << "Unsupported component type" << LL_ENDL; + switch (accessor.mComponentType) + { + 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; } } @@ -400,7 +392,7 @@ namespace LL //========================================================================================================= // boost::json copying utilities // ======================================================================================================== - + //====================== unspecialized base template, single value =========================== // to/from Value @@ -528,7 +520,7 @@ namespace LL } return false; } - + template inline bool write(const std::unordered_map& src, string_view member, boost::json::object& dst, const std::unordered_map& default_value = std::unordered_map()) { @@ -571,6 +563,44 @@ namespace LL 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) @@ -881,7 +911,7 @@ namespace LL return true; } - // + // // ======================================================================================================== } diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h index 859e202738..59f2ba38db 100644 --- a/indra/newview/gltf/common.h +++ b/indra/newview/gltf/common.h @@ -60,6 +60,7 @@ namespace LL constexpr S32 MIRRORED_REPEAT = 33648; constexpr S32 REPEAT = 10497; + class Asset; } } diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 1bde7327e6..886b8f79c9 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 @@ -30,12 +30,10 @@ #include "buffer_util.h" #include "../llviewershadermgr.h" -#include "../lltinygltfhelper.h" - using namespace LL::GLTF; using namespace boost::json; -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 @@ -85,10 +83,19 @@ void Primitive::allocateGLResources(Asset& asset) { Accessor& accessor = asset.mAccessors[mIndices]; copy(asset, accessor, mIndexArray); + + for (auto& idx : mIndexArray) + { + if (idx >= mPositions.size()) + { + LL_WARNS("GLTF") << "Invalid index array" << LL_ENDL; + return false; + } + } } U32 mask = ATTRIBUTE_MASK; - + if (!mWeights.empty()) { mask |= LLVertexBuffer::MAP_WEIGHT4; @@ -130,7 +137,7 @@ void Primitive::allocateGLResources(Asset& asset) { mColors.resize(mPositions.size(), LLColor4U::white); } - + // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { @@ -148,7 +155,7 @@ void Primitive::allocateGLResources(Asset& asset) { mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); } - + mVertexBuffer->setNormalData(mNormals.data()); if (mTangents.empty()) @@ -175,10 +182,12 @@ void Primitive::allocateGLResources(Asset& asset) mVertexBuffer->setWeight4Data(weight_data.data()); } - + createOctree(); - + mVertexBuffer->unbind(); + + return true; } void initOctreeTriangle(LLVolumeTriangle* tri, F32 scaler, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2) @@ -224,7 +233,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 @@ -241,14 +250,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 @@ -272,7 +281,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 @@ -296,14 +305,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; @@ -357,23 +366,23 @@ Primitive::~Primitive() mOctree = nullptr; } -U32 gltf_mode_to_gl_mode(U32 mode) +LLRender::eGeomModes gltf_mode_to_gl_mode(Primitive::Mode mode) { switch (mode) { - case TINYGLTF_MODE_POINTS: + case Primitive::Mode::POINTS: return LLRender::POINTS; - case TINYGLTF_MODE_LINE: + case Primitive::Mode::LINES: return LLRender::LINES; - case TINYGLTF_MODE_LINE_LOOP: + case Primitive::Mode::LINE_LOOP: return LLRender::LINE_LOOP; - case TINYGLTF_MODE_LINE_STRIP: + case Primitive::Mode::LINE_STRIP: return LLRender::LINE_STRIP; - case TINYGLTF_MODE_TRIANGLES: + case Primitive::Mode::TRIANGLES: return LLRender::TRIANGLES; - case TINYGLTF_MODE_TRIANGLE_STRIP: + case Primitive::Mode::TRIANGLE_STRIP: return LLRender::TRIANGLE_STRIP; - case TINYGLTF_MODE_TRIANGLE_FAN: + case Primitive::Mode::TRIANGLE_FAN: return LLRender::TRIANGLE_FAN; default: return LLRender::TRIANGLES; @@ -383,7 +392,7 @@ U32 gltf_mode_to_gl_mode(U32 mode) void Primitive::serialize(boost::json::object& dst) const { write(mMaterial, "material", dst, -1); - write(mMode, "mode", dst, TINYGLTF_MODE_TRIANGLES); + write(mMode, "mode", dst, Primitive::Mode::TRIANGLES); write(mIndices, "indices", dst, INVALID_INDEX); write(mAttributes, "attributes", dst); } @@ -402,24 +411,3 @@ const Primitive& Primitive::operator=(const Value& src) return *this; } -const Primitive& Primitive::operator=(const tinygltf::Primitive& src) -{ - // 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; - } - - mGLMode = gltf_mode_to_gl_mode(mMode); - - return *this; -} diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index 18aadce808..9914ff3b08 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -48,6 +48,17 @@ namespace LL class Primitive { public: + enum class Mode : U8 + { + POINTS, + LINES, + LINE_LOOP, + LINE_STRIP, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLE_FAN + }; + ~Primitive(); // GPU copy of mesh data @@ -66,10 +77,10 @@ namespace LL // raycast acceleration structure LLPointer mOctree; std::vector mOctreeTriangles; - + S32 mMaterial = -1; - S32 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 mAttributes; @@ -77,7 +88,7 @@ namespace LL // 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, @@ -86,12 +97,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 ); - + void serialize(boost::json::object& obj) const; const Primitive& operator=(const Value& src); - const Primitive& operator=(const tinygltf::Primitive& src); - void allocateGLResources(Asset& asset); + bool prep(Asset& asset); }; } } -- cgit v1.2.3 From 15fd13f83036ff781160957a21bb2d59771044bc Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 29 May 2024 16:56:39 -0500 Subject: #1530 Increase joint limit for GLTF Assets (#1582) * Migrate GLTF scene rendering to its own shaders * Add support for ambient occlusion map separate from metallic roughness map (or absent) * Use UBO's for GLTF joints * Better error handling of downloading GLTF assets --- indra/newview/gltf/accessor.cpp | 8 +- indra/newview/gltf/animation.cpp | 66 +++++++++++ indra/newview/gltf/asset.cpp | 229 ++------------------------------------- indra/newview/gltf/asset.h | 12 +- indra/newview/gltf/common.h | 16 +++ 5 files changed, 101 insertions(+), 230 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 0619c617e2..5f4e3ca3a8 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -132,7 +132,13 @@ bool Buffer::prep(Asset& asset) { // loaded from an asset, fetch the buffer data from the asset store LLFileSystem file(id, LLAssetType::AT_GLTF_BIN, LLFileSystem::READ); - mData.resize(file.getSize()); + 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(), mData.size())) { LL_WARNS("GLTF") << "Failed to load buffer data from asset: " << id << LL_ENDL; diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp index f18bba788c..45e9e1ddef 100644 --- a/indra/newview/gltf/animation.cpp +++ b/indra/newview/gltf/animation.cpp @@ -28,6 +28,7 @@ #include "asset.h" #include "buffer_util.h" +#include "../llskinningutil.h" using namespace LL::GLTF; using namespace boost::json; @@ -362,6 +363,71 @@ const Animation& Animation::operator=(const Value& src) return *this; } +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); + } + + U32 joint_count = llmin(max_joints, mJoints.size()); + + std::vector 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 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) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 485984fac1..7870eb28b0 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -84,7 +84,7 @@ namespace LL void Scene::updateTransforms(Asset& asset) { mat4 identity = glm::identity(); - + for (auto& nodeIndex : mNodes) { Node& node = asset.mNodes[nodeIndex]; @@ -116,7 +116,7 @@ void Node::updateTransforms(Asset& asset, const mat4& parentMatrix) { makeMatrixValid(); mAssetMatrix = parentMatrix * mMatrix; - + mAssetMatrixInv = glm::inverse(mAssetMatrix); S32 my_index = this - &asset.mNodes[0]; @@ -356,94 +356,6 @@ const Image& Image::operator=(const Value& src) return *this; } -void Asset::render(bool opaque, bool rigged) -{ - if (rigged) - { - gGL.loadIdentity(); - } - - for (auto& node : mNodes) - { - if (node.mSkin != INVALID_INDEX) - { - if (rigged) - { - Skin& skin = mSkins[node.mSkin]; - skin.uploadMatrixPalette(*this, node); - } - else - { - //skip static nodes if we're rendering rigged - continue; - } - } - else if (rigged) - { - // skip rigged nodes if we're not rendering rigged - continue; - } - - if (node.mMesh != INVALID_INDEX) - { - Mesh& mesh = mMeshes[node.mMesh]; - for (auto& primitive : mesh.mPrimitives) - { - if (!rigged) - { - gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix)); - } - bool cull = true; - if (primitive.mMaterial != INVALID_INDEX) - { - Material& material = mMaterials[primitive.mMaterial]; - bool mat_opaque = material.mAlphaMode != Material::AlphaMode::BLEND; - - if (mat_opaque != opaque) - { - continue; - } - - material.bind(*this); - - cull = !material.mDoubleSided; - } - else - { - if (!opaque) - { - continue; - } - LLFetchedGLTFMaterial::sDefault.bind(); - } - - LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0); - - 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()); - } - - } - } - } -} - -void Asset::renderOpaque() -{ - render(true); -} - -void Asset::renderTransparent() -{ - render(false); -} - void Asset::update() { F32 dt = gFrameTimeSeconds - mLastUpdateTime; @@ -461,6 +373,11 @@ void Asset::update() } updateTransforms(); + + for (auto& skin : mSkins) + { + skin.uploadMatrixPalette(*this); + } } } @@ -1063,90 +980,6 @@ bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRough return !(*this == rhs); } -static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, LLViewerTexture* fallback) -{ - if (info.mIndex != INVALID_INDEX) - { - LLViewerTexture* tex = asset.mImages[asset.mTextures[info.mIndex].mSource].mTexture; - if (tex) - { - tex->addTextureStats(2048.f * 2048.f); - LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex); - } - else - { - LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback); - } - } - else - { - LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback); - } -} - -void Material::bind(Asset& asset) -{ - // bind for rendering (derived from LLFetchedGLTFMaterial::bind) - // glTF 2.0 Specification 3.9.4. Alpha Coverage - // mAlphaCutoff is only valid for LLGLTFMaterial::ALPHA_MODE_MASK - F32 min_alpha = -1.0; - - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - - if (!LLPipeline::sShadowRender || (mAlphaMode == Material::AlphaMode::BLEND)) - { - if (mAlphaMode == Material::AlphaMode::MASK) - { - // dividing the alpha cutoff by transparency here allows the shader to compare against - // the alpha value of the texture without needing the transparency value - if (mPbrMetallicRoughness.mBaseColorFactor.a > 0.f) - { - min_alpha = mAlphaCutoff / mPbrMetallicRoughness.mBaseColorFactor.a; - } - else - { - min_alpha = 1024.f; - } - } - shader->uniform1f(LLShaderMgr::MINIMUM_ALPHA, min_alpha); - } - - bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep); - - F32 base_color_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(base_color_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, (F32*)base_color_packed); - - if (!LLPipeline::sShadowRender) - { - bindTexture(asset, LLShaderMgr::BUMP_MAP, mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep); - bindTexture(asset, LLShaderMgr::SPECULAR_MAP, mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep); - bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep); - - // NOTE: base color factor is baked into vertex stream - - shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, mPbrMetallicRoughness.mRoughnessFactor); - shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, mPbrMetallicRoughness.mMetallicFactor); - shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(mEmissiveFactor)); - - F32 normal_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(normal_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, (F32*)normal_packed); - - F32 metallic_roughness_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(metallic_roughness_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, (F32*)metallic_roughness_packed); - - F32 emissive_packed[8]; - //mTextureTransform[GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed); - LLGLTFMaterial::sDefault.mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(emissive_packed); - shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, (F32*)emissive_packed); - } -} - void Material::serialize(object& dst) const { write(mName, "name", dst); @@ -1264,52 +1097,4 @@ const Sampler& Sampler::operator=(const Value& src) return *this; } -void Skin::uploadMatrixPalette(Asset& asset, Node& node) -{ - // prepare matrix palette - - // modelview will be applied by the shader, so assume matrix palette is in asset space - std::vector t_mp; - - t_mp.resize(mJoints.size()); - - for (U32 i = 0; i < mJoints.size(); ++i) - { - Node& joint = asset.mNodes[mJoints[i]]; - t_mp[i] = joint.mRenderMatrix * mInverseBindMatricesData[i]; - } - - std::vector glmp; - - glmp.resize(mJoints.size() * 12); - - F32* mp = glmp.data(); - - for (U32 i = 0; i < mJoints.size(); ++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]; - } - - LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, - mJoints.size(), - GL_FALSE, - (GLfloat*)glmp.data()); -} diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 761e746aa1..022fc484c2 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -118,8 +118,6 @@ namespace LL F32 mAlphaCutoff = 0.5f; bool mDoubleSided = false; - // bind for rendering - void bind(Asset& asset); const Material& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; @@ -197,14 +195,18 @@ namespace LL class Skin { public: + ~Skin(); + S32 mInverseBindMatrices = INVALID_INDEX; S32 mSkeleton = INVALID_INDEX; + + U32 mUBO = 0; std::vector mJoints; std::string mName; std::vector mInverseBindMatricesData; bool prep(Asset& asset); - void uploadMatrixPalette(Asset& asset, Node& node); + void uploadMatrixPalette(Asset& asset); const Skin& operator=(const Value& src); void serialize(boost::json::object& dst) const; @@ -332,10 +334,6 @@ namespace LL // update node render transforms void updateRenderTransforms(const mat4& modelview); - void render(bool opaque, bool rigged = false); - void renderOpaque(); - void renderTransparent(); - // 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 S32 lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h index 59f2ba38db..4f660d7cfc 100644 --- a/indra/newview/gltf/common.h +++ b/indra/newview/gltf/common.h @@ -36,6 +36,7 @@ #include "glm/ext/quaternion_float.hpp" #include "glm/gtx/quaternion.hpp" #include "glm/gtx/matrix_decompose.hpp" +#include // Common types and constants used in the GLTF implementation namespace LL @@ -62,6 +63,21 @@ namespace LL 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; } } -- cgit v1.2.3 From e279aae51a0f43cba0e284da4c0ea7c168316ca1 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Thu, 30 May 2024 13:42:27 -0500 Subject: #1597 Fix for some GLTF transforms not loading properly. Also incidental fix for unreachable code error. --- indra/newview/gltf/accessor.cpp | 6 ++-- indra/newview/gltf/asset.cpp | 5 ++- indra/newview/gltf/buffer_util.h | 78 +++++++++++++++++++--------------------- 3 files changed, 42 insertions(+), 47 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 5f4e3ca3a8..9f1cb0c1cd 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -124,8 +124,10 @@ void Buffer::erase(Asset& asset, S32 offset, S32 length) bool Buffer::prep(Asset& asset) { - // PRECONDITION: mByteLength must not be 0 - llassert(mByteLength != 0); + if (mByteLength == 0) + { + return false; + } LLUUID id; if (mUri.size() == UUID_STR_SIZE && LLUUID::parseUUID(mUri, &id) && id.notNull()) diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 7870eb28b0..7d379c2528 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -300,8 +300,8 @@ void Node::serialize(object& dst) const { write(mName, "name", dst); write(mMatrix, "matrix", dst, glm::identity()); - write(mRotation, "rotation", dst); - write(mTranslation, "translation", dst); + write(mRotation, "rotation", dst, glm::identity()); + 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); @@ -312,7 +312,6 @@ 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); diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index 72e69c9599..548d4ec5d6 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -610,14 +610,17 @@ namespace LL const boost::json::array& arr = src.as_array(); if (arr.size() == 4) { - if (arr[0].is_double() && - arr[1].is_double() && - arr[2].is_double() && - arr[3].is_double()) - { - dst = vec4(arr[0].get_double(), arr[1].get_double(), arr[2].get_double(), arr[3].get_double()); - return true; - } + vec4 v; + std::error_code ec; + + v.x = arr[0].to_number(ec); if (ec) return false; + v.y = arr[1].to_number(ec); if (ec) return false; + v.z = arr[2].to_number(ec); if (ec) return false; + v.w = arr[3].to_number(ec); if (ec) return false; + + dst = v; + + return true; } } return false; @@ -645,17 +648,13 @@ namespace LL const boost::json::array& arr = src.as_array(); if (arr.size() == 4) { - if (arr[0].is_double() && - arr[1].is_double() && - arr[2].is_double() && - arr[3].is_double()) - { - dst.x = arr[0].get_double(); - dst.y = arr[1].get_double(); - dst.z = arr[2].get_double(); - dst.w = arr[3].get_double(); - return true; - } + std::error_code ec; + dst.x = arr[0].to_number(ec); if (ec) return false; + dst.y = arr[1].to_number(ec); if (ec) return false; + dst.z = arr[2].to_number(ec); if (ec) return false; + dst.w = arr[3].to_number(ec); if (ec) return false; + + return true; } } return false; @@ -684,12 +683,13 @@ namespace LL 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()); - } + std::error_code ec; + vec3 t; + t.x = arr[0].to_number(ec); if (ec) return false; + t.y = arr[1].to_number(ec); if (ec) return false; + t.z = arr[2].to_number(ec); if (ec) return false; + + dst = t; return true; } } @@ -731,12 +731,10 @@ namespace LL template<> inline bool copy(const Value& src, F32& dst) { - if (src.is_double()) - { - dst = src.get_double(); - return true; - } - return false; + std::error_code ec; + F32 t = src.to_number(ec); if (ec) return false; + dst = t; + return true; } template<> @@ -770,12 +768,10 @@ namespace LL template<> inline bool copy(const Value& src, F64& dst) { - if (src.is_double()) - { - dst = src.get_double(); - return true; - } - return false; + std::error_code ec; + F64 t = src.to_number(ec); if (ec) return false; + dst = t; + return true; } template<> @@ -860,11 +856,9 @@ namespace LL for (U32 i = 0; i < arr.size(); ++i) { - if (arr[i].is_double()) - { - p[i] = arr[i].get_double(); - } - else + std::error_code ec; + p[i] = arr[i].to_number(ec); + if (ec) { return false; } -- cgit v1.2.3 From 33ddedd6b557ed9130dd8cd3b8327a697614a3ac Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 7 Jun 2024 09:51:32 -0500 Subject: #1638 Add permissions checks to GLTF Save As and Upload buttons (#1653) --- indra/newview/gltf/asset.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 022fc484c2..fd7ea17f05 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -368,6 +368,10 @@ namespace LL // 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); -- cgit v1.2.3 From 227e9be06832515fd10eb496d4a2a4528d1ebd92 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 10 Jun 2024 10:43:38 -0500 Subject: #1654 generate normals and tangents according to gltf specification (#1662) * Disable unloading of objects in background. * Add unlit GLTF shader variant --- indra/newview/gltf/buffer_util.h | 10 ++ indra/newview/gltf/primitive.cpp | 352 ++++++++++++++++++++++++++++++++++----- indra/newview/gltf/primitive.h | 15 +- 3 files changed, 323 insertions(+), 54 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index 548d4ec5d6..bd43f60d19 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -170,6 +170,16 @@ namespace LL dst.set(src[0], src[1], src[2], src[3]); } + template<> + inline void copyVec4(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* src, LLColor4U& dst) { diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 886b8f79c9..8d9880951b 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -30,9 +30,204 @@ #include "buffer_util.h" #include "../llviewershadermgr.h" +#include "mikktspace/mikktspace.hh" + +#include "meshoptimizer/meshoptimizer.h" + + using namespace LL::GLTF; using namespace boost::json; + +// Mesh data useful for Mikktspace tangent generation (and flat normal generation) +struct MikktMesh +{ + std::vector p; + std::vector n; + std::vector tc; + std::vector w; + std::vector t; + std::vector c; + std::vector j; + + // 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(); + U32 vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); + + if (prim->mMode != Primitive::Mode::TRIANGLES) + { + LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32) prim->mMode << LL_ENDL; + return false; + } + + p.resize(vert_count); + n.resize(vert_count); + tc.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); + } + + for (int i = 0; i < vert_count; ++i) + { + U32 idx = indexed ? prim->mIndexArray[i] : i; + + p[i].set(prim->mPositions[idx].getF32ptr()); + tc[i].set(prim->mTexCoords[idx]); + c[i] = prim->mColors[idx]; + + if (has_normals) + { + n[i].set(prim->mNormals[idx].getF32ptr()); + } + + if (rigged) + { + w[i].set(prim->mWeights[idx].getF32ptr()); + j[i] = prim->mJoints[idx]; + } + } + + return true; + } + + void genNormals() + { + U32 tri_count = p.size() / 3; + for (U32 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 + meshopt_Stream mos[] = + { + { &p[0], sizeof(LLVector3), sizeof(LLVector3) }, + { &n[0], sizeof(LLVector3), sizeof(LLVector3) }, + { &t[0], sizeof(LLVector4), sizeof(LLVector4) }, + { &tc[0], sizeof(LLVector2), sizeof(LLVector2) }, + { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) }, + { w.empty() ? nullptr : &w[0], sizeof(LLVector4), sizeof(LLVector4) }, + { j.empty() ? nullptr : &j[0], sizeof(U64), sizeof(U64) } + }; + + std::vector remap; + remap.resize(p.size()); + + U32 stream_count = w.empty() ? 5 : 7; + + size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos, stream_count); + + prim->mTexCoords.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); + } + + 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->mTexCoords[dst_idx] = tc[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]; + } + } + + 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 = tc[face_num * 3 + vert_num].mV; + return mikk::float3(uv[0], 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); + } +}; + + bool Primitive::prep(Asset& asset) { // allocate vertex buffer @@ -94,26 +289,12 @@ bool Primitive::prep(Asset& asset) } } - U32 mask = ATTRIBUTE_MASK; + U32 mask = LLVertexBuffer::MAP_VERTEX; if (!mWeights.empty()) { mask |= LLVertexBuffer::MAP_WEIGHT4; - } - - if (LLGLSLShader::sCurBoundShaderPtr == nullptr) - { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer - gDebugProgram.bind(); - } - mVertexBuffer = new LLVertexBuffer(mask); - mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size()*2); // double the size of the index buffer for 32-bit indices - - mVertexBuffer->setBuffer(); - mVertexBuffer->setPositionData(mPositions.data()); - - if (!mIndexArray.empty()) - { - mVertexBuffer->setIndexData(mIndexArray.data()); + mask |= LLVertexBuffer::MAP_JOINT; } if (mTexCoords.empty()) @@ -121,23 +302,17 @@ bool Primitive::prep(Asset& asset) mTexCoords.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()); - - for (auto& tc : mTexCoords) - { - tc[1] = 1.f - tc[1]; - } + // TODO: support more than one texcoord set (or no texcoords) + mask |= LLVertexBuffer::MAP_TEXCOORD0; if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } + // TODO: support colorless vertex buffers + mask |= LLVertexBuffer::MAP_COLOR; + // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { @@ -149,44 +324,131 @@ bool Primitive::prep(Asset& asset) } } - mVertexBuffer->setColorData(mColors.data()); + mShaderVariant = 0; if (mNormals.empty()) { - mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); + 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; + } + 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); + } + } + + bool unlit = (mShaderVariant & LLGLSLShader::GLTFVariant::UNLIT) != 0; + + if (mTangents.empty() && !unlit) + { // NOTE: must be done last because tangent generation rewrites the other arrays + // adapted from usage of Mikktspace in llvolume.cpp + if (mMode == Mode::POINTS || mMode == Mode::LINES || mMode == Mode::LINE_LOOP || mMode == Mode::LINE_STRIP) + { + // for points and lines, just make sure tangent is perpendicular to normal + mTangents.resize(mNormals.size()); + LLVector4a up(0.f, 0.f, 1.f, 0.f); + LLVector4a left(1.f, 0.f, 0.f, 0.f); + for (U32 i = 0; i < mNormals.size(); ++i) + { + if (fabsf(mNormals[i].getF32ptr()[2]) < 0.999f) + { + mTangents[i] = up.cross3(mNormals[i]); + } + else + { + mTangents[i] = left.cross3(mNormals[i]); + } + + mTangents[i].getF32ptr()[3] = 1.f; + } + } + else + { + MikktMesh data; + if (!data.copy(this)) + { + return false; + } + + data.genTangents(); + data.write(this); + } } - mVertexBuffer->setNormalData(mNormals.data()); - if (mTangents.empty()) + if (!unlit) { - // TODO: generate tangents if needed - mTangents.resize(mPositions.size(), LLVector4a(1, 0, 0, 1)); + mask |= LLVertexBuffer::MAP_NORMAL; + mask |= LLVertexBuffer::MAP_TANGENT; + } + + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); } - mVertexBuffer->setTangentData(mTangents.data()); + 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()); + mVertexBuffer->setColorData(mColors.data()); + + if (!mNormals.empty()) + { + mVertexBuffer->setNormalData(mNormals.data()); + } + if (!mTangents.empty()) + { + mVertexBuffer->setTangentData(mTangents.data()); + } if (!mWeights.empty()) { - std::vector weight_data; - weight_data.resize(mWeights.size()); + mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED; + mVertexBuffer->setWeight4Data(mWeights.data()); + mVertexBuffer->setJointData(mJoints.data()); + } - F32 max_weight = 1.f - FLT_EPSILON*100.f; - LLVector4a maxw(max_weight, max_weight, max_weight, max_weight); - for (U32 i = 0; i < mWeights.size(); ++i) - { - LLVector4a& w = weight_data[i]; - w.setMin(mWeights[i], maxw); - w.add(mJoints[i]); - }; + // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) + for (auto& tc : mTexCoords) + { + tc[1] = 1.f - tc[1]; + } + mVertexBuffer->setTexCoordData(mTexCoords.data()); + for (auto& tc : mTexCoords) + { + tc[1] = 1.f - tc[1]; + } - mVertexBuffer->setWeight4Data(weight_data.data()); + if (!mIndexArray.empty()) + { + mVertexBuffer->setIndexData(mIndexArray.data()); } createOctree(); mVertexBuffer->unbind(); + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } + return true; } diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index 9914ff3b08..f9d7c63c65 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -38,13 +38,6 @@ namespace LL 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: @@ -64,12 +57,12 @@ namespace LL // GPU copy of mesh data LLPointer mVertexBuffer; - // CPU copy of mesh data + // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code std::vector mTexCoords; std::vector mNormals; std::vector mTangents; std::vector mPositions; - std::vector mJoints; + std::vector mJoints; std::vector mWeights; std::vector mColors; std::vector mIndexArray; @@ -82,6 +75,10 @@ namespace LL Mode mMode = Mode::TRIANGLES; // default to triangles LLRender::eGeomModes mGLMode = LLRender::TRIANGLES; // for use with LLRender S32 mIndices = -1; + + // shader variant according to LLGLSLShader::GLTFVariant flags + U8 mShaderVariant = 0; + std::unordered_map mAttributes; // create octree based on vertex buffer -- cgit v1.2.3 From 4522f33d2bad2cc0f67e10a0b0ad3cc7c1b43fbd Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 10 Jun 2024 16:57:31 -0500 Subject: #1677 Add GLTF extensions serialization and support for KHR_materials_unlit (#1686) --- indra/newview/gltf/asset.cpp | 40 +++++++++++++++- indra/newview/gltf/asset.h | 19 ++++++++ indra/newview/gltf/buffer_util.h | 98 ++++++++++++++++++++++++++++++++++++++++ indra/newview/gltf/primitive.cpp | 24 +++++++--- 4 files changed, 173 insertions(+), 8 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 7d379c2528..a4efb25860 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -39,10 +39,15 @@ using namespace LL::GLTF; using namespace boost::json; + namespace LL { namespace GLTF { + static std::unordered_set ExtensionsSupported = { + "KHR_materials_unlit" + }; + Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) { if (alpha_mode == "OPAQUE") @@ -382,6 +387,22 @@ void Asset::update() bool Asset::prep() { + // check required extensions and fail if not supported + bool unsupported = false; + for (auto& extension : mExtensionsRequired) + { + if (ExtensionsSupported.find(extension) == ExtensionsSupported.end()) + { + LL_WARNS() << "Unsupported extension: " << extension << LL_ENDL; + unsupported = true; + } + } + + if (unsupported) + { + return false; + } + // do buffers first as other resources depend on them for (auto& buffer : mBuffers) { @@ -600,6 +621,8 @@ const Asset& Asset::operator=(const Value& src) copy(obj, "accessors", mAccessors); copy(obj, "animations", mAnimations); copy(obj, "skins", mSkins); + copy(obj, "extensionsUsed", mExtensionsUsed); + copy(obj, "extensionsRequired", mExtensionsRequired); } return *this; @@ -628,6 +651,8 @@ void Asset::serialize(object& dst) const write(mAccessors, "accessors", dst); write(mAnimations, "animations", dst); write(mSkins, "skins", dst); + write(mExtensionsUsed, "extensionsUsed", dst); + write(mExtensionsRequired, "extensionsRequired", dst); } bool Asset::save(const std::string& filename) @@ -979,6 +1004,17 @@ bool Material::PbrMetallicRoughness::operator!=(const Material::PbrMetallicRough return !(*this == rhs); } +const Material::Unlit& Material::Unlit::operator=(const Value& src) +{ + mPresent = true; + return *this; +} + +void Material::Unlit::serialize(object& dst) const +{ + // no members and object has already been created, nothing to do +} + void Material::serialize(object& dst) const { write(mName, "name", dst); @@ -990,6 +1026,7 @@ void Material::serialize(object& dst) const 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) @@ -1005,6 +1042,8 @@ const Material& Material::operator=(const Value& src) copy(src, "alphaMode", mAlphaMode); copy(src, "alphaCutoff", mAlphaCutoff); copy(src, "doubleSided", mDoubleSided); + copy_extensions(src, + "KHR_materials_unlit", &mUnlit ); } return *this; } @@ -1027,7 +1066,6 @@ const Mesh& Mesh::operator=(const Value& src) } return *this; - } bool Mesh::prep(Asset& asset) diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index fd7ea17f05..8f28e5905f 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -49,10 +49,26 @@ namespace LL { class Asset; + class Extension + { + public: + // true if this extension is present in the gltf file + // otherwise false + bool mPresent = false; + }; + + class Material { public: + 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, @@ -117,6 +133,7 @@ namespace LL AlphaMode mAlphaMode = AlphaMode::OPAQUE; F32 mAlphaCutoff = 0.5f; bool mDoubleSided = false; + Unlit mUnlit; const Material& operator=(const Value& src); void serialize(boost::json::object& dst) const; @@ -300,6 +317,8 @@ namespace LL std::vector mAccessors; std::vector mAnimations; std::vector mSkins; + std::vector mExtensionsUsed; + std::vector mExtensionsRequired; std::string mVersion; std::string mGenerator; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index bd43f60d19..c26752a6b6 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -519,6 +519,104 @@ namespace LL return true; } + + // to/from extension + + // for internal use only, use copy_extensions instead + template + inline bool _copy_extension(const boost::json::object& extensions, std::string_view member, T* dst) + { + if (extensions.contains(member)) + { + return copy(extensions.at(member), *dst); + } + + return false; + } + + // 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 + inline bool copy_extensions(const boost::json::value& src, Types... args) + { + // 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 + 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, + // "KHR_materials_unlit", mUnlit, + // "KHR_materials_pbrSpecularGlossiness", mPbrSpecularGlossiness); + // returns true if any of the extensions are written + template + 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 diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 8d9880951b..bc333aff69 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -313,6 +313,10 @@ bool Primitive::prep(Asset& asset) // TODO: support colorless vertex buffers mask |= LLVertexBuffer::MAP_COLOR; + mShaderVariant = 0; + + bool unlit = false; + // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { @@ -322,11 +326,15 @@ bool Primitive::prep(Asset& asset) { dst = LLColor4U(baseColor * LLColor4(dst)); } - } - mShaderVariant = 0; + if (material.mUnlit.mPresent) + { // material uses KHR_materials_unlit + mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT; + unlit = true; + } + } - if (mNormals.empty()) + if (mNormals.empty() && !unlit) { mTangents.clear(); @@ -334,6 +342,7 @@ bool Primitive::prep(Asset& asset) { //no normals and no surfaces, this primitive is unlit mTangents.clear(); mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT; + unlit = true; } else { @@ -350,8 +359,6 @@ bool Primitive::prep(Asset& asset) } } - bool unlit = (mShaderVariant & LLGLSLShader::GLTFVariant::UNLIT) != 0; - if (mTangents.empty() && !unlit) { // NOTE: must be done last because tangent generation rewrites the other arrays // adapted from usage of Mikktspace in llvolume.cpp @@ -388,10 +395,13 @@ bool Primitive::prep(Asset& asset) } } - - if (!unlit) + if (!mNormals.empty()) { mask |= LLVertexBuffer::MAP_NORMAL; + } + + if (!mTangents.empty()) + { mask |= LLVertexBuffer::MAP_TANGENT; } -- cgit v1.2.3 From 3d4bc98f6be51ebb8ac43af395e54bb6b608c9ae Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Tue, 11 Jun 2024 17:38:48 +0300 Subject: Trim remaining trailing whitespaces after #1695 --- indra/newview/gltf/accessor.h | 4 ++-- indra/newview/gltf/animation.h | 4 ++-- indra/newview/gltf/asset.cpp | 12 ++++++------ indra/newview/gltf/buffer_util.h | 2 +- indra/newview/gltf/primitive.cpp | 18 +++++++++--------- indra/newview/gltf/primitive.h | 6 +++--- 6 files changed, 23 insertions(+), 23 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/accessor.h b/indra/newview/gltf/accessor.h index 9b8265d8da..6e8871ef61 100644 --- a/indra/newview/gltf/accessor.h +++ b/indra/newview/gltf/accessor.h @@ -61,9 +61,9 @@ namespace LL std::string mName; const BufferView& operator=(const tinygltf::BufferView& src); - + }; - + class Accessor { public: diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h index 869eae963a..66ccd14c5c 100644 --- a/indra/newview/gltf/animation.h +++ b/indra/newview/gltf/animation.h @@ -159,7 +159,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; @@ -168,7 +168,7 @@ namespace LL std::vector mScaleChannels; const Animation& operator=(const tinygltf::Animation& src); - + void allocateGLResources(Asset& asset); void update(Asset& asset, float dt); diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index fbc3648fd2..973a460b73 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -71,7 +71,7 @@ void Node::updateTransforms(Asset& asset, const LLMatrix4a& parentMatrix) makeMatrixValid(); matMul(mMatrix, parentMatrix, mAssetMatrix); mAssetMatrixInv = inverse(mAssetMatrix); - + S32 my_index = this - &asset.mNodes[0]; for (auto& childIndex : mChildren) @@ -133,7 +133,7 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, { if (node.mMesh != INVALID_INDEX) { - + bool newHit = false; // transform start and end to this node's local space @@ -162,7 +162,7 @@ S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, if (newHit) { // shorten line segment on hit - node.mAssetMatrix.affineTransform(p, asset_end); + node.mAssetMatrix.affineTransform(p, asset_end); // transform results back to asset space if (intersection) @@ -229,7 +229,7 @@ void Node::makeMatrixValid() sc.set_scale(mScale); glh::matrix4f t; - //t = sc * rot * trans; + //t = sc * rot * trans; //t = trans * rot * sc; // best so far, still wrong on negative scale //t = sc * trans * rot; t = trans * sc * rot; @@ -538,7 +538,7 @@ const Asset& Asset::operator=(const tinygltf::Model& src) { mSkins[i] = src.skins[i]; } - + return *this; } @@ -616,7 +616,7 @@ void Skin::uploadMatrixPalette(Asset& asset, Node& node) for (U32 i = 0; i < mJoints.size(); ++i) { Node& joint = asset.mNodes[mJoints[i]]; - + //t_mp[i].set_value(joint.mRenderMatrix.getF32ptr()); //t_mp[i] = t_mp[i] * mInverseBindMatricesData[i]; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index 4e6f5901e7..a448f7a484 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -31,7 +31,7 @@ // 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 diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index d536f6a4cd..f1f0cf48f6 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 @@ -86,7 +86,7 @@ void Primitive::allocateGLResources(Asset& asset) } U32 mask = ATTRIBUTE_MASK; - + if (!mWeights.empty()) { mask |= LLVertexBuffer::MAP_WEIGHT4; @@ -124,7 +124,7 @@ void Primitive::allocateGLResources(Asset& asset) { mColors.resize(mPositions.size(), LLColor4U::white); } - + // bake material basecolor into color array if (mMaterial != INVALID_INDEX) { @@ -142,7 +142,7 @@ void Primitive::allocateGLResources(Asset& asset) { mNormals.resize(mPositions.size(), LLVector4a(0, 0, 1, 0)); } - + mVertexBuffer->setNormalData(mNormals.data()); if (mTangents.empty()) @@ -169,9 +169,9 @@ void Primitive::allocateGLResources(Asset& asset) mVertexBuffer->setWeight4Data(weight_data.data()); } - + createOctree(); - + mVertexBuffer->unbind(); } @@ -235,9 +235,9 @@ 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); } @@ -297,7 +297,7 @@ void Primitive::createOctree() { // nothing to do, no volume... maybe add some collision geometry around these primitive types? } - + else { LL_ERRS() << "Unsupported Primitive mode" << LL_ENDL; diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index 07e8e7deb2..09ab3d9ead 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -64,7 +64,7 @@ namespace LL // raycast acceleration structure LLPointer mOctree; std::vector mOctreeTriangles; - + S32 mMaterial = -1; U32 mMode = TINYGLTF_MODE_TRIANGLES; // default to triangles U32 mGLMode = LLRender::TRIANGLES; @@ -75,7 +75,7 @@ namespace LL // 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,7 +84,7 @@ 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); -- cgit v1.2.3 From 429c92ad75fd3b3f7b9dfc52ed034b25004a3b9c Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 11 Jun 2024 13:27:54 -0500 Subject: #1687 Add support for KHR_texture_transform (#1717) --- indra/newview/gltf/asset.cpp | 51 ++++++++++++++++++++++++++++++++++------ indra/newview/gltf/asset.h | 17 ++++++++++++++ indra/newview/gltf/buffer_util.h | 37 +++++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 9 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index a4efb25860..4c1da3e645 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -45,7 +45,8 @@ namespace LL namespace GLTF { static std::unordered_set ExtensionsSupported = { - "KHR_materials_unlit" + "KHR_materials_unlit", + "KHR_texture_transform" }; Material::AlphaMode gltf_alpha_mode_to_enum(const std::string& alpha_mode) @@ -906,6 +907,7 @@ 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"); } const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) @@ -914,6 +916,7 @@ const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) { copy(src, "index", mIndex); copy(src, "texCoord", mTexCoord); + copy_extensions(src, "KHR_texture_transform", &mTextureTransform); } return *this; @@ -931,17 +934,16 @@ bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const void Material::OcclusionTextureInfo::serialize(object& dst) const { - write(mIndex, "index", dst, INVALID_INDEX); - write(mTexCoord, "texCoord", dst, 0); + TextureInfo::serialize(dst); write(mStrength, "strength", dst, 1.f); } const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const Value& src) { + TextureInfo::operator=(src); + if (src.is_object()) { - copy(src, "index", mIndex); - copy(src, "texCoord", mTexCoord); copy(src, "strength", mStrength); } @@ -950,13 +952,13 @@ const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=( void Material::NormalTextureInfo::serialize(object& dst) const { - write(mIndex, "index", dst, INVALID_INDEX); - write(mTexCoord, "texCoord", dst, 0); + 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()) { copy(src, "index", mIndex); @@ -1015,6 +1017,41 @@ void Material::Unlit::serialize(object& dst) const // no members and object has already been created, nothing to do } +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; +} + + +const TextureTransform& TextureTransform::operator=(const Value& src) +{ + mPresent = true; + if (src.is_object()) + { + copy(src, "offset", mOffset); + copy(src, "rotation", mRotation); + copy(src, "scale", mScale); + copy(src, "texCoord", mTexCoord); + } + + return *this; +} + +void 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, 0); +} + + void Material::serialize(object& dst) const { write(mName, "name", dst); diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index 8f28e5905f..bca269d5dc 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -57,6 +57,21 @@ namespace LL 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 { @@ -82,6 +97,8 @@ namespace LL S32 mIndex = INVALID_INDEX; S32 mTexCoord = 0; + TextureTransform mTextureTransform; + bool operator==(const TextureInfo& rhs) const; bool operator!=(const TextureInfo& rhs) const; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index c26752a6b6..943a1748f9 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -590,8 +590,8 @@ namespace LL // Write all extensions to dst.extensions // Usage: // write_extensions(dst, - // "KHR_materials_unlit", mUnlit, - // "KHR_materials_pbrSpecularGlossiness", mPbrSpecularGlossiness); + // mUnlit, "KHR_materials_unlit", + // mPbrSpecularGlossiness, "KHR_materials_pbrSpecularGlossiness"); // returns true if any of the extensions are written template inline bool write_extensions(boost::json::object& dst, Types... args) @@ -816,6 +816,39 @@ namespace LL 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; + vec3 t; + t.x = arr[0].to_number(ec); if (ec) return false; + t.y = arr[1].to_number(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) -- cgit v1.2.3 From f40fbdf4ad27a547e30781cd44cd6847d68d3300 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 11 Jun 2024 17:10:13 -0500 Subject: #1718 Add GLTF support for multiple texcoords (#1720) * Fix for GLTF MeshPrimitiveModes test --- indra/newview/gltf/asset.cpp | 20 +++- indra/newview/gltf/asset.h | 6 ++ indra/newview/gltf/buffer_util.h | 2 +- indra/newview/gltf/primitive.cpp | 201 +++++++++++++++++++++++++++++---------- indra/newview/gltf/primitive.h | 3 +- 5 files changed, 181 insertions(+), 51 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 4c1da3e645..21be69aae2 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -910,6 +910,24 @@ void Material::TextureInfo::serialize(object& dst) const write_extensions(dst, &mTextureTransform, "KHR_texture_transform"); } +S32 Material::TextureInfo::getTexCoord() const +{ + if (mTextureTransform.mPresent && mTextureTransform.mTexCoord != INVALID_INDEX) + { + 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; +} + const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) { if (src.is_object()) @@ -1048,7 +1066,7 @@ 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, 0); + write(mTexCoord, "texCoord", dst, -1); } diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index bca269d5dc..ea3f7d480a 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -102,6 +102,10 @@ namespace LL 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; }; @@ -152,6 +156,8 @@ namespace LL bool mDoubleSided = false; Unlit mUnlit; + bool isMultiUV() const; + const Material& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index 943a1748f9..c1101818b7 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -826,7 +826,7 @@ namespace LL if (arr.size() == 2) { std::error_code ec; - vec3 t; + vec2 t; t.x = arr[0].to_number(ec); if (ec) return false; t.y = arr[1].to_number(ec); if (ec) return false; diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index bc333aff69..4cff0622b3 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -42,13 +42,14 @@ using namespace boost::json; // Mesh data useful for Mikktspace tangent generation (and flat normal generation) struct MikktMesh { - std::vector p; - std::vector n; - std::vector tc; - std::vector w; - std::vector t; - std::vector c; - std::vector j; + std::vector p; //positions + std::vector n; //normals + std::vector t; //tangents + std::vector tc0; //texcoords 0 + std::vector tc1; //texcoords 1 + std::vector c; //colors + std::vector w; //weights + std::vector j; //joints // initialize from src primitive and make an unrolled triangle list // returns false if the Primitive cannot be converted to a triangle list @@ -57,15 +58,28 @@ struct MikktMesh bool indexed = !prim->mIndexArray.empty(); U32 vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); - if (prim->mMode != Primitive::Mode::TRIANGLES) + U32 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) { - LL_WARNS("GLTF") << "Unsupported primitive mode for conversion to triangles: " << (S32) prim->mMode << LL_ENDL; + 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; + p.resize(vert_count); n.resize(vert_count); - tc.resize(vert_count); + tc0.resize(vert_count); c.resize(vert_count); bool has_normals = !prim->mNormals.empty(); @@ -78,6 +92,7 @@ struct MikktMesh { t.resize(vert_count); } + bool rigged = !prim->mWeights.empty(); if (rigged) { @@ -85,23 +100,69 @@ struct MikktMesh j.resize(vert_count); } - for (int i = 0; i < vert_count; ++i) + bool multi_uv = !prim->mTexCoords1.empty(); + if (multi_uv) { - U32 idx = indexed ? prim->mIndexArray[i] : i; + tc1.resize(vert_count); + } - p[i].set(prim->mPositions[idx].getF32ptr()); - tc[i].set(prim->mTexCoords[idx]); - c[i] = prim->mColors[idx]; + for (int tri_idx = 0; tri_idx < 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 (has_normals) + if (tri_idx % 2 != 0) + { + std::swap(idx[1], idx[2]); + } + } + else if (prim->mMode == Primitive::Mode::TRIANGLE_FAN) { - n[i].set(prim->mNormals[idx].getF32ptr()); + idx[0] = 0; + idx[1] = tri_idx + 1; + idx[2] = tri_idx + 2; } - if (rigged) + if (indexed) { - w[i].set(prim->mWeights[idx].getF32ptr()); - j[i] = prim->mJoints[idx]; + 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]]; + } } } @@ -138,25 +199,34 @@ struct MikktMesh void write(Primitive* prim) const { //re-weld - meshopt_Stream mos[] = + std::vector mos = { { &p[0], sizeof(LLVector3), sizeof(LLVector3) }, { &n[0], sizeof(LLVector3), sizeof(LLVector3) }, { &t[0], sizeof(LLVector4), sizeof(LLVector4) }, - { &tc[0], sizeof(LLVector2), sizeof(LLVector2) }, - { &c[0], sizeof(LLColor4U), sizeof(LLColor4U) }, - { w.empty() ? nullptr : &w[0], sizeof(LLVector4), sizeof(LLVector4) }, - { j.empty() ? nullptr : &j[0], sizeof(U64), sizeof(U64) } + { &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 remap; remap.resize(p.size()); - U32 stream_count = w.empty() ? 5 : 7; + U32 stream_count = mos.size(); - size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos, stream_count); + size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos.data(), stream_count); - prim->mTexCoords.resize(vert_count); + prim->mTexCoords0.resize(vert_count); prim->mNormals.resize(vert_count); prim->mTangents.resize(vert_count); prim->mPositions.resize(vert_count); @@ -166,6 +236,10 @@ struct MikktMesh prim->mWeights.resize(vert_count); prim->mJoints.resize(vert_count); } + if (!tc1.empty()) + { + prim->mTexCoords1.resize(vert_count); + } prim->mIndexArray.resize(remap.size()); @@ -178,7 +252,7 @@ struct MikktMesh prim->mPositions[dst_idx].load3(p[src_idx].mV); prim->mNormals[dst_idx].load3(n[src_idx].mV); - prim->mTexCoords[dst_idx] = tc[src_idx]; + prim->mTexCoords0[dst_idx] = tc0[src_idx]; prim->mTangents[dst_idx].loadua(t[src_idx].mV); prim->mColors[dst_idx] = c[src_idx]; @@ -187,6 +261,11 @@ struct MikktMesh 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; @@ -210,8 +289,8 @@ struct MikktMesh mikk::float3 GetTexCoord(const uint32_t face_num, const uint32_t vert_num) { - F32* uv = tc[face_num * 3 + vert_num].mV; - return mikk::float3(uv[0], uv[1], 1.0f); + 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) @@ -228,6 +307,14 @@ struct MikktMesh }; +static void vertical_flip(std::vector& texcoords) +{ + for (auto& tc : texcoords) + { + tc[1] = 1.f - tc[1]; + } +} + bool Primitive::prep(Asset& asset) { // allocate vertex buffer @@ -261,7 +348,11 @@ bool Primitive::prep(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") { @@ -297,24 +388,28 @@ bool Primitive::prep(Asset& asset) mask |= LLVertexBuffer::MAP_JOINT; } - if (mTexCoords.empty()) + if (mTexCoords0.empty()) { - mTexCoords.resize(mPositions.size()); + mTexCoords0.resize(mPositions.size()); } - // TODO: support more than one texcoord set (or no texcoords) mask |= LLVertexBuffer::MAP_TEXCOORD0; + if (!mTexCoords1.empty()) + { + mask |= LLVertexBuffer::MAP_TEXCOORD1; + } + if (mColors.empty()) { mColors.resize(mPositions.size(), LLColor4U::white); } + mShaderVariant = 0; + // TODO: support colorless vertex buffers mask |= LLVertexBuffer::MAP_COLOR; - mShaderVariant = 0; - bool unlit = false; // bake material basecolor into color array @@ -332,6 +427,11 @@ bool Primitive::prep(Asset& asset) mShaderVariant |= LLGLSLShader::GLTFVariant::UNLIT; unlit = true; } + + if (material.isMultiUV()) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::MULTI_UV; + } } if (mNormals.empty() && !unlit) @@ -434,15 +534,17 @@ bool Primitive::prep(Asset& asset) } // 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()); - for (auto& tc : mTexCoords) + vertical_flip(mTexCoords0); + mVertexBuffer->setTexCoord0Data(mTexCoords0.data()); + vertical_flip(mTexCoords0); + + if (!mTexCoords1.empty()) { - tc[1] = 1.f - tc[1]; + vertical_flip(mTexCoords1); + mVertexBuffer->setTexCoord1Data(mTexCoords1.data()); + vertical_flip(mTexCoords1); } + if (!mIndexArray.empty()) { @@ -453,10 +555,13 @@ bool Primitive::prep(Asset& asset) mVertexBuffer->unbind(); - Material& material = asset.mMaterials[mMaterial]; - if (material.mAlphaMode == Material::AlphaMode::BLEND) + if (mMaterial != INVALID_INDEX) { - mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } } return true; @@ -614,7 +719,7 @@ 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 diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index f9d7c63c65..7cc05cf831 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -58,7 +58,8 @@ namespace LL LLPointer mVertexBuffer; // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code - std::vector mTexCoords; + std::vector mTexCoords0; + std::vector mTexCoords1; std::vector mNormals; std::vector mTangents; std::vector mPositions; -- cgit v1.2.3 From a7b0f9391146b42dd5cd5f47f845de81bfdb6820 Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Tue, 11 Jun 2024 15:39:48 -0700 Subject: Fixed signed/unsigned warnings after they got enabled in the maint-A merge --- indra/newview/gltf/accessor.cpp | 5 +++-- indra/newview/gltf/animation.cpp | 11 +++++------ indra/newview/gltf/primitive.cpp | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 16 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 9f1cb0c1cd..2ef9237f2d 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -108,7 +108,8 @@ void Buffer::erase(Asset& asset, S32 offset, S32 length) mData.erase(mData.begin() + offset, mData.begin() + offset + length); - mByteLength = mData.size(); + llassert(mData.size() <= size_t(INT_MAX)); + mByteLength = S32(mData.size()); for (BufferView& view : asset.mBufferViews) { @@ -141,7 +142,7 @@ bool Buffer::prep(Asset& asset) } mData.resize(mByteLength); - if (!file.read((U8*)mData.data(), mData.size())) + if (!file.read((U8*)mData.data(), mByteLength)) { LL_WARNS("GLTF") << "Failed to load buffer data from asset: " << id << LL_ENDL; return false; diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp index 45e9e1ddef..8f53c28539 100644 --- a/indra/newview/gltf/animation.cpp +++ b/indra/newview/gltf/animation.cpp @@ -189,16 +189,15 @@ 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++) { if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1]) @@ -382,7 +381,7 @@ void Skin::uploadMatrixPalette(Asset& asset) glGenBuffers(1, &mUBO); } - U32 joint_count = llmin(max_joints, mJoints.size()); + size_t joint_count = llmin(max_joints, mJoints.size()); std::vector t_mp; diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index bc333aff69..197ffb68e8 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -55,7 +55,7 @@ struct MikktMesh bool copy(const Primitive* prim) { bool indexed = !prim->mIndexArray.empty(); - U32 vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); + auto vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); if (prim->mMode != Primitive::Mode::TRIANGLES) { @@ -85,7 +85,7 @@ struct MikktMesh j.resize(vert_count); } - for (int i = 0; i < vert_count; ++i) + for (U32 i = 0; i < vert_count; ++i) { U32 idx = indexed ? prim->mIndexArray[i] : i; @@ -110,8 +110,8 @@ struct MikktMesh void genNormals() { - U32 tri_count = p.size() / 3; - for (U32 i = 0; i < tri_count; ++i) + 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]; @@ -166,7 +166,7 @@ struct MikktMesh prim->mWeights.resize(vert_count); prim->mJoints.resize(vert_count); } - + prim->mIndexArray.resize(remap.size()); for (int i = 0; i < remap.size(); ++i) @@ -411,7 +411,10 @@ bool Primitive::prep(Asset& asset) } mVertexBuffer = new LLVertexBuffer(mask); - mVertexBuffer->allocateBuffer(mPositions.size(), mIndexArray.size() * 2); // double the size of the index buffer for 32-bit indices + // 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()); @@ -619,8 +622,8 @@ const LLVolumeTriangle* Primitive::lineSegmentIntersect(const LLVector4a& start, 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); -- cgit v1.2.3 From 8729c0d2c41d7acb2357a5ba9f3a713fe9bcc426 Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Wed, 12 Jun 2024 11:41:20 -0700 Subject: Fixup more signed/unsigned warnings after merge. --- indra/newview/gltf/primitive.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index dd37b5b4d0..2280c7004e 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -56,9 +56,9 @@ struct MikktMesh bool copy(const Primitive* prim) { bool indexed = !prim->mIndexArray.empty(); - auto vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); + size_t vert_count = indexed ? prim->mIndexArray.size() : prim->mPositions.size(); - U32 triangle_count = 0; + size_t triangle_count = 0; if (prim->mMode == Primitive::Mode::TRIANGLE_STRIP || prim->mMode == Primitive::Mode::TRIANGLE_FAN) @@ -76,6 +76,7 @@ struct MikktMesh } 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); @@ -106,7 +107,7 @@ struct MikktMesh tc1.resize(vert_count); } - for (int tri_idx = 0; tri_idx < triangle_count; ++tri_idx) + for (U32 tri_idx = 0; tri_idx < U32(triangle_count); ++tri_idx) { U32 idx[3]; @@ -222,7 +223,7 @@ struct MikktMesh std::vector remap; remap.resize(p.size()); - U32 stream_count = mos.size(); + size_t stream_count = mos.size(); size_t vert_count = meshopt_generateVertexRemapMulti(&remap[0], nullptr, p.size(), p.size(), mos.data(), stream_count); -- cgit v1.2.3 From 8444cd9562a6a7b755fcb075864e205122354192 Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Wed, 12 Jun 2024 13:51:21 -0700 Subject: Fix whitespace pre-commit hook failures --- indra/newview/gltf/buffer_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index c1101818b7..40f9448aaf 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -845,7 +845,7 @@ namespace LL arr.resize(2); arr[0] = src.x; arr[1] = src.y; - + return true; } -- cgit v1.2.3 From 80ea30af1a8b38360f71c29cc45872c4399dab0d Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 21 Jun 2024 13:13:08 -0500 Subject: #1769 gltf optimization pass (#1816) #1814 and #1517 Fix mirror update rate and occlusion culling --- indra/newview/gltf/animation.cpp | 98 ++++++++----- indra/newview/gltf/animation.h | 3 + indra/newview/gltf/asset.cpp | 299 +++++++++++++++++++++++++++++++++------ indra/newview/gltf/asset.h | 127 +++++++++++------ indra/newview/gltf/common.h | 14 ++ indra/newview/gltf/primitive.cpp | 90 +++++++----- indra/newview/gltf/primitive.h | 21 ++- 7 files changed, 489 insertions(+), 163 deletions(-) (limited to 'indra/newview/gltf') diff --git a/indra/newview/gltf/animation.cpp b/indra/newview/gltf/animation.cpp index 8b85eba3e5..3dff67d746 100644 --- a/indra/newview/gltf/animation.cpp +++ b/indra/newview/gltf/animation.cpp @@ -70,6 +70,14 @@ bool Animation::prep(Asset& asset) } } + for (auto& channel : mScaleChannels) + { + if (!channel.prep(asset, mSamplers[channel.mSampler])) + { + return false; + } + } + return true; } @@ -82,18 +90,37 @@ void Animation::update(Asset& asset, F32 dt) void Animation::apply(Asset& asset, float time) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; + // convert time to animation loop time time = fmod(time, mMaxTime - mMinTime) + mMinTime; // apply each channel - for (auto& channel : mRotationChannels) { - channel.apply(asset, mSamplers[channel.mSampler], time); + LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - rotation"); + + for (auto& channel : mRotationChannels) + { + channel.apply(asset, mSamplers[channel.mSampler], time); + } + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - translation"); + + for (auto& channel : mTranslationChannels) + { + channel.apply(asset, mSamplers[channel.mSampler], time); + } } - for (auto& channel : mTranslationChannels) { - channel.apply(asset, mSamplers[channel.mSampler], time); + LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfanim - scale"); + + for (auto& channel : mScaleChannels) + { + channel.apply(asset, mSamplers[channel.mSampler], time); + } } }; @@ -178,7 +205,8 @@ const Animation::Channel& Animation::Channel::operator=(const Value& src) void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F32& t) { - LL_PROFILE_ZONE_SCOPED; + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; + llassert(mFrameTimes.size() > 1); // if there is only one frame, there is no need to interpolate if (time < mMinTime) { @@ -187,32 +215,33 @@ void Animation::Sampler::getFrameInfo(Asset& asset, F32 time, U32& frameIndex, F return; } - if (mFrameTimes.size() > 1) + frameIndex = U32(mFrameTimes.size()) - 2; + t = 1.f; + + if (time > mMaxTime) { - llassert(mFrameTimes.size() <= size_t(U32_MAX)); - frameIndex = U32(mFrameTimes.size()) - 2; - t = 1.f; + return; + } - if (time > mMaxTime) - { - return; - } + if (time < mLastFrameTime) + { + mLastFrameIndex = 0; + } - for (U32 i = 0; i < (U32)mFrameTimes.size() - 1; i++) + mLastFrameTime = time; + + U32 idx = mLastFrameIndex; + + for (U32 i = idx; i < (U32)mFrameTimes.size() - 1; i++) + { + if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1]) { - if (time >= mFrameTimes[i] && time < mFrameTimes[i + 1]) - { - frameIndex = i; - t = (time - mFrameTimes[i]) / (mFrameTimes[i + 1] - mFrameTimes[i]); - return; - } + frameIndex = i; + t = (time - mFrameTimes[i]) / (mFrameTimes[i + 1] - mFrameTimes[i]); + mLastFrameIndex = frameIndex; + return; } } - else - { - frameIndex = 0; - t = 0.0f; - } } bool Animation::RotationChannel::prep(Asset& asset, Animation::Sampler& sampler) @@ -231,14 +260,14 @@ void Animation::RotationChannel::apply(Asset& asset, Sampler& sampler, F32 time) Node& node = asset.mNodes[mTarget.mNode]; - sampler.getFrameInfo(asset, time, frameIndex, t); - - if (sampler.mFrameTimes.size() == 1) + if (sampler.mFrameTimes.size() < 2) { node.setRotation(mRotations[0]); } else { + sampler.getFrameInfo(asset, time, frameIndex, t); + // interpolate quat qf = glm::slerp(mRotations[frameIndex], mRotations[frameIndex + 1], t); @@ -264,14 +293,14 @@ void Animation::TranslationChannel::apply(Asset& asset, Sampler& sampler, F32 ti Node& node = asset.mNodes[mTarget.mNode]; - sampler.getFrameInfo(asset, time, frameIndex, t); - - if (sampler.mFrameTimes.size() == 1) + if (sampler.mFrameTimes.size() < 2) { node.setTranslation(mTranslations[0]); } else { + sampler.getFrameInfo(asset, time, frameIndex, t); + // interpolate const vec3& v0 = mTranslations[frameIndex]; const vec3& v1 = mTranslations[frameIndex + 1]; @@ -298,14 +327,14 @@ void Animation::ScaleChannel::apply(Asset& asset, Sampler& sampler, F32 time) Node& node = asset.mNodes[mTarget.mNode]; - sampler.getFrameInfo(asset, time, frameIndex, t); - - if (sampler.mFrameTimes.size() == 1) + if (sampler.mFrameTimes.size() < 2) { node.setScale(mScales[0]); } else { + sampler.getFrameInfo(asset, time, frameIndex, t); + // interpolate const vec3& v0 = mScales[frameIndex]; const vec3& v1 = mScales[frameIndex + 1]; @@ -373,6 +402,7 @@ Skin::~Skin() void Skin::uploadMatrixPalette(Asset& asset) { // prepare matrix palette + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; U32 max_joints = LLSkinningUtil::getMaxGLTFJointCount(); diff --git a/indra/newview/gltf/animation.h b/indra/newview/gltf/animation.h index d5426fd4ce..ab8839470a 100644 --- a/indra/newview/gltf/animation.h +++ b/indra/newview/gltf/animation.h @@ -49,6 +49,9 @@ namespace LL S32 mOutput = INVALID_INDEX; std::string mInterpolation; + F32 mLastFrameTime = 0.f; + U32 mLastFrameIndex = 0; + bool prep(Asset& asset); void serialize(boost::json::object& dst) const; diff --git a/indra/newview/gltf/asset.cpp b/indra/newview/gltf/asset.cpp index 21be69aae2..a454e68a92 100644 --- a/indra/newview/gltf/asset.cpp +++ b/indra/newview/gltf/asset.cpp @@ -35,6 +35,7 @@ #include "buffer_util.h" #include #include "llimagejpeg.h" +#include "../llskinningutil.h" using namespace LL::GLTF; using namespace boost::json; @@ -86,7 +87,6 @@ namespace LL } } - void Scene::updateTransforms(Asset& asset) { mat4 identity = glm::identity(); @@ -98,26 +98,6 @@ void Scene::updateTransforms(Asset& asset) } } -void Scene::updateRenderTransforms(Asset& asset, const mat4& modelview) -{ - for (auto& nodeIndex : mNodes) - { - Node& node = asset.mNodes[nodeIndex]; - node.updateRenderTransforms(asset, modelview); - } -} - -void Node::updateRenderTransforms(Asset& asset, const mat4& modelview) -{ - mRenderMatrix = modelview * mMatrix; - - for (auto& childIndex : mChildren) - { - Node& child = asset.mNodes[childIndex]; - child.updateRenderTransforms(asset, mRenderMatrix); - } -} - void Node::updateTransforms(Asset& asset, const mat4& parentMatrix) { makeMatrixValid(); @@ -137,19 +117,119 @@ void Node::updateTransforms(Asset& asset, const mat4& parentMatrix) void Asset::updateTransforms() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; for (auto& scene : mScenes) { scene.updateTransforms(*this); } + + uploadTransforms(); } -void Asset::updateRenderTransforms(const mat4& modelview) +void Asset::uploadTransforms() { - // use mAssetMatrix to update render transforms from node list - for (auto& node : mNodes) + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; + // prepare matrix palette + U32 max_nodes = LLSkinningUtil::getMaxGLTFJointCount(); + + size_t node_count = llmin(max_nodes, mNodes.size()); + + std::vector t_mp; + + t_mp.resize(node_count); + + for (U32 i = 0; i < node_count; ++i) { - node.mRenderMatrix = modelview * node.mAssetMatrix; + Node& node = mNodes[i]; + // build matrix palette in asset space + t_mp[i] = node.mAssetMatrix; } + + std::vector glmp; + + glmp.resize(node_count * 12); + + F32* mp = glmp.data(); + + for (U32 i = 0; i < node_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]; + } + + if (mNodesUBO == 0) + { + glGenBuffers(1, &mNodesUBO); + } + + glBindBuffer(GL_UNIFORM_BUFFER, mNodesUBO); + glBufferData(GL_UNIFORM_BUFFER, glmp.size() * sizeof(F32), glmp.data(), GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); +} + +void Asset::uploadMaterials() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; + // see pbrmetallicroughnessV.glsl for the layout of the material UBO + std::vector md; + + U32 material_size = sizeof(vec4) * 12; + U32 max_materials = gGLManager.mMaxUniformBlockSize / material_size; + + U32 mat_count = (U32)mMaterials.size(); + mat_count = llmin(mat_count, max_materials); + + md.resize(mat_count * 12); + + for (U32 i = 0; i < mat_count*12; i += 12) + { + Material& material = mMaterials[i/12]; + + // add texture transforms and UV indices + material.mPbrMetallicRoughness.mBaseColorTexture.mTextureTransform.getPacked(&md[i+0]); + md[i + 1].g = (F32)material.mPbrMetallicRoughness.mBaseColorTexture.getTexCoord(); + material.mNormalTexture.mTextureTransform.getPacked(&md[i + 2]); + md[i + 3].g = (F32)material.mNormalTexture.getTexCoord(); + material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTextureTransform.getPacked(&md[i+4]); + md[i + 5].g = (F32)material.mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord(); + material.mEmissiveTexture.mTextureTransform.getPacked(&md[i + 6]); + md[i + 7].g = (F32)material.mEmissiveTexture.getTexCoord(); + material.mOcclusionTexture.mTextureTransform.getPacked(&md[i + 8]); + md[i + 9].g = (F32)material.mOcclusionTexture.getTexCoord(); + + // add material properties + F32 min_alpha = material.mAlphaMode == Material::AlphaMode::MASK ? material.mAlphaCutoff : -1.0f; + md[i + 10] = vec4(material.mEmissiveFactor, 1.f); + md[i + 11] = vec4(0.f, + material.mPbrMetallicRoughness.mRoughnessFactor, + material.mPbrMetallicRoughness.mMetallicFactor, + min_alpha); + } + + if (mMaterialsUBO == 0) + { + glGenBuffers(1, &mMaterialsUBO); + } + + glBindBuffer(GL_UNIFORM_BUFFER, mMaterialsUBO); + glBufferData(GL_UNIFORM_BUFFER, md.size() * sizeof(vec4), md.data(), GL_STREAM_DRAW); + glBindBuffer(GL_UNIFORM_BUFFER, 0); } S32 Asset::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, @@ -363,6 +443,7 @@ const Image& Image::operator=(const Value& src) void Asset::update() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; F32 dt = gFrameTimeSeconds - mLastUpdateTime; if (dt > 0.f) @@ -383,11 +464,27 @@ void Asset::update() { skin.uploadMatrixPalette(*this); } + + uploadMaterials(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltf - addTextureStats"); + + for (auto& image : mImages) + { + if (image.mTexture.notNull()) + { // HACK - force texture to be loaded full rez + // TODO: calculate actual vsize + image.mTexture->addTextureStats(2048.f * 2048.f); + } + } + } } } bool Asset::prep() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; // check required extensions and fail if not supported bool unsupported = false; for (auto& extension : mExtensionsRequired) @@ -445,6 +542,127 @@ bool Asset::prep() } } + // prepare vertex buffers + + // material count is number of materials + 1 for default material + U32 mat_count = (U32) mMaterials.size() + 1; + + if (LLGLSLShader::sCurBoundShaderPtr == nullptr) + { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer + gDebugProgram.bind(); + } + + for (S32 double_sided = 0; double_sided < 2; ++double_sided) + { + RenderData& rd = mRenderData[double_sided]; + for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) + { + rd.mBatches[i].resize(mat_count); + } + + // for each material + for (S32 mat_id = -1; mat_id < (S32)mMaterials.size(); ++mat_id) + { + // for each shader variant + U32 vertex_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + U32 index_count[LLGLSLShader::NUM_GLTF_VARIANTS] = { 0 }; + + S32 ds_mat = mat_id == -1 ? 0 : mMaterials[mat_id].mDoubleSided; + if (ds_mat != double_sided) + { + continue; + } + + for (U32 variant = 0; variant < LLGLSLShader::NUM_GLTF_VARIANTS; ++variant) + { + U32 attribute_mask = 0; + // for each mesh + for (auto& mesh : mMeshes) + { + // for each primitive + for (auto& primitive : mesh.mPrimitives) + { + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + // accumulate vertex and index counts + primitive.mVertexOffset = vertex_count[variant]; + primitive.mIndexOffset = index_count[variant]; + + vertex_count[variant] += primitive.getVertexCount(); + index_count[variant] += primitive.getIndexCount(); + + // all primitives of a given variant and material should all have the same attribute mask + llassert(attribute_mask == 0 || primitive.mAttributeMask == attribute_mask); + attribute_mask |= primitive.mAttributeMask; + } + } + } + + // allocate vertex buffer and pack it + if (vertex_count[variant] > 0) + { + U32 mat_idx = mat_id + 1; + LLVertexBuffer* vb = new LLVertexBuffer(attribute_mask); + + rd.mBatches[variant][mat_idx].mVertexBuffer = vb; + vb->allocateBuffer(vertex_count[variant], + index_count[variant] * 2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used + vb->setBuffer(); + + for (auto& mesh : mMeshes) + { + for (auto& primitive : mesh.mPrimitives) + { + if (primitive.mMaterial == mat_id && primitive.mShaderVariant == variant) + { + primitive.upload(vb); + } + } + } + + vb->unmapBuffer(); + + vb->unbind(); + } + } + } + } + + // sanity check that all primitives have a vertex buffer + for (auto& mesh : mMeshes) + { + for (auto& primitive : mesh.mPrimitives) + { + llassert(primitive.mVertexBuffer.notNull()); + } + } + + // build render batches + for (S32 node_id = 0; node_id < mNodes.size(); ++node_id) + { + Node& node = mNodes[node_id]; + + if (node.mMesh != INVALID_INDEX) + { + auto& mesh = mMeshes[node.mMesh]; + + S32 mat_idx = mesh.mPrimitives[0].mMaterial + 1; + + S32 double_sided = mat_idx == 0 ? 0 : mMaterials[mat_idx - 1].mDoubleSided; + + for (S32 j = 0; j < mesh.mPrimitives.size(); ++j) + { + auto& primitive = mesh.mPrimitives[j]; + + S32 variant = primitive.mShaderVariant; + + RenderData& rd = mRenderData[double_sided]; + RenderBatch& rb = rd.mBatches[variant][mat_idx]; + + rb.mPrimitives.push_back({ j, node_id }); + } + } + } return true; } @@ -455,6 +673,7 @@ Asset::Asset(const Value& src) bool Asset::load(std::string_view filename) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF; mFilename = filename; std::string ext = gDirUtilp->getExtension(mFilename); @@ -903,14 +1122,14 @@ bool Image::save(Asset& asset, const std::string& folder) return true; } -void Material::TextureInfo::serialize(object& dst) const +void TextureInfo::serialize(object& dst) const { write(mIndex, "index", dst, INVALID_INDEX); write(mTexCoord, "texCoord", dst, 0); write_extensions(dst, &mTextureTransform, "KHR_texture_transform"); } -S32 Material::TextureInfo::getTexCoord() const +S32 TextureInfo::getTexCoord() const { if (mTextureTransform.mPresent && mTextureTransform.mTexCoord != INVALID_INDEX) { @@ -928,7 +1147,7 @@ bool Material::isMultiUV() const mEmissiveTexture.getTexCoord() != 0; } -const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) +const TextureInfo& TextureInfo::operator=(const Value& src) { if (src.is_object()) { @@ -940,23 +1159,23 @@ const Material::TextureInfo& Material::TextureInfo::operator=(const Value& src) return *this; } -bool Material::TextureInfo::operator==(const Material::TextureInfo& rhs) const +bool TextureInfo::operator==(const TextureInfo& rhs) const { return mIndex == rhs.mIndex && mTexCoord == rhs.mTexCoord; } -bool Material::TextureInfo::operator!=(const Material::TextureInfo& rhs) const +bool TextureInfo::operator!=(const TextureInfo& rhs) const { return !(*this == rhs); } -void Material::OcclusionTextureInfo::serialize(object& dst) const +void OcclusionTextureInfo::serialize(object& dst) const { TextureInfo::serialize(dst); write(mStrength, "strength", dst, 1.f); } -const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=(const Value& src) +const OcclusionTextureInfo& OcclusionTextureInfo::operator=(const Value& src) { TextureInfo::operator=(src); @@ -968,13 +1187,13 @@ const Material::OcclusionTextureInfo& Material::OcclusionTextureInfo::operator=( return *this; } -void Material::NormalTextureInfo::serialize(object& dst) const +void NormalTextureInfo::serialize(object& dst) const { TextureInfo::serialize(dst); write(mScale, "scale", dst, 1.f); } -const Material::NormalTextureInfo& Material::NormalTextureInfo::operator=(const Value& src) +const NormalTextureInfo& NormalTextureInfo::operator=(const Value& src) { TextureInfo::operator=(src); if (src.is_object()) @@ -1035,18 +1254,12 @@ void Material::Unlit::serialize(object& dst) const // no members and object has already been created, nothing to do } -void TextureTransform::getPacked(F32* packed) const +void TextureTransform::getPacked(vec4* 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; + packed[0] = vec4(mScale.x, mScale.y, mRotation, mOffset.x); + packed[1] = vec4(mOffset.y, 0.f, 0.f, 0.f); } - const TextureTransform& TextureTransform::operator=(const Value& src) { mPresent = true; diff --git a/indra/newview/gltf/asset.h b/indra/newview/gltf/asset.h index ea3f7d480a..27821659db 100644 --- a/indra/newview/gltf/asset.h +++ b/indra/newview/gltf/asset.h @@ -34,6 +34,7 @@ #include "boost/json.hpp" #include "common.h" #include "../llviewertexture.h" +#include "llglslshader.h" extern F32SecondsImplicit gFrameTimeSeconds; @@ -65,14 +66,51 @@ namespace LL 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; + // get the texture transform as a packed array of vec4's + // dst MUST point to at least 2 vec4's + void getPacked(vec4* dst) const; const TextureTransform& operator=(const Value& src); void serialize(boost::json::object& dst) const; }; + class TextureInfo + { + public: + S32 mIndex = INVALID_INDEX; + S32 mTexCoord = 0; + + TextureTransform mTextureTransform; + + bool operator==(const TextureInfo& rhs) const; + bool operator!=(const TextureInfo& rhs) const; + + // get the UV channel that should be used for sampling this texture + // returns mTextureTransform.mTexCoord if present and valid, otherwise mTexCoord + S32 getTexCoord() const; + + const TextureInfo& operator=(const Value& src); + void serialize(boost::json::object& dst) const; + }; + + class NormalTextureInfo : public TextureInfo + { + public: + F32 mScale = 1.0f; + + const NormalTextureInfo& operator=(const Value& src); + void serialize(boost::json::object& dst) const; + }; + + class OcclusionTextureInfo : public TextureInfo + { + public: + F32 mStrength = 1.0f; + + const OcclusionTextureInfo& operator=(const Value& src); + void serialize(boost::json::object& dst) const; + }; + class Material { public: @@ -91,42 +129,6 @@ namespace LL 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 { @@ -179,7 +181,6 @@ namespace LL { public: mat4 mMatrix = glm::identity(); //local transform - mat4 mRenderMatrix; //transform for rendering mat4 mAssetMatrix; //transform from local to asset space mat4 mAssetMatrixInv; //transform from asset to local space @@ -206,10 +207,6 @@ namespace LL 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 mat4& modelview); - // update mAssetMatrix and mAssetMatrixInv void updateTransforms(Asset& asset, const mat4& parentMatrix); @@ -322,6 +319,31 @@ namespace LL bool prep(Asset& asset); }; + // Render Batch -- vertex buffer and list of primitives to render using + // said vertex buffer + class RenderBatch + { + public: + struct PrimitiveData + { + S32 mPrimitiveIndex = INVALID_INDEX; + S32 mNodeIndex = INVALID_INDEX; + }; + + LLPointer mVertexBuffer; + std::vector mPrimitives; + }; + + class RenderData + { + public: + // list of render batches + // indexed by [material index + 1](0 is reserved for default material) + // there should be exactly one render batch per material per variant + std::vector mBatches[LLGLSLShader::NUM_GLTF_VARIANTS]; + }; + + // C++ representation of a GLTF Asset class Asset { @@ -359,6 +381,16 @@ namespace LL // the last time update() was called according to gFrameTimeSeconds F32 mLastUpdateTime = gFrameTimeSeconds; + // data used for rendering + // 0 - single sided + // 1 - double sided + RenderData mRenderData[2]; + + // UBO for storing node transforms + U32 mNodesUBO = 0; + + // UBO for storing material data + U32 mMaterialsUBO = 0; // prepare for first time use bool prep(); @@ -373,8 +405,11 @@ namespace LL // update asset-to-node and node-to-asset transforms void updateTransforms(); - // update node render transforms - void updateRenderTransforms(const mat4& modelview); + // upload matrices to UBO + void uploadTransforms(); + + // upload materils to UBO + void uploadMaterials(); // return the index of the node that the line segment intersects with, or -1 if no hit // input and output values must be in this asset's local coordinate frame diff --git a/indra/newview/gltf/common.h b/indra/newview/gltf/common.h index 4f660d7cfc..b9698d4017 100644 --- a/indra/newview/gltf/common.h +++ b/indra/newview/gltf/common.h @@ -64,6 +64,9 @@ namespace LL class Asset; class Material; + class TextureInfo; + class NormalTextureInfo; + class OcclusionTextureInfo; class Mesh; class Node; class Scene; @@ -78,6 +81,17 @@ namespace LL class Accessor; class BufferView; class Buffer; + + enum class TextureType : U8 + { + BASE_COLOR = 0, + NORMAL, + METALLIC_ROUGHNESS, + OCCLUSION, + EMISSIVE + }; + + constexpr U32 TEXTURE_TYPE_COUNT = 5; } } diff --git a/indra/newview/gltf/primitive.cpp b/indra/newview/gltf/primitive.cpp index 2280c7004e..e1579374d4 100644 --- a/indra/newview/gltf/primitive.cpp +++ b/indra/newview/gltf/primitive.cpp @@ -380,11 +380,22 @@ bool Primitive::prep(Asset& asset) } } } + else + { //everything must be indexed at runtime + mIndexArray.resize(mPositions.size()); + for (U32 i = 0; i < mPositions.size(); ++i) + { + mIndexArray[i] = i; + } + } U32 mask = LLVertexBuffer::MAP_VERTEX; + mShaderVariant = 0; + if (!mWeights.empty()) { + mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED; mask |= LLVertexBuffer::MAP_WEIGHT4; mask |= LLVertexBuffer::MAP_JOINT; } @@ -406,9 +417,6 @@ bool Primitive::prep(Asset& asset) mColors.resize(mPositions.size(), LLColor4U::white); } - mShaderVariant = 0; - - // TODO: support colorless vertex buffers mask |= LLVertexBuffer::MAP_COLOR; bool unlit = false; @@ -506,69 +514,79 @@ bool Primitive::prep(Asset& asset) mask |= LLVertexBuffer::MAP_TANGENT; } - if (LLGLSLShader::sCurBoundShaderPtr == nullptr) - { // make sure a shader is bound to satisfy mVertexBuffer->setBuffer - gDebugProgram.bind(); + mAttributeMask = mask; + + if (mMaterial != INVALID_INDEX) + { + Material& material = asset.mMaterials[mMaterial]; + if (material.mAlphaMode == Material::AlphaMode::BLEND) + { + mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + } } - mVertexBuffer = new LLVertexBuffer(mask); + createOctree(); + + return true; +} + +void Primitive::upload(LLVertexBuffer* buffer) +{ + mVertexBuffer = buffer; // we store these buffer sizes as S32 elsewhere llassert(mPositions.size() <= size_t(S32_MAX)); llassert(mIndexArray.size() <= size_t(S32_MAX / 2)); - 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()); + llassert(mVertexBuffer != nullptr); + + // assert that buffer can hold this primitive + llassert(mVertexBuffer->getNumVerts() >= mPositions.size() + mVertexOffset); + llassert(mVertexBuffer->getNumIndices() >= mIndexArray.size() + mIndexOffset); + llassert(mVertexBuffer->getTypeMask() == mAttributeMask); + + U32 offset = mVertexOffset; + U32 count = getVertexCount(); + + mVertexBuffer->setPositionData(mPositions.data(), offset, count); + mVertexBuffer->setColorData(mColors.data(), offset, count); if (!mNormals.empty()) { - mVertexBuffer->setNormalData(mNormals.data()); + mVertexBuffer->setNormalData(mNormals.data(), offset, count); } if (!mTangents.empty()) { - mVertexBuffer->setTangentData(mTangents.data()); + mVertexBuffer->setTangentData(mTangents.data(), offset, count); } if (!mWeights.empty()) { - mShaderVariant |= LLGLSLShader::GLTFVariant::RIGGED; - mVertexBuffer->setWeight4Data(mWeights.data()); - mVertexBuffer->setJointData(mJoints.data()); + mVertexBuffer->setWeight4Data(mWeights.data(), offset, count); + mVertexBuffer->setJointData(mJoints.data(), offset, count); } // flip texcoord y, upload, then flip back (keep the off-spec data in vram only) vertical_flip(mTexCoords0); - mVertexBuffer->setTexCoord0Data(mTexCoords0.data()); + mVertexBuffer->setTexCoord0Data(mTexCoords0.data(), offset, count); vertical_flip(mTexCoords0); if (!mTexCoords1.empty()) { vertical_flip(mTexCoords1); - mVertexBuffer->setTexCoord1Data(mTexCoords1.data()); + mVertexBuffer->setTexCoord1Data(mTexCoords1.data(), offset, count); vertical_flip(mTexCoords1); } - 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) + std::vector index_array; + index_array.resize(mIndexArray.size()); + for (U32 i = 0; i < mIndexArray.size(); ++i) { - mShaderVariant |= LLGLSLShader::GLTFVariant::ALPHA_BLEND; + index_array[i] = mIndexArray[i] + mVertexOffset; } + mVertexBuffer->setIndexData(index_array.data(), mIndexOffset, getIndexCount()); } - - return true; } void initOctreeTriangle(LLVolumeTriangle* tri, F32 scaler, S32 i0, S32 i1, S32 i2, const LLVector4a& v0, const LLVector4a& v1, const LLVector4a& v2) @@ -616,7 +634,7 @@ void Primitive::createOctree() if (mMode == Mode::TRIANGLES) { - const U32 num_triangles = mVertexBuffer->getNumIndices() / 3; + const U32 num_triangles = getIndexCount() / 3; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); @@ -640,7 +658,7 @@ void Primitive::createOctree() } else if (mMode == Mode::TRIANGLE_STRIP) { - const U32 num_triangles = mVertexBuffer->getNumIndices() - 2; + const U32 num_triangles = getIndexCount() - 2; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); @@ -664,7 +682,7 @@ void Primitive::createOctree() } else if (mMode == Mode::TRIANGLE_FAN) { - const U32 num_triangles = mVertexBuffer->getNumIndices() - 2; + const U32 num_triangles = getIndexCount() - 2; // Initialize all the triangles we need mOctreeTriangles.resize(num_triangles); diff --git a/indra/newview/gltf/primitive.h b/indra/newview/gltf/primitive.h index 7cc05cf831..304eb26432 100644 --- a/indra/newview/gltf/primitive.h +++ b/indra/newview/gltf/primitive.h @@ -54,10 +54,7 @@ namespace LL ~Primitive(); - // GPU copy of mesh data - LLPointer mVertexBuffer; - - // CPU copy of mesh data, keep these as LLVector types for compatibility with raycasting code + // CPU copy of mesh data std::vector mTexCoords0; std::vector mTexCoords1; std::vector mNormals; @@ -80,6 +77,17 @@ namespace LL // shader variant according to LLGLSLShader::GLTFVariant flags U8 mShaderVariant = 0; + // vertex attribute mask + U32 mAttributeMask = 0; + + // backpointer to vertex buffer (owned by Asset) + LLPointer mVertexBuffer; + U32 mVertexOffset = 0; + U32 mIndexOffset = 0; + + U32 getVertexCount() const { return (U32) mPositions.size(); } + U32 getIndexCount() const { return (U32) mIndexArray.size(); } + std::unordered_map mAttributes; // create octree based on vertex buffer @@ -100,6 +108,11 @@ namespace LL const Primitive& operator=(const Value& src); bool prep(Asset& asset); + + // upload geometry to given vertex buffer + // asserts that buffer is bound + // asserts that buffer is valid for this primitive + void upload(LLVertexBuffer* buffer); }; } } -- cgit v1.2.3