summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2025-09-14 20:48:06 +0800
committerErik Kundiman <erik@megapahit.org>2025-09-14 21:11:39 +0800
commitdf871e95234b670012908d06a6ecc62418e18aa2 (patch)
tree7a721d104abe00be5f4e7a706bed8e69421c2f8a
parentca7673a2c9acb160af03cf8318450c36516219bf (diff)
parent27c360f30890d010bbc97963f59011692a39ba4f (diff)
Merge remote-tracking branch 'mobserveur/experimental2'
-rw-r--r--indra/newview/pipeline.cpp235
-rw-r--r--indra/newview/pipeline.h5
-rw-r--r--indra/newview/skins/default/xui/en/panel_preferences_graphics3.xml2
3 files changed, 148 insertions, 94 deletions
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 84b83357b2..fb04722864 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -914,13 +914,8 @@ bool LLPipeline::allocateScreenBufferInternal(U32 resX, U32 resY, U32 type_)
GLuint AAFormat = GL_RGBA8;
if(mHDRDisplay && MPColorPrecision == 2)
{
- LL_WARNS() << "AA SET TO GL_RGBA16F" << LL_ENDL;
AAFormat = GL_RGBA16F;
}
- else
- {
- LL_WARNS() << "AA SET TO GL_RGBA8" << LL_ENDL;
- }
if (!mFXAAMap.allocate(resX, resY, AAFormat)) return false;
if (RenderFSAAType == 2)
@@ -934,6 +929,8 @@ bool LLPipeline::allocateScreenBufferInternal(U32 resX, U32 resY, U32 type_)
mSMAABlendBuffer.release();
}
+ mDummyRT.allocate(8, 8, GL_RGBA8, false);
+
//water reflection texture (always needed as scratch space whether or not transparent water is enabled)
mWaterDis.allocate(resX, resY, screenFormat, true);
@@ -1231,6 +1228,8 @@ void LLPipeline::releaseGLBuffers()
mSMAASearchMap = 0;
}
+ mDummyRT.release();
+
releaseLUTBuffers();
mWaterDis.release();
@@ -1378,7 +1377,7 @@ void LLPipeline::createGLBuffers()
LLImageGL::generateTextures(1, &mNoiseMap);
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseMap);
- LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB, GL_FLOAT, noise, false);
+ LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB8, noiseRes, noiseRes, GL_RGB, GL_UNSIGNED_INT, noise, false);
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
}
@@ -1393,7 +1392,7 @@ void LLPipeline::createGLBuffers()
LLImageGL::generateTextures(1, &mTrueNoiseMap);
gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mTrueNoiseMap);
- LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB,GL_FLOAT, noise, false);
+ LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB, GL_FLOAT, noise, false);
gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT);
}
@@ -5829,19 +5828,19 @@ void LLPipeline::setupHWLights()
if(!mHDRDisplay)
{
- F32 max_color = llmax(mSunDiffuse.mV[0], mSunDiffuse.mV[1], mSunDiffuse.mV[2]);
- if (max_color > 1.f)
- {
- mSunDiffuse *= 1.f/max_color;
- }
- mSunDiffuse.clamp();
+ F32 max_color = llmax(mSunDiffuse.mV[0], mSunDiffuse.mV[1], mSunDiffuse.mV[2]);
+ if (max_color > 1.f)
+ {
+ mSunDiffuse *= 1.f/max_color;
+ }
+ mSunDiffuse.clamp();
- max_color = llmax(mMoonDiffuse.mV[0], mMoonDiffuse.mV[1], mMoonDiffuse.mV[2]);
- if (max_color > 1.f)
- {
- mMoonDiffuse *= 1.f/max_color;
- }
- mMoonDiffuse.clamp();
+ max_color = llmax(mMoonDiffuse.mV[0], mMoonDiffuse.mV[1], mMoonDiffuse.mV[2]);
+ if (max_color > 1.f)
+ {
+ mMoonDiffuse *= 1.f/max_color;
+ }
+ mMoonDiffuse.clamp();
}
// prevent underlighting from having neither lightsource facing us
@@ -7158,11 +7157,17 @@ void LLPipeline::generateLuminance(LLRenderTarget* src, LLRenderTarget* dst)
gLuminanceProgram.uniform1f(diffuse_luminance_scale_s, diffuse_luminance_scale);
renderTriangle();
+
+ gLuminanceProgram.unbindTexture(LLShaderMgr::DEFERRED_DIFFUSE);
+ gLuminanceProgram.unbindTexture(LLShaderMgr::DEFERRED_EMISSIVE);
+ gLuminanceProgram.unbindTexture(LLShaderMgr::NORMAL_MAP);
+ gLuminanceProgram.unbind();
+
dst->flush();
// note -- unbind AFTER the glGenerateMipMap so time in generatemipmap can be profiled under "Luminance"
// also note -- keep an eye on the performance of glGenerateMipmap, might need to replace it with a mip generation shader
- gLuminanceProgram.unbind();
+ //gLuminanceProgram.unbind();
}
LOG_GLERROR("LLPipeline::generateLuminance()");
@@ -7273,6 +7278,9 @@ void LLPipeline::generateExposure(LLRenderTarget* src, LLRenderTarget* dst, bool
{
//gGL.getTexUnit(channel)->unbind(mLastExposure.getUsage());
}
+
+ shader->unbindTexture(LLShaderMgr::DEFERRED_DIFFUSE);
+ shader->unbindTexture(LLShaderMgr::EXPOSURE_MAP);
shader->unbind();
dst->flush();
}
@@ -7328,7 +7336,8 @@ void LLPipeline::tonemap(LLRenderTarget* src, LLRenderTarget* dst)
renderTriangle();
- //gGL.getTexUnit(channel)->unbind(src->getUsage());
+ shader.unbindTexture(LLShaderMgr::DEFERRED_DIFFUSE);
+ shader.unbindTexture(LLShaderMgr::EXPOSURE_MAP);
shader.unbind();
}
dst->flush();
@@ -7337,13 +7346,14 @@ void LLPipeline::tonemap(LLRenderTarget* src, LLRenderTarget* dst)
void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst)
{
- dst->bindTarget("gammaCorrect", 1);
- dst->clear();
+ LLGLDepthTest depth(GL_FALSE, GL_FALSE);
+
// gamma correct lighting
{
LL_PROFILE_GPU_ZONE("gamma correct");
- LLGLDepthTest depth(GL_FALSE, GL_FALSE);
+ dst->bindTarget("gammaCorrect", 1);
+ dst->clear(GL_COLOR_BUFFER_BIT);
static LLCachedControl<bool> buildNoPost(gSavedSettings, "RenderDisablePostProcessing", false);
static LLCachedControl<bool> should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", false);
@@ -7358,7 +7368,7 @@ void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst)
shader.bind();
shader.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_POINT);
- //screensize isn't a uniform int he shader, we comment out for now
+ //screensize isn't a uniform in the shader, we comment out for now
//shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, (GLfloat)src->getWidth(), (GLfloat)src->getHeight());
if(mHDRDisplay)
@@ -7369,9 +7379,12 @@ void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst)
renderTriangle();
+ shader.unbindTexture(LLShaderMgr::DEFERRED_DIFFUSE);
shader.unbind();
+
+ dst->flush();
}
- dst->flush();
+
LOG_GLERROR("LLPipeline::gammaCorrect()");
}
@@ -7450,13 +7463,12 @@ void LLPipeline::generateGlow(LLRenderTarget* src)
gPipeline.enableLightsFullbright();
renderTriangle();
+ gGlowExtractProgram.unbindTexture(LLShaderMgr::DIFFUSE_MAP);
gGlowExtractProgram.unbindTexture(LLShaderMgr::GLOW_NOISE_MAP);
-
+ gGlowExtractProgram.unbind();
mGlow[2].flush();
}
- gGlowExtractProgram.unbind();
-
// power of two between 1 and 1024
U32 glowResPow = RenderGlowResolutionPow;
const U32 glow_res = llmax(1, llmin(1024, 1 << glowResPow));
@@ -7558,6 +7570,46 @@ bool LLPipeline::applyCAS(LLRenderTarget* src, LLRenderTarget* dst)
return true;
}
+bool LLPipeline::generateFXAABuffer(LLRenderTarget* src)
+{
+ llassert(!gCubeSnapshot);
+ bool multisample = RenderFSAAType == 1 && gFXAAProgram[0].isComplete() && mFXAAMap.isComplete();
+
+ if(!multisample) return false;
+
+ LL_PROFILE_GPU_ZONE("aa");
+
+ // bake out texture2D with RGBL for FXAA shader
+ mFXAAMap.bindTarget("applyFXAA", 1);
+ mFXAAMap.clear(GL_COLOR_BUFFER_BIT);
+
+ LLGLSLShader* shader = &gGlowCombineFXAAProgram;
+ shader->bind();
+
+ S32 channel = shader->enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, src->getUsage());
+ if (channel > -1)
+ {
+ src->bindTexture(0, channel, LLTexUnit::TFO_BILINEAR);
+ }
+
+ {
+ LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS);
+ renderTriangle();
+ }
+
+ shader->disableTexture(LLShaderMgr::DEFERRED_DIFFUSE, src->getUsage());
+ shader->unbind();
+
+ mFXAAMap.flush();
+
+ LLGLDepthTest depth(GL_FALSE, GL_FALSE);
+ mDummyRT.bindTarget("dummy",1);
+ mDummyRT.clear(GL_COLOR_BUFFER_BIT);
+ mDummyRT.flush();
+
+ return true;
+}
+
bool LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst)
{
{
@@ -7571,8 +7623,6 @@ bool LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst)
S32 width = dst->getWidth();
S32 height = dst->getHeight();
- //LL_WARNS() << "dst width=" << width << LL_ENDL;
-
// bake out texture2D with RGBL for FXAA shader
mFXAAMap.bindTarget("applyFXAA", 1);
mFXAAMap.clear(GL_COLOR_BUFFER_BIT);
@@ -7611,20 +7661,17 @@ bool LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst)
mFXAAMap.bindTexture(0, channel, LLTexUnit::TFO_BILINEAR);
}
-/*
+ /*
gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft;
gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom;
gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth();
gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight();
glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]);
-*/
+ */
F32 scale_x = (F32)width / mFXAAMap.getWidth();
F32 scale_y = (F32)height / mFXAAMap.getHeight();
-
- //LL_WARNS() << "vp width=" << gGLViewport[2] << " scale=" << scale_x << LL_ENDL;
-
shader->uniform2f(LLShaderMgr::FXAA_TC_SCALE, scale_x, scale_y);
shader->uniform2f(LLShaderMgr::FXAA_RCP_SCREEN_RES, 1.f / width * scale_x, 1.f / height * scale_y);
shader->uniform4f(LLShaderMgr::FXAA_RCP_FRAME_OPT, -0.5f / width * scale_x, -0.5f / height * scale_y,
@@ -7653,12 +7700,14 @@ bool LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst)
return true;
}
-void LLPipeline::generateSMAABuffers(LLRenderTarget* src)
+bool LLPipeline::generateSMAABuffers(LLRenderTarget* src)
{
llassert(!gCubeSnapshot);
- if(RenderFSAAType < 2) return;
+ if(RenderFSAAType < 2) return false;
bool multisample = gSMAAEdgeDetectProgram[0].isComplete() && mFXAAMap.isComplete() && mSMAABlendBuffer.isComplete();
+ if(!multisample) return false;
+
// Present everything.
if (multisample)
{
@@ -7700,30 +7749,20 @@ void LLPipeline::generateSMAABuffers(LLRenderTarget* src)
else
{
gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mSMAASampleMap);
- //gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
+ gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR);
}
- gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
}
- //if (use_stencil)
- //{
- // glStencilFunc(GL_ALWAYS, 1, 0xFF);
- // glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
- // glStencilMask(0xFF);
- //}
-
renderTriangle();
+ edge_shader.unbindTexture(LLShaderMgr::DEFERRED_DIFFUSE);
+ gGL.getTexUnit(channel)->unbindFast(LLTexUnit::TT_TEXTURE);
edge_shader.unbind();
dest.flush();
-
- //gGL.getTexUnit(channel)->unbindFast(LLTexUnit::TT_TEXTURE);
}
{
- //LLGLState stencil(GL_STENCIL_TEST, use_stencil);
-
// Bind setup:
LLRenderTarget& dest = mSMAABlendBuffer;
LLGLSLShader& blend_weights_shader = gSMAABlendWeightsProgram[fsaa_quality];
@@ -7755,17 +7794,8 @@ void LLPipeline::generateSMAABuffers(LLRenderTarget* src)
gGL.getTexUnit(search_tex_channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
}
- //if (use_stencil)
- //{
- // glStencilFunc(GL_EQUAL, 1, 0xFF);
- // glStencilMask(0x00);
- //}
-
renderTriangle();
- //if (use_stencil)
- //{
- // glStencilFunc(GL_ALWAYS, 0, 0xFF);
- //}
+
blend_weights_shader.unbind();
dest.flush();
gGL.getTexUnit(edge_tex_channel)->unbindFast(LLTexUnit::TT_TEXTURE);
@@ -7773,7 +7803,10 @@ void LLPipeline::generateSMAABuffers(LLRenderTarget* src)
gGL.getTexUnit(search_tex_channel)->unbindFast(LLTexUnit::TT_TEXTURE);
}
}
+
LOG_GLERROR("LLPipeline::generateSMAABuffers()");
+
+ return true;
}
bool LLPipeline::applySMAA(LLRenderTarget* src, LLRenderTarget* dst)
@@ -7791,9 +7824,6 @@ bool LLPipeline::applySMAA(LLRenderTarget* src, LLRenderTarget* dst)
{
LL_PROFILE_GPU_ZONE("aa");
-
- generateSMAABuffers(src);
-
static LLCachedControl<U32> aa_quality(gSavedSettings, "RenderFSAASamples", 0U);
U32 fsaa_quality = std::clamp(aa_quality(), 0U, 3U);
@@ -7805,11 +7835,8 @@ bool LLPipeline::applySMAA(LLRenderTarget* src, LLRenderTarget* dst)
LLGLDepthTest depth(GL_FALSE, GL_FALSE);
static LLCachedControl<bool> use_sample(gSavedSettings, "RenderSMAAUseSample", false);
- //static LLCachedControl<bool> use_stencil(gSavedSettings, "RenderSMAAUseStencil", true);
{
- //LLGLDisable stencil(GL_STENCIL_TEST);
-
// Bind setup:
LLRenderTarget* bound_target = dst;
LLGLSLShader& blend_shader = gSMAANeighborhoodBlendProgram[fsaa_quality];
@@ -7834,11 +7861,10 @@ bool LLPipeline::applySMAA(LLRenderTarget* src, LLRenderTarget* dst)
}
renderTriangle();
-
- bound_target->flush();
- blend_shader.unbind();
gGL.getTexUnit(diffuse_channel)->unbindFast(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(blend_channel)->unbindFast(LLTexUnit::TT_TEXTURE);
+ blend_shader.unbind();
+ bound_target->flush();
}
}
@@ -8205,6 +8231,20 @@ void LLPipeline::renderFinalize()
LL_RECORD_BLOCK_TIME(FTM_RENDER_BLOOM);
LL_PROFILE_GPU_ZONE("renderFinalize");
+#if LL_DARWIN
+ //gGL.debugTexUnits();
+
+ // Aims to fix the slowdowns in the post processing
+ for (S32 i = 0; i < 16; i++)
+ {
+ if (gGL.getTexUnit(i)->getCurrType() != LLTexUnit::TT_NONE)
+ {
+ gGL.getTexUnit(i)->unbind(gGL.getTexUnit(i)->getCurrType());
+ gGL.getTexUnit(i)->disable();
+ }
+ }
+#endif
+
gGL.color4f(1, 1, 1, 1);
LLGLDepthTest depth(GL_FALSE);
LLGLDisable blend(GL_BLEND);
@@ -8226,56 +8266,62 @@ void LLPipeline::renderFinalize()
LLRenderTarget* postHDRBuffer = &mRT->screen;
- generateSMAABuffers(&mRT->screen);
-
if (hdr)
{
+ if(generateFXAABuffer(postHDRBuffer));
+ else generateSMAABuffers(postHDRBuffer);
+
copyScreenSpaceReflections(&mRT->screen, &mSceneMap);
if(!mHDRDisplay)
{
- generateLuminance(&mRT->screen, &mLuminanceMap);
- generateExposure(&mLuminanceMap, &mExposureMap);
+ generateLuminance(&mRT->screen, &mLuminanceMap);
+
+ generateExposure(&mLuminanceMap, &mExposureMap);
tonemap(&mRT->screen, &mRT->deferredLight);
postHDRBuffer = &mRT->deferredLight;
}
}
- gammaCorrect(postHDRBuffer, &mPostMaps[activeRT]);
-
-
- if(hdr)
- {
- generateGlow(&mPostMaps[activeRT]);
- }
+ gammaCorrect(postHDRBuffer, &mPostMaps[1 - activeRT]);
+ activeRT = 1 - activeRT;
- LLVertexBuffer::unbind();
+ generateGlow(&mPostMaps[activeRT]);
- if(hdr)
+ if(!hdr)
{
- if(renderBloom(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]))
+ if(generateFXAABuffer(postHDRBuffer))
+ {
+ }
+ else if(generateSMAABuffers(postHDRBuffer))
+ {
+ }
+ else
{
- activeRT = 1 - activeRT;
}
}
- if(hdr)
+ if(applyFXAA(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]))
{
- combineGlow(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]);
activeRT = 1 - activeRT;
}
-
- if(applyFXAA(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]))
+ else if(applySMAA(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]))
{
activeRT = 1 - activeRT;
}
- if(applySMAA(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]))
+ if(hdr)
{
- activeRT = 1 - activeRT;
+ if(renderBloom(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]))
+ {
+ activeRT = 1 - activeRT;
+ }
}
+ combineGlow(&mPostMaps[activeRT], &mPostMaps[1 - activeRT]);
+ activeRT = 1 - activeRT;
+
if(!mHDRDisplay)
{
//CAS breaks the hdr colors for now.
@@ -8371,8 +8417,12 @@ void LLPipeline::renderFinalize()
renderTriangle();
}
+ gDeferredPostNoDoFNoiseProgram.unbindTexture(LLShaderMgr::DEFERRED_DIFFUSE);
+ gDeferredPostNoDoFNoiseProgram.unbindTexture(LLShaderMgr::DEFERRED_DEPTH);
gDeferredPostNoDoFNoiseProgram.unbind();
+ gGL.flush();
+
gGL.setSceneBlendType(LLRender::BT_ALPHA);
if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES))
@@ -10385,7 +10435,8 @@ void LLPipeline::generateSunShadow(LLCamera& camera)
update_min_max(min, max, fp[i]);
}
- F32 maxDist = gSavedSettings.getF32("MPRenderShadowMaxDist");
+ static LLCachedControl<F32> maxDist(gSavedSettings, "MPRenderShadowMaxDist", 128);
+
near_clip = llclamp(-max.mV[2], 0.01f, 4.0f);
F32 far_clip = llclamp(-min.mV[2]*1.1, 16.0f, maxDist);
far_clip = llmin(far_clip, camera.getFar());
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 4bba462f78..f33495fd69 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -162,8 +162,9 @@ public:
void gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst);
void generateGlow(LLRenderTarget* src);
bool applyCAS(LLRenderTarget* src, LLRenderTarget* dst);
+ bool generateFXAABuffer(LLRenderTarget* src);
bool applyFXAA(LLRenderTarget* src, LLRenderTarget* dst);
- void generateSMAABuffers(LLRenderTarget* src);
+ bool generateSMAABuffers(LLRenderTarget* src);
bool applySMAA(LLRenderTarget* src, LLRenderTarget* dst);
bool renderDoF(LLRenderTarget* src, LLRenderTarget* dst);
void copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst);
@@ -745,6 +746,8 @@ public:
LLRenderTarget mFXAAMap;
LLRenderTarget mSMAABlendBuffer;
+ LLRenderTarget mDummyRT;
+
// render ui to buffer target
LLRenderTarget mUIScreen;
diff --git a/indra/newview/skins/default/xui/en/panel_preferences_graphics3.xml b/indra/newview/skins/default/xui/en/panel_preferences_graphics3.xml
index c7a1959853..50d3958186 100644
--- a/indra/newview/skins/default/xui/en/panel_preferences_graphics3.xml
+++ b/indra/newview/skins/default/xui/en/panel_preferences_graphics3.xml
@@ -296,7 +296,7 @@ left="55"
top_pad="40"
font="SansSerifSmall"
>
-Beware: SMAA + Sharpness currently causes a slow down on Mac.
+Beware: some combinations cause a slow down on Mac.
</text>
<text