summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llrender/llglslshader.cpp34
-rw-r--r--indra/llrender/llglslshader.h2
-rw-r--r--indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl3
-rw-r--r--indra/newview/app_settings/shaders/class1/effects/glowExtractV.glsl1
-rw-r--r--indra/newview/app_settings/shaders/class1/effects/glowV.glsl20
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/glowcombineF.glsl3
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/glowcombineFXAAF.glsl3
-rw-r--r--indra/newview/pipeline.cpp251
8 files changed, 158 insertions, 159 deletions
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 95a100a8e6..61a17e5f52 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -32,6 +32,7 @@
#include "llfile.h"
#include "llrender.h"
#include "llvertexbuffer.h"
+#include "llrendertarget.h"
#if LL_DARWIN
#include "OpenGL/OpenGL.h"
@@ -1084,6 +1085,39 @@ S32 LLGLSLShader::bindTexture(S32 uniform, LLTexture* texture, LLTexUnit::eTextu
return uniform;
}
+S32 LLGLSLShader::bindTexture(S32 uniform, LLRenderTarget* texture, bool depth, LLTexUnit::eTextureFilterOptions mode)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;
+
+ if (uniform < 0 || uniform >= (S32)mTexture.size())
+ {
+ LL_SHADER_UNIFORM_ERRS() << "Uniform out of range: " << uniform << LL_ENDL;
+ return -1;
+ }
+
+ uniform = getTextureChannel(uniform);
+
+ if (uniform > -1)
+ {
+ gGL.getTexUnit(uniform)->bindManual(texture->getUsage(), texture->getTexture(0));
+
+
+ gGL.getTexUnit(uniform)->setTextureFilteringOption(mode);
+ }
+
+ return uniform;
+}
+
+S32 LLGLSLShader::bindTexture(const std::string& uniform, LLRenderTarget* texture, bool depth, LLTexUnit::eTextureFilterOptions mode)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;
+
+ S32 channel = 0;
+ channel = getUniformLocation(uniform);
+
+ return bindTexture(channel, texture, depth, mode);
+}
+
S32 LLGLSLShader::unbindTexture(const std::string& uniform, LLTexUnit::eTextureType mode)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;
diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index b0d5f308f3..37f86acd4e 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -243,6 +243,8 @@ public:
// You can reuse the return value to unbind a texture when required.
S32 bindTexture(const std::string& uniform, LLTexture* texture, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE, LLTexUnit::eTextureColorSpace space = LLTexUnit::TCS_LINEAR);
S32 bindTexture(S32 uniform, LLTexture* texture, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE, LLTexUnit::eTextureColorSpace space = LLTexUnit::TCS_LINEAR);
+ S32 bindTexture(const std::string& uniform, LLRenderTarget* texture, bool depth = false, LLTexUnit::eTextureFilterOptions mode = LLTexUnit::TFO_BILINEAR);
+ S32 bindTexture(S32 uniform, LLRenderTarget* texture, bool depth = false, LLTexUnit::eTextureFilterOptions mode = LLTexUnit::TFO_BILINEAR);
S32 unbindTexture(const std::string& uniform, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE);
S32 unbindTexture(S32 uniform, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE);
diff --git a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl
index 221de0b095..bdbc0056f8 100644
--- a/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl
+++ b/indra/newview/app_settings/shaders/class1/deferred/postDeferredGammaCorrect.glsl
@@ -34,7 +34,6 @@ out vec4 frag_color;
#endif
uniform sampler2D diffuseRect;
-uniform sampler2D emissiveRect;
uniform sampler2D exposureMap;
uniform vec2 screen_res;
@@ -183,7 +182,7 @@ vec3 legacyGamma(vec3 color)
void main()
{
//this is the one of the rare spots where diffuseRect contains linear color values (not sRGB)
- vec4 diff = texture2D(diffuseRect, vary_fragcoord) + texture2D(emissiveRect, vary_fragcoord);
+ vec4 diff = texture2D(diffuseRect, vary_fragcoord);
diff.rgb = toneMap(diff.rgb);
diff.rgb = legacyGamma(diff.rgb);
diff --git a/indra/newview/app_settings/shaders/class1/effects/glowExtractV.glsl b/indra/newview/app_settings/shaders/class1/effects/glowExtractV.glsl
index db0662ad89..fdca5018b0 100644
--- a/indra/newview/app_settings/shaders/class1/effects/glowExtractV.glsl
+++ b/indra/newview/app_settings/shaders/class1/effects/glowExtractV.glsl
@@ -26,7 +26,6 @@
uniform mat4 modelview_projection_matrix;
ATTRIBUTE vec3 position;
-ATTRIBUTE vec2 texcoord0;
VARYING vec2 vary_texcoord0;
diff --git a/indra/newview/app_settings/shaders/class1/effects/glowV.glsl b/indra/newview/app_settings/shaders/class1/effects/glowV.glsl
index ea66e8271b..63ca15139c 100644
--- a/indra/newview/app_settings/shaders/class1/effects/glowV.glsl
+++ b/indra/newview/app_settings/shaders/class1/effects/glowV.glsl
@@ -26,7 +26,6 @@
uniform mat4 modelview_projection_matrix;
ATTRIBUTE vec3 position;
-ATTRIBUTE vec2 texcoord0;
uniform vec2 glowDelta;
@@ -39,12 +38,15 @@ void main()
{
gl_Position = vec4(position, 1.0);
- vary_texcoord0.xy = texcoord0 + glowDelta*(-3.5);
- vary_texcoord1.xy = texcoord0 + glowDelta*(-2.5);
- vary_texcoord2.xy = texcoord0 + glowDelta*(-1.5);
- vary_texcoord3.xy = texcoord0 + glowDelta*(-0.5);
- vary_texcoord0.zw = texcoord0 + glowDelta*(0.5);
- vary_texcoord1.zw = texcoord0 + glowDelta*(1.5);
- vary_texcoord2.zw = texcoord0 + glowDelta*(2.5);
- vary_texcoord3.zw = texcoord0 + glowDelta*(3.5);
+ vec2 texcoord = position.xy * 0.5 + 0.5;
+
+
+ vary_texcoord0.xy = texcoord + glowDelta*(-3.5);
+ vary_texcoord1.xy = texcoord + glowDelta*(-2.5);
+ vary_texcoord2.xy = texcoord + glowDelta*(-1.5);
+ vary_texcoord3.xy = texcoord + glowDelta*(-0.5);
+ vary_texcoord0.zw = texcoord + glowDelta*(0.5);
+ vary_texcoord1.zw = texcoord + glowDelta*(1.5);
+ vary_texcoord2.zw = texcoord + glowDelta*(2.5);
+ vary_texcoord3.zw = texcoord + glowDelta*(3.5);
}
diff --git a/indra/newview/app_settings/shaders/class1/interface/glowcombineF.glsl b/indra/newview/app_settings/shaders/class1/interface/glowcombineF.glsl
index 770c436ede..3b25a5df16 100644
--- a/indra/newview/app_settings/shaders/class1/interface/glowcombineF.glsl
+++ b/indra/newview/app_settings/shaders/class1/interface/glowcombineF.glsl
@@ -31,12 +31,13 @@ out vec4 frag_color;
uniform sampler2D diffuseRect;
uniform sampler2D depthMap;
+uniform sampler2D emissiveRect;
in vec2 tc;
void main()
{
- frag_color = texture2D(diffuseRect, tc);
+ frag_color = texture2D(diffuseRect, tc) + texture2D(emissiveRect, tc);
gl_FragDepth = texture(depthMap, tc).r;
}
diff --git a/indra/newview/app_settings/shaders/class1/interface/glowcombineFXAAF.glsl b/indra/newview/app_settings/shaders/class1/interface/glowcombineFXAAF.glsl
index c50548d528..6cc83138a2 100644
--- a/indra/newview/app_settings/shaders/class1/interface/glowcombineFXAAF.glsl
+++ b/indra/newview/app_settings/shaders/class1/interface/glowcombineFXAAF.glsl
@@ -30,6 +30,7 @@
out vec4 frag_color;
uniform sampler2D diffuseRect;
+uniform sampler2D emissiveRect;
uniform vec2 screen_res;
@@ -37,7 +38,7 @@ in vec2 vary_tc;
void main()
{
- vec3 col = texture(diffuseRect, vary_tc).rgb;
+ vec3 col = texture(diffuseRect, vary_tc).rgb + texture(emissiveRect, vary_tc).rgb;
frag_color = vec4(col.rgb, dot(col.rgb, vec3(0.299, 0.587, 0.144)));
}
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 991e694cfc..dfe365b737 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -148,7 +148,6 @@ LLColor4 LLPipeline::PreviewSpecular2;
LLVector3 LLPipeline::PreviewDirection0;
LLVector3 LLPipeline::PreviewDirection1;
LLVector3 LLPipeline::PreviewDirection2;
-F32 LLPipeline::RenderGlowMinLuminance;
F32 LLPipeline::RenderGlowMaxExtractAlpha;
F32 LLPipeline::RenderGlowWarmthAmount;
LLVector3 LLPipeline::RenderGlowLumWeights;
@@ -502,7 +501,6 @@ void LLPipeline::init()
connectRefreshCachedSettingsSafe("PreviewDirection0");
connectRefreshCachedSettingsSafe("PreviewDirection1");
connectRefreshCachedSettingsSafe("PreviewDirection2");
- connectRefreshCachedSettingsSafe("RenderGlowMinLuminance");
connectRefreshCachedSettingsSafe("RenderGlowMaxExtractAlpha");
connectRefreshCachedSettingsSafe("RenderGlowWarmthAmount");
connectRefreshCachedSettingsSafe("RenderGlowLumWeights");
@@ -986,7 +984,6 @@ void LLPipeline::refreshCachedSettings()
PreviewDirection0 = gSavedSettings.getVector3("PreviewDirection0");
PreviewDirection1 = gSavedSettings.getVector3("PreviewDirection1");
PreviewDirection2 = gSavedSettings.getVector3("PreviewDirection2");
- RenderGlowMinLuminance = gSavedSettings.getF32("RenderGlowMinLuminance");
RenderGlowMaxExtractAlpha = gSavedSettings.getF32("RenderGlowMaxExtractAlpha");
RenderGlowWarmthAmount = gSavedSettings.getF32("RenderGlowWarmthAmount");
RenderGlowLumWeights = gSavedSettings.getVector3("RenderGlowLumWeights");
@@ -6913,122 +6910,6 @@ void LLPipeline::renderPostProcess()
gGL.setColorMask(true, true);
glClearColor(0, 0, 0, 0);
- if (sRenderGlow)
- {
- LL_PROFILE_GPU_ZONE("glow");
- mGlow[2].bindTarget();
- mGlow[2].clear();
-
- gGlowExtractProgram.bind();
- F32 minLum = llmax((F32)RenderGlowMinLuminance, 0.0f);
- F32 maxAlpha = RenderGlowMaxExtractAlpha;
- F32 warmthAmount = RenderGlowWarmthAmount;
- LLVector3 lumWeights = RenderGlowLumWeights;
- LLVector3 warmthWeights = RenderGlowWarmthWeights;
-
- gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MIN_LUMINANCE, minLum);
- gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MAX_EXTRACT_ALPHA, maxAlpha);
- gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_LUM_WEIGHTS, lumWeights.mV[0], lumWeights.mV[1],
- lumWeights.mV[2]);
- gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_WARMTH_WEIGHTS, warmthWeights.mV[0], warmthWeights.mV[1],
- warmthWeights.mV[2]);
- gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_WARMTH_AMOUNT, warmthAmount);
-
- {
- LLGLEnable blend_on(GL_BLEND);
- LLGLEnable test(GL_ALPHA_TEST);
-
- gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA);
-
- mRT->screen.bindTexture(0, 0, LLTexUnit::TFO_POINT);
-
- gGL.color4f(1, 1, 1, 1);
- gPipeline.enableLightsFullbright();
- gGL.begin(LLRender::TRIANGLE_STRIP);
- gGL.texCoord2f(tc1.mV[0], tc1.mV[1]);
- gGL.vertex2f(-1, -1);
-
- gGL.texCoord2f(tc1.mV[0], tc2.mV[1]);
- gGL.vertex2f(-1, 3);
-
- gGL.texCoord2f(tc2.mV[0], tc1.mV[1]);
- gGL.vertex2f(3, -1);
-
- gGL.end();
-
- gGL.getTexUnit(0)->unbind(mRT->screen.getUsage());
-
- mGlow[2].flush();
-
- tc1.setVec(0, 0);
- tc2.setVec(2, 2);
- }
-
- // power of two between 1 and 1024
- U32 glowResPow = RenderGlowResolutionPow;
- const U32 glow_res = llmax(1, llmin(1024, 1 << glowResPow));
-
- S32 kernel = RenderGlowIterations * 2;
- F32 delta = RenderGlowWidth / glow_res;
- // Use half the glow width if we have the res set to less than 9 so that it looks
- // almost the same in either case.
- if (glowResPow < 9)
- {
- delta *= 0.5f;
- }
- F32 strength = RenderGlowStrength;
-
- gGlowProgram.bind();
- gGlowProgram.uniform1f(LLShaderMgr::GLOW_STRENGTH, strength);
-
- for (S32 i = 0; i < kernel; i++)
- {
- mGlow[i % 2].bindTarget();
- mGlow[i % 2].clear();
-
- if (i == 0)
- {
- gGL.getTexUnit(0)->bind(&mGlow[2]);
- }
- else
- {
- gGL.getTexUnit(0)->bind(&mGlow[(i - 1) % 2]);
- }
-
- if (i % 2 == 0)
- {
- gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, delta, 0);
- }
- else
- {
- gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, 0, delta);
- }
-
- gGL.begin(LLRender::TRIANGLE_STRIP);
- gGL.texCoord2f(tc1.mV[0], tc1.mV[1]);
- gGL.vertex2f(-1, -1);
-
- gGL.texCoord2f(tc1.mV[0], tc2.mV[1]);
- gGL.vertex2f(-1, 3);
-
- gGL.texCoord2f(tc2.mV[0], tc1.mV[1]);
- gGL.vertex2f(3, -1);
-
- gGL.end();
-
- mGlow[i % 2].flush();
- }
-
- gGlowProgram.unbind();
- gGL.setSceneBlendType(LLRender::BT_ALPHA);
- }
- else // !sRenderGlow, skip the glow ping-pong and just clear the result target
- {
- mGlow[1].bindTarget();
- mGlow[1].clear();
- mGlow[1].flush();
- }
-
gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft;
gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom;
gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth();
@@ -7338,9 +7219,6 @@ void LLPipeline::renderFinalize()
assertInitialized();
- LLVector2 tc1(0, 0);
- LLVector2 tc2((F32) mRT->screen.getWidth() * 2, (F32) mRT->screen.getHeight() * 2);
-
LL_RECORD_BLOCK_TIME(FTM_RENDER_BLOOM);
LL_PROFILE_GPU_ZONE("renderFinalize");
@@ -7448,30 +7326,14 @@ void LLPipeline::renderFinalize()
LLGLDepthTest depth(GL_FALSE, GL_FALSE);
- LLVector2 tc1(0, 0);
- LLVector2 tc2((F32)screenTarget()->getWidth() * 2, (F32)screenTarget()->getHeight() * 2);
-
// Apply gamma correction to the frame here.
gDeferredPostGammaCorrectProgram.bind();
S32 channel = 0;
- channel = gDeferredPostGammaCorrectProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, screenTarget()->getUsage());
- if (channel > -1)
- {
- screenTarget()->bindTexture(0, channel, LLTexUnit::TFO_POINT);
- }
- channel = gDeferredPostGammaCorrectProgram.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE, screenTarget()->getUsage());
- if (channel > -1)
- {
- mGlow[1].bindTexture(0, channel, LLTexUnit::TFO_BILINEAR);
- }
+ gDeferredPostGammaCorrectProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, screenTarget(), false, LLTexUnit::TFO_POINT);
- channel = gDeferredPostGammaCorrectProgram.enableTexture(LLShaderMgr::EXPOSURE_MAP, mExposureMap.getUsage());
- if (channel > -1)
- {
- mExposureMap.bindTexture(0, channel);
- }
+ gDeferredPostGammaCorrectProgram.bindTexture(LLShaderMgr::EXPOSURE_MAP, &mExposureMap);
gDeferredPostGammaCorrectProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, screenTarget()->getWidth(), screenTarget()->getHeight());
@@ -7495,6 +7357,101 @@ void LLPipeline::renderFinalize()
LLVertexBuffer::unbind();
}
+ if (sRenderGlow)
+ {
+ LL_PROFILE_GPU_ZONE("glow");
+ mGlow[2].bindTarget();
+ mGlow[2].clear();
+
+ gGlowExtractProgram.bind();
+ F32 maxAlpha = RenderGlowMaxExtractAlpha;
+ F32 warmthAmount = RenderGlowWarmthAmount;
+ LLVector3 lumWeights = RenderGlowLumWeights;
+ LLVector3 warmthWeights = RenderGlowWarmthWeights;
+
+ gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MIN_LUMINANCE, 9999);
+ gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MAX_EXTRACT_ALPHA, maxAlpha);
+ gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_LUM_WEIGHTS, lumWeights.mV[0], lumWeights.mV[1],
+ lumWeights.mV[2]);
+ gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_WARMTH_WEIGHTS, warmthWeights.mV[0], warmthWeights.mV[1],
+ warmthWeights.mV[2]);
+ gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_WARMTH_AMOUNT, warmthAmount);
+
+ {
+ LLGLEnable blend_on(GL_BLEND);
+ LLGLEnable test(GL_ALPHA_TEST);
+
+ gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA);
+
+ gGlowExtractProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mPostMap);
+
+ gGL.color4f(1, 1, 1, 1);
+ gPipeline.enableLightsFullbright();
+
+ mScreenTriangleVB->setBuffer();
+ mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
+
+ 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));
+
+ S32 kernel = RenderGlowIterations * 2;
+ F32 delta = RenderGlowWidth / glow_res;
+ // Use half the glow width if we have the res set to less than 9 so that it looks
+ // almost the same in either case.
+ if (glowResPow < 9)
+ {
+ delta *= 0.5f;
+ }
+ F32 strength = RenderGlowStrength;
+
+ gGlowProgram.bind();
+ gGlowProgram.uniform1f(LLShaderMgr::GLOW_STRENGTH, strength);
+
+ for (S32 i = 0; i < kernel; i++)
+ {
+ mGlow[i % 2].bindTarget();
+ mGlow[i % 2].clear();
+
+ if (i == 0)
+ {
+ gGlowProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mGlow[2]);
+ }
+ else
+ {
+ gGlowProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mGlow[(i - 1) % 2]);
+ }
+
+ if (i % 2 == 0)
+ {
+ gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, delta, 0);
+ }
+ else
+ {
+ gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, 0, delta);
+ }
+
+ mScreenTriangleVB->setBuffer();
+ mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3);
+
+ mGlow[i % 2].flush();
+ }
+
+ gGlowProgram.unbind();
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+ }
+ else // !sRenderGlow, skip the glow ping-pong and just clear the result target
+ {
+ mGlow[1].bindTarget();
+ mGlow[1].clear();
+ mGlow[1].flush();
+ }
+
{
llassert(!gCubeSnapshot);
bool multisample = RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete();
@@ -7525,6 +7482,12 @@ void LLPipeline::renderFinalize()
mPostMap.bindTexture(0, channel);
}
+ channel = shader->enableTexture(LLShaderMgr::DEFERRED_EMISSIVE, mGlow[1].getUsage());
+ if (channel > -1)
+ {
+ mGlow[1].bindTexture(0, channel, LLTexUnit::TFO_BILINEAR);
+ }
+
{
LLGLDepthTest depth_test(GL_FALSE, GL_FALSE, GL_ALWAYS);
mScreenTriangleVB->setBuffer();
@@ -7584,11 +7547,9 @@ void LLPipeline::renderFinalize()
shader->bind();
- S32 screen_channel = shader->getTextureChannel(LLShaderMgr::DEFERRED_DIFFUSE);
- S32 depth_channel = shader->getTextureChannel(LLShaderMgr::DEFERRED_DEPTH);
-
- gGL.getTexUnit(screen_channel)->bind(&mPostMap);
- gGL.getTexUnit(depth_channel)->bind(&mRT->deferredScreen, true);
+ shader->bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, &mPostMap);
+ shader->bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true);
+ shader->bindTexture(LLShaderMgr::DEFERRED_EMISSIVE, &mGlow[1]);
gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft;
gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom;