123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- /**
- * @file llgltfmateriallist.cpp
- * @brief The LLGLTFMaterialList class implementation
- *
- * $LicenseInfo:firstyear=2022&license=viewergpl$
- *
- * Copyright (c) 2022, Linden Research, Inc.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/LicenseInfo$
- */
- #include "llviewerprecompiledheaders.h"
- #include <sstream>
- #include "tinygltf/tiny_gltf.h"
- #include "llgltfmateriallist.h"
- #include "indra_constants.h" // For BLANK_MATERIAL_ASSET_ID
- #include "llassetstorage.h"
- #include "llcorehttputil.h"
- #include "llfilesystem.h"
- #include "llsdserialize.h"
- #include "llworkqueue.h"
- #include "llagent.h"
- #include "llappviewer.h"
- #include "hbfloaterdebugtags.h"
- #include "llpipeline.h"
- #include "lltinygltfhelper.h"
- #include "llviewercontrol.h"
- #include "llviewerobjectlist.h"
- #include "llviewerregion.h"
- #include "llvlcomposition.h" // For LLTerrain::isAsset()
- #include "llvocache.h"
- #include "llworld.h"
- LLGLTFMaterialList gGLTFMaterialList;
- LLGLTFMaterialList::modify_queue_t LLGLTFMaterialList::sModifyQueue;
- LLGLTFMaterialList::apply_queue_t LLGLTFMaterialList::sApplyQueue;
- LLSD LLGLTFMaterialList::sUpdates;
- LLGLTFMaterialList::selection_cb_list_t LLGLTFMaterialList::sSelectionCallbacks;
- ///////////////////////////////////////////////////////////////////////////////
- // LLGLTFMaterialList class
- ///////////////////////////////////////////////////////////////////////////////
- void LLGLTFMaterialList::applyQueuedOverrides(LLViewerObject* objp)
- {
- if (objp)
- {
- // The override cache is the authoritative source of the most recent
- // override data
- LLViewerRegion* regionp = objp->getRegion();
- if (regionp)
- {
- regionp->applyCacheMiscExtras(objp);
- }
- }
- }
- void LLGLTFMaterialList::queueModify(const LLViewerObject* objp, S32 side,
- const LLGLTFMaterial* matp)
- {
- if (!objp || objp->getRenderMaterialID(side).isNull())
- {
- llwarns << "Cannot modify: NULL " << (objp ? "material" : "object")
- << llendl;
- return;
- }
- const LLUUID& obj_id = objp->getID();
- if (matp)
- {
- if (objp->getDebugUpdateMsg())
- {
- llinfos << "Queuing material modification for face " << side
- << " of object " << obj_id << llendl;
- }
- sModifyQueue.emplace_back(obj_id, *matp, side, true);
- return;
- }
- if (objp->getDebugUpdateMsg())
- {
- llinfos << "Queuing material reset for face " << side << " of object "
- << obj_id << llendl;
- }
- sModifyQueue.emplace_back(obj_id, LLGLTFMaterial(), side, false);
- }
- void LLGLTFMaterialList::queueApply(const LLViewerObject* objp, S32 side,
- const LLUUID& asset_id)
- {
- const LLUUID& obj_id = objp->getID();
- const LLGLTFMaterial* matp = objp->getTE(side)->getGLTFMaterialOverride();
- if (matp)
- {
- if (objp->getDebugUpdateMsg())
- {
- llinfos << "Queuing material applying for face " << side
- << " of object " << obj_id << llendl;
- }
- LLGLTFMaterial* cleared_matp = new LLGLTFMaterial(*matp);
- cleared_matp->setBaseMaterial();
- sApplyQueue.emplace_back(obj_id, asset_id, cleared_matp, side);
- return;
- }
- if (objp->getDebugUpdateMsg())
- {
- llinfos << "Queuing material reset for face " << side << " of object "
- << obj_id << llendl;
- }
- sApplyQueue.emplace_back(obj_id, asset_id, nullptr, side);
- }
- void LLGLTFMaterialList::queueApply(const LLViewerObject* objp, S32 side,
- const LLUUID& asset_id,
- const LLGLTFMaterial* matp)
- {
- if (!matp || asset_id.isNull())
- {
- queueApply(objp, side, asset_id);
- return;
- }
- sApplyQueue.emplace_back(objp->getID(), asset_id,
- new LLGLTFMaterial(*matp), side);
- }
- void LLGLTFMaterialList::queueUpdate(const LLSD& data)
- {
- if (!sUpdates.isArray())
- {
- sUpdates = LLSD::emptyArray();
- }
- sUpdates[sUpdates.size()] = data;
- }
- void LLGLTFMaterialList::flushUpdates(done_cb_t callback)
- {
- LLSD& data = sUpdates;
- S32 i = data.size();
- const LLUUID& debugged_id = LLViewerObject::getDebuggedObjectId();
- for (ModifyMaterialData& e : sModifyQueue)
- {
- data[i]["object_id"] = e.object_id;
- data[i]["side"] = e.side;
- if (e.has_override)
- {
- data[i]["gltf_json"] = e.override_data.asJSON();
- }
- else
- {
- // Clear all overrides
- data[i]["gltf_json"] = "";
- }
- if (e.object_id == debugged_id)
- {
- llinfos << "Sending modifications for object " << debugged_id
- << " on side " << e.side << ". JSON data: "
- << data[i]["gltf_json"] << llendl;
- }
- ++i;
- }
- sModifyQueue.clear();
- for (auto& e : sApplyQueue)
- {
- data[i]["object_id"] = e.object_id;
- data[i]["side"] = e.side;
- data[i]["asset_id"] = e.asset_id;
- if (e.override_data.notNull())
- {
- data[i]["gltf_json"] = e.override_data->asJSON();
- }
- else
- {
- // Clear all overrides
- data[i]["gltf_json"] = "";
- }
- if (e.object_id == debugged_id)
- {
- llinfos << "Applying material to object " << debugged_id
- << " on side " << e.side << " asset Id " << e.asset_id
- << ". JSON data: " << data[i]["gltf_json"] << llendl;
- }
- ++i;
- }
- sApplyQueue.clear();
- if (data.size() == 0)
- {
- return;
- }
- const std::string& cap_url =
- gAgent.getRegionCapability("ModifyMaterialParams");
- if (cap_url.empty())
- {
- LL_DEBUGS("GLTF") << "No ModifyMaterialParams capability. Aborted"
- << LL_ENDL;
- return;
- }
- gCoros.launch("modifyMaterialCoro",
- boost::bind(&LLGLTFMaterialList::modifyMaterialCoro, cap_url,
- data, callback));
- sUpdates = LLSD::emptyArray();
- }
- //static
- void LLGLTFMaterialList::addSelectionUpdateCallback(update_cb_t callback)
- {
- sSelectionCallbacks.insert(callback);
- }
- //static
- void LLGLTFMaterialList::doSelectionCallbacks(const LLUUID& obj_id, S32 side)
- {
- for (auto& callback : sSelectionCallbacks)
- {
- callback(obj_id, side);
- }
- }
- struct GLTFAssetLoadUserData
- {
- GLTFAssetLoadUserData() = default;
- LL_INLINE GLTFAssetLoadUserData(tinygltf::Model model,
- LLFetchedGLTFMaterial* matp)
- : mModelIn(model),
- mMaterial(matp)
- {
- }
- tinygltf::Model mModelIn;
- LLPointer<LLFetchedGLTFMaterial> mMaterial;
- };
- // Work done via the general queue thread pool.
- //static
- bool LLGLTFMaterialList::decodeAsset(const LLUUID& id,
- GLTFAssetLoadUserData* asset_data)
- {
- LLFileSystem file(id);
- S32 size = file.getSize();
- if (!size)
- {
- llwarns << "Cannot read asset cache file for " << id << llendl;
- return false;
- }
- std::string buffer(size + 1, '\0');
- file.read((U8*)buffer.data(), size);
- // Read file into buffer
- std::stringstream llsdstream(buffer);
- LLSD asset;
- if (!LLSDSerialize::deserialize(asset, llsdstream, -1))
- {
- llwarns << "Failed to deserialize material LLSD for " << id << llendl;
- return false;
- }
- if (!asset.has("version"))
- {
- llwarns << "Missing GLTF version in material LLSD for " << id
- << llendl;
- return false;
- }
- std::string data = asset["version"].asString();
- if (!LLGLTFMaterial::isAcceptedVersion(data))
- {
- llwarns << "Unsupported GLTF version " << data << " for " << id
- << llendl;
- return false;
- }
- if (!asset.has("type"))
- {
- llwarns << "Missing GLTF asset type in material LLSD for " << id
- << llendl;
- return false;
- }
- data = asset["type"].asString();
- if (data != LLGLTFMaterial::ASSET_TYPE)
- {
- llwarns << "Incorrect GLTF asset type '" << data << "' for " << id
- << llendl;
- return false;
- }
- if (!asset.has("data") || !asset["data"].isString())
- {
- llwarns << "Invalid GLTF asset data for " << id << llendl;
- return false;
- }
- data = asset["data"].asString();
- std::string warn_msg, error_msg;
- tinygltf::TinyGLTF gltf;
- if (!gltf.LoadASCIIFromString(&asset_data->mModelIn, &error_msg, &warn_msg,
- data.c_str(), data.length(), ""))
- {
- llwarns << "Failed to decode material asset " << id
- << ". tinygltf reports: \n" << warn_msg << "\n"
- << error_msg << llendl;
- return false;
- }
- return true;
- }
- // Work on the main thread via the main loop work queue.
- //static
- void LLGLTFMaterialList::decodeAssetCallback(const LLUUID& id,
- GLTFAssetLoadUserData* asset_data,
- bool result)
- {
- if (asset_data->mMaterial.isNull()) // Paranoia ? HB
- {
- LL_DEBUGS("GLTF") << "NULL material returned for " << id << LL_ENDL;
- return;
- }
- if (result)
- {
- asset_data->mMaterial->setFromModel(asset_data->mModelIn,
- 0/*only one index*/);
- }
- else
- {
- LL_DEBUGS("GLTF") << "Failed to get material " << id << LL_ENDL;
- }
- asset_data->mMaterial->materialComplete(true);
- delete asset_data;
- }
- void LLGLTFMaterialList::onAssetLoadComplete(const LLUUID& id,
- LLAssetType::EType asset_type,
- void* user_data, S32 status,
- LLExtStat ext_status)
- {
- GLTFAssetLoadUserData* asset_data = (GLTFAssetLoadUserData*)user_data;
- if (status != LL_ERR_NOERR)
- {
- // A failure is "normal" if that asset Id was actually a terrain
- // texture Id and not a PBR material, so do not log an error. HB
- if (!LLTerrain::isAsset(id))
- {
- llwarns << "Error getting material asset data: "
- << LLAssetStorage::getErrorString(status)
- << " (" << status << ")" << llendl;
- }
- asset_data->mMaterial->materialComplete(false);
- delete asset_data;
- return;
- }
- if (!gMainloopWorkp)
- {
- // We are likely shutting down... HB
- return;
- }
- static LLWorkQueue::weak_t general_queue =
- LLWorkQueue::getNamedInstance("General");
- gMainloopWorkp->postTo(general_queue,
- // Work done on general queue
- [id, asset_data]()
- {
- return decodeAsset(id, asset_data);
- },
- // Callback to main thread
- [id, asset_data](bool result)
- {
- decodeAssetCallback(id, asset_data, result);
- });
- }
- LLFetchedGLTFMaterial* LLGLTFMaterialList::getMaterial(const LLUUID& id)
- {
- id_mat_map_t::iterator iter = mList.find(id);
- if (iter != mList.end())
- {
- return iter->second;
- }
- LLFetchedGLTFMaterial* matp = new LLFetchedGLTFMaterial();
- mList[id] = matp;
- matp->materialBegin();
- GLTFAssetLoadUserData* datap = new GLTFAssetLoadUserData();
- datap->mMaterial = matp;
- if (gAssetStoragep) // In case this would trigger after logoff... HB
- {
- gAssetStoragep->getAssetData(id, LLAssetType::AT_MATERIAL,
- onAssetLoadComplete, (void*)datap);
- }
- return matp;
- }
- void LLGLTFMaterialList::flushMaterials()
- {
- // Similar variant to what textures use (TextureFetchUpdateMinCount in LL's
- // PBR viewer code).
- static LLCachedControl<U32> min_update_count(gSavedSettings,
- "TextureFetchUpdateMinMediumPriority");
- // Update min_update_count or 5% of materials, whichever is greater
- U32 update_count = llmax((U32)min_update_count, mList.size() / 20);
- update_count = llmin(update_count, (U32)mList.size());
- constexpr F32 TIMEOUT = 30.f;
- F32 cur_time = gFrameTimeSeconds;
- // Advance iter one past the last key we updated
- id_mat_map_t::iterator iter = mList.find(mLastUpdateKey);
- if (iter != mList.end())
- {
- ++iter;
- }
- while (update_count-- > 0)
- {
- if (iter == mList.end())
- {
- iter = mList.begin();
- }
- LLPointer<LLFetchedGLTFMaterial> material = iter->second;
- if (material->getNumRefs() == 2) // This one plus one from the list
- {
- if (!material->mActive && cur_time > material->mExpectedFlushTime)
- {
- iter = mList.erase(iter);
- }
- else
- {
- if (material->mActive)
- {
- material->mExpectedFlushTime = cur_time + TIMEOUT;
- material->mActive = false;
- }
- ++iter;
- }
- }
- else
- {
- material->mActive = true;
- ++iter;
- }
- }
- if (iter != mList.end())
- {
- mLastUpdateKey = iter->first;
- }
- else
- {
- mLastUpdateKey.setNull();
- }
- }
- //static
- void LLGLTFMaterialList::modifyMaterialCoro(const std::string& cap_url,
- LLSD overrides, done_cb_t callback)
- {
- LL_DEBUGS("GLTF") << "Applying override via ModifyMaterialParams cap: "
- << overrides << LL_ENDL;
- LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions);
- options->setFollowRedirects(true);
- LLCoreHttpUtil::HttpCoroutineAdapter adapter("modifyMaterialCoro");
- LLSD result = adapter.postAndSuspend(cap_url, overrides, options);
- LLCore::HttpStatus status =
- LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
- bool success = true;
- if (!status)
- {
- llwarns << "Failed to modify material." << llendl;
- success = false;
- }
- else if (!result["success"].asBoolean())
- {
- llwarns << "Failed to modify material: " << result["message"] << LL_ENDL;
- success = false;
- }
- if (callback)
- {
- callback(success);
- }
- }
- void LLGLTFMaterialList::applyOverrideMessage(LLMessageSystem* msg,
- const std::string& data_in)
- {
- if (!msg) return; // Paranoia
- const LLHost& host = msg->getSender();
- LLViewerRegion* regionp = gWorld.getRegion(host);
- if (!regionp)
- {
- return;
- }
- std::stringstream llsdstream(data_in);
- LLSD data;
- LLSDSerialize::fromNotation(data, llsdstream, data_in.size());
- const LLSD& tes = data["te"];
- if (!tes.isArray())
- {
- llwarns_once << "Malformed message: no 'te' array." << llendl;
- return;
- }
- U32 local_id = data.get("id").asInteger();
- LLUUID obj_id;
- gObjectList.getUUIDFromLocal(obj_id, local_id, host.getAddress(),
- host.getPort());
- LLViewerObject* objp = NULL;
- bool set_debug_tag = false;
- if (obj_id.notNull())
- {
- set_debug_tag = obj_id == LLViewerObject::getDebuggedObjectId() &&
- !HBFloaterDebugTags::debugTagActive("GLTF");
- if (set_debug_tag)
- {
- HBFloaterDebugTags::setTag("GLTF", true);
- }
- LL_DEBUGS("GLTF") << "Received PBR material data for object " << obj_id
- << ": " << data_in << LL_ENDL;
- objp = gObjectList.findObject(obj_id);
- // Note: objp may be NULL if the viewer has not heard about the object
- // yet...
- if (objp && gShowObjectUpdates)
- {
- // Display a cyan blip for override updates when "Show objects
- // updates" is enabled.
- gPipeline.addDebugBlip(objp->getPositionAgent(), LLColor4::cyan);
- }
- }
- bool has_te[MAX_TES] = { false };
- fast_hset<S32> selected_tes;
- LLGLTFOverrideCacheEntry entry;
- entry.mLocalId = local_id;
- entry.mRegionHandle = regionp->getHandle();
- const LLSD& od = data["od"];
- for (U32 i = 0, count = llmin(tes.size(), MAX_TES); i < count; ++i)
- {
- LL_DEBUGS("GLTF") << "Face " << i << ", material override: ";
- if (od[i].isDefined())
- {
- LL_CONT << od[i];
- }
- else
- {
- LL_CONT << "none.";
- }
- LL_CONT << LL_ENDL;
- // Note: setTEGLTFMaterialOverride() and cache will take ownership.
- LLGLTFMaterial* matp = new LLGLTFMaterial();
- matp->applyOverrideLLSD(od[i]);
- S32 te = tes[i].asInteger();
- has_te[te] = true;
- entry.mSides[te] = od[i];
- entry.mGLTFMaterial[te] = matp;
- if (objp)
- {
- objp->setTEGLTFMaterialOverride(te, matp);
- LLTextureEntry* tep = objp->getTE(te);
- if (tep && tep->isSelected())
- {
- selected_tes.insert(te);
- }
- }
- }
- if (objp)
- {
- // Null out overrides on TEs that should not have them
- for (U32 te = 0, count = llmin(objp->getNumTEs(), MAX_TES); te < count;
- ++te)
- {
- if (!has_te[te])
- {
- LLTextureEntry* tep = objp->getTE(te);
- if (tep && tep->getGLTFMaterialOverride())
- {
- objp->setTEGLTFMaterialOverride(te, NULL);
- selected_tes.insert(te);
- }
- }
- }
- }
- regionp->cacheFullUpdateGLTFOverride(entry);
- if (!selected_tes.empty())
- {
- for (fast_hset<S32>::iterator it = selected_tes.begin(),
- end = selected_tes.end();
- it != end; ++it)
- {
- doSelectionCallbacks(obj_id, *it);
- }
- }
- if (set_debug_tag)
- {
- HBFloaterDebugTags::setTag("GLTF", false);
- }
- }
|