secondlife/viewer#1184: Use more robust/memory-friendly setLoadedCallback for minimap gen. Fix some emissive maps.

master
Cosmic Linden 2024-04-12 11:27:24 -07:00
parent 47255bf44d
commit aac18ada71
4 changed files with 207 additions and 81 deletions

View File

@ -1874,6 +1874,75 @@ void LLImageRaw::compositeRowScaled4onto3( U8* in, U8* out, S32 in_pixel_len, S3
}
}
void LLImageRaw::addEmissive(LLImageRaw* src)
{
LLImageRaw* dst = this; // Just for clarity.
if (!validateSrcAndDst(__FUNCTION__, src, dst))
{
return;
}
llassert((3 == src->getComponents()) || (4 == src->getComponents()));
llassert(3 == dst->getComponents());
if( 3 == dst->getComponents() )
{
if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) )
{
addEmissiveUnscaled(src);
}
else
{
addEmissiveScaled(src);
}
}
}
void LLImageRaw::addEmissiveUnscaled(LLImageRaw* src)
{
LLImageRaw* dst = this; // Just for clarity.
llassert((3 == src->getComponents()) || (4 == src->getComponents()));
llassert((3 == dst->getComponents()) || (4 == dst->getComponents()));
llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) );
U8* const src_data = src->getData();
U8* const dst_data = dst->getData();
for(S32 y = 0; y < dst->getHeight(); ++y)
{
const S32 src_row_offset = src->getComponents() * src->getWidth() * y;
const S32 dst_row_offset = dst->getComponents() * dst->getWidth() * y;
for (S32 x = 0; x < dst->getWidth(); ++x)
{
const S32 src_offset = src_row_offset + (x * src->getComponents());
const S32 dst_offset = dst_row_offset + (x * dst->getComponents());
U8* const src_pixel = src_data + src_offset;
U8* const dst_pixel = dst_data + dst_offset;
dst_pixel[0] = llmin(255, dst_pixel[0] + src_pixel[0]);
dst_pixel[1] = llmin(255, dst_pixel[1] + src_pixel[1]);
dst_pixel[2] = llmin(255, dst_pixel[2] + src_pixel[2]);
}
}
}
void LLImageRaw::addEmissiveScaled(LLImageRaw* src)
{
LL_INFOS() << __FUNCTION__ << LL_ENDL;
LLImageRaw* dst = this; // Just for clarity.
llassert( (4 == src->getComponents()) && (3 == dst->getComponents()) );
LLImageRaw temp(dst->getWidth(), dst->getHeight(), dst->getComponents());
llassert_always(temp.getDataSize() > 0);
temp.copyScaled(src);
dst->addEmissiveUnscaled(&temp);
}
bool LLImageRaw::validateSrcAndDst(std::string func, LLImageRaw* src, LLImageRaw* dst)
{
if (!src || !dst || src->isBufferInvalid() || dst->isBufferInvalid())

View File

@ -276,6 +276,12 @@ public:
// Src and dst are same size. Src has 4 components. Dst has 3 components.
void compositeUnscaled4onto3( LLImageRaw* src );
// Emissive operations used by minimap
// Roughly emulates GLTF emissive texture, but is not GLTF-compliant
// *TODO: Remove in favor of shader
void addEmissive(LLImageRaw* src);
void addEmissiveScaled(LLImageRaw* src);
void addEmissiveUnscaled(LLImageRaw* src);
protected:
// Create an image from a local file (generally used in tools)
//bool createFromFile(const std::string& filename, bool j2c_lowest_mip_only = false);

View File

@ -28,6 +28,8 @@
#include "llvlcomposition.h"
#include <functional>
#include "llerror.h"
#include "v3math.h"
#include "llsurface.h"
@ -513,6 +515,108 @@ BOOL LLVLComposition::generateComposition()
return LLTerrainMaterials::generateMaterials();
}
namespace
{
void prepare_fallback_image(LLImageRaw* raw_image)
{
raw_image->resize(BASE_SIZE, BASE_SIZE, 4);
raw_image->fill(LLColor4U::white);
}
// Check if the raw image is loaded for this texture at a discard
// level the minimap can use, and if not then try to get it loaded.
bool prepare_raw_image(LLPointer<LLImageRaw>& raw_image, bool emissive, LLViewerFetchedTexture* tex, bool& delete_raw_post)
{
if (!tex)
{
if (!emissive)
{
prepare_fallback_image(raw_image);
}
else
{
llassert(!raw_image);
raw_image = nullptr;
}
return true;
}
if (raw_image)
{
// Callback already initiated
if (raw_image->getDataSize() > 0)
{
// Callback finished
delete_raw_post = true;
return true;
}
else
{
return false;
}
}
raw_image = new LLImageRaw();
S32 ddiscard = 0;
{
S32 min_dim = llmin(tex->getFullWidth(), tex->getFullHeight());
while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL)
{
ddiscard++;
min_dim /= 2;
}
}
struct PendingImage
{
LLImageRaw* mRawImage;
S32 mDesiredDiscard;
LLUUID mTextureId;
PendingImage(LLImageRaw* raw_image, S32 ddiscard, const LLUUID& texture_id)
: mRawImage(raw_image)
, mDesiredDiscard(ddiscard)
, mTextureId(texture_id)
{
mRawImage->ref();
}
~PendingImage()
{
mRawImage->unref();
}
};
PendingImage* pending_image = new PendingImage(raw_image, ddiscard, tex->getID());
loaded_callback_func cb = [](BOOL success, LLViewerFetchedTexture * src_vi, LLImageRaw * src, LLImageRaw * src_aux, S32 discard_level, BOOL is_final, void* userdata) {
PendingImage* pending = (PendingImage*)userdata;
// Owning LLVLComposition still exists
// Assume mRawImage only used by single LLVLComposition for now
const bool in_use_by_composition = pending->mRawImage->getNumRefs() > 1;
llassert(pending->mRawImage->getNumRefs());
llassert(pending->mRawImage->getNumRefs() <= 2);
const bool needs_data = !pending->mRawImage->getDataSize();
if (in_use_by_composition && needs_data)
{
if (success && pending->mDesiredDiscard == discard_level)
{
pending->mRawImage->resize(BASE_SIZE, BASE_SIZE, src->getComponents());
pending->mRawImage->copyScaled(src);
}
else if (is_final)
{
prepare_fallback_image(pending->mRawImage);
}
}
if (is_final) { delete userdata; }
};
tex->setLoadedCallback(cb, ddiscard, true, false, pending_image, nullptr);
tex->forceToSaveRawImage(ddiscard);
return false;
}
};
BOOL LLVLComposition::generateMinimapTileLand(const F32 x, const F32 y,
const F32 width, const F32 height)
{
@ -580,96 +684,28 @@ BOOL LLVLComposition::generateMinimapTileLand(const F32 x, const F32 y,
}
if (!tex) { tex = LLViewerFetchedTexture::sWhiteImagep; }
bool delete_raw_post = false;
bool delete_raw_post_emissive = false;
if (!prepare_raw_image(mRawImagesBaseColor[i], false, tex, delete_raw_post)) { return FALSE; }
if (tex_emissive && !prepare_raw_image(mRawImagesEmissive[i], true, tex_emissive, delete_raw_post_emissive)) { return FALSE; }
// tex_emissive can be null, and then will be ignored
S32 ddiscard = 0;
{
S32 min_dim = llmin(tex->getFullWidth(), tex->getFullHeight());
while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL)
{
ddiscard++;
min_dim /= 2;
}
}
S32 ddiscard_emissive = 0;
if (tex_emissive)
{
S32 min_dim_emissive = llmin(tex_emissive->getFullWidth(), tex_emissive->getFullHeight());
while (min_dim_emissive > BASE_SIZE && ddiscard_emissive < MAX_DISCARD_LEVEL)
{
ddiscard_emissive++;
min_dim_emissive /= 2;
}
}
// *NOTE: It is probably safe to call destroyRawImage no matter
// what, as LLViewerFetchedTexture::mRawImage is managed by
// LLPointer and not modified with the rare exception of
// icons (see BOOST_ICON). Nevertheless, gate this fix for now, as
// it may have unintended consequences on texture loading.
// We may want to also set the boost level in setDetailAssetID, but
// that is not guaranteed to work if a texture is loaded on an object
// before being loaded as terrain, so we will need this fix
// regardless.
static LLCachedControl<bool> sRenderTerrainPBREnabled(gSavedSettings, "RenderTerrainPBREnabled", false);
BOOL delete_raw = (tex->reloadRawImage(ddiscard) != NULL || sRenderTerrainPBREnabled);
BOOL delete_raw_emissive = (tex_emissive &&
(tex_emissive->reloadRawImage(ddiscard_emissive) != NULL || sRenderTerrainPBREnabled));
if(tex->getRawImageLevel() != ddiscard)
{
// Raw image is not ready, will enter here again later.
if (tex->getFetchPriority() <= 0.0f && !tex->hasSavedRawImage())
{
boost_minimap_texture(tex, TERRAIN_DECODE_PRIORITY);
tex->forceToRefetchTexture(ddiscard);
}
if(delete_raw)
{
tex->destroyRawImage() ;
}
return FALSE;
}
if (tex_emissive)
{
if(tex_emissive->getRawImageLevel() != ddiscard_emissive)
{
// Raw image is not ready, will enter here again later.
if (tex_emissive->getFetchPriority() <= 0.0f && !tex_emissive->hasSavedRawImage())
{
boost_minimap_texture(tex_emissive, TERRAIN_DECODE_PRIORITY);
tex_emissive->forceToRefetchTexture(ddiscard_emissive);
}
if(delete_raw_emissive)
{
tex_emissive->destroyRawImage() ;
}
return FALSE;
}
}
mRawImages[i] = tex->getRawImage() ;
if(delete_raw)
{
tex->destroyRawImage() ;
}
// In the simplest case, the minimap image is just the base color.
// This will be replaced if we need to do any tinting/compositing.
mRawImages[i] = mRawImagesBaseColor[i];
// *TODO: This isn't quite right for PBR:
// 1) It does not convert the color images from SRGB to linear
// before mixing (which will always require copying the image).
// 2) It mixes emissive and base color before mixing terrain
// materials, but it should be the other way around
// 3) The composite function used to put emissive into base color
// is not an alpha blend.
// Long-term, we should consider a method that is more
// maintainable. Shaders, perhaps? Bake shaders to textures?
LLPointer<LLImageRaw> raw_emissive;
if (tex_emissive)
{
raw_emissive = tex_emissive->getRawImage();
raw_emissive = mRawImagesEmissive[i];
if (has_emissive_factor ||
tex_emissive->getWidth(tex_emissive->getRawImageLevel()) != BASE_SIZE ||
tex_emissive->getHeight(tex_emissive->getRawImageLevel()) != BASE_SIZE ||
@ -677,7 +713,7 @@ BOOL LLVLComposition::generateMinimapTileLand(const F32 x, const F32 y,
{
LLPointer<LLImageRaw> newraw_emissive = new LLImageRaw(BASE_SIZE, BASE_SIZE, 4);
// Copy RGB, leave alpha alone (set to opaque by default)
newraw_emissive->copy(mRawImages[i]);
newraw_emissive->copy(mRawImagesEmissive[i]);
if (has_emissive_factor)
{
newraw_emissive->tint(emissive_factor);
@ -702,7 +738,7 @@ BOOL LLVLComposition::generateMinimapTileLand(const F32 x, const F32 y,
MAX_WATER_COLOR.mV[VZ],
255);
}
newraw->composite(mRawImages[i]);
newraw->composite(mRawImagesBaseColor[i]);
if (has_base_color_factor)
{
newraw->tint(base_color_factor);
@ -710,16 +746,24 @@ BOOL LLVLComposition::generateMinimapTileLand(const F32 x, const F32 y,
// Apply emissive texture
if (raw_emissive)
{
newraw->composite(raw_emissive);
newraw->addEmissive(raw_emissive);
}
mRawImages[i] = newraw; // deletes old
}
if (delete_raw_emissive)
if (delete_raw_post)
{
tex->destroyRawImage();
}
if (delete_raw_post_emissive)
{
tex_emissive->destroyRawImage();
}
// Remove intermediary image references
mRawImagesBaseColor[i] = nullptr;
mRawImagesEmissive[i] = nullptr;
}
st_data[i] = mRawImages[i]->getData();
st_data_size[i] = mRawImages[i]->getDataSize();
@ -893,6 +937,8 @@ void LLVLComposition::setDetailAssetID(S32 asset, const LLUUID& id)
}
LLTerrainMaterials::setDetailAssetID(asset, id);
mRawImages[asset] = NULL;
mRawImagesBaseColor[asset] = NULL;
mRawImagesEmissive[asset] = NULL;
}
void LLVLComposition::setStartHeight(S32 corner, const F32 start_height)

View File

@ -130,8 +130,13 @@ protected:
BOOL mParamsReady = FALSE;
LLSurface *mSurfacep;
// Final minimap raw images
LLPointer<LLImageRaw> mRawImages[LLTerrainMaterials::ASSET_COUNT];
// Only non-null during minimap tile generation
LLPointer<LLImageRaw> mRawImagesBaseColor[LLTerrainMaterials::ASSET_COUNT];
LLPointer<LLImageRaw> mRawImagesEmissive[LLTerrainMaterials::ASSET_COUNT];
F32 mStartHeight[CORNER_COUNT];
F32 mHeightRange[CORNER_COUNT];