diff options
author | Dave Parks <davep@lindenlab.com> | 2024-05-28 09:45:40 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-28 09:45:40 -0500 |
commit | 2f4120038429c6aff865f153f708ceefb60d67f4 (patch) | |
tree | 85e63488eb66760996fd2e0c55c40fdc7b5b38b3 | |
parent | db627bc3544b828982a2a366972e0adf86e42f75 (diff) |
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
32 files changed, 1220 insertions, 1331 deletions
diff --git a/indra/llcommon/lluuid.h b/indra/llcommon/lluuid.h index 68c4b05fdc..b178868591 100644 --- a/indra/llcommon/lluuid.h +++ b/indra/llcommon/lluuid.h @@ -37,8 +37,8 @@ class LLMutex; const S32 UUID_BYTES = 16; const S32 UUID_WORDS = 4; -const S32 UUID_STR_LENGTH = 37; // actually wrong, should be 36 and use size below -const S32 UUID_STR_SIZE = 37; +const S32 UUID_STR_LENGTH = 37; // number of bytes needed to store a UUID as a string +const S32 UUID_STR_SIZE = 36; // .size() of a UUID in a std::string const S32 UUID_BASE85_LENGTH = 21; // including the trailing NULL. struct uuid_time_t { diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 863a28996d..acd382d9a8 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -2233,6 +2233,61 @@ LLImageFormatted* LLImageFormatted::createFromType(S8 codec) } // static +S8 LLImageFormatted::getCodecFromMimeType(std::string_view mimetype) +{ + if (mimetype == "image/bmp") + { + return IMG_CODEC_BMP; + } + else if (mimetype == "image/tga") + { + return IMG_CODEC_TGA; + } + else if (mimetype == "image/jpeg") + { + return IMG_CODEC_JPEG; + } + else if (mimetype == "image/png") + { + return IMG_CODEC_PNG; + } + else if (mimetype == "image/j2c") + { + return IMG_CODEC_J2C; + } + else if (mimetype == "image/dxt") + { + return IMG_CODEC_DXT; + } + return IMG_CODEC_INVALID; +} + +// static +LLImageFormatted* LLImageFormatted::createFromMimeType(std::string_view mimetype) +{ + S8 codec = getCodecFromMimeType(mimetype); + return createFromType(codec); +} + +// static +LLImageFormatted* LLImageFormatted::loadFromMemory(const U8* data_in, U32 size, std::string_view mimetype) +{ + LLImageFormatted* image = createFromMimeType(mimetype); + if (image) + { + U8* data = image->allocateData(size); + memcpy(data, data_in, size); + + if (!image->updateData()) + { + delete image; + image = NULL; + } + } + return image; +} + +// static LLImageFormatted* LLImageFormatted::createFromExtension(const std::string& instring) { std::string exten; @@ -2412,6 +2467,7 @@ void LLImageFormatted::appendData(U8 *data, S32 size) //---------------------------------------------------------------------------- + bool LLImageFormatted::load(const std::string &filename, int load_size) { resetLastError(); diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index 7f759f7679..42eecbb97c 100644 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -322,7 +322,10 @@ class LLImageFormatted : public LLImageBase { public: static LLImageFormatted* createFromType(S8 codec); + static LLImageFormatted* loadFromMemory(const U8* data, U32 size, std::string_view mimetype); static LLImageFormatted* createFromExtension(const std::string& instring); + static LLImageFormatted* createFromMimeType(std::string_view mimetype); + static S8 getCodecFromMimeType(std::string_view mimetype); protected: /*virtual*/ ~LLImageFormatted(); diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index f6b1ca3741..ecd0c6908b 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -1049,6 +1049,12 @@ void LLGLSLShader::bind() } } +void LLGLSLShader::bind(LLGLSLShader::GLTFVariant variant) +{ + llassert(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS); + mGLTFVariants[variant].bind(); +} + void LLGLSLShader::bind(bool rigged) { if (rigged) diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 75e1bdfd54..fa01d212e1 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -44,6 +44,7 @@ public: bool hasTransport = false; // implies no lighting (it's possible to have neither though) bool hasSkinning = false; bool hasObjectSkinning = false; + bool mGLTF = false; bool hasAtmospherics = false; bool hasGamma = false; bool hasShadows = false; @@ -318,6 +319,22 @@ public: // this pointer should be set to whichever shader represents this shader's rigged variant LLGLSLShader* mRiggedVariant = nullptr; + // variants for use by GLTF renderer + // "this" is considered to be OPAQUE + enum GLTFVariant + { + STATIC_OPAQUE, + STATIC_BLEND, + RIGGED_OPAQUE, + RIGGED_BLEND, + NUM_GLTF_VARIANTS + }; + + std::vector<LLGLSLShader> mGLTFVariants; + + //helper to bind GLTF variant + void bind(GLTFVariant variant); + // hacky flag used for optimization in LLDrawPoolAlpha bool mCanBindFast = false; diff --git a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl new file mode 100644 index 0000000000..dbee58b76a --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessF.glsl @@ -0,0 +1,117 @@ +/** + * @file pbrmetallicroughnessF.glsl + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, 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$ + */ + +/*[EXTRA_CODE_HERE]*/ + + +// GLTF pbrMetallicRoughness implementation + +uniform sampler2D diffuseMap; //always in sRGB space + +uniform float metallicFactor; +uniform float roughnessFactor; +uniform vec3 emissiveColor; +uniform sampler2D bumpMap; +uniform sampler2D emissiveMap; +uniform sampler2D specularMap; // Packed: Occlusion, Metal, Roughness + +out vec4 frag_data[4]; + +in vec3 vary_position; +in vec4 vertex_color; +in vec3 vary_normal; +in vec3 vary_tangent; +flat in float vary_sign; + +in vec2 base_color_texcoord; +in vec2 normal_texcoord; +in vec2 metallic_roughness_texcoord; +in vec2 emissive_texcoord; + +uniform float minimum_alpha; // PBR alphaMode: MASK, See: mAlphaCutoff, setAlphaCutoff() + +vec3 linear_to_srgb(vec3 c); +vec3 srgb_to_linear(vec3 c); + +uniform vec4 clipPlane; +uniform float clipSign; + +void mirrorClip(vec3 pos); + +uniform mat3 normal_matrix; + +void main() +{ + mirrorClip(vary_position); + + vec4 basecolor = texture(diffuseMap, base_color_texcoord.xy).rgba; + basecolor.rgb = srgb_to_linear(basecolor.rgb); + + basecolor *= vertex_color; + + if (basecolor.a < minimum_alpha) + { + discard; + } + + vec3 col = basecolor.rgb; + + // from mikktspace.com + vec3 vNt = texture(bumpMap, normal_texcoord.xy).xyz*2.0-1.0; + float sign = vary_sign; + vec3 vN = vary_normal; + vec3 vT = vary_tangent.xyz; + + vec3 vB = sign * cross(vN, vT); + vec3 tnorm = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); + + // RGB = Occlusion, Roughness, Metal + // default values, see LLViewerTexture::sDefaultPBRORMImagep + // occlusion 1.0 + // roughness 0.0 + // metal 0.0 + vec3 spec = texture(specularMap, metallic_roughness_texcoord.xy).rgb; + + spec.g *= roughnessFactor; + spec.b *= metallicFactor; + + vec3 emissive = emissiveColor; + emissive *= srgb_to_linear(texture(emissiveMap, emissive_texcoord.xy).rgb); + + tnorm *= gl_FrontFacing ? 1.0 : -1.0; + + //spec.rgb = vec3(1,1,0); + //col = vec3(0,0,0); + //emissive = vary_tangent.xyz*0.5+0.5; + //emissive = vec3(sign*0.5+0.5); + //emissive = vNt * 0.5 + 0.5; + //emissive = tnorm*0.5+0.5; + // See: C++: addDeferredAttachments(), GLSL: softenLightF + frag_data[0] = max(vec4(col, 0.0), vec4(0)); // Diffuse + frag_data[1] = max(vec4(spec.rgb,0.0), vec4(0)); // PBR linear packed Occlusion, Roughness, Metal. + frag_data[2] = vec4(tnorm, GBUFFER_FLAG_HAS_PBR); // normal, environment intensity, flags + frag_data[3] = max(vec4(emissive,0), vec4(0)); // PBR sRGB Emissive +} + diff --git a/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl new file mode 100644 index 0000000000..0add509002 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/gltf/pbrmetallicroughnessV.glsl @@ -0,0 +1,106 @@ +/** + * @file pbrmetallicroughnessV.glsl + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, 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$ + */ + +// GLTF pbrMetallicRoughness implementation + +uniform mat4 modelview_matrix; + +#ifdef HAS_SKIN +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else +uniform mat3 normal_matrix; +uniform mat4 modelview_projection_matrix; +#endif +uniform mat4 texture_matrix0; + +uniform vec4[2] texture_base_color_transform; +uniform vec4[2] texture_normal_transform; +uniform vec4[2] texture_metallic_roughness_transform; +uniform vec4[2] texture_emissive_transform; + +in vec3 position; +in vec4 diffuse_color; +in vec3 normal; +in vec4 tangent; +in vec2 texcoord0; + +out vec2 base_color_texcoord; +out vec2 normal_texcoord; +out vec2 metallic_roughness_texcoord; +out vec2 emissive_texcoord; + +out vec4 vertex_color; + +out vec3 vary_tangent; +flat out float vary_sign; +out vec3 vary_normal; +out vec3 vary_position; + +vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); +vec3 tangent_space_transform(vec4 vertex_tangent, vec3 vertex_normal, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); + +void main() +{ +#ifdef HAS_SKIN + mat4 mat = getObjectSkinnedTransform(); + + mat = modelview_matrix * mat; + + vec3 pos = (mat*vec4(position.xyz,1.0)).xyz; + vary_position = pos; + gl_Position = projection_matrix*vec4(pos,1.0); + +#else + vary_position = (modelview_matrix*vec4(position.xyz, 1.0)).xyz; + //transform vertex + gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); +#endif + + base_color_texcoord = texture_transform(texcoord0, texture_base_color_transform, texture_matrix0); + normal_texcoord = texture_transform(texcoord0, texture_normal_transform, texture_matrix0); + metallic_roughness_texcoord = texture_transform(texcoord0, texture_metallic_roughness_transform, texture_matrix0); + emissive_texcoord = texture_transform(texcoord0, texture_emissive_transform, texture_matrix0); + +#ifdef HAS_SKIN + vec3 n = (mat*vec4(normal.xyz+position.xyz,1.0)).xyz-pos.xyz; + vec3 t = (mat*vec4(tangent.xyz+position.xyz,1.0)).xyz-pos.xyz; +#else //HAS_SKIN + vec3 n = normal_matrix * normal; + vec3 t = normal_matrix * tangent.xyz; +#endif + + n = normalize(n); + + vary_tangent = normalize(tangent_space_transform(vec4(t, tangent.w), n, texture_normal_transform, texture_matrix0)); + vary_sign = tangent.w; + vary_normal = n; + + vertex_color = diffuse_color; +} + + + + 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<double> mMax; - std::vector<double> mMin; + enum class Type : U8 + { + SCALAR, + VEC2, + VEC3, + VEC4, + MAT2, + MAT3, + MAT4 + }; - enum class Type : S32 + enum class ComponentType : U32 { - SCALAR = TINYGLTF_TYPE_SCALAR, - VEC2 = TINYGLTF_TYPE_VEC2, - VEC3 = TINYGLTF_TYPE_VEC3, - VEC4 = TINYGLTF_TYPE_VEC4, - MAT2 = TINYGLTF_TYPE_MAT2, - MAT3 = TINYGLTF_TYPE_MAT3, - MAT4 = TINYGLTF_TYPE_MAT4 + BYTE = 5120, + UNSIGNED_BYTE = 5121, + SHORT = 5122, + UNSIGNED_SHORT = 5123, + UNSIGNED_INT = 5125, + FLOAT = 5126 }; + std::vector<double> mMax; + std::vector<double> mMin; + std::string mName; + S32 mBufferView = INVALID_INDEX; + S32 mByteOffset = 0; + ComponentType mComponentType = ComponentType::BYTE; + S32 mCount = 0; Type mType = Type::SCALAR; bool mNormalized = false; - 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<F32> 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<quat> 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<vec3> 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<vec3> 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 <boost/url.hpp> +#include "llimagejpeg.h" using namespace LL::GLTF; using namespace boost::json; @@ -75,268 +77,14 @@ namespace LL return "OPAQUE"; } } - - template <typename T, typename U> - void copy(const std::vector<T>& src, std::vector<U>& 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<mat4>()) - { - 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<quat>()) - { - 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<mat4>(); - + 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<mat4>(); - 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<char>(file)), std::istreambuf_iterator<char>()); + 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<LLImageRaw> 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<LLFetchedGLTFMaterial> 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<double> mWeights; std::string mName; - const Mesh& operator=(const tinygltf::Mesh& src); const Mesh& operator=(const Value& src); void serialize(boost::json::object& dst) const; - void allocateGLResources(Asset& asset); + bool prep(Asset& asset); }; class Node @@ -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<mat4> 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<S32> mNodes; std::string mName; - const Scene& operator=(const tinygltf::Scene& src); const Scene& operator=(const Value& src); void serialize(boost::json::object& dst) const; @@ -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<U8> mData; S32 mWidth = -1; S32 mHeight = -1; S32 mComponent = -1; @@ -283,19 +264,20 @@ namespace LL LLPointer<LLViewerFetchedTexture> 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<typename T> inline bool write(const std::unordered_map<std::string, T>& src, string_view member, boost::json::object& dst, const std::unordered_map<std::string, T>& default_value = std::unordered_map<std::string, T>()) { @@ -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<LLVolumeOctree> mOctree; std::vector<LLVolumeTriangle> 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<std::string, S32> 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); }; } } diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 5413a7d021..eb11c60e43 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -107,32 +107,6 @@ void GLTFSceneManager::saveAs() } } -void GLTFSceneManager::decomposeSelection() -{ - LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); - if (obj && obj->mGLTFAsset) - { - LLFilePickerReplyThread::startPicker( - [](const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter) - { - if (LLAppViewer::instance()->quitRequested()) - { - return; - } - if (filenames.size() > 0) - { - GLTFSceneManager::instance().decomposeSelection(filenames[0]); - } - }, - LLFilePicker::FFSAVE_GLTF, - "scene.gltf"); - } - else - { - LLNotificationsUtil::add("GLTFSaveSelection"); - } -} - void GLTFSceneManager::uploadSelection() { if (mUploadingAsset) @@ -153,77 +127,74 @@ void GLTFSceneManager::uploadSelection() for (auto& image : asset.mImages) { - if (!image.mData.empty()) + if (image.mTexture.notNull()) { mPendingImageUploads++; - LLPointer<LLImageRaw> raw = new LLImageRaw(image.mWidth, image.mHeight, image.mComponent); - U8* data = raw->allocateData(); - llassert_always(image.mData.size() == raw->getDataSize()); - memcpy(data, image.mData.data(), image.mData.size()); - - // for GLTF native content, store image in GLTF orientation - raw->verticalFlip(); - - LLPointer<LLImageJ2C> j2c = LLViewerTextureList::convertToUploadFile(raw); + LLPointer<LLImageRaw> raw = image.mTexture->getCachedRawImage(); - std::string buffer; - buffer.assign((const char*)j2c->getData(), j2c->getDataSize()); + if (raw.notNull()) + { + LLPointer<LLImageJ2C> j2c = LLViewerTextureList::convertToUploadFile(raw); - LLUUID asset_id = LLUUID::generateNewID(); + std::string buffer; + buffer.assign((const char*)j2c->getData(), j2c->getDataSize()); - std::string name; - S32 idx = (S32)(&image - &asset.mImages[0]); + LLUUID asset_id = LLUUID::generateNewID(); - if (image.mName.empty()) - { + std::string name; + S32 idx = (S32)(&image - &asset.mImages[0]); - name = llformat("Image_%d", idx); - } - else - { - name = image.mName; - } - - LLNewBufferedResourceUploadInfo::uploadFailure_f failure = [this](LLUUID assetId, LLSD response, std::string reason) + if (image.mName.empty()) { - // TODO: handle failure - mPendingImageUploads--; - return false; - }; - - LLNewBufferedResourceUploadInfo::uploadFinish_f finish = [this, idx, raw, j2c](LLUUID assetId, LLSD response) + name = llformat("Image_%d", idx); + } + else { - if (mUploadingAsset && mUploadingAsset->mImages.size() > idx) + name = image.mName; + } + + LLNewBufferedResourceUploadInfo::uploadFailure_f failure = [this](LLUUID assetId, LLSD response, std::string reason) { - mUploadingAsset->mImages[idx].mUri = assetId.asString(); + // TODO: handle failure mPendingImageUploads--; - } - }; - - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(j2c); - - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>( - buffer, - asset_id, - name, - name, - 0, - LLFolderType::FT_TEXTURE, - LLInventoryType::IT_TEXTURE, - LLAssetType::AT_TEXTURE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost, - false, - finish, - failure)); - - upload_new_resource(uploadInfo); - - image.clearData(asset); + return false; + }; + + + LLNewBufferedResourceUploadInfo::uploadFinish_f finish = [this, idx, raw, j2c](LLUUID assetId, LLSD response) + { + if (mUploadingAsset && mUploadingAsset->mImages.size() > idx) + { + mUploadingAsset->mImages[idx].mUri = assetId.asString(); + mPendingImageUploads--; + } + }; + + S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(j2c); + + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared<LLNewBufferedResourceUploadInfo>( + buffer, + asset_id, + name, + name, + 0, + LLFolderType::FT_TEXTURE, + LLInventoryType::IT_TEXTURE, + LLAssetType::AT_TEXTURE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost, + false, + finish, + failure)); + + upload_new_resource(uploadInfo); + + image.clearData(asset); + } } } @@ -297,62 +268,45 @@ void GLTFSceneManager::uploadSelection() } } -void GLTFSceneManager::decomposeSelection(const std::string& filename) -{ - LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); - if (obj && obj->mGLTFAsset) - { - // copy asset out for decomposition - Asset asset = *obj->mGLTFAsset; - - // decompose the asset into component parts - asset.decompose(filename); - - // copy decomposed asset into tinygltf for serialization - tinygltf::Model model; - asset.save(model); - - LLTinyGLTFHelper::saveModel(filename, model); - } -} - void GLTFSceneManager::save(const std::string& filename) { LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); if (obj && obj->mGLTFAsset) { Asset* asset = obj->mGLTFAsset.get(); - tinygltf::Model model; - asset->save(model); - - LLTinyGLTFHelper::saveModel(filename, model); + if (!asset->save(filename)) + { + LLNotificationsUtil::add("GLTFSaveFailed"); + } } } void GLTFSceneManager::load(const std::string& filename) { - tinygltf::Model model; - LLTinyGLTFHelper::loadModel(filename, model); - std::shared_ptr<Asset> asset = std::make_shared<Asset>(); - *asset = model; - gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions - asset->allocateGLResources(filename, model); - asset->updateTransforms(); + if (asset->load(filename)) + { + gDebugProgram.bind(); // bind a shader to satisfy LLVertexBuffer assertions + asset->updateTransforms(); - // hang the asset off the currently selected object, or off of the avatar if no object is selected - LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); + // hang the asset off the currently selected object, or off of the avatar if no object is selected + LLViewerObject* obj = LLSelectMgr::instance().getSelection()->getFirstRootObject(); - if (obj) - { // assign to self avatar - obj->mGLTFAsset = asset; - obj->markForUpdate(); - if (std::find(mObjects.begin(), mObjects.end(), obj) == mObjects.end()) - { - mObjects.push_back(obj); + if (obj) + { // assign to self avatar + obj->mGLTFAsset = asset; + obj->markForUpdate(); + if (std::find(mObjects.begin(), mObjects.end(), obj) == mObjects.end()) + { + mObjects.push_back(obj); + } } } + else + { + LLNotificationsUtil::add("GLTFLoadFailed"); + } } GLTFSceneManager::~GLTFSceneManager() @@ -392,32 +346,19 @@ void GLTFSceneManager::onGLTFBinLoadComplete(const LLUUID& id, LLAssetType::ETyp // find the Buffer with the given id in the asset if (obj->mGLTFAsset) { - for (auto& buffer : obj->mGLTFAsset->mBuffers) - { - LLUUID buffer_id; - if (LLUUID::parseUUID(buffer.mUri, &buffer_id) && buffer_id == id) - { - LLFileSystem file(id, asset_type, LLFileSystem::READ); - - buffer.mData.resize(file.getSize()); - file.read((U8*)buffer.mData.data(), buffer.mData.size()); + obj->mGLTFAsset->mPendingBuffers--; - obj->mGLTFAsset->mPendingBuffers--; - if (obj->mGLTFAsset->mPendingBuffers == 0) - { - obj->mGLTFAsset->allocateGLResources(); - GLTFSceneManager& mgr = GLTFSceneManager::instance(); - if (std::find(mgr.mObjects.begin(), mgr.mObjects.end(), obj) == mgr.mObjects.end()) - { - GLTFSceneManager::instance().mObjects.push_back(obj); - } - } + if (obj->mGLTFAsset->mPendingBuffers == 0) + { + obj->mGLTFAsset->prep(); + GLTFSceneManager& mgr = GLTFSceneManager::instance(); + if (std::find(mgr.mObjects.begin(), mgr.mObjects.end(), obj) == mgr.mObjects.end()) + { + GLTFSceneManager::instance().mObjects.push_back(obj); } } } - - } } else @@ -492,30 +433,9 @@ void GLTFSceneManager::update() { if (mPendingImageUploads == 0 && mPendingBinaryUploads == 0) { - std::string filename(gDirUtilp->getTempDir() + "/upload.gltf"); -#if 0 - tinygltf::Model model; - mUploadingAsset->save(model); - - tinygltf::TinyGLTF writer; - - writer.WriteGltfSceneToFile(&model, filename, false, false, true, false); -#else boost::json::object obj; mUploadingAsset->serialize(obj); - std::string json = boost::json::serialize(obj, {}); - - { - std::ofstream o(filename); - o << json; - } -#endif - - std::ifstream t(filename); - std::stringstream str; - str << t.rdbuf(); - - std::string buffer = str.str(); + std::string buffer = boost::json::serialize(obj, {}); LLNewBufferedResourceUploadInfo::uploadFailure_f failure = [this](LLUUID assetId, LLSD response, std::string reason) { diff --git a/indra/newview/gltfscenemanager.h b/indra/newview/gltfscenemanager.h index 5434f6ddb2..53b4ff0e4e 100644 --- a/indra/newview/gltfscenemanager.h +++ b/indra/newview/gltfscenemanager.h @@ -52,8 +52,6 @@ namespace LL void saveAs(); // open filepicker and choose file to save selected asset to void save(const std::string& filename); // save selected asset to filename (suitable for use in external programs) - void decomposeSelection(); // open file picker and choose a location to decompose to - void decomposeSelection(const std::string& filename); // decompose selected asset into simulator-ready .gltf, .bin, and .j2c files void uploadSelection(); // decompose selected asset and upload to simulator void update(); diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 329ac3fc90..0afb275d13 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -515,11 +515,11 @@ bool LLFilePicker::getSaveFile(ESaveFilter filter, const std::string& filename, case FFSAVE_GLTF: if (filename.empty()) { - wcsncpy( mFilesW,L"untitled.glb", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + wcsncpy( mFilesW,L"untitled.gltf", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ } - mOFN.lpstrDefExt = L"glb"; + mOFN.lpstrDefExt = L"gltf"; mOFN.lpstrFilter = - L"glTF Asset File (*.gltf *.glb)\0*.gltf;*.glb\0" \ + L"glTF Asset File (*.gltf)\0*.gltf\0" \ L"\0"; break; case FFSAVE_XML: @@ -790,7 +790,7 @@ void set_nav_save_data(LLFilePicker::ESaveFilter filter, std::string &extension, case LLFilePicker::FFSAVE_GLTF: type = "\?\?\?\?"; creator = "\?\?\?\?"; - extension = "glb,gltf"; + extension = "gltf"; break; case LLFilePicker::FFSAVE_XML: diff --git a/indra/newview/lllocalbitmaps.cpp b/indra/newview/lllocalbitmaps.cpp index 8aadf1f533..27de9aea0d 100644 --- a/indra/newview/lllocalbitmaps.cpp +++ b/indra/newview/lllocalbitmaps.cpp @@ -1091,8 +1091,9 @@ bool LLLocalBitmapMgr::checkTextureDimensions(std::string filename) return false; } - S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); - S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); + // allow loading up to 4x max rez but implicitly downrez to max rez before upload + S32 max_width = gSavedSettings.getS32("max_texture_dimension_X")*4; + S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y")*4; if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) { @@ -1137,6 +1138,20 @@ void LLLocalBitmapMgr::delUnit(LLUUID tracking_id) } } +LLUUID LLLocalBitmapMgr::getTrackingID(const LLUUID& world_id) const +{ + for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getWorldID() == world_id) + { + return unit->getTrackingID(); + } + } + + return LLUUID::null; +} + LLUUID LLLocalBitmapMgr::getWorldID(const LLUUID &tracking_id) const { LLUUID world_id = LLUUID::null; diff --git a/indra/newview/lllocalbitmaps.h b/indra/newview/lllocalbitmaps.h index e0cd9d172f..e169f96e70 100644 --- a/indra/newview/lllocalbitmaps.h +++ b/indra/newview/lllocalbitmaps.h @@ -135,6 +135,7 @@ public: void delUnit(LLUUID tracking_id); bool checkTextureDimensions(std::string filename); + LLUUID getTrackingID(const LLUUID& world_id) const; LLUUID getWorldID(const LLUUID &tracking_id) const; bool isLocal(const LLUUID& world_id) const; std::string getFilename(const LLUUID &tracking_id) const; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index fd16b07985..6a61c8bac8 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -8102,15 +8102,6 @@ class LLAdvancedClickGLTFSaveAs : public view_listener_t } }; -class LLAdvancedClickGLTFDecompose : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LL::GLTFSceneManager::instance().decomposeSelection(); - return true; - } -}; - class LLAdvancedClickGLTFUpload: public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9793,7 +9784,6 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedClickHDRIPreview(), "Advanced.ClickHDRIPreview"); view_listener_t::addMenu(new LLAdvancedClickGLTFOpen(), "Advanced.ClickGLTFOpen"); view_listener_t::addMenu(new LLAdvancedClickGLTFSaveAs(), "Advanced.ClickGLTFSaveAs"); - view_listener_t::addMenu(new LLAdvancedClickGLTFDecompose(), "Advanced.ClickGLTFDecompose"); view_listener_t::addMenu(new LLAdvancedClickGLTFUpload(), "Advanced.ClickGLTFUpload"); view_listener_t::addMenu(new LLAdvancedClickResizeWindow(), "Advanced.ClickResizeWindow"); view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache"); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index f1fdfc548b..e35375f43c 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -507,192 +507,6 @@ void process_layer_data(LLMessageSystem *mesgsys, void **user_data) } } -// S32 exported_object_count = 0; -// S32 exported_image_count = 0; -// S32 current_object_count = 0; -// S32 current_image_count = 0; - -// extern LLNotifyBox *gExporterNotify; -// extern LLUUID gExporterRequestID; -// extern std::string gExportDirectory; - -// extern LLUploadDialog *gExportDialog; - -// std::string gExportedFile; - -// std::map<LLUUID, std::string> gImageChecksums; - -// void export_complete() -// { -// LLUploadDialog::modalUploadFinished(); -// gExporterRequestID.setNull(); -// gExportDirectory = ""; - -// LLFILE* fXML = LLFile::fopen(gExportedFile, "rb"); /* Flawfinder: ignore */ -// fseek(fXML, 0, SEEK_END); -// long length = ftell(fXML); -// fseek(fXML, 0, SEEK_SET); -// U8 *buffer = new U8[length + 1]; -// size_t nread = fread(buffer, 1, length, fXML); -// if (nread < (size_t) length) -// { -// LL_WARNS("Messaging") << "Short read" << LL_ENDL; -// } -// buffer[nread] = '\0'; -// fclose(fXML); - -// char *pos = (char *)buffer; -// while ((pos = strstr(pos+1, "<sl:image ")) != 0) -// { -// char *pos_check = strstr(pos, "checksum=\""); - -// if (pos_check) -// { -// char *pos_uuid = strstr(pos_check, "\">"); - -// if (pos_uuid) -// { -// char image_uuid_str[UUID_STR_SIZE]; /* Flawfinder: ignore */ -// memcpy(image_uuid_str, pos_uuid+2, UUID_STR_SIZE-1); /* Flawfinder: ignore */ -// image_uuid_str[UUID_STR_SIZE-1] = 0; - -// LLUUID image_uuid(image_uuid_str); - -// LL_INFOS("Messaging") << "Found UUID: " << image_uuid << LL_ENDL; - -// std::map<LLUUID, std::string>::iterator itor = gImageChecksums.find(image_uuid); -// if (itor != gImageChecksums.end()) -// { -// LL_INFOS("Messaging") << "Replacing with checksum: " << itor->second << LL_ENDL; -// if (!itor->second.empty()) -// { -// memcpy(&pos_check[10], itor->second.c_str(), 32); /* Flawfinder: ignore */ -// } -// } -// } -// } -// } - -// LLFILE* fXMLOut = LLFile::fopen(gExportedFile, "wb"); /* Flawfinder: ignore */ -// if (fwrite(buffer, 1, length, fXMLOut) != length) -// { -// LL_WARNS("Messaging") << "Short write" << LL_ENDL; -// } -// fclose(fXMLOut); - -// delete [] buffer; -// } - - -// void exported_item_complete(const LLTSCode status, void *user_data) -// { -// //std::string *filename = (std::string *)user_data; - -// if (status < LLTS_OK) -// { -// LL_WARNS("Messaging") << "Export failed!" << LL_ENDL; -// } -// else -// { -// ++current_object_count; -// if (current_image_count == exported_image_count && current_object_count == exported_object_count) -// { -// LL_INFOS("Messaging") << "*** Export complete ***" << LL_ENDL; - -// export_complete(); -// } -// else -// { -// gExportDialog->setMessage(llformat("Exported %d/%d object files, %d/%d textures.", current_object_count, exported_object_count, current_image_count, exported_image_count)); -// } -// } -// } - -// struct exported_image_info -// { -// LLUUID image_id; -// std::string filename; -// U32 image_num; -// }; - -// void exported_j2c_complete(const LLTSCode status, void *user_data) -// { -// exported_image_info *info = (exported_image_info *)user_data; -// LLUUID image_id = info->image_id; -// U32 image_num = info->image_num; -// std::string filename = info->filename; -// delete info; - -// if (status < LLTS_OK) -// { -// LL_WARNS("Messaging") << "Image download failed!" << LL_ENDL; -// } -// else -// { -// LLFILE* fIn = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ -// if (fIn) -// { -// LLPointer<LLImageJ2C> ImageUtility = new LLImageJ2C; -// LLPointer<LLImageTGA> TargaUtility = new LLImageTGA; - -// fseek(fIn, 0, SEEK_END); -// S32 length = ftell(fIn); -// fseek(fIn, 0, SEEK_SET); -// U8 *buffer = ImageUtility->allocateData(length); -// if (fread(buffer, 1, length, fIn) != length) -// { -// LL_WARNS("Messaging") << "Short read" << LL_ENDL; -// } -// fclose(fIn); -// LLFile::remove(filename); - -// // Convert to TGA -// LLPointer<LLImageRaw> image = new LLImageRaw(); - -// ImageUtility->updateData(); -// ImageUtility->decode(image, 100000.0f); - -// TargaUtility->encode(image); -// U8 *data = TargaUtility->getData(); -// S32 data_size = TargaUtility->getDataSize(); - -// std::string file_path = gDirUtilp->getDirName(filename); - -// std::string output_file = llformat("%s/image-%03d.tga", file_path.c_str(), image_num);//filename; -// //S32 name_len = output_file.length(); -// //strcpy(&output_file[name_len-3], "tga"); -// LLFILE* fOut = LLFile::fopen(output_file, "wb"); /* Flawfinder: ignore */ -// char md5_hash_string[33]; /* Flawfinder: ignore */ -// strcpy(md5_hash_string, "00000000000000000000000000000000"); /* Flawfinder: ignore */ -// if (fOut) -// { -// if (fwrite(data, 1, data_size, fOut) != data_size) -// { -// LL_WARNS("Messaging") << "Short write" << LL_ENDL; -// } -// fseek(fOut, 0, SEEK_SET); -// fclose(fOut); -// fOut = LLFile::fopen(output_file, "rb"); /* Flawfinder: ignore */ -// LLMD5 my_md5_hash(fOut); -// my_md5_hash.hex_digest(md5_hash_string); -// } - -// gImageChecksums.insert(std::pair<LLUUID, std::string>(image_id, md5_hash_string)); -// } -// } - -// ++current_image_count; -// if (current_image_count == exported_image_count && current_object_count == exported_object_count) -// { -// LL_INFOS("Messaging") << "*** Export textures complete ***" << LL_ENDL; -// export_complete(); -// } -// else -// { -// gExportDialog->setMessage(llformat("Exported %d/%d object files, %d/%d textures.", current_object_count, exported_object_count, current_image_count, exported_image_count)); -// } -//} - void process_derez_ack(LLMessageSystem*, void**) { if(gViewerWindow) gViewerWindow->getWindow()->decBusyCount(); diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 9958526a2b..0ca05b3460 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -228,6 +228,9 @@ LLGLSLShader gDeferredPBRAlphaProgram; LLGLSLShader gDeferredSkinnedPBRAlphaProgram; LLGLSLShader gDeferredPBRTerrainProgram; +LLGLSLShader gGLTFPBRMetallicRoughnessProgram; + + //helper for making a rigged variant of a given shader static bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader) { @@ -235,6 +238,7 @@ static bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader riggedShader.mFeatures = shader.mFeatures; riggedShader.mFeatures.hasObjectSkinning = true; riggedShader.mDefines = shader.mDefines; // NOTE: Must come before addPermutation + riggedShader.addPermutation("HAS_SKIN", "1"); riggedShader.mShaderFiles = shader.mShaderFiles; riggedShader.mShaderLevel = shader.mShaderLevel; @@ -244,6 +248,49 @@ static bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader return riggedShader.createShader(NULL, NULL); } + +static bool make_gltf_variant(LLGLSLShader& shader, LLGLSLShader& variant, bool alpha_blend, bool rigged) +{ + variant.mName = shader.mName.c_str(); + variant.mFeatures = shader.mFeatures; + variant.mShaderFiles = shader.mShaderFiles; + variant.mShaderLevel = shader.mShaderLevel; + variant.mShaderGroup = shader.mShaderGroup; + + variant.mDefines = shader.mDefines; // NOTE: Must come before addPermutation + + if (alpha_blend) + { + variant.addPermutation("ALPHA_BLEND", "1"); + } + if (rigged) + { + variant.addPermutation("HAS_SKIN", "1"); + variant.mFeatures.hasObjectSkinning = true; + } + + return variant.createShader(NULL, NULL); +} + +static bool make_gltf_variants(LLGLSLShader& shader) +{ + shader.mFeatures.mGLTF = true; + shader.mGLTFVariants.resize(LLGLSLShader::NUM_GLTF_VARIANTS); + + for (U32 i = 0; i < LLGLSLShader::NUM_GLTF_VARIANTS; ++i) + { + bool alpha_blend = i & 1; + bool rigged = i & 2; + + if (!make_gltf_variant(shader, shader.mGLTFVariants[i], alpha_blend, rigged)) + { + return false; + } + } + + return true; +} + #ifdef SHOW_ASSERT // return true if there are no redundant shaders in the given vector // also checks for redundant variants @@ -329,6 +376,7 @@ void LLViewerShaderMgr::finalizeShaderList() mShaderList.push_back(&gDeferredDiffuseProgram); mShaderList.push_back(&gDeferredBumpProgram); mShaderList.push_back(&gDeferredPBROpaqueProgram); + mShaderList.push_back(&gGLTFPBRMetallicRoughnessProgram); mShaderList.push_back(&gDeferredAvatarProgram); mShaderList.push_back(&gDeferredTerrainProgram); mShaderList.push_back(&gDeferredPBRTerrainProgram); @@ -1019,6 +1067,7 @@ bool LLViewerShaderMgr::loadShadersDeferred() gHUDPBROpaqueProgram.unload(); gPBRGlowProgram.unload(); gDeferredPBROpaqueProgram.unload(); + gGLTFPBRMetallicRoughnessProgram.unload(); gDeferredSkinnedPBROpaqueProgram.unload(); gDeferredPBRAlphaProgram.unload(); gDeferredSkinnedPBRAlphaProgram.unload(); @@ -1211,6 +1260,22 @@ bool LLViewerShaderMgr::loadShadersDeferred() if (success) { + gGLTFPBRMetallicRoughnessProgram.mName = "GLTF PBR Metallic Roughness Shader"; + gGLTFPBRMetallicRoughnessProgram.mFeatures.hasSrgb = true; + + gGLTFPBRMetallicRoughnessProgram.mShaderFiles.clear(); + gGLTFPBRMetallicRoughnessProgram.mShaderFiles.push_back(make_pair("gltf/pbrmetallicroughnessV.glsl", GL_VERTEX_SHADER)); + gGLTFPBRMetallicRoughnessProgram.mShaderFiles.push_back(make_pair("gltf/pbrmetallicroughnessF.glsl", GL_FRAGMENT_SHADER)); + gGLTFPBRMetallicRoughnessProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gGLTFPBRMetallicRoughnessProgram.clearPermutations(); + + success = make_gltf_variants(gGLTFPBRMetallicRoughnessProgram); + + llassert(success); + } + + if (success) + { gPBRGlowProgram.mName = " PBR Glow Shader"; gPBRGlowProgram.mFeatures.hasSrgb = true; gPBRGlowProgram.mShaderFiles.clear(); diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 06c0c62c70..7f913e4153 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -335,6 +335,12 @@ LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const s return gTextureList.getImageFromUrl(url, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); } +//static +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromMemory(const U8* data, U32 size, std::string_view mimetype) +{ + return gTextureList.getImageFromMemory(data, size, mimetype); +} + LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromHost(const LLUUID& image_id, FTType f_type, LLHost host) { return gTextureList.getImageFromHost(image_id, f_type, host); diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index d3d76f38e8..3c4a5c18ac 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -440,8 +440,6 @@ private: bool mForceCallbackFetch; protected: - std::string mLocalFileName; - S32 mOrigWidth; S32 mOrigHeight; @@ -692,6 +690,8 @@ public: static LLViewerFetchedTexture* getFetchedTextureFromHost(const LLUUID& image_id, FTType f_type, LLHost host) ; + static LLViewerFetchedTexture* getFetchedTextureFromMemory(const U8* data, U32 size, std::string_view mimetype); + static void init() ; static void cleanup() ; }; diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 0016ba6155..37ff5038cd 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -360,7 +360,7 @@ void LLViewerTextureList::shutdown() mImageList.clear(); - mInitialized = false; //prevent loading textures again. + mInitialized = false ; //prevent loading textures again. } void LLViewerTextureList::dump() @@ -518,6 +518,26 @@ LLViewerFetchedTexture* LLViewerTextureList::getImageFromUrl(const std::string& return imagep; } +LLViewerFetchedTexture* LLViewerTextureList::getImageFromMemory(const U8* data, U32 size, std::string_view mimetype) +{ + LLPointer<LLImageFormatted> image = LLImageFormatted::loadFromMemory(data, size, mimetype); + + if (image) + { + LLPointer<LLImageRaw> raw_image = new LLImageRaw(); + image->decode(raw_image, 0.f); + LLPointer<LLViewerFetchedTexture> imagep = new LLViewerFetchedTexture(raw_image, FTT_LOCAL_FILE, true); + addImage(imagep, TEX_LIST_STANDARD); + + imagep->dontDiscard(); + imagep->setBoostLevel(LLViewerFetchedTexture::BOOST_PREVIEW); + return imagep; + } + else + { + return nullptr; + } +} LLViewerFetchedTexture* LLViewerTextureList::getImage(const LLUUID &image_id, FTType f_type, @@ -631,8 +651,8 @@ LLViewerFetchedTexture* LLViewerTextureList::createImage(const LLUUID &image_id, imagep->forceActive() ; } - mFastCacheList.insert(imagep); - imagep->setInFastCacheList(true); + mFastCacheList.insert(imagep); + imagep->setInFastCacheList(true); return imagep ; } @@ -1310,63 +1330,63 @@ bool LLViewerTextureList::createUploadFile(const std::string& filename, LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; try { - // Load the image - LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec); - if (image.isNull()) - { - LL_WARNS() << "Couldn't open the image to be uploaded." << LL_ENDL; - return false; - } - if (!image->load(filename)) - { - image->setLastError("Couldn't load the image to be uploaded."); - return false; - } - // Decompress or expand it in a raw image structure - LLPointer<LLImageRaw> raw_image = new LLImageRaw; - if (!image->decode(raw_image, 0.0f)) - { - image->setLastError("Couldn't decode the image to be uploaded."); - return false; - } - // Check the image constraints - if ((image->getComponents() != 3) && (image->getComponents() != 4)) - { - image->setLastError("Image files with less than 3 or more than 4 components are not supported."); - return false; - } - if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) - { - std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", - min_image_dimentions, - min_image_dimentions, - image->getWidth(), - image->getHeight()); - image->setLastError(reason); - return false; - } - // Convert to j2c (JPEG2000) and save the file locally - LLPointer<LLImageJ2C> compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); - if (compressedImage.isNull()) - { - image->setLastError("Couldn't convert the image to jpeg2000."); - LL_INFOS() << "Couldn't convert to j2c, file : " << filename << LL_ENDL; - return false; - } - if (!compressedImage->save(out_filename)) - { - image->setLastError("Couldn't create the jpeg2000 image for upload."); - LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; - return false; - } - // Test to see if the encode and save worked - LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C; - if (!integrity_test->loadAndValidate(out_filename)) - { - image->setLastError("The created jpeg2000 image is corrupt."); - LL_INFOS() << "Image file : " << out_filename << " is corrupt" << LL_ENDL; - return false; - } + // Load the image + LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec); + if (image.isNull()) + { + LL_WARNS() << "Couldn't open the image to be uploaded." << LL_ENDL; + return false; + } + if (!image->load(filename)) + { + image->setLastError("Couldn't load the image to be uploaded."); + return false; + } + // Decompress or expand it in a raw image structure + LLPointer<LLImageRaw> raw_image = new LLImageRaw; + if (!image->decode(raw_image, 0.0f)) + { + image->setLastError("Couldn't decode the image to be uploaded."); + return false; + } + // Check the image constraints + if ((image->getComponents() != 3) && (image->getComponents() != 4)) + { + image->setLastError("Image files with less than 3 or more than 4 components are not supported."); + return false; + } + if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + image->getWidth(), + image->getHeight()); + image->setLastError(reason); + return false; + } + // Convert to j2c (JPEG2000) and save the file locally + LLPointer<LLImageJ2C> compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); + if (compressedImage.isNull()) + { + image->setLastError("Couldn't convert the image to jpeg2000."); + LL_INFOS() << "Couldn't convert to j2c, file : " << filename << LL_ENDL; + return false; + } + if (!compressedImage->save(out_filename)) + { + image->setLastError("Couldn't create the jpeg2000 image for upload."); + LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; + return false; + } + // Test to see if the encode and save worked + LLPointer<LLImageJ2C> integrity_test = new LLImageJ2C; + if (!integrity_test->loadAndValidate( out_filename )) + { + image->setLastError("The created jpeg2000 image is corrupt."); + LL_INFOS() << "Image file : " << out_filename << " is corrupt" << LL_ENDL; + return false; + } } catch (...) { diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index da1c013335..ba468bab7e 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -192,6 +192,8 @@ private: const LLUUID& force_id = LLUUID::null ); + LLViewerFetchedTexture* getImageFromMemory(const U8* data, U32 size, std::string_view mimetype); + LLViewerFetchedTexture* createImage(const LLUUID &image_id, FTType f_type, bool usemipmap = true, diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 8c9561fe64..daf8dd4208 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -2880,14 +2880,6 @@ function="World.EnvPreset" function="Advanced.ClickGLTFSaveAs" /> </menu_item_call> <menu_item_call - label="Decompose..." - name="Decompose..."> - <menu_item_call.on_enable - function="EnableGLTF"/> - <menu_item_call.on_click - function="Advanced.ClickGLTFDecompose" /> - </menu_item_call> - <menu_item_call label="Upload..." name="Upload..."> <menu_item_call.on_enable diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index e3b9f744e9..1584de6880 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -12472,6 +12472,28 @@ are wearing now. <notification icon="alertmodal.tga" + name="GLTFLoadFailed" + type="alert"> + Failed to load GLTF file. See log for details. + <tag>fail</tag> + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="GLTFSaveFailed" + type="alert"> + Failed to save GLTF file. See log for details. + <tag>fail</tag> + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" name="GLTFSaveSelection" type="alert"> You must select an object that has a GLTF asset associated with it. |