phoenix-firestorm/indra/newview/llterrainpaintmap.cpp

287 lines
12 KiB
C++

/**
* @file llterrainpaintmap.cpp
* @brief Utilities for managing terrain paint maps
*
* $LicenseInfo:firstyear=2001&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$
*/
#include "llterrainpaintmap.h"
#include "llviewerprecompiledheaders.h"
// library includes
#include "llglslshader.h"
#include "llrendertarget.h"
#include "llvertexbuffer.h"
// newview includes
#include "llrender.h"
#include "llsurface.h"
#include "llsurfacepatch.h"
#include "llviewercamera.h"
#include "llviewerregion.h"
#include "llviewershadermgr.h"
#include "llviewertexture.h"
// static
bool LLTerrainPaintMap::bakeHeightNoiseIntoPBRPaintMapRGB(const LLViewerRegion& region, 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());
const LLSurface& surface = region.getLand();
const U32 patch_count = surface.getPatchesPerEdge();
// *TODO: mHeightsGenerated isn't guaranteed to be true. Assume terrain is
// loaded for now. Would be nice to fix the loading issue or find a better
// heuristic to determine that the terrain is sufficiently loaded.
#if 0
// Don't proceed if the region heightmap isn't loaded
for (U32 rj = 0; rj < patch_count; ++rj)
{
for (U32 ri = 0; ri < patch_count; ++ri)
{
const LLSurfacePatch* patch = surface.getPatch(ri, rj);
if (!patch->isHeightsGenerated())
{
LL_WARNS() << "Region heightmap not fully loaded" << LL_ENDL;
return false;
}
}
}
#endif
// 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,
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();
// Render terrain heightmap to paint map via shader
// Set up viewport, camera, and orthographic projection matrix. Position
// the camera such that the camera points straight down, and the region
// completely covers the "screen". Since orthographic projection does not
// distort, we arbitrarily choose the near plane and far plane to cover the
// full span of region heights, plus a small amount of padding to account
// for rounding errors.
const F32 region_width = region.getWidth();
const F32 region_half_width = region_width / 2.0f;
const F32 region_camera_height = surface.getMaxZ() + DEFAULT_NEAR_PLANE;
LLViewerCamera camera;
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);
camera.setAspect(F32(scratch_target.getHeight()) / F32(scratch_target.getWidth()));
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());
// Manually get modelview matrix from camera orientation.
glm::mat4 modelview(glm::make_mat4((GLfloat *) OGL_TO_CFR_ROTATION));
GLfloat ogl_matrix[16];
camera.getOpenGLTransform(ogl_matrix);
modelview *= glm::make_mat4(ogl_matrix);
gGL.matrixMode(LLRender::MM_MODELVIEW);
gGL.loadMatrix(glm::value_ptr(modelview));
// Override the projection matrix from the camera
gGL.matrixMode(LLRender::MM_PROJECTION);
gGL.pushMatrix();
gGL.loadIdentity();
llassert(camera_origin.mV[VZ] >= surface.getMaxZ());
const F32 region_high_near = camera_origin.mV[VZ] - surface.getMaxZ();
constexpr F32 far_plane_delta = 0.25f;
const F32 region_low_far = camera_origin.mV[VZ] - surface.getMinZ() + far_plane_delta;
gGL.ortho(-region_half_width, region_half_width, -region_half_width, region_half_width, region_high_near, region_low_far);
// No need to call camera.setPerspective because we don't need the clip planes. It would be inaccurate due to the perspective rendering anyway.
// Need to get the full resolution vertices in order to get an accurate
// paintmap. It's not sufficient to iterate over the surface patches, as
// they may be at lower LODs.
// The functionality here is a subset of
// LLVOSurfacePatch::getTerrainGeometry. Unlike said function, we don't
// care about stride length since we're always rendering at full
// resolution. We also don't care about normals/tangents because those
// don't contribute to the paintmap.
// *NOTE: The actual getTerrainGeometry fits the terrain vertices snugly
// under the 16-bit indices limit. For the sake of simplicity, that has not
// been replicated here.
std::vector<LLPointer<LLDrawInfo>> infos;
// Vertex and index counts adapted from LLVOSurfacePatch::getGeomSizesMain,
// with additional vertices added as we are including the north and east
// edges here.
const U32 patch_size = (U32)surface.getGridsPerPatchEdge();
constexpr U32 stride = 1;
const U32 vert_size = (patch_size / stride) + 1;
const U32 n = vert_size * vert_size;
const U32 ni = 6 * (vert_size - 1) * (vert_size - 1);
const U32 region_vertices = n * patch_count * patch_count;
const U32 region_indices = ni * patch_count * patch_count;
if (LLGLSLShader::sCurBoundShaderPtr == nullptr)
{ // make sure a shader is bound to satisfy mVertexBuffer->setBuffer
gDebugProgram.bind();
}
LLPointer<LLVertexBuffer> buf = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD1);
{
buf->allocateBuffer(region_vertices, region_indices*2); // hack double index count... TODO: find a better way to indicate 32-bit indices will be used
buf->setBuffer();
U32 vertex_total = 0;
std::vector<U32> index_array(region_indices);
std::vector<LLVector4a> positions(region_vertices);
std::vector<LLVector2> texcoords1(region_vertices);
auto idx = index_array.begin();
auto pos = positions.begin();
auto tex1 = texcoords1.begin();
for (U32 rj = 0; rj < patch_count; ++rj)
{
for (U32 ri = 0; ri < patch_count; ++ri)
{
const U32 index_offset = vertex_total;
for (U32 j = 0; j < (vert_size - 1); ++j)
{
for (U32 i = 0; i < (vert_size - 1); ++i)
{
// y
// 2....3
// ^ . .
// | 0....1
// |
// -------> x
//
// triangle 1: 0,1,2
// triangle 2: 1,3,2
// 0: vert0
// 1: vert0 + 1
// 2: vert0 + vert_size
// 3: vert0 + vert_size + 1
const U32 vert0 = index_offset + i + (j*vert_size);
*idx++ = vert0;
*idx++ = vert0 + 1;
*idx++ = vert0 + vert_size;
*idx++ = vert0 + 1;
*idx++ = vert0 + vert_size + 1;
*idx++ = vert0 + vert_size;
}
}
const LLSurfacePatch* patch = surface.getPatch(ri, rj);
for (U32 j = 0; j < vert_size; ++j)
{
for (U32 i = 0; i < vert_size; ++i)
{
LLVector3 scratch3;
LLVector3 pos3;
LLVector2 tex0_temp;
LLVector2 tex1_temp;
patch->eval(i, j, stride, &pos3, &scratch3, &tex0_temp, &tex1_temp);
(*pos++).set(pos3.mV[VX], pos3.mV[VY], pos3.mV[VZ]);
*tex1++ = tex1_temp;
vertex_total++;
}
}
}
}
buf->setIndexData(index_array.data(), 0, (U32)index_array.size());
buf->setPositionData(positions.data(), 0, (U32)positions.size());
buf->setTexCoord1Data(texcoords1.data(), 0, (U32)texcoords1.size());
buf->unmapBuffer();
buf->unbind();
}
// Draw the region in agent space at full resolution
{
LLGLSLShader::unbind();
// *NOTE: A theoretical non-PBR terrain bake program would be
// *slightly* different, due the texture terrain shader not having an
// alpha ramp threshold (TERRAIN_RAMP_MIX_THRESHOLD)
LLGLSLShader& shader = gPBRTerrainBakeProgram;
shader.bind();
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);
S32 alpha_ramp = shader.enableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
LLPointer<LLViewerTexture> alpha_ramp_texture = LLViewerTextureManager::getFetchedTexture(IMG_ALPHA_GRAD_2D);
gGL.getTexUnit(alpha_ramp)->bind(alpha_ramp_texture);
gGL.getTexUnit(alpha_ramp)->setTextureAddressMode(LLTexUnit::TAM_CLAMP);
buf->setBuffer();
for (U32 rj = 0; rj < patch_count; ++rj)
{
for (U32 ri = 0; ri < patch_count; ++ri)
{
const U32 patch_index = ri + (rj * patch_count);
const U32 index_offset = ni * patch_index;
const U32 vertex_offset = n * patch_index;
llassert(index_offset + ni <= region_indices);
llassert(vertex_offset + n <= region_vertices);
buf->drawRange(LLRender::TRIANGLES, vertex_offset, vertex_offset + n - 1, ni, index_offset);
}
}
shader.disableTexture(LLViewerShaderMgr::TERRAIN_ALPHARAMP);
gGL.getTexUnit(alpha_ramp)->unbind(LLTexUnit::TT_TEXTURE);
gGL.getTexUnit(alpha_ramp)->disable();
gGL.getTexUnit(alpha_ramp)->activate();
shader.unbind();
}
gGL.matrixMode(LLRender::MM_PROJECTION);
gGL.popMatrix();
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);
if (!success)
{
LL_WARNS() << "Failed to copy framebuffer to paintmap" << LL_ENDL;
}
glGenerateMipmap(GL_TEXTURE_2D);
stop_glerror();
scratch_target.flush();
LLGLSLShader::unbind();
return success;
}