/** * @file llgltfmaterialpreviewmgr.cpp * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2023, 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$ */ #include "llviewerprecompiledheaders.h" #include "llgltfmaterialpreviewmgr.h" #include #include #include "llavatarappearancedefines.h" #include "llenvironment.h" #include "llselectmgr.h" #include "llviewercamera.h" #include "llviewercontrol.h" #include "llviewerobject.h" #include "llviewershadermgr.h" #include "llviewertexturelist.h" #include "llviewerwindow.h" #include "llvolumemgr.h" #include "pipeline.h" LLGLTFMaterialPreviewMgr gGLTFMaterialPreviewMgr; namespace { constexpr S32 FULLY_LOADED = 0; constexpr S32 NOT_LOADED = 99; }; LLGLTFPreviewTexture::MaterialLoadLevels::MaterialLoadLevels() { for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) { levels[i] = NOT_LOADED; } } bool LLGLTFPreviewTexture::MaterialLoadLevels::isFullyLoaded() { for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) { if (levels[i] != FULLY_LOADED) { return false; } } return true; } S32& LLGLTFPreviewTexture::MaterialLoadLevels::operator[](size_t i) { llassert(i >= 0 && i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT); return levels[i]; } const S32& LLGLTFPreviewTexture::MaterialLoadLevels::operator[](size_t i) const { llassert(i >= 0 && i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT); return levels[i]; } bool LLGLTFPreviewTexture::MaterialLoadLevels::operator<(const MaterialLoadLevels& other) const { bool less = false; for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) { if (((*this)[i] > other[i])) { return false; } less = less || ((*this)[i] < other[i]); } return less; } bool LLGLTFPreviewTexture::MaterialLoadLevels::operator>(const MaterialLoadLevels& other) const { bool great = false; for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) { if (((*this)[i] < other[i])) { return false; } great = great || ((*this)[i] > other[i]); } return great; } namespace { void fetch_texture_for_ui(LLPointer& img, const LLUUID& id) { if (!img && id.notNull()) { if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) { LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); if (obj) { LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(id); img = viewerTexture ? dynamic_cast(viewerTexture) : NULL; } } else { img = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); } } if (img) { img->setBoostLevel(LLGLTexture::BOOST_PREVIEW); img->forceToSaveRawImage(0); } }; // *NOTE: Does not use the same conventions as texture discard level. Lower is better. S32 get_texture_load_level(const LLPointer& texture) { if (!texture) { return FULLY_LOADED; } const S32 raw_level = texture->getDiscardLevel(); if (raw_level < 0) { return NOT_LOADED; } return raw_level; } LLGLTFPreviewTexture::MaterialLoadLevels get_material_load_levels(LLFetchedGLTFMaterial& material) { llassert(!material.isFetching()); using MaterialTextures = LLPointer*[LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT]; MaterialTextures textures; textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] = &material.mBaseColorTexture; textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] = &material.mNormalTexture; textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] = &material.mMetallicRoughnessTexture; textures[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] = &material.mEmissiveTexture; LLGLTFPreviewTexture::MaterialLoadLevels levels; for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) { fetch_texture_for_ui(*textures[i], material.mTextureId[i]); levels[i] = get_texture_load_level(*textures[i]); } return levels; } // Is the material loaded enough to start rendering a preview? bool is_material_loaded_enough_for_ui(LLFetchedGLTFMaterial& material) { if (material.isFetching()) { return false; } LLGLTFPreviewTexture::MaterialLoadLevels levels = get_material_load_levels(material); for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) { if (levels[i] == NOT_LOADED) { return false; } } return true; } }; // namespace LLGLTFPreviewTexture::LLGLTFPreviewTexture(LLPointer material, S32 width) : LLViewerDynamicTexture(width, width, 4, EOrder::ORDER_MIDDLE, false) , mGLTFMaterial(material) { } // static LLPointer LLGLTFPreviewTexture::create(LLPointer material) { return new LLGLTFPreviewTexture(material, LLPipeline::MAX_PREVIEW_WIDTH); } bool LLGLTFPreviewTexture::needsRender() { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; if (!mShouldRender && mBestLoad.isFullyLoaded()) { return false; } MaterialLoadLevels current_load = get_material_load_levels(*mGLTFMaterial.get()); if (current_load < mBestLoad) { mShouldRender = true; mBestLoad = current_load; return true; } return false; } void LLGLTFPreviewTexture::preRender(bool clear_depth) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; llassert(mShouldRender); if (!mShouldRender) { return; } LLViewerDynamicTexture::preRender(clear_depth); } namespace { struct GLTFPreviewModel { GLTFPreviewModel(LLPointer& info, const LLMatrix4& mat) : mDrawInfo(info) , mModelMatrix(mat) { mDrawInfo->mModelMatrix = &mModelMatrix; } GLTFPreviewModel(GLTFPreviewModel&) = delete; ~GLTFPreviewModel() { // No model matrix necromancy llassert(gGLLastMatrix != &mModelMatrix); gGLLastMatrix = nullptr; } LLPointer mDrawInfo; LLMatrix4 mModelMatrix; // Referenced by mDrawInfo }; using PreviewSpherePart = std::unique_ptr; using PreviewSphere = std::vector; // Like LLVolumeGeometryManager::registerFace but without batching or too-many-indices/vertices checking. PreviewSphere create_preview_sphere(LLPointer& material, const LLMatrix4& model_matrix) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; const LLColor4U vertex_color(material->mBaseColor); LLPrimitive prim; prim.init_primitive(LL_PCODE_VOLUME); LLVolumeParams params; params.setType(LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE); params.setBeginAndEndS(0.f, 1.f); params.setBeginAndEndT(0.f, 1.f); params.setRatio(1, 1); params.setShear(0, 0); constexpr auto MAX_LOD = LLVolumeLODGroup::NUM_LODS - 1; prim.setVolume(params, MAX_LOD); LLVolume* volume = prim.getVolume(); llassert(volume); for (LLVolumeFace& face : volume->getVolumeFaces()) { face.createTangents(); } PreviewSphere preview_sphere; preview_sphere.reserve(volume->getNumFaces()); LLPointer buf = new LLVertexBuffer( LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TANGENT ); U32 nv = 0; U32 ni = 0; for (LLVolumeFace& face : volume->getVolumeFaces()) { nv += face.mNumVertices; ni += face.mNumIndices; } buf->allocateBuffer(nv, ni); // UV hacks // Higher factor helps to see more details on the preview sphere const LLVector2 uv_factor(2.0f, 2.0f); // Offset places center of material in center of view const LLVector2 uv_offset(-0.5f, -0.5f); LLStrider indices; LLStrider positions; LLStrider normals; LLStrider texcoords; LLStrider colors; LLStrider tangents; buf->getIndexStrider(indices); buf->getVertexStrider(positions); buf->getNormalStrider(normals); buf->getTexCoord0Strider(texcoords); buf->getColorStrider(colors); buf->getTangentStrider(tangents); U32 index_offset = 0; U32 vertex_offset = 0; for (const LLVolumeFace& face : volume->getVolumeFaces()) { for (S32 i = 0; i < face.mNumIndices; ++i) { *indices++ = face.mIndices[i] + vertex_offset; } for (S32 v = 0; v < face.mNumVertices; ++v) { *positions++ = face.mPositions[v]; *normals++ = face.mNormals[v]; LLVector2 uv(face.mTexCoords[v]); uv.scaleVec(uv_factor); uv += uv_offset; *texcoords++ = uv; *colors++ = vertex_color; *tangents++ = face.mTangents[v]; } constexpr LLViewerTexture* no_media = nullptr; LLPointer info = new LLDrawInfo(U16(vertex_offset), U16(vertex_offset + face.mNumVertices - 1), face.mNumIndices, index_offset, no_media, buf.get()); info->mGLTFMaterial = material; preview_sphere.emplace_back(std::make_unique(info, model_matrix)); index_offset += face.mNumIndices; vertex_offset += face.mNumVertices; } buf->unmapBuffer(); return preview_sphere; } void set_preview_sphere_material(PreviewSphere& preview_sphere, LLPointer& material) { llassert(!preview_sphere.empty()); if (preview_sphere.empty()) { return; } const LLColor4U vertex_color(material->mBaseColor); // See comments about unmapBuffer in llvertexbuffer.h for (PreviewSpherePart& part : preview_sphere) { LLDrawInfo* info = part->mDrawInfo.get(); info->mGLTFMaterial = material; LLVertexBuffer* buf = info->mVertexBuffer.get(); LLStrider colors; const S32 count = info->mEnd - info->mStart + 1; buf->getColorStrider(colors, info->mStart, count); for (S32 i = 0; i < count; ++i) { *colors++ = vertex_color; } buf->unmapBuffer(); } } PreviewSphere& get_preview_sphere(LLPointer& material, const LLMatrix4& model_matrix) { static PreviewSphere preview_sphere; if (preview_sphere.empty()) { preview_sphere = create_preview_sphere(material, model_matrix); } else { set_preview_sphere_material(preview_sphere, material); } return preview_sphere; } // Final, direct modifications to shader constants, just before render void fixup_shader_constants(LLGLSLShader& shader) { // Sunlight intensity of 0 no matter what shader.uniform1i(LLShaderMgr::SUN_UP_FACTOR, 1); shader.uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, 1, LLColor3::white.mV); shader.uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, 0.0f); // Ignore sun shadow (if enabled) for (U32 i = 0; i < 6; i++) { const S32 channel = shader.getTextureChannel(LLShaderMgr::DEFERRED_SHADOW0+i); if (channel != -1) { gGL.getTexUnit(channel)->bind(LLViewerFetchedTexture::sWhiteImagep, true); } } } // Set a variable to a value temporarily, and restor the variable's old value // when this object leaves scope. template struct SetTemporarily { T* mRef; T mOldVal; SetTemporarily(T* var, T temp_val) { mRef = var; mOldVal = *mRef; *mRef = temp_val; } ~SetTemporarily() { *mRef = mOldVal; } }; }; // namespace bool LLGLTFPreviewTexture::render() { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; if (!mShouldRender) { return false; } glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); LLGLDepthTest(GL_FALSE); LLGLDisable stencil(GL_STENCIL_TEST); LLGLDisable scissor(GL_SCISSOR_TEST); SetTemporarily no_dof(&LLPipeline::RenderDepthOfField, false); SetTemporarily no_glow(&LLPipeline::sRenderGlow, false); SetTemporarily no_ssr(&LLPipeline::RenderScreenSpaceReflections, false); SetTemporarily no_aa(&LLPipeline::RenderFSAAType, U32(0)); SetTemporarily use_auxiliary_render_target(&gPipeline.mRT, &gPipeline.mAuxillaryRT); LLVector3 light_dir3(1.0f, 1.0f, 1.0f); light_dir3.normalize(); const LLVector4 light_dir = LLVector4(light_dir3, 0); const S32 old_local_light_count = gSavedSettings.get("RenderLocalLightCount"); gSavedSettings.set("RenderLocalLightCount", 0); gPipeline.mReflectionMapManager.forceDefaultProbeAndUpdateUniforms(); LLViewerCamera camera; // Calculate the object distance at which the object of a given radius will // span the partial width of the screen given by fill_ratio. // Assume the primitive has a scale of 1 (this is the default). constexpr F32 fill_ratio = 0.8f; constexpr F32 object_radius = 0.5f; const F32 object_distance = (object_radius / fill_ratio) * tan(camera.getDefaultFOV()); // Negative coordinate shows the textures on the sphere right-side up, when // combined with the UV hacks in create_preview_sphere const LLVector3 object_position(0.0, -object_distance, 0.0); LLMatrix4 object_transform; object_transform.translate(object_position); // Set up camera and viewport const LLVector3 origin(0.0, 0.0, 0.0); camera.lookAt(origin, object_position); camera.setAspect((F32)(mFullHeight / mFullWidth)); const LLRect texture_rect(0, mFullHeight, mFullWidth, 0); camera.setPerspective(NOT_FOR_SELECTION, texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight(), false, camera.getNear(), MAX_FAR_CLIP*2.f); // Generate sphere object on-the-fly. Discard afterwards. (Vertex buffer is // discarded, but the sphere should be cached in LLVolumeMgr.) PreviewSphere& preview_sphere = get_preview_sphere(mGLTFMaterial, object_transform); gPipeline.setupHWLights(); glm::mat4 mat = get_current_modelview(); glm::vec4 transformed_light_dir = glm::make_vec4(light_dir.mV); transformed_light_dir = mat * transformed_light_dir; SetTemporarily force_sun_direction_high_graphics(&gPipeline.mTransformedSunDir, LLVector4(glm::value_ptr(transformed_light_dir))); // Override lights to ensure the sun is always shining from a certain direction (low graphics) // See also force_sun_direction_high_graphics and fixup_shader_constants { LLLightState* light = gGL.getLight(0); light->setPosition(light_dir); constexpr bool sun_up = true; light->setSunPrimary(sun_up); } LLRenderTarget& screen = gPipeline.mAuxillaryRT.screen; // *HACK: Force reset of the model matrix gGLLastMatrix = nullptr; #if 0 if (mGLTFMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_OPAQUE || mGLTFMaterial->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) { // *TODO: Opaque/alpha mask rendering } else #endif { // Alpha blend rendering screen.bindTarget(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); LLGLSLShader& shader = gDeferredPBRAlphaProgram; gPipeline.bindDeferredShader(shader); fixup_shader_constants(shader); for (PreviewSpherePart& part : preview_sphere) { LLRenderPass::pushGLTFBatch(*part->mDrawInfo); } gPipeline.unbindDeferredShader(shader); screen.flush(); } // *HACK: Hide mExposureMap from generateExposure gPipeline.mExposureMap.swapFBORefs(gPipeline.mLastExposure); gPipeline.copyScreenSpaceReflections(&screen, &gPipeline.mSceneMap); gPipeline.generateLuminance(&screen, &gPipeline.mLuminanceMap); gPipeline.generateExposure(&gPipeline.mLuminanceMap, &gPipeline.mExposureMap, /*use_history = */ false); gPipeline.gammaCorrect(&screen, &gPipeline.mPostMap); LLVertexBuffer::unbind(); gPipeline.generateGlow(&gPipeline.mPostMap); gPipeline.combineGlow(&gPipeline.mPostMap, &screen); gPipeline.renderDoF(&screen, &gPipeline.mPostMap); gPipeline.applyFXAA(&gPipeline.mPostMap, &screen); // *HACK: Restore mExposureMap (it will be consumed by generateExposure next frame) gPipeline.mExposureMap.swapFBORefs(gPipeline.mLastExposure); // Final render gDeferredPostNoDoFProgram.bind(); // From LLPipeline::renderFinalize: "Whatever is last in the above post processing chain should _always_ be rendered directly here. If not, expect problems." gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, &screen); gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, mBoundTarget, true); { LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); gPipeline.mScreenTriangleVB->setBuffer(); gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); } gDeferredPostNoDoFProgram.unbind(); // Clean up gPipeline.setupHWLights(); gPipeline.mReflectionMapManager.forceDefaultProbeAndUpdateUniforms(false); gSavedSettings.set("RenderLocalLightCount", old_local_light_count); return true; } void LLGLTFPreviewTexture::postRender(bool success) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; if (!mShouldRender) { return; } mShouldRender = false; LLViewerDynamicTexture::postRender(success); } LLPointer LLGLTFMaterialPreviewMgr::getPreview(LLPointer &material) { if (!material) { return nullptr; } static LLCachedControl sUIPreviewMaterial(gSavedSettings, "UIPreviewMaterial", false); if (!sUIPreviewMaterial) { fetch_texture_for_ui(material->mBaseColorTexture, material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); return material->mBaseColorTexture; } if (!is_material_loaded_enough_for_ui(*material)) { return nullptr; } return LLGLTFPreviewTexture::create(material); }