phoenix-firestorm/indra/newview/llgltfmaterialpreviewmgr.cpp

591 lines
19 KiB
C++

/**
* @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 <memory>
#include <vector>
#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<LLViewerFetchedTexture>& 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<LLViewerFetchedTexture*>(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<LLViewerFetchedTexture>& 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<LLViewerFetchedTexture>*[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<LLFetchedGLTFMaterial> material, S32 width)
: LLViewerDynamicTexture(width, width, 4, EOrder::ORDER_MIDDLE, false)
, mGLTFMaterial(material)
{
}
// static
LLPointer<LLGLTFPreviewTexture> LLGLTFPreviewTexture::create(LLPointer<LLFetchedGLTFMaterial> material)
{
return new LLGLTFPreviewTexture(material, LLPipeline::MAX_BAKE_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<LLDrawInfo>& 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<LLDrawInfo> mDrawInfo;
LLMatrix4 mModelMatrix; // Referenced by mDrawInfo
};
using PreviewSpherePart = std::unique_ptr<GLTFPreviewModel>;
using PreviewSphere = std::vector<PreviewSpherePart>;
// Like LLVolumeGeometryManager::registerFace but without batching or too-many-indices/vertices checking.
PreviewSphere create_preview_sphere(LLPointer<LLFetchedGLTFMaterial>& 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<LLVertexBuffer> 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<U16> indices;
LLStrider<LLVector4a> positions;
LLStrider<LLVector4a> normals;
LLStrider<LLVector2> texcoords;
LLStrider<LLColor4U> colors;
LLStrider<LLVector4a> 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<LLDrawInfo> 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<GLTFPreviewModel>(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<LLFetchedGLTFMaterial>& 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<LLColor4U> 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<LLFetchedGLTFMaterial>& 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<typename T>
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<bool> no_dof(&LLPipeline::RenderDepthOfField, false);
SetTemporarily<bool> no_glow(&LLPipeline::sRenderGlow, false);
SetTemporarily<bool> no_ssr(&LLPipeline::RenderScreenSpaceReflections, false);
SetTemporarily<U32> no_fxaa(&LLPipeline::RenderFSAASamples, U32(0));
SetTemporarily<LLPipeline::RenderTargetPack*> 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<S32>("RenderLocalLightCount");
gSavedSettings.set<S32>("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(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();
glh::matrix4f mat = copy_matrix(gGLModelView);
glh::vec4f transformed_light_dir(light_dir.mV);
mat.mult_matrix_vec(transformed_light_dir);
SetTemporarily<LLVector4> force_sun_direction_high_graphics(&gPipeline.mTransformedSunDir, LLVector4(transformed_light_dir.v));
// 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<S32>("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<LLViewerTexture> LLGLTFMaterialPreviewMgr::getPreview(LLPointer<LLFetchedGLTFMaterial> &material)
{
if (!material)
{
return nullptr;
}
static LLCachedControl<bool> 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);
}