1
0
forked from 0ad/0ad
0ad/source/renderer/TerrainOverlay.cpp
bb 157c6af18e Make the space in 0 A.D. non-breaking throughout the codebase.
Avoid cases of filenames
Update years in terms and other legal(ish) documents
Don't update years in license headers, since change is not meaningful

Will add linter rule in seperate commit

Happy recompiling everyone!

Original Patch By: Nescio
Comment By: Gallaecio
Differential Revision: D2620
This was SVN commit r27786.
2023-07-27 20:54:46 +00:00

411 lines
13 KiB
C++

/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "TerrainOverlay.h"
#include "graphics/Color.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "graphics/Terrain.h"
#include "lib/bits.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TerrainRenderer.h"
#include "simulation2/system/SimContext.h"
#include <algorithm>
namespace
{
// Global overlay list management:
std::vector<std::pair<ITerrainOverlay*, int>> g_TerrainOverlayList;
void AdjustOverlayGraphicsPipelineState(
Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc, const bool drawHidden)
{
pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden;
pipelineStateDesc.blendState.enabled = true;
pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
Renderer::Backend::BlendFactor::SRC_ALPHA;
pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
Renderer::Backend::BlendOp::ADD;
pipelineStateDesc.rasterizationState.cullMode =
drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK;
}
CShaderTechniquePtr CreateOverlayTileShaderTechnique(const bool drawHidden)
{
return g_Renderer.GetShaderManager().LoadEffect(
str_debug_line, {},
[drawHidden](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
{
AdjustOverlayGraphicsPipelineState(pipelineStateDesc, drawHidden);
// To ensure that outlines are drawn on top of the terrain correctly (and
// don't Z-fight and flicker nastily), use detph bias to pull them towards
// the camera.
pipelineStateDesc.rasterizationState.depthBiasEnabled = true;
pipelineStateDesc.rasterizationState.depthBiasConstantFactor = -1.0f;
pipelineStateDesc.rasterizationState.depthBiasSlopeFactor = -1.0f;
});
}
CShaderTechniquePtr CreateOverlayOutlineShaderTechnique(const bool drawHidden)
{
return g_Renderer.GetShaderManager().LoadEffect(
str_debug_line, {},
[drawHidden](Renderer::Backend::SGraphicsPipelineStateDesc& pipelineStateDesc)
{
AdjustOverlayGraphicsPipelineState(pipelineStateDesc, drawHidden);
pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
});
}
} // anonymous namespace
ITerrainOverlay::ITerrainOverlay(int priority)
{
// Add to global list of overlays
g_TerrainOverlayList.emplace_back(this, priority);
// Sort by overlays by priority. Do stable sort so that adding/removing
// overlays doesn't randomly disturb all the existing ones (which would
// be noticeable if they have the same priority and overlap).
std::stable_sort(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
[](const std::pair<ITerrainOverlay*, int>& a, const std::pair<ITerrainOverlay*, int>& b) {
return a.second < b.second;
});
}
ITerrainOverlay::~ITerrainOverlay()
{
std::vector<std::pair<ITerrainOverlay*, int> >::iterator newEnd =
std::remove_if(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
[this](const std::pair<ITerrainOverlay*, int>& a) { return a.first == this; });
g_TerrainOverlayList.erase(newEnd, g_TerrainOverlayList.end());
}
void ITerrainOverlay::RenderOverlaysBeforeWater(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (before)");
GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays before water");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderBeforeWater(deviceCommandContext);
}
void ITerrainOverlay::RenderOverlaysAfterWater(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup)
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (after)");
GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays after water");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderAfterWater(deviceCommandContext, cullGroup);
}
//////////////////////////////////////////////////////////////////////////
TerrainOverlay::TerrainOverlay(
const CSimContext& simContext, int priority /* = 100 */)
: ITerrainOverlay(priority), m_Terrain(&simContext.GetTerrain())
{
m_OverlayTechTile = CreateOverlayTileShaderTechnique(false);
m_OverlayTechTileHidden = CreateOverlayTileShaderTechnique(true);
m_OverlayTechOutline = CreateOverlayOutlineShaderTechnique(false);
m_OverlayTechOutlineHidden = CreateOverlayOutlineShaderTechnique(true);
const std::array<Renderer::Backend::SVertexAttributeFormat, 1> attributes{{
{Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32B32_SFLOAT, 0, sizeof(float) * 3,
Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
}};
m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
}
void TerrainOverlay::StartRender()
{
}
void TerrainOverlay::EndRender()
{
}
void TerrainOverlay::GetTileExtents(
ssize_t& min_i_inclusive, ssize_t& min_j_inclusive,
ssize_t& max_i_inclusive, ssize_t& max_j_inclusive)
{
// Default to whole map
min_i_inclusive = min_j_inclusive = 0;
max_i_inclusive = max_j_inclusive = m_Terrain->GetTilesPerSide()-1;
}
void TerrainOverlay::RenderBeforeWater(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
{
if (!m_Terrain)
return; // should never happen, but let's play it safe
StartRender();
ssize_t min_i, min_j, max_i, max_j;
GetTileExtents(min_i, min_j, max_i, max_j);
// Clamp the min to 0, but the max to -1 - so tile -1 can never be rendered,
// but if unclamped_max<0 then no tiles at all will be rendered. And the same
// for the upper limit.
min_i = Clamp<ssize_t>(min_i, 0, m_Terrain->GetTilesPerSide());
min_j = Clamp<ssize_t>(min_j, 0, m_Terrain->GetTilesPerSide());
max_i = Clamp<ssize_t>(max_i, -1, m_Terrain->GetTilesPerSide()-1);
max_j = Clamp<ssize_t>(max_j, -1, m_Terrain->GetTilesPerSide()-1);
for (m_j = min_j; m_j <= max_j; ++m_j)
for (m_i = min_i; m_i <= max_i; ++m_i)
ProcessTile(deviceCommandContext, m_i, m_j);
EndRender();
}
void TerrainOverlay::RenderTile(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden)
{
RenderTile(deviceCommandContext, color, drawHidden, m_i, m_j);
}
void TerrainOverlay::RenderTile(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden, ssize_t i, ssize_t j)
{
// TODO: unnecessary computation calls has been removed but we should use
// a vertex buffer or a vertex shader with a texture.
// Not sure if it's possible on old OpenGL.
CVector3D pos[2][2];
for (int di = 0; di < 2; ++di)
for (int dj = 0; dj < 2; ++dj)
m_Terrain->CalcPosition(i + di, j + dj, pos[di][dj]);
std::vector<float> vertices;
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
if (m_Terrain->GetTriangulationDir(i, j))
{
ADD(pos[0][0]);
ADD(pos[1][0]);
ADD(pos[0][1]);
ADD(pos[1][0]);
ADD(pos[1][1]);
ADD(pos[0][1]);
}
else
{
ADD(pos[0][0]);
ADD(pos[1][0]);
ADD(pos[1][1]);
ADD(pos[1][1]);
ADD(pos[0][1]);
ADD(pos[0][0]);
}
#undef ADD
const CShaderTechniquePtr& shaderTechnique = drawHidden ? m_OverlayTechTileHidden : m_OverlayTechTile;
deviceCommandContext->SetGraphicsPipelineState(
shaderTechnique->GetGraphicsPipelineState());
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* overlayShader = shaderTechnique->GetShader();
const CMatrix3D transform =
g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
deviceCommandContext->SetUniform(
overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray());
deviceCommandContext->SetUniform(
overlayShader->GetBindingSlot(str_color), color.AsFloatArray());
deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
deviceCommandContext->SetVertexBufferData(
0, vertices.data(), vertices.size() * sizeof(vertices[0]));
deviceCommandContext->Draw(0, vertices.size() / 3);
deviceCommandContext->EndPass();
}
void TerrainOverlay::RenderTileOutline(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden)
{
RenderTileOutline(deviceCommandContext, color, drawHidden, m_i, m_j);
}
void TerrainOverlay::RenderTileOutline(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden, ssize_t i, ssize_t j)
{
std::vector<float> vertices;
#define ADD(i, j) \
m_Terrain->CalcPosition(i, j, position); \
vertices.emplace_back(position.X); \
vertices.emplace_back(position.Y); \
vertices.emplace_back(position.Z);
CVector3D position;
ADD(i, j);
ADD(i + 1, j);
ADD(i + 1, j + 1);
ADD(i, j);
ADD(i + 1, j + 1);
ADD(i, j + 1);
#undef ADD
const CShaderTechniquePtr& shaderTechnique = drawHidden ? m_OverlayTechOutlineHidden : m_OverlayTechOutline;
deviceCommandContext->SetGraphicsPipelineState(
shaderTechnique->GetGraphicsPipelineState());
deviceCommandContext->BeginPass();
Renderer::Backend::IShaderProgram* overlayShader = shaderTechnique->GetShader();
const CMatrix3D transform =
g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
deviceCommandContext->SetUniform(
overlayShader->GetBindingSlot(str_transform), transform.AsFloatArray());
deviceCommandContext->SetUniform(
overlayShader->GetBindingSlot(str_color), color.AsFloatArray());
deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
deviceCommandContext->SetVertexBufferData(
0, vertices.data(), vertices.size() * sizeof(vertices[0]));
deviceCommandContext->Draw(0, vertices.size() / 3);
deviceCommandContext->EndPass();
}
//////////////////////////////////////////////////////////////////////////
TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) :
ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile)
{
}
TerrainTextureOverlay::~TerrainTextureOverlay() = default;
void TerrainTextureOverlay::RenderAfterWater(
Renderer::Backend::IDeviceCommandContext* deviceCommandContext, int cullGroup)
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
ssize_t w = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
ssize_t h = (ssize_t)(terrain->GetTilesPerSide() * m_TexelsPerTile);
const uint32_t requiredWidth = round_up_to_pow2(w);
const uint32_t requiredHeight = round_up_to_pow2(h);
// Recreate the texture with new size if necessary
if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight)
{
m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerrainOverlayTexture",
Renderer::Backend::ITexture::Usage::TRANSFER_DST |
Renderer::Backend::ITexture::Usage::SAMPLED,
Renderer::Backend::Format::R8G8B8A8_UNORM, requiredWidth, requiredHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
}
u8* data = (u8*)calloc(w * h, 4);
BuildTextureRGBA(data, w, h);
deviceCommandContext->UploadTextureRegion(
m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, data, w * h * 4, 0, 0, w, h);
free(data);
const CVector2D textureTransform{
m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE),
m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE)};
g_Renderer.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture(
deviceCommandContext, cullGroup, textureTransform, m_Texture.get());
}
SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const
{
static u8 colors[][3] =
{
{ 255, 0, 0 },
{ 0, 255, 0 },
{ 0, 0, 255 },
{ 255, 255, 0 },
{ 255, 0, 255 },
{ 0, 255, 255 },
{ 255, 255, 255 },
{ 127, 0, 0 },
{ 0, 127, 0 },
{ 0, 0, 127 },
{ 127, 127, 0 },
{ 127, 0, 127 },
{ 0, 127, 127 },
{ 127, 127, 127},
{ 255, 127, 0 },
{ 127, 255, 0 },
{ 255, 0, 127 },
{ 127, 0, 255},
{ 0, 255, 127 },
{ 0, 127, 255},
{ 255, 127, 127},
{ 127, 255, 127},
{ 127, 127, 255},
{ 127, 255, 255 },
{ 255, 127, 255 },
{ 255, 255, 127 },
};
size_t c = idx % ARRAY_SIZE(colors);
return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha);
}