summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcosmic-linden <111533034+cosmic-linden@users.noreply.github.com>2024-10-14 13:42:03 -0700
committerGitHub <noreply@github.com>2024-10-14 13:42:03 -0700
commita2899a3c6cc859dfde4d9692b0dde6e778184dc4 (patch)
tree73aafb1cc89cdca2d90dffbfd19479168ac55a82
parent1983342a0578a32a2a5f148514e708d3be27d13f (diff)
parent07e33dfac78b170d235069fd035771016d3e0403 (diff)
Merge pull request #2823 from secondlife/v-1883
secondlife/viewer#1883: Terrain paint client prototype WIP code check-in
-rw-r--r--indra/llrender/llglslshader.cpp22
-rw-r--r--indra/llrender/llglslshader.h2
-rw-r--r--indra/llrender/llshadermgr.cpp3
-rw-r--r--indra/llrender/llshadermgr.h1
-rw-r--r--indra/newview/app_settings/settings.xml2
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl2
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl44
-rw-r--r--indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl39
-rw-r--r--indra/newview/lldrawpoolterrain.cpp8
-rw-r--r--indra/newview/llterrainpaintmap.cpp712
-rw-r--r--indra/newview/llterrainpaintmap.h128
-rw-r--r--indra/newview/llviewercontrol.cpp1
-rw-r--r--indra/newview/llviewermenu.cpp97
-rw-r--r--indra/newview/llviewershadermgr.cpp29
-rw-r--r--indra/newview/llviewershadermgr.h1
-rw-r--r--indra/newview/llvlcomposition.cpp4
-rw-r--r--indra/newview/llvlcomposition.h16
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml7
18 files changed, 1104 insertions, 14 deletions
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index 6ba5463acd..bbbce1965e 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -1138,6 +1138,28 @@ S32 LLGLSLShader::bindTexture(S32 uniform, LLTexture* texture, LLTexUnit::eTextu
return uniform;
}
+// For LLImageGL-wrapped textures created via GL elsewhere with our API only. Use with caution.
+S32 LLGLSLShader::bindTextureImageGL(S32 uniform, LLImageGL* texture, LLTexUnit::eTextureType mode)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;
+
+ if (uniform < 0 || uniform >= (S32)mTexture.size())
+ {
+ LL_WARNS_ONCE("Shader") << "Uniform index out of bounds. Size: " << (S32)mUniform.size() << " index: " << uniform << LL_ENDL;
+ llassert(false);
+ return -1;
+ }
+
+ uniform = mTexture[uniform];
+
+ if (uniform > -1)
+ {
+ gGL.getTexUnit(uniform)->bind(texture);
+ }
+
+ return uniform;
+}
+
S32 LLGLSLShader::bindTexture(S32 uniform, LLRenderTarget* texture, bool depth, LLTexUnit::eTextureFilterOptions mode, U32 index)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER;
diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h
index 2d669c70a9..c24daaf686 100644
--- a/indra/llrender/llglslshader.h
+++ b/indra/llrender/llglslshader.h
@@ -264,6 +264,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);
S32 bindTexture(S32 uniform, LLTexture* texture, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE);
+ // For LLImageGL-wrapped textures created via GL elsewhere with our API only. Use with caution.
+ S32 bindTextureImageGL(S32 uniform, LLImageGL* texture, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE);
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, U32 index = 0);
S32 unbindTexture(const std::string& uniform, LLTexUnit::eTextureType mode = LLTexUnit::TT_TEXTURE);
diff --git a/indra/llrender/llshadermgr.cpp b/indra/llrender/llshadermgr.cpp
index 6097b09d96..796805e2a5 100644
--- a/indra/llrender/llshadermgr.cpp
+++ b/indra/llrender/llshadermgr.cpp
@@ -1190,8 +1190,9 @@ void LLShaderMgr::initAttribsAndUniforms()
mReservedUniforms.push_back("gltf_material_id"); // (GLTF)
mReservedUniforms.push_back("terrain_texture_transforms"); // (GLTF)
+ mReservedUniforms.push_back("terrain_stamp_scale");
- llassert(mReservedUniforms.size() == LLShaderMgr::TERRAIN_TEXTURE_TRANSFORMS +1);
+ llassert(mReservedUniforms.size() == LLShaderMgr::TERRAIN_STAMP_SCALE +1);
mReservedUniforms.push_back("viewport");
diff --git a/indra/llrender/llshadermgr.h b/indra/llrender/llshadermgr.h
index 1bae0cd8a0..ff07ce454b 100644
--- a/indra/llrender/llshadermgr.h
+++ b/indra/llrender/llshadermgr.h
@@ -67,6 +67,7 @@ public:
GLTF_MATERIAL_ID, // "gltf_material_id" (GLTF)
TERRAIN_TEXTURE_TRANSFORMS, // "terrain_texture_transforms" (GLTF)
+ TERRAIN_STAMP_SCALE, // "terrain_stamp_scale"
VIEWPORT, // "viewport"
LIGHT_POSITION, // "light_position"
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 1d54b6bb7e..eed9396789 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -15367,7 +15367,7 @@
<key>Type</key>
<string>U32</string>
<key>Value</key>
- <integer>5</integer>
+ <integer>8</integer>
</map>
<key>TerrainPaintResolution</key>
<map>
diff --git a/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl b/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl
index cf20653a0f..a79a56d725 100644
--- a/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl
+++ b/indra/newview/app_settings/shaders/class1/interface/pbrTerrainBakeF.glsl
@@ -1,5 +1,5 @@
/**
- * @file terrainBakeF.glsl
+ * @file pbrTerrainBakeF.glsl
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
diff --git a/indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl b/indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl
new file mode 100644
index 0000000000..e79e9010e6
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/interface/terrainStampF.glsl
@@ -0,0 +1,44 @@
+/**
+ * @file terrainStampF.glsl
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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$
+ */
+
+/*[EXTRA_CODE_HERE]*/
+
+out vec4 frag_color;
+
+// Paint texture to stamp into the "paintmap"
+uniform sampler2D diffuseMap;
+
+in vec2 vary_texcoord0;
+
+void main()
+{
+ vec4 col = texture(diffuseMap, vary_texcoord0);
+ if (col.a <= 0.0f)
+ {
+ discard;
+ }
+
+ frag_color = max(col, vec4(0));
+}
diff --git a/indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl b/indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl
new file mode 100644
index 0000000000..294fa6be26
--- /dev/null
+++ b/indra/newview/app_settings/shaders/class1/interface/terrainStampV.glsl
@@ -0,0 +1,39 @@
+/**
+ * @file terrainStampV.glsl
+ *
+ * $LicenseInfo:firstyear=2007&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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$
+ */
+
+uniform mat4 modelview_projection_matrix;
+uniform vec2 terrain_stamp_scale;
+
+in vec3 position;
+
+out vec2 vary_texcoord0;
+
+void main()
+{
+ gl_Position = modelview_projection_matrix * vec4(position, 1.0);
+ // Positions without transforms are treated as UVs for the purpose of this shader.
+ vary_texcoord0.xy = terrain_stamp_scale * position.xy;
+}
+
diff --git a/indra/newview/lldrawpoolterrain.cpp b/indra/newview/lldrawpoolterrain.cpp
index 5e676bc5b3..57def49539 100644
--- a/indra/newview/lldrawpoolterrain.cpp
+++ b/indra/newview/lldrawpoolterrain.cpp
@@ -296,7 +296,7 @@ void LLDrawPoolTerrain::renderFullShaderTextures()
// GL_BLEND disabled by default
drawLoop();
- // Disable multitexture
+ // Disable textures
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_DETAIL0);
sShader->disableTexture(LLViewerShaderMgr::TERRAIN_DETAIL1);
@@ -557,7 +557,7 @@ void LLDrawPoolTerrain::renderFullShaderPBR(bool use_local_materials)
// GL_BLEND disabled by default
drawLoop();
- // Disable multitexture
+ // Disable textures
if (paint_type == TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE)
{
@@ -769,7 +769,7 @@ void LLDrawPoolTerrain::renderFull4TU()
}
LLVertexBuffer::unbind();
- // Disable multitexture
+ // Disable textures
gGL.getTexUnit(3)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(3)->disable();
gGL.getTexUnit(3)->activate();
@@ -949,7 +949,7 @@ void LLDrawPoolTerrain::renderFull2TU()
// Restore blend state
gGL.setSceneBlendType(LLRender::BT_ALPHA);
- // Disable multitexture
+ // Disable textures
gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(1)->disable();
diff --git a/indra/newview/llterrainpaintmap.cpp b/indra/newview/llterrainpaintmap.cpp
index 8ccde74c93..a858f92d8e 100644
--- a/indra/newview/llterrainpaintmap.cpp
+++ b/indra/newview/llterrainpaintmap.cpp
@@ -31,6 +31,8 @@
// library includes
#include "llglslshader.h"
#include "llrendertarget.h"
+#include "llrender2dutils.h"
+#include "llshadermgr.h"
#include "llvertexbuffer.h"
// newview includes
@@ -38,18 +40,31 @@
#include "llsurface.h"
#include "llsurfacepatch.h"
#include "llviewercamera.h"
+#include "llviewercontrol.h"
#include "llviewerregion.h"
#include "llviewershadermgr.h"
#include "llviewertexture.h"
-// static
-bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex)
+namespace
+{
+#ifdef SHOW_ASSERT
+void check_tex(const LLViewerTexture& tex)
{
llassert(tex.getComponents() == 3);
llassert(tex.getWidth() > 0 && tex.getHeight() > 0);
llassert(tex.getWidth() == tex.getHeight());
llassert(tex.getPrimaryFormat() == GL_RGB);
llassert(tex.getGLTexture());
+}
+#else
+#define check_tex(tex)
+#endif
+} // namespace
+
+// static
+bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex)
+{
+ check_tex(tex);
const LLSurface& surface = region.getLand();
const U32 patch_count = surface.getPatchesPerEdge();
@@ -76,8 +91,8 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
// Bind the debug shader and render terrain to tex
// Use a scratch render target because its dimensions may exceed the standard bake target, and this is a one-off bake
LLRenderTarget scratch_target;
- const S32 dim = llmin(tex.getWidth(), tex.getHeight());
- scratch_target.allocate(dim, dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
+ const S32 max_dim = llmax(tex.getWidth(), tex.getHeight());
+ scratch_target.allocate(max_dim, max_dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
LLTexUnit::eTextureMipGeneration::TMG_NONE);
if (!scratch_target.isComplete())
{
@@ -104,6 +119,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
const F32 region_half_width = region_width / 2.0f;
const F32 region_camera_height = surface.getMaxZ() + DEFAULT_NEAR_PLANE;
LLViewerCamera camera;
+ // TODO: Huh... I just realized this view vector is not completely vertical
const LLVector3 region_center = LLVector3(region_half_width, region_half_width, 0.0) + region.getOriginAgent();
const LLVector3 camera_origin = LLVector3(0.0f, 0.0f, region_camera_height) + region_center;
camera.lookAt(camera_origin, region_center, LLVector3::y_axis);
@@ -237,6 +253,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
S32 alpha_ramp = shader.enableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
LLPointer<LLViewerTexture> alpha_ramp_texture = LLViewerTextureManager::getFetchedTexture(IMG_ALPHA_GRAD_2D);
+ // TODO: Consider using LLGLSLShader::bindTexture
gGL.getTexUnit(alpha_ramp)->bind(alpha_ramp_texture);
gGL.getTexUnit(alpha_ramp)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
@@ -250,6 +267,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
const U32 vertex_offset = n * patch_index;
llassert(index_offset + ni <= region_indices);
llassert(vertex_offset + n <= region_vertices);
+ // TODO: Try a single big drawRange and see if that still works
buf->drawRange(LLRender::TRIANGLES, vertex_offset, vertex_offset + n - 1, ni, index_offset);
}
}
@@ -269,7 +287,7 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
gGL.flush();
LLVertexBuffer::unbind();
// Final step: Copy the output to the terrain paintmap
- const bool success = tex.getGLTexture()->setSubImageFromFrameBuffer(0, 0, 0, 0, dim, dim);
+ const bool success = tex.getGLTexture()->setSubImageFromFrameBuffer(0, 0, 0, 0, tex.getWidth(), tex.getHeight());
if (!success)
{
LL_WARNS() << "Failed to copy framebuffer to paintmap" << LL_ENDL;
@@ -283,3 +301,687 @@ bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion&
return success;
}
+
+// *TODO: Decide when to apply the paint queue - ideally once per frame per region
+// Applies paints and then clears the paint queue
+// *NOTE The paint queue is also cleared when setting the paintmap texture
+void LLTerrainPaintMap::applyPaintQueueRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue)
+{
+ if (queue.empty()) { return; }
+
+ check_tex(tex);
+
+ gGL.getTexUnit(0)->bind(tex.getGLTexture(), false, true);
+
+ // glTexSubImage2D replaces all pixels in the rectangular region. That
+ // makes it unsuitable for alpha.
+ llassert(queue.getComponents() == LLTerrainPaint::RGB);
+ constexpr GLenum pixformat = GL_RGB;
+
+ const std::vector<LLTerrainPaint::ptr_t>& queue_list = queue.get();
+ for (size_t i = 0; i < queue_list.size(); ++i)
+ {
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for the paintmap (this could
+ // change in the future).
+ queue.convertBitDepths(i, 8);
+ const LLTerrainPaint::ptr_t& paint = queue_list[i];
+
+ if (paint->mData.empty()) { continue; }
+ constexpr GLint level = 0;
+ if ((paint->mStartX >= tex.getWidth() - 1) || (paint->mStartY >= tex.getHeight() - 1)) { continue; }
+ constexpr GLint miplevel = 0;
+ const S32 x_offset = paint->mStartX;
+ const S32 y_offset = paint->mStartY;
+ const S32 width = llmin(paint->mWidthX, tex.getWidth() - x_offset);
+ const S32 height = llmin(paint->mWidthY, tex.getHeight() - y_offset);
+ const U8* pixels = paint->mData.data();
+ constexpr GLenum pixtype = GL_UNSIGNED_BYTE;
+ // *TODO: Performance suggestion: Use the sub-image utility function
+ // that LLImageGL::setSubImage uses to split texture updates into
+ // lines, if that's faster.
+ glTexSubImage2D(GL_TEXTURE_2D, miplevel, x_offset, y_offset, width, height, pixformat, pixtype, pixels);
+ stop_glerror();
+ }
+
+ // Generating mipmaps at the end...
+ glGenerateMipmap(GL_TEXTURE_2D);
+ stop_glerror();
+
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+
+ queue.clear();
+}
+
+namespace
+{
+
+// A general-purpose vertex buffer of a quad for stamping textures on the z=0
+// plane.
+// *NOTE: Because we know the vertex XY coordinates go from 0 to 1
+// pre-transform, UVs can be calculated from the vertices
+LLVertexBuffer* get_paint_triangle_buffer()
+{
+ static LLPointer<LLVertexBuffer> buf = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX);
+ static bool initialized = false;
+ if (!initialized)
+ {
+ // Two triangles forming a square from (0,0) to (1,1)
+ buf->allocateBuffer(/*vertices =*/ 4, /*indices =*/ 6);
+ LLStrider<U16> indices;
+ LLStrider<LLVector3> vertices;
+ buf->getVertexStrider(vertices);
+ buf->getIndexStrider(indices);
+ // y
+ // 2....3
+ // ^ . .
+ // | 0....1
+ // |
+ // -------> x
+ //
+ // triangle 1: 0,1,2
+ // triangle 2: 1,3,2
+ (*(vertices++)).set(0.0f, 0.0f, 0.0f);
+ (*(vertices++)).set(1.0f, 0.0f, 0.0f);
+ (*(vertices++)).set(0.0f, 1.0f, 0.0f);
+ (*(vertices++)).set(1.0f, 1.0f, 0.0f);
+ *(indices++) = 0;
+ *(indices++) = 1;
+ *(indices++) = 2;
+ *(indices++) = 1;
+ *(indices++) = 3;
+ *(indices++) = 2;
+ buf->unmapBuffer();
+ }
+ return buf.get();
+}
+
+};
+
+// static
+LLTerrainPaintQueue LLTerrainPaintMap::convertPaintQueueRGBAToRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue_in)
+{
+ check_tex(tex);
+ llassert(queue_in.getComponents() == LLTerrainPaint::RGBA);
+
+ // TODO: Avoid allocating a scratch render buffer and use mAuxillaryRT instead
+ // TODO: even if it means performing extra render operations to apply the paints, in rare cases where the paints can't all fit within an area that can be represented by the buffer
+ LLRenderTarget scratch_target;
+ const S32 max_dim = llmax(tex.getWidth(), tex.getHeight());
+ scratch_target.allocate(max_dim, max_dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
+ LLTexUnit::eTextureMipGeneration::TMG_NONE);
+ if (!scratch_target.isComplete())
+ {
+ llassert(false);
+ LL_WARNS() << "Failed to allocate render target" << LL_ENDL;
+ return false;
+ }
+ gGL.getTexUnit(0)->disable();
+ stop_glerror();
+
+ scratch_target.bindTarget();
+ glClearColor(0, 0, 0, 0);
+ scratch_target.clear();
+ const F32 target_half_width = (F32)scratch_target.getWidth() / 2.0f;
+ const F32 target_half_height = (F32)scratch_target.getHeight() / 2.0f;
+
+ LLVertexBuffer* buf = get_paint_triangle_buffer();
+
+ // Update projection matrix and viewport
+ // *NOTE: gl_state_for_2d also sets the modelview matrix. This will be overridden later.
+ {
+ stop_glerror();
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.pushMatrix();
+ gGL.loadIdentity();
+ gGL.ortho(-target_half_width, target_half_width, -target_half_height, target_half_height, 0.25f, 1.0f);
+ stop_glerror();
+ const LLRect texture_rect(0, scratch_target.getHeight(), scratch_target.getWidth(), 0);
+ glViewport(texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight());
+ }
+
+ // View matrix
+ // Coordinates should be in pixels. 1.0f = 1 pixel on the framebuffer.
+ // Camera is centered in the middle of the framebuffer.
+ glm::mat4 view(glm::make_mat4((GLfloat*) OGL_TO_CFR_ROTATION));
+ {
+ LLViewerCamera camera;
+ const LLVector3 camera_origin(target_half_width, target_half_height, 0.5f);
+ const LLVector3 camera_look_down(target_half_width, target_half_height, 0.0f);
+ camera.lookAt(camera_origin, camera_look_down, LLVector3::y_axis);
+ camera.setAspect(F32(scratch_target.getHeight()) / F32(scratch_target.getWidth()));
+ GLfloat ogl_matrix[16];
+ camera.getOpenGLTransform(ogl_matrix);
+ view *= glm::make_mat4(ogl_matrix);
+ }
+
+ LLGLDisable stencil(GL_STENCIL_TEST);
+ LLGLDisable scissor(GL_SCISSOR_TEST);
+ LLGLEnable cull_face(GL_CULL_FACE);
+ LLGLDepthTest depth_test(GL_FALSE, GL_FALSE, GL_ALWAYS);
+ LLGLEnable blend(GL_BLEND);
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+
+ LLGLSLShader& shader = gTerrainStampProgram;
+ shader.bind();
+
+ // First, apply the paint map as the background
+ {
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3((F32)tex.getWidth(), (F32)tex.getHeight(), 1.0f));
+ model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ shader.bindTexture(LLShaderMgr::DIFFUSE_MAP, &tex);
+ // We care about the whole paintmap, which is already a power of two.
+ // Hence, TERRAIN_STAMP_SCALE = (1.0,1.0)
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, 1.0f, 1.0f);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ LLTerrainPaintQueue queue_out(LLTerrainPaint::RGB);
+
+ // Incrementally apply each RGBA paint to the render target, then extract
+ // the result back into memory as an RGB paint.
+ // Put each result in queue_out.
+ const std::vector<LLTerrainPaint::ptr_t>& queue_in_list = queue_in.get();
+ for (size_t i = 0; i < queue_in_list.size(); ++i)
+ {
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for paint operations (this
+ // could change in the future).
+ queue_in.convertBitDepths(i, 8);
+ const LLTerrainPaint::ptr_t& paint_in = queue_in_list[i];
+
+ // Modelview matrix for the current paint
+ // View matrix is already computed. Just need the model matrix.
+ // Orthographic projection matrix is already updated
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3(paint_in->mWidthX, paint_in->mWidthY, 1.0f));
+ model = glm::translate(model, glm::vec3(paint_in->mStartX, paint_in->mStartY, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ // Generate temporary stamp texture from paint contents.
+ // Our stamp image needs to be a power of two.
+ // Because the paint data may not cover a whole power-of-two region,
+ // allocate a bigger 2x2 image if needed, but set the image data later
+ // for a subset of the image.
+ // Pixel data outside this subset is left undefined. We will use
+ // TERRAIN_STAMP_SCALE in the stamp shader to define the subset of the
+ // image we care about.
+ const U32 width_rounded = 1 << U32(ceil(log2(F32(paint_in->mWidthX))));
+ const U32 height_rounded = 1 << U32(ceil(log2(F32(paint_in->mWidthY))));
+ LLPointer<LLImageGL> stamp_image;
+ {
+ // Create image object (dimensions not yet initialized in GL)
+ U32 stamp_tex_name;
+ LLImageGL::generateTextures(1, &stamp_tex_name);
+ const U32 components = paint_in->mComponents;
+ constexpr LLGLenum target = GL_TEXTURE_2D;
+ const LLGLenum internal_format = paint_in->mComponents == 4 ? GL_RGBA8 : GL_RGB8;
+ const LLGLenum format = paint_in->mComponents == 4 ? GL_RGBA : GL_RGB;
+ constexpr LLGLenum type = GL_UNSIGNED_BYTE;
+ stamp_image = new LLImageGL(stamp_tex_name, components, target, internal_format, format, type, LLTexUnit::TAM_WRAP);
+ // Nearest-neighbor filtering to reduce surprises
+ stamp_image->setFilteringOption(LLTexUnit::TFO_POINT);
+
+ // Initialize the image dimensions in GL
+ constexpr U8* undefined_data_for_now = nullptr;
+ gGL.getTexUnit(0)->bind(stamp_image, false, true);
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width_rounded, height_rounded, 0, format, type, undefined_data_for_now);
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+ stamp_image->setSize(width_rounded, height_rounded, components);
+ stamp_image->setDiscardLevel(0);
+
+ // Manually set a subset of the image in GL
+ const U8* data = paint_in->mData.data();
+ const S32 data_width = paint_in->mWidthX;
+ const S32 data_height = paint_in->mWidthY;
+ constexpr S32 origin = 0;
+ // width = data_width; height = data_height. i.e.: Copy the full
+ // contents of data into the image.
+ stamp_image->setSubImage(data, data_width, data_height, origin, origin, /*width=*/data_width, /*height=*/data_height);
+ }
+
+ // Apply ("stamp") the paint to the render target
+ {
+ shader.bindTextureImageGL(LLShaderMgr::DIFFUSE_MAP, stamp_image);
+ const F32 width_fraction = F32(paint_in->mWidthX) / F32(width_rounded);
+ const F32 height_fraction = F32(paint_in->mWidthY) / F32(height_rounded);
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, width_fraction, height_fraction);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ // Extract the result back into memory as an RGB paint
+ LLTerrainPaint::ptr_t paint_out = std::make_shared<LLTerrainPaint>();
+ {
+ paint_out->mStartX = paint_in->mStartX;
+ paint_out->mStartY = paint_in->mStartY;
+ paint_out->mWidthX = paint_in->mWidthX;
+ paint_out->mWidthY = paint_in->mWidthY;
+ paint_out->mBitDepth = 8; // Will be reduced to TerrainPaintBitDepth bits later
+ paint_out->mComponents = LLTerrainPaint::RGB;
+#ifdef SHOW_ASSERT
+ paint_out->assert_confined_to(tex);
+#endif
+ paint_out->confine_to(tex);
+ paint_out->mData.resize(paint_out->mComponents * paint_out->mWidthX * paint_out->mWidthY);
+ constexpr GLint miplevel = 0;
+ const S32 x_offset = paint_out->mStartX;
+ const S32 y_offset = paint_out->mStartY;
+ const S32 width = paint_out->mWidthX;
+ const S32 height = paint_out->mWidthY;
+ constexpr GLenum pixformat = GL_RGB;
+ constexpr GLenum pixtype = GL_UNSIGNED_BYTE;
+ llassert(paint_out->mData.size() <= std::numeric_limits<GLsizei>::max());
+ const GLsizei buf_size = (GLsizei)paint_out->mData.size();
+ U8* pixels = paint_out->mData.data();
+ glReadPixels(x_offset, y_offset, width, height, pixformat, pixtype, pixels);
+ }
+
+ // Enqueue the result to the new paint queue, with bit depths per color
+ // channel reduced from 8 to 5, and reduced from RGBA (paintmap
+ // sub-rectangle update with alpha mask) to RGB (paintmap sub-rectangle
+ // update without alpha mask). This format is suitable for sending
+ // over the network.
+ // *TODO: At some point, queue_out will pass through a network
+ // round-trip which will reduce the bit depth, making the
+ // pre-conversion step not necessary.
+ queue_out.enqueue(paint_out);
+ LLCachedControl<U32> bit_depth(gSavedSettings, "TerrainPaintBitDepth");
+ queue_out.convertBitDepths(queue_out.size()-1, bit_depth);
+ }
+
+ queue_in.clear();
+
+ scratch_target.flush();
+
+ LLGLSLShader::unbind();
+
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.popMatrix();
+
+ return queue_out;
+}
+
+// static
+LLTerrainPaintQueue LLTerrainPaintMap::convertBrushQueueToPaintRGB(const LLViewerRegion& region, LLViewerTexture& tex, LLTerrainBrushQueue& queue_in)
+{
+ check_tex(tex);
+
+ // TODO: Avoid allocating a scratch render buffer and use mAuxillaryRT instead
+ // TODO: even if it means performing extra render operations to apply the brushes, in rare cases where the paints can't all fit within an area that can be represented by the buffer
+ LLRenderTarget scratch_target;
+ const S32 max_dim = llmax(tex.getWidth(), tex.getHeight());
+ scratch_target.allocate(max_dim, max_dim, GL_RGB, false, LLTexUnit::eTextureType::TT_TEXTURE,
+ LLTexUnit::eTextureMipGeneration::TMG_NONE);
+ if (!scratch_target.isComplete())
+ {
+ llassert(false);
+ LL_WARNS() << "Failed to allocate render target" << LL_ENDL;
+ return false;
+ }
+ gGL.getTexUnit(0)->disable();
+ stop_glerror();
+
+ scratch_target.bindTarget();
+ glClearColor(0, 0, 0, 0);
+ scratch_target.clear();
+ const F32 target_half_width = (F32)scratch_target.getWidth() / 2.0f;
+ const F32 target_half_height = (F32)scratch_target.getHeight() / 2.0f;
+
+ LLVertexBuffer* buf = get_paint_triangle_buffer();
+
+ // Update projection matrix and viewport
+ // *NOTE: gl_state_for_2d also sets the modelview matrix. This will be overridden later.
+ {
+ stop_glerror();
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.pushMatrix();
+ gGL.loadIdentity();
+ gGL.ortho(-target_half_width, target_half_width, -target_half_height, target_half_height, 0.25f, 1.0f);
+ stop_glerror();
+ const LLRect texture_rect(0, scratch_target.getHeight(), scratch_target.getWidth(), 0);
+ glViewport(texture_rect.mLeft, texture_rect.mBottom, texture_rect.getWidth(), texture_rect.getHeight());
+ }
+
+ // View matrix
+ // Coordinates should be in pixels. 1.0f = 1 pixel on the framebuffer.
+ // Camera is centered in the middle of the framebuffer.
+ glm::mat4 view(glm::make_mat4((GLfloat*)OGL_TO_CFR_ROTATION));
+ {
+ LLViewerCamera camera;
+ const LLVector3 camera_origin(target_half_width, target_half_height, 0.5f);
+ const LLVector3 camera_look_down(target_half_width, target_half_height, 0.0f);
+ camera.lookAt(camera_origin, camera_look_down, LLVector3::y_axis);
+ camera.setAspect(F32(scratch_target.getHeight()) / F32(scratch_target.getWidth()));
+ GLfloat ogl_matrix[16];
+ camera.getOpenGLTransform(ogl_matrix);
+ view *= glm::make_mat4(ogl_matrix);
+ }
+
+ LLGLDisable stencil(GL_STENCIL_TEST);
+ LLGLDisable scissor(GL_SCISSOR_TEST);
+ LLGLEnable cull_face(GL_CULL_FACE);
+ LLGLDepthTest depth_test(GL_FALSE, GL_FALSE, GL_ALWAYS);
+ LLGLEnable blend(GL_BLEND);
+ gGL.setSceneBlendType(LLRender::BT_ALPHA);
+
+ LLGLSLShader& shader = gTerrainStampProgram;
+ shader.bind();
+
+ // First, apply the paint map as the background
+ {
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3((F32)tex.getWidth(), (F32)tex.getHeight(), 1.0f));
+ model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ shader.bindTexture(LLShaderMgr::DIFFUSE_MAP, &tex);
+ // We care about the whole paintmap, which is already a power of two.
+ // Hence, TERRAIN_STAMP_SCALE = (1.0,1.0)
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, 1.0f, 1.0f);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ LLTerrainPaintQueue queue_out(LLTerrainPaint::RGB);
+
+ // Incrementally apply each brush stroke to the render target, then extract
+ // the result back into memory as an RGB paint.
+ // Put each result in queue_out.
+ const std::vector<LLTerrainBrush::ptr_t>& brush_list = queue_in.get();
+ for (size_t i = 0; i < brush_list.size(); ++i)
+ {
+ const LLTerrainBrush::ptr_t& brush_in = brush_list[i];
+
+ // Modelview matrix for the current brush
+ // View matrix is already computed. Just need the model matrix.
+ // Orthographic projection matrix is already updated
+ // *NOTE: Brush path information is in region space. It will need to be
+ // converted to paintmap pixel space before it makes sense.
+ F32 brush_width_x;
+ F32 brush_width_y;
+ F32 brush_start_x;
+ F32 brush_start_y;
+ {
+ F32 min_x = brush_in->mPath[0].mV[VX];
+ F32 max_x = min_x;
+ F32 min_y = brush_in->mPath[0].mV[VY];
+ F32 max_y = min_y;
+ for (size_t i = 1; i < brush_in->mPath.size(); ++i)
+ {
+ const F32 x = brush_in->mPath[i].mV[VX];
+ const F32 y = brush_in->mPath[i].mV[VY];
+ min_x = llmin(min_x, x);
+ max_x = llmax(max_x, x);
+ min_y = llmin(min_y, y);
+ max_y = llmax(max_y, y);
+ }
+ brush_width_x = brush_in->mBrushSize + (max_x - min_x);
+ brush_width_y = brush_in->mBrushSize + (max_y - min_y);
+ brush_start_x = min_x - (brush_in->mBrushSize / 2.0f);
+ brush_start_y = min_y - (brush_in->mBrushSize / 2.0f);
+ // Convert brush path information to paintmap pixel space from region
+ // space.
+ brush_width_x *= tex.getWidth() / region.getWidth();
+ brush_width_y *= tex.getHeight() / region.getWidth();
+ brush_start_x *= tex.getWidth() / region.getWidth();
+ brush_start_y *= tex.getHeight() / region.getWidth();
+ }
+ glm::mat4 model;
+ {
+ model = glm::scale(model, glm::vec3(brush_width_x, brush_width_y, 1.0f));
+ model = glm::translate(model, glm::vec3(brush_start_x, brush_start_y, 0.0f));
+ }
+ glm::mat4 modelview = view * model;
+ gGL.matrixMode(LLRender::MM_MODELVIEW);
+ gGL.loadMatrix(glm::value_ptr(modelview));
+
+ // Apply the "brush" to the render target
+ {
+ // TODO: Use different shader for this - currently this is using the stamp shader. The white image is just a placeholder for now
+ shader.bindTexture(LLShaderMgr::DIFFUSE_MAP, LLViewerFetchedTexture::sWhiteImagep);
+ shader.uniform2f(LLShaderMgr::TERRAIN_STAMP_SCALE, 1.0f, 1.0f);
+ buf->setBuffer();
+ buf->draw(LLRender::TRIANGLES, buf->getIndicesSize(), 0);
+ }
+
+ // Extract the result back into memory as an RGB paint
+ LLTerrainPaint::ptr_t paint_out = std::make_shared<LLTerrainPaint>();
+ {
+ paint_out->mStartX = U16(floor(brush_start_x));
+ paint_out->mStartY = U16(floor(brush_start_y));
+ const F32 dX = brush_start_x - F32(paint_out->mStartX);
+ const F32 dY = brush_start_y - F32(paint_out->mStartY);
+ paint_out->mWidthX = U16(ceil(brush_width_x + dX));
+ paint_out->mWidthY = U16(ceil(brush_width_y + dY));
+ paint_out->mBitDepth = 8; // Will be reduced to TerrainPaintBitDepth bits later
+ paint_out->mComponents = LLTerrainPaint::RGB;
+ // The brush strokes are expected to sometimes partially venture
+ // outside of the paintmap bounds.
+ paint_out->confine_to(tex);
+ paint_out->mData.resize(paint_out->mComponents * paint_out->mWidthX * paint_out->mWidthY);
+ constexpr GLint miplevel = 0;
+ const S32 x_offset = paint_out->mStartX;
+ const S32 y_offset = paint_out->mStartY;
+ const S32 width = paint_out->mWidthX;
+ const S32 height = paint_out->mWidthY;
+ constexpr GLenum pixformat = GL_RGB;
+ constexpr GLenum pixtype = GL_UNSIGNED_BYTE;
+ llassert(paint_out->mData.size() <= std::numeric_limits<GLsizei>::max());
+ const GLsizei buf_size = (GLsizei)paint_out->mData.size();
+ U8* pixels = paint_out->mData.data();
+ glReadPixels(x_offset, y_offset, width, height, pixformat, pixtype, pixels);
+ }
+
+ // Enqueue the result to the new paint queue, with bit depths per color
+ // channel reduced from 8 to 5, and reduced from RGBA (paintmap
+ // sub-rectangle update with alpha mask) to RGB (paintmap sub-rectangle
+ // update without alpha mask). This format is suitable for sending
+ // over the network.
+ // *TODO: At some point, queue_out will pass through a network
+ // round-trip which will reduce the bit depth, making the
+ // pre-conversion step not necessary.
+ queue_out.enqueue(paint_out);
+ LLCachedControl<U32> bit_depth(gSavedSettings, "TerrainPaintBitDepth");
+ queue_out.convertBitDepths(queue_out.size()-1, bit_depth);
+ }
+
+ queue_in.clear();
+
+ scratch_target.flush();
+
+ LLGLSLShader::unbind();
+
+ gGL.matrixMode(LLRender::MM_PROJECTION);
+ gGL.popMatrix();
+
+ return queue_out;
+}
+
+template<typename T>
+LLTerrainQueue<T>::LLTerrainQueue(LLTerrainQueue<T>& other)
+{
+ *this = other;
+}
+
+template<typename T>
+LLTerrainQueue<T>& LLTerrainQueue<T>::operator=(LLTerrainQueue<T>& other)
+{
+ mList = other.mList;
+ return *this;
+}
+
+template<typename T>
+bool LLTerrainQueue<T>::enqueue(std::shared_ptr<T>& t, bool dry_run)
+{
+ if (!dry_run) { mList.push_back(t); }
+ return true;
+}
+
+template<typename T>
+bool LLTerrainQueue<T>::enqueue(std::vector<std::shared_ptr<T>>& list)
+{
+ constexpr bool dry_run = true;
+ for (auto& t : list)
+ {
+ if (!enqueue(t, dry_run)) { return false; }
+ }
+ for (auto& t : list)
+ {
+ enqueue(t);
+ }
+ return true;
+}
+
+template<typename T>
+size_t LLTerrainQueue<T>::size() const
+{
+ return mList.size();
+}
+
+template<typename T>
+bool LLTerrainQueue<T>::empty() const
+{
+ return mList.empty();
+}
+
+template<typename T>
+void LLTerrainQueue<T>::clear()
+{
+ mList.clear();
+}
+
+void LLTerrainPaint::assert_confined_to(const LLTexture& tex) const
+{
+ llassert(mStartX >= 0 && mStartX < tex.getWidth());
+ llassert(mStartY >= 0 && mStartY < tex.getHeight());
+ llassert(mWidthX <= tex.getWidth() - mStartX);
+ llassert(mWidthY <= tex.getHeight() - mStartY);
+}
+
+void LLTerrainPaint::confine_to(const LLTexture& tex)
+{
+ mStartX = llmax(mStartX, 0);
+ mStartY = llmax(mStartY, 0);
+ mWidthX = llmin(mWidthX, tex.getWidth() - mStartX);
+ mWidthY = llmin(mWidthY, tex.getHeight() - mStartY);
+ assert_confined_to(tex);
+}
+
+LLTerrainPaintQueue::LLTerrainPaintQueue(U8 components)
+: mComponents(components)
+{
+ llassert(mComponents == LLTerrainPaint::RGB || mComponents == LLTerrainPaint::RGBA);
+}
+
+LLTerrainPaintQueue::LLTerrainPaintQueue(LLTerrainPaintQueue& other)
+: LLTerrainQueue<LLTerrainPaint>(other)
+, mComponents(other.mComponents)
+{
+ llassert(mComponents == LLTerrainPaint::RGB || mComponents == LLTerrainPaint::RGBA);
+}
+
+LLTerrainPaintQueue& LLTerrainPaintQueue::operator=(LLTerrainPaintQueue& other)
+{
+ LLTerrainQueue<LLTerrainPaint>::operator=(other);
+ mComponents = other.mComponents;
+ return *this;
+}
+
+bool LLTerrainPaintQueue::enqueue(LLTerrainPaint::ptr_t& paint, bool dry_run)
+{
+ llassert(paint);
+ if (!paint) { return false; }
+
+ // The paint struct should be pre-validated before this code is reached.
+ llassert(!paint->mData.empty());
+ // The internal paint map image is currently 8 bits, so that's the maximum
+ // allowed bit depth.
+ llassert(paint->mBitDepth > 0 && paint->mBitDepth <= 8);
+ llassert(paint->mData.size() == (mComponents * paint->mWidthX * paint->mWidthY));
+ llassert(paint->mWidthX > 0);
+ llassert(paint->mWidthY > 0);
+#ifdef SHOW_ASSERT
+ static LLCachedControl<U32> max_texture_width(gSavedSettings, "RenderMaxTextureResolution", 2048);
+#endif
+ llassert(paint->mWidthX <= max_texture_width);
+ llassert(paint->mWidthY <= max_texture_width);
+ llassert(paint->mStartX < max_texture_width);
+ llassert(paint->mStartY < max_texture_width);
+
+ return LLTerrainQueue<LLTerrainPaint>::enqueue(paint, dry_run);
+}
+
+bool LLTerrainPaintQueue::enqueue(LLTerrainPaintQueue& queue)
+{
+ return LLTerrainQueue<LLTerrainPaint>::enqueue(queue.mList);
+}
+
+void LLTerrainPaintQueue::convertBitDepths(size_t index, U8 target_bit_depth)
+{
+ llassert(target_bit_depth > 0 && target_bit_depth <= 8);
+ llassert(index < mList.size());
+
+ LLTerrainPaint::ptr_t& paint = mList[index];
+ if (paint->mBitDepth == target_bit_depth) { return; }
+
+ const F32 old_bit_max = F32((1 << paint->mBitDepth) - 1);
+ const F32 new_bit_max = F32((1 << target_bit_depth) - 1);
+ const F32 bit_conversion_factor = new_bit_max / old_bit_max;
+
+ for (U8& color : paint->mData)
+ {
+ color = (U8)llround(F32(color) * bit_conversion_factor);
+ }
+
+ paint->mBitDepth = target_bit_depth;
+}
+
+LLTerrainBrushQueue::LLTerrainBrushQueue()
+: LLTerrainQueue<LLTerrainBrush>()
+{
+}
+
+LLTerrainBrushQueue::LLTerrainBrushQueue(LLTerrainBrushQueue& other)
+: LLTerrainQueue<LLTerrainBrush>(other)
+{
+}
+
+LLTerrainBrushQueue& LLTerrainBrushQueue::operator=(LLTerrainBrushQueue& other)
+{
+ LLTerrainQueue<LLTerrainBrush>::operator=(other);
+ return *this;
+}
+
+bool LLTerrainBrushQueue::enqueue(LLTerrainBrush::ptr_t& brush, bool dry_run)
+{
+ llassert(brush->mBrushSize > 0);
+ llassert(!brush->mPath.empty());
+ llassert(brush->mPathOffset < brush->mPath.size());
+ llassert(brush->mPathOffset < 2); // Harmless, but doesn't do anything useful, so might be a sign of implementation error
+ return LLTerrainQueue<LLTerrainBrush>::enqueue(brush, dry_run);
+}
+
+bool LLTerrainBrushQueue::enqueue(LLTerrainBrushQueue& queue)
+{
+ return LLTerrainQueue<LLTerrainBrush>::enqueue(queue.mList);
+}
+
+template class LLTerrainQueue<LLTerrainPaint>;
+template class LLTerrainQueue<LLTerrainBrush>;
diff --git a/indra/newview/llterrainpaintmap.h b/indra/newview/llterrainpaintmap.h
index 66827862c5..d4859b747b 100644
--- a/indra/newview/llterrainpaintmap.h
+++ b/indra/newview/llterrainpaintmap.h
@@ -26,9 +26,20 @@
#pragma once
+#include <memory>
+#include <vector>
+
+#include "stdtypes.h"
+#include "v2math.h"
+
+class LLTexture;
class LLViewerRegion;
class LLViewerTexture;
+class LLTerrainPaintQueue;
+class LLTerrainBrushQueue;
+// TODO: Terrain painting across regions. Assuming painting is confined to one
+// region for now.
class LLTerrainPaintMap
{
public:
@@ -39,4 +50,121 @@ public:
// to type TERRAIN_PAINT_TYPE_PBR_PAINTMAP.
// Returns true if successful
static bool bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, LLViewerTexture& tex);
+
+ // This operation clears the queue
+ // TODO: Decide if clearing the queue is needed - seems inconsistent
+ static void applyPaintQueueRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue);
+
+ static LLTerrainPaintQueue convertPaintQueueRGBAToRGB(LLViewerTexture& tex, LLTerrainPaintQueue& queue_in);
+ // TODO: Implement (it's similar to convertPaintQueueRGBAToRGB but different shader + need to calculate the dimensions + need a different vertex buffer for each brush stroke)
+ static LLTerrainPaintQueue convertBrushQueueToPaintRGB(const LLViewerRegion& region, LLViewerTexture& tex, LLTerrainBrushQueue& queue_in);
+};
+
+template<typename T>
+class LLTerrainQueue
+{
+public:
+ LLTerrainQueue() = default;
+ LLTerrainQueue(LLTerrainQueue<T>& other);
+ LLTerrainQueue& operator=(LLTerrainQueue<T>& other);
+
+ bool enqueue(std::shared_ptr<T>& t, bool dry_run = false);
+ size_t size() const;
+ bool empty() const;
+ void clear();
+
+ const std::vector<std::shared_ptr<T>>& get() const { return mList; }
+
+protected:
+ bool enqueue(std::vector<std::shared_ptr<T>>& list);
+
+ std::vector<std::shared_ptr<T>> mList;
+};
+
+// Enqueued paint operations, in texture coordinates.
+// mData is always RGB or RGBA (determined by mComponents), with each U8
+// storing one color with a max value of (1 >> mBitDepth) - 1
+struct LLTerrainPaint
+{
+ using ptr_t = std::shared_ptr<LLTerrainPaint>;
+
+ U16 mStartX;
+ U16 mStartY;
+ U16 mWidthX;
+ U16 mWidthY;
+ U8 mBitDepth;
+ U8 mComponents;
+ static constexpr U8 RGB = 3;
+ static constexpr U8 RGBA = 4;
+ std::vector<U8> mData;
+
+ // Asserts that this paint's start/width fit within the bounds of the
+ // provided texture dimensions.
+ void assert_confined_to(const LLTexture& tex) const;
+ // Confines this paint's start/width so it fits within the bounds of the
+ // provided texture dimensions.
+ // Does not allocate mData.
+ void confine_to(const LLTexture& tex);
+};
+
+class LLTerrainPaintQueue : public LLTerrainQueue<LLTerrainPaint>
+{
+public:
+ LLTerrainPaintQueue() = delete;
+ // components determines what type of LLTerrainPaint is allowed. Must be 3 (RGB) or 4 (RGBA)
+ LLTerrainPaintQueue(U8 components);
+ LLTerrainPaintQueue(LLTerrainPaintQueue& other);
+ LLTerrainPaintQueue& operator=(LLTerrainPaintQueue& other);
+
+ bool enqueue(LLTerrainPaint::ptr_t& paint, bool dry_run = false);
+ bool enqueue(LLTerrainPaintQueue& queue);
+
+ U8 getComponents() const { return mComponents; }
+ // Convert mBitDepth for the LLTerrainPaint in the queue at index
+ // If mBitDepth is already equal to target_bit_depth, no conversion takes
+ // place.
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for the paintmap (this could
+ // change in the future).
+ void convertBitDepths(size_t index, U8 target_bit_depth);
+
+private:
+ U8 mComponents;
+};
+
+struct LLTerrainBrush
+{
+ using ptr_t = std::shared_ptr<LLTerrainBrush>;
+
+ // Width of the brush in region space. The brush is a square texture with
+ // alpha.
+ F32 mBrushSize;
+ // Brush path points in region space, excluding the vertical axis, which
+ // does not contribute to the paint map.
+ std::vector<LLVector2> mPath;
+ // Offset of the brush path to actually start drawing at. An offset of 0
+ // indicates that a brush stroke has just started (i.e. the user just
+ // pressed down the mouse button). An offset greater than 0 indicates the
+ // continuation of a brush stroke. Skipped entries in mPath are not drawn
+ // directly, but are used for stroke orientation and path interpolation.
+ // TODO: For the initial implementation, mPathOffset will be 0 and mPath
+ // will be of length of at most 1, leading to discontinuous paint paths.
+ // Then, mPathOffset may be 1 or 0, 1 indicating the continuation of a
+ // stroke with linear interpolation. It is unlikely that we will implement
+ // anything more sophisticated than that for now.
+ U8 mPathOffset;
+ // Indicates if this is the end of the brush stroke. Can occur if the mouse
+ // button is lifted, or if the mouse temporarily stops while held down.
+ bool mPathEnd;
+};
+
+class LLTerrainBrushQueue : public LLTerrainQueue<LLTerrainBrush>
+{
+public:
+ LLTerrainBrushQueue();
+ LLTerrainBrushQueue(LLTerrainBrushQueue& other);
+ LLTerrainBrushQueue& operator=(LLTerrainBrushQueue& other);
+
+ bool enqueue(LLTerrainBrush::ptr_t& brush, bool dry_run = false);
+ bool enqueue(LLTerrainBrushQueue& queue);
};
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index f8a315d4d8..184c0e7d8b 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -911,6 +911,7 @@ void settings_setup_listeners()
setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorByDistEnabled", handleUserImpostorByDistEnabledChanged);
setting_setup_signal_listener(gSavedSettings, "TuningFPSStrategy", handleFPSTuningStrategyChanged);
{
+ setting_setup_signal_listener(gSavedSettings, "LocalTerrainPaintEnabled", handleSetShaderChanged);
setting_setup_signal_listener(gSavedSettings, "LocalTerrainPaintEnabled", handleLocalTerrainChanged);
const char* transform_suffixes[] = {
"ScaleU",
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 120a3094cd..39213569a5 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -117,6 +117,7 @@
#include "lltoolmgr.h"
#include "lltoolpie.h"
#include "lltoolselectland.h"
+#include "llterrainpaintmap.h"
#include "lltrans.h"
#include "llviewerdisplay.h" //for gWindowResized
#include "llviewergenericmessage.h"
@@ -1429,16 +1430,21 @@ class LLAdvancedTerrainCreateLocalPaintMap : public view_listener_t
return false;
}
+ // This calls gLocalTerrainMaterials.setPaintType
+ // It also ensures the terrain bake shader is compiled (handleSetShaderChanged), so call this first
+ // *TODO: Fix compile errors in shader so it can be used for all platforms. Then we can unhide the shader from behind this setting and remove the hook to handleSetShaderChanged. This advanced setting is intended to be used as a local setting for testing terrain, not a feature flag, but it is currently used like a feature flag as a temporary hack.
+ // *TODO: Ideally we would call setPaintType *after* the paint map is well-defined. The terrain draw pool should be able to handle an undefined paint map in the meantime.
+ gSavedSettings.setBOOL("LocalTerrainPaintEnabled", true);
+
U16 dim = (U16)gSavedSettings.getU32("TerrainPaintResolution");
// Ensure a reasonable image size of power two
const U32 max_resolution = gSavedSettings.getU32("RenderMaxTextureResolution");
dim = llclamp(dim, 16, max_resolution);
dim = 1 << U32(std::ceil(std::log2(dim)));
+ // TODO: Could probably get away with not using image raw here, now that we aren't writing bits via the CPU (see load_exr for example)
LLPointer<LLImageRaw> image_raw = new LLImageRaw(dim,dim,3);
LLPointer<LLViewerTexture> tex = LLViewerTextureManager::getLocalTexture(image_raw.get(), true);
const bool success = LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(*region, *tex);
- // This calls gLocalTerrainMaterials.setPaintType
- gSavedSettings.setBOOL("LocalTerrainPaintEnabled", true);
// If baking the paintmap failed, set the paintmap to nullptr. This
// causes LLDrawPoolTerrain to use a blank paintmap instead.
if (!success) { tex = nullptr; }
@@ -1448,6 +1454,92 @@ class LLAdvancedTerrainCreateLocalPaintMap : public view_listener_t
}
};
+class LLAdvancedTerrainEditLocalPaintMap : public view_listener_t
+{
+ bool handleEvent(const LLSD& userdata)
+ {
+ LLViewerTexture* tex = gLocalTerrainMaterials.getPaintMap();
+ if (!tex)
+ {
+ LL_WARNS() << "No local paint map available to edit" << LL_ENDL;
+ return false;
+ }
+
+ LLTerrainBrushQueue& brush_queue = gLocalTerrainMaterials.getBrushQueue();
+ LLTerrainPaintQueue& paint_request_queue = gLocalTerrainMaterials.getPaintRequestQueue();
+
+ const LLViewerRegion* region = gAgent.getRegion();
+ if (!region)
+ {
+ LL_WARNS() << "No current region for calculating paint operations" << LL_ENDL;
+ return false;
+ }
+ // TODO: Create the brush
+ // Just a dab for now
+ LLTerrainBrush::ptr_t brush = std::make_shared<LLTerrainBrush>();
+ brush->mBrushSize = 16.0f;
+ brush->mPath.emplace_back(17.0f, 17.0f);
+ brush->mPathOffset = 0;
+ brush->mPathEnd = true;
+ brush_queue.enqueue(brush);
+ LLTerrainPaintQueue brush_as_paint_queue = LLTerrainPaintMap::convertBrushQueueToPaintRGB(*region, *tex, brush_queue);
+ //paint_send_queue.enqueue(brush_as_paint_queue); // TODO: What was this line for? (it might also be a leftover line from an unfinished edit)
+
+ // TODO: Keep this around for later testing (i.e. when reducing framebuffer size and the offsets that requires)
+#if 0
+ // Enqueue a paint
+ // Modifies a subsection of the region paintmap with the material in
+ // the last slot.
+ // It is currently the responsibility of the paint queue to convert
+ // incoming bits to the right bit depth for the paintmap (this could
+ // change in the future).
+ LLTerrainPaint::ptr_t paint = std::make_shared<LLTerrainPaint>();
+ const U16 width = 33;
+ paint->mStartX = 1;
+ paint->mStartY = 1;
+ paint->mWidthX = width;
+ paint->mWidthY = width;
+ constexpr U8 bit_depth = 5;
+ paint->mBitDepth = bit_depth;
+ constexpr U8 max_value = (1 << bit_depth) - 1;
+ const size_t pixel_count = width * width;
+ const U8 components = LLTerrainPaint::RGBA;
+ paint->mComponents = components;
+ paint->mData.resize(components * pixel_count);
+ for (size_t h = 0; h < paint->mWidthY; ++h)
+ {
+ for (size_t w = 0; w < paint->mWidthX; ++w)
+ {
+ const size_t pixel = (h * paint->mWidthX) + w;
+ // Solid blue color
+ paint->mData[(components*pixel) + components - 2] = max_value; // blue
+ //// Alpha grid: 1.0 if odd for either dimension, 0.0 otherwise
+ //const U8 alpha = U8(F32(max_value) * F32(bool(w % 2) || bool(h % 2)));
+ //paint->mData[(components*pixel) + components - 1] = alpha; // alpha
+ // Alpha "frame"
+ const bool edge = w == 0 || h == 0 || w == (paint->mWidthX - 1) || h == (paint->mWidthY - 1);
+ const bool near_edge_frill = ((w == 1 || w == (paint->mWidthX - 2)) && (h % 2 == 0)) ||
+ ((h == 1 || h == (paint->mWidthY - 2)) && (w % 2 == 0));
+ const U8 alpha = U8(F32(max_value) * F32(edge || near_edge_frill));
+ paint->mData[(components*pixel) + components - 1] = alpha; // alpha
+ }
+ }
+ paint_request_queue.enqueue(paint);
+#endif
+
+ // Apply the paint queues ad-hoc right here for now.
+ // *TODO: Eventually the paint queue(s) should be applied at a
+ // predictable time in the viewer frame loop.
+ // TODO: In hindsight... maybe we *should* bind the paintmap to the render buffer. That makes a lot more sense, and we wouldn't have to reduce its resolution by settling for the bake buffer. If we do that, make a comment above convertPaintQueueRGBAToRGB that the texture is modified!
+ LLTerrainPaintQueue paint_send_queue = LLTerrainPaintMap::convertPaintQueueRGBAToRGB(*tex, paint_request_queue);
+ LLTerrainPaintQueue& paint_map_queue = gLocalTerrainMaterials.getPaintMapQueue();
+ paint_map_queue.enqueue(paint_send_queue);
+ LLTerrainPaintMap::applyPaintQueueRGB(*tex, paint_map_queue);
+
+ return true;
+ }
+};
+
class LLAdvancedTerrainDeleteLocalPaintMap : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
@@ -9811,6 +9903,7 @@ void initialize_menus()
// Develop > Terrain
view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain");
view_listener_t::addMenu(new LLAdvancedTerrainCreateLocalPaintMap(), "Advanced.TerrainCreateLocalPaintMap");
+ view_listener_t::addMenu(new LLAdvancedTerrainEditLocalPaintMap(), "Advanced.TerrainEditLocalPaintMap");
view_listener_t::addMenu(new LLAdvancedTerrainDeleteLocalPaintMap(), "Advanced.TerrainDeleteLocalPaintMap");
// Advanced > UI
diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp
index a8fe221d98..a6d397c039 100644
--- a/indra/newview/llviewershadermgr.cpp
+++ b/indra/newview/llviewershadermgr.cpp
@@ -100,6 +100,7 @@ LLGLSLShader gReflectionProbeDisplayProgram;
LLGLSLShader gCopyProgram;
LLGLSLShader gCopyDepthProgram;
LLGLSLShader gPBRTerrainBakeProgram;
+LLGLSLShader gTerrainStampProgram;
//object shaders
LLGLSLShader gObjectPreviewProgram;
@@ -3170,6 +3171,34 @@ bool LLViewerShaderMgr::loadShadersInterface()
}
}
+ if (gSavedSettings.getBOOL("LocalTerrainPaintEnabled"))
+ {
+ if (success)
+ {
+ LLGLSLShader* shader = &gTerrainStampProgram;
+ U32 bit_depth = gSavedSettings.getU32("TerrainPaintBitDepth");
+ // LLTerrainPaintMap currently uses an RGB8 texture internally
+ bit_depth = llclamp(bit_depth, 1, 8);
+ shader->mName = llformat("Terrain Stamp Shader RGB%o", bit_depth);
+
+ shader->mShaderFiles.clear();
+ shader->mShaderFiles.push_back(make_pair("interface/terrainStampV.glsl", GL_VERTEX_SHADER));
+ shader->mShaderFiles.push_back(make_pair("interface/terrainStampF.glsl", GL_FRAGMENT_SHADER));
+ shader->mShaderLevel = mShaderLevel[SHADER_INTERFACE];
+ const U32 value_range = (1 << bit_depth) - 1;
+ shader->addPermutation("TERRAIN_PAINT_PRECISION", llformat("%d", value_range));
+ success = success && shader->createShader();
+ //llassert(success);
+ if (!success)
+ {
+ LL_WARNS() << "Failed to create shader '" << shader->mName << "', disabling!" << LL_ENDL;
+ gSavedSettings.setBOOL("RenderCanUseTerrainBakeShaders", false);
+ // continue as if this shader never happened
+ success = true;
+ }
+ }
+ }
+
if (success)
{
gAlphaMaskProgram.mName = "Alpha Mask Shader";
diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h
index b08796025a..e654967c46 100644
--- a/indra/newview/llviewershadermgr.h
+++ b/indra/newview/llviewershadermgr.h
@@ -175,6 +175,7 @@ extern LLGLSLShader gReflectionProbeDisplayProgram;
extern LLGLSLShader gCopyProgram;
extern LLGLSLShader gCopyDepthProgram;
extern LLGLSLShader gPBRTerrainBakeProgram;
+extern LLGLSLShader gTerrainStampProgram;
//output tex0[tc0] - tex1[tc1]
extern LLGLSLShader gTwoTextureCompareProgram;
diff --git a/indra/newview/llvlcomposition.cpp b/indra/newview/llvlcomposition.cpp
index 077e6e6cb1..ca76d93cd7 100644
--- a/indra/newview/llvlcomposition.cpp
+++ b/indra/newview/llvlcomposition.cpp
@@ -320,7 +320,11 @@ LLViewerTexture* LLTerrainMaterials::getPaintMap()
void LLTerrainMaterials::setPaintMap(LLViewerTexture* paint_map)
{
llassert(!paint_map || mPaintType == TERRAIN_PAINT_TYPE_PBR_PAINTMAP);
+ const bool changed = paint_map != mPaintMap;
mPaintMap = paint_map;
+ // The paint map has changed, so edits are no longer valid
+ mPaintRequestQueue.clear();
+ mPaintMapQueue.clear();
}
// Boost the texture loading priority
diff --git a/indra/newview/llvlcomposition.h b/indra/newview/llvlcomposition.h
index f15f9bff6a..21fd484375 100644
--- a/indra/newview/llvlcomposition.h
+++ b/indra/newview/llvlcomposition.h
@@ -31,6 +31,7 @@
#include "llviewershadermgr.h"
#include "llviewertexture.h"
#include "llpointer.h"
+#include "llterrainpaintmap.h"
#include "llimage.h"
@@ -87,6 +88,18 @@ public:
void setPaintType(U32 paint_type) { mPaintType = paint_type; }
LLViewerTexture* getPaintMap();
void setPaintMap(LLViewerTexture* paint_map);
+ // Queue of client-triggered brush operations that need to be converted
+ // into a form that can be sent to the server.
+ // TODO: Consider getting rid of mPaintRequestQueue, as it's not really needed (brushes go directly to RGB queue)
+ LLTerrainBrushQueue& getBrushQueue() { return mBrushQueue; }
+ // Queue of client-triggered paint operations that need to be converted
+ // into a form that can be sent to the server.
+ // Paints in this queue are in RGBA format.
+ LLTerrainPaintQueue& getPaintRequestQueue() { return mPaintRequestQueue; }
+ // Paint queue for current paint map - this queue gets applied directly to
+ // the paint map. Paints within are assumed to have already been sent to
+ // the server. Paints in this queue are in RGB format.
+ LLTerrainPaintQueue& getPaintMapQueue() { return mPaintMapQueue; }
protected:
void unboost();
@@ -105,6 +118,9 @@ protected:
U32 mPaintType = TERRAIN_PAINT_TYPE_HEIGHTMAP_WITH_NOISE;
LLPointer<LLViewerTexture> mPaintMap;
+ LLTerrainBrushQueue mBrushQueue;
+ LLTerrainPaintQueue mPaintRequestQueue{U8(4)};
+ LLTerrainPaintQueue mPaintMapQueue{U8(3)};
};
// Local materials to override all regions
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 1c645d8d70..53615968e0 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -3807,6 +3807,13 @@ function="World.EnvPreset"
</menu_item_call>
<menu_item_call
enabled="true"
+ label="Edit Local Paintmap"
+ name="Edit Local Paintmap">
+ <menu_item_call.on_click
+ function="Advanced.TerrainEditLocalPaintMap" />
+ </menu_item_call>
+ <menu_item_call
+ enabled="true"
label="Delete Local Paintmap"
name="Delete Local Paintmap">
<menu_item_call.on_click