Index: ps/trunk/source/renderer/OverlayRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26834)
+++ ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26835)
@@ -1,780 +1,781 @@
/* Copyright (C) 2022 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 .
*/
#include "precompiled.h"
#include "OverlayRenderer.h"
#include "graphics/Camera.h"
#include "graphics/LOSTexture.h"
#include "graphics/Overlay.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "lib/hash.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "maths/Quaternion.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/DebugRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TexturedLineRData.h"
#include "renderer/VertexArray.h"
#include "renderer/VertexBuffer.h"
#include "renderer/VertexBufferManager.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/SimContext.h"
#include
namespace
{
CShaderTechniquePtr GetOverlayLineShaderTechnique(const CShaderDefines& defines)
{
return g_Renderer.GetShaderManager().LoadEffect(str_overlay_line, defines);
}
} // anonymous namespace
/**
* Key used to group quads into batches for more efficient rendering. Currently groups by the combination
* of the main texture and the texture mask, to minimize texture swapping during rendering.
*/
struct QuadBatchKey
{
QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask)
: m_Texture(texture), m_TextureMask(textureMask)
{ }
bool operator==(const QuadBatchKey& other) const
{
return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask);
}
CTexturePtr m_Texture;
CTexturePtr m_TextureMask;
};
struct QuadBatchHash
{
std::size_t operator()(const QuadBatchKey& d) const
{
size_t seed = 0;
hash_combine(seed, d.m_Texture);
hash_combine(seed, d.m_TextureMask);
return seed;
}
};
/**
* Holds information about a single quad rendering batch.
*/
class QuadBatchData : public CRenderData
{
public:
QuadBatchData() : m_IndicesBase(0), m_NumRenderQuads(0) { }
/// Holds the quad overlay structures requested to be rendered in this batch. Must be cleared
/// after each frame.
std::vector m_Quads;
/// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals).
size_t m_IndicesBase;
/// Amount of quads to actually render in this batch. Potentially (although unlikely to be)
/// different from m_Quads.size() due to restrictions on the total amount of quads that can be
/// rendered. Must be reset after each frame.
size_t m_NumRenderQuads;
};
struct OverlayRendererInternals
{
using QuadBatchMap = std::unordered_map;
OverlayRendererInternals();
~OverlayRendererInternals(){ }
std::vector lines;
std::vector texlines;
std::vector sprites;
std::vector quads;
std::vector spheres;
QuadBatchMap quadBatchMap;
// Dedicated vertex/index buffers for rendering all quads (to within the limits set by
// MAX_QUAD_OVERLAYS).
VertexArray quadVertices;
VertexArray::Attribute quadAttributePos;
VertexArray::Attribute quadAttributeColor;
VertexArray::Attribute quadAttributeUV;
VertexIndexArray quadIndices;
- /// Maximum amount of quad overlays we support for rendering. This limit is set to be able to
- /// render all quads from a single dedicated VB without having to reallocate it, which is much
- /// faster in the typical case of rendering only a handful of quads. When modifying this value,
- /// you must take care for the new amount of quads to fit in a single VBO (which is not likely
- /// to be a problem).
+ // Maximum amount of quad overlays we support for rendering. This limit is set to be able to
+ // render all quads from a single dedicated VB without having to reallocate it, which is much
+ // faster in the typical case of rendering only a handful of quads. When modifying this value,
+ // you must take care for the new amount of quads to fit in a single backend buffer (which is
+ // not likely to be a problem).
static const size_t MAX_QUAD_OVERLAYS = 1024;
// Sets of commonly-(re)used shader defines.
CShaderDefines defsOverlayLineNormal;
CShaderDefines defsOverlayLineAlwaysVisible;
CShaderDefines defsQuadOverlay;
// Geometry for a unit sphere
std::vector sphereVertexes;
std::vector sphereIndexes;
void GenerateSphere();
- /// Performs one-time setup. Called from CRenderer::Open, after graphics capabilities have
- /// been detected. Note that no VBOs must be created before this is called, since the shader
- /// path and graphics capabilities are not guaranteed to be stable before this point.
+ // Performs one-time setup. Called from CRenderer::Open, after graphics capabilities have
+ // been detected. Note that no backend buffer must be created before this is called, since
+ // the shader path and graphics capabilities are not guaranteed to be stable before this
+ // point.
void Initialize();
};
const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f;
OverlayRendererInternals::OverlayRendererInternals()
: quadVertices(Renderer::Backend::GL::CBuffer::Type::VERTEX, true),
quadIndices(false)
{
quadAttributePos.format = Renderer::Backend::Format::R32G32B32_SFLOAT;
quadVertices.AddAttribute(&quadAttributePos);
quadAttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
quadVertices.AddAttribute(&quadAttributeColor);
quadAttributeUV.format = Renderer::Backend::Format::R16G16_SINT;
quadVertices.AddAttribute(&quadAttributeUV);
// Note that we're reusing the textured overlay line shader for the quad overlay rendering. This
// is because their code is almost identical; the only difference is that for the quad overlays
// we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the
// shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define.
defsOverlayLineNormal.Add(str_USE_OBJECTCOLOR, str_1);
defsOverlayLineAlwaysVisible.Add(str_USE_OBJECTCOLOR, str_1);
defsOverlayLineAlwaysVisible.Add(str_IGNORE_LOS, str_1);
}
void OverlayRendererInternals::Initialize()
{
// Perform any initialization after graphics capabilities have been detected. Notably,
- // only at this point can we safely allocate VBOs (in contrast to e.g. in the constructor),
+ // only at this point can we safely allocate backend buffer (in contrast to e.g. in the constructor),
// because their creation depends on the shader path, which is not reliably set before this point.
quadVertices.SetNumberOfVertices(MAX_QUAD_OVERLAYS * 4);
quadVertices.Layout(); // allocate backing store
quadIndices.SetNumberOfVertices(MAX_QUAD_OVERLAYS * 6);
quadIndices.Layout(); // allocate backing store
// Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the
// indices are always the same; we can therefore fill in all the indices once and pretty much forget about
// them. We then also no longer need its backing store, since we never change any indices afterwards.
VertexArrayIterator index = quadIndices.GetIterator();
for (u16 i = 0; i < static_cast(MAX_QUAD_OVERLAYS); ++i)
{
*index++ = i * 4 + 0;
*index++ = i * 4 + 1;
*index++ = i * 4 + 2;
*index++ = i * 4 + 2;
*index++ = i * 4 + 3;
*index++ = i * 4 + 0;
}
quadIndices.Upload();
quadIndices.FreeBackingStore();
}
OverlayRenderer::OverlayRenderer()
{
m = new OverlayRendererInternals();
}
OverlayRenderer::~OverlayRenderer()
{
delete m;
}
void OverlayRenderer::Initialize()
{
m->Initialize();
}
void OverlayRenderer::Submit(SOverlayLine* line)
{
m->lines.push_back(line);
}
void OverlayRenderer::Submit(SOverlayTexturedLine* line)
{
// Simplify the rest of the code by guaranteeing non-empty lines
if (line->m_Coords.empty())
return;
m->texlines.push_back(line);
}
void OverlayRenderer::Submit(SOverlaySprite* overlay)
{
m->sprites.push_back(overlay);
}
void OverlayRenderer::Submit(SOverlayQuad* overlay)
{
m->quads.push_back(overlay);
}
void OverlayRenderer::Submit(SOverlaySphere* overlay)
{
m->spheres.push_back(overlay);
}
void OverlayRenderer::EndFrame()
{
m->lines.clear();
m->texlines.clear();
m->sprites.clear();
m->quads.clear();
m->spheres.clear();
// this should leave the capacity unchanged, which is okay since it
// won't be very large or very variable
// Empty the batch rendering data structures, but keep their key mappings around for the next frames
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& quadBatchData = (it->second);
quadBatchData.m_Quads.clear();
quadBatchData.m_NumRenderQuads = 0;
quadBatchData.m_IndicesBase = 0;
}
}
void OverlayRenderer::PrepareForRendering()
{
PROFILE3("prepare overlays");
// This is where we should do something like sort the overlays by
// color/sprite/etc for more efficient rendering
for (size_t i = 0; i < m->texlines.size(); ++i)
{
SOverlayTexturedLine* line = m->texlines[i];
if (!line->m_RenderData)
{
line->m_RenderData = std::make_shared();
line->m_RenderData->Update(*line);
// We assume the overlay line will get replaced by the caller
// if terrain changes, so we don't need to detect that here and
// call Update again. Also we assume the caller won't change
// any of the parameters after first submitting the line.
}
}
// Group quad overlays by their texture/mask combination for efficient rendering
// TODO: consider doing this directly in Submit()
for (size_t i = 0; i < m->quads.size(); ++i)
{
SOverlayQuad* const quad = m->quads[i];
QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask);
QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist
// add overlay to list of quads
batchRenderData.m_Quads.push_back(quad);
}
const CVector3D vOffset(0, OverlayRenderer::OVERLAY_VOFFSET, 0);
// Write quad overlay vertices/indices to VA backing store
VertexArrayIterator vertexPos = m->quadAttributePos.GetIterator();
VertexArrayIterator vertexColor = m->quadAttributeColor.GetIterator();
VertexArrayIterator vertexUV = m->quadAttributeUV.GetIterator();
size_t indicesIdx = 0;
size_t totalNumQuads = 0;
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& batchRenderData = (it->second);
batchRenderData.m_NumRenderQuads = 0;
if (batchRenderData.m_Quads.empty())
continue;
// Remember the current index into the (entire) indices array as our base offset for this batch
batchRenderData.m_IndicesBase = indicesIdx;
// points to the index where each iteration's vertices will be appended
for (size_t i = 0; i < batchRenderData.m_Quads.size() && totalNumQuads < OverlayRendererInternals::MAX_QUAD_OVERLAYS; i++)
{
const SOverlayQuad* quad = batchRenderData.m_Quads[i];
const SColor4ub quadColor = quad->m_Color.AsSColor4ub();
*vertexPos++ = quad->m_Corners[0] + vOffset;
*vertexPos++ = quad->m_Corners[1] + vOffset;
*vertexPos++ = quad->m_Corners[2] + vOffset;
*vertexPos++ = quad->m_Corners[3] + vOffset;
(*vertexUV)[0] = 0;
(*vertexUV)[1] = 0;
++vertexUV;
(*vertexUV)[0] = 0;
(*vertexUV)[1] = 1;
++vertexUV;
(*vertexUV)[0] = 1;
(*vertexUV)[1] = 1;
++vertexUV;
(*vertexUV)[0] = 1;
(*vertexUV)[1] = 0;
++vertexUV;
*vertexColor++ = quadColor;
*vertexColor++ = quadColor;
*vertexColor++ = quadColor;
*vertexColor++ = quadColor;
indicesIdx += 6;
totalNumQuads++;
batchRenderData.m_NumRenderQuads++;
}
}
m->quadVertices.Upload();
// don't free the backing store! we'll overwrite it on the next frame to save a reallocation.
m->quadVertices.PrepareForRendering();
}
void OverlayRenderer::RenderOverlaysBeforeWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
PROFILE3_GPU("overlays (before)");
GPU_SCOPED_LABEL(deviceCommandContext, "Render overlays before water");
for (SOverlayLine* line : m->lines)
{
if (line->m_Coords.empty())
continue;
g_Renderer.GetDebugRenderer().DrawLine(line->m_Coords, line->m_Color, static_cast(line->m_Thickness));
}
}
void OverlayRenderer::RenderOverlaysAfterWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
PROFILE3_GPU("overlays (after)");
GPU_SCOPED_LABEL(deviceCommandContext, "Render overlays after water");
RenderTexturedOverlayLines(deviceCommandContext);
RenderQuadOverlays(deviceCommandContext);
RenderSphereOverlays(deviceCommandContext);
}
void OverlayRenderer::RenderTexturedOverlayLines(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (m->texlines.empty())
return;
CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
// ----------------------------------------------------------------------------------------
CShaderTechniquePtr shaderTechTexLineNormal = GetOverlayLineShaderTechnique(m->defsOverlayLineNormal);
if (shaderTechTexLineNormal)
{
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
shaderTechTexLineNormal->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthWriteEnabled = false;
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;
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
deviceCommandContext->BeginPass();
Renderer::Backend::GL::CShaderProgram* shaderTexLineNormal = shaderTechTexLineNormal->GetShader();
shaderTexLineNormal->BindTexture(str_losTex, los.GetTexture());
shaderTexLineNormal->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shaderTexLineNormal->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
// batch render only the non-always-visible overlay lines using the normal shader
RenderTexturedOverlayLines(deviceCommandContext, shaderTexLineNormal, false);
deviceCommandContext->EndPass();
}
// ----------------------------------------------------------------------------------------
CShaderTechniquePtr shaderTechTexLineAlwaysVisible = GetOverlayLineShaderTechnique(m->defsOverlayLineAlwaysVisible);
if (shaderTechTexLineAlwaysVisible)
{
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
shaderTechTexLineAlwaysVisible->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthWriteEnabled = false;
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;
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
deviceCommandContext->BeginPass();
Renderer::Backend::GL::CShaderProgram* shaderTexLineAlwaysVisible = shaderTechTexLineAlwaysVisible->GetShader();
// TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted
shaderTexLineAlwaysVisible->BindTexture(str_losTex, los.GetTexture());
shaderTexLineAlwaysVisible->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shaderTexLineAlwaysVisible->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
// batch render only the always-visible overlay lines using the LoS-ignored shader
RenderTexturedOverlayLines(deviceCommandContext, shaderTexLineAlwaysVisible, true);
deviceCommandContext->EndPass();
}
// ----------------------------------------------------------------------------------------
// TODO: the shaders should probably be responsible for unbinding their textures
deviceCommandContext->BindTexture(1, GL_TEXTURE_2D, 0);
deviceCommandContext->BindTexture(0, GL_TEXTURE_2D, 0);
}
void OverlayRenderer::RenderTexturedOverlayLines(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
Renderer::Backend::GL::CShaderProgram* shader, bool alwaysVisible)
{
for (size_t i = 0; i < m->texlines.size(); ++i)
{
SOverlayTexturedLine* line = m->texlines[i];
// render only those lines matching the requested alwaysVisible status
if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible)
continue;
ENSURE(line->m_RenderData);
line->m_RenderData->Render(deviceCommandContext, *line, shader);
}
}
void OverlayRenderer::RenderQuadOverlays(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (m->quadBatchMap.empty())
return;
CShaderTechniquePtr shaderTech = GetOverlayLineShaderTechnique(m->defsQuadOverlay);
if (!shaderTech)
return;
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
shaderTech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthWriteEnabled = false;
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;
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
deviceCommandContext->BeginPass();
Renderer::Backend::GL::CShaderProgram* shader = shaderTech->GetShader();
CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
shader->BindTexture(str_losTex, los.GetTexture());
shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
m->quadVertices.UploadIfNeeded(deviceCommandContext);
m->quadIndices.UploadIfNeeded(deviceCommandContext);
const uint32_t vertexStride = m->quadVertices.GetStride();
const uint32_t firstVertexOffset = m->quadVertices.GetOffset() * vertexStride;
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& batchRenderData = it->second;
const size_t batchNumQuads = batchRenderData.m_NumRenderQuads;
if (batchNumQuads == 0)
continue;
const QuadBatchKey& maskPair = it->first;
maskPair.m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext);
maskPair.m_TextureMask->UploadBackendTextureIfNeeded(deviceCommandContext);
shader->BindTexture(str_baseTex, maskPair.m_Texture->GetBackendTexture());
shader->BindTexture(str_maskTex, maskPair.m_TextureMask->GetBackendTexture());
// TODO: move setting format out of the loop, we might want move the offset
// to the index offset when it's supported.
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
m->quadAttributePos.format, firstVertexOffset + m->quadAttributePos.offset, vertexStride, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::COLOR,
m->quadAttributeColor.format, firstVertexOffset + m->quadAttributeColor.offset, vertexStride, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
m->quadAttributeUV.format, firstVertexOffset + m->quadAttributeUV.offset, vertexStride, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV1,
m->quadAttributeUV.format, firstVertexOffset + m->quadAttributeUV.offset, vertexStride, 0);
deviceCommandContext->SetVertexBuffer(0, m->quadVertices.GetBuffer());
deviceCommandContext->SetIndexBuffer(m->quadIndices.GetBuffer());
deviceCommandContext->DrawIndexed(m->quadIndices.GetOffset() + batchRenderData.m_IndicesBase, batchNumQuads * 6, 0);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2;
}
deviceCommandContext->EndPass();
// TODO: the shader should probably be responsible for unbinding its textures
deviceCommandContext->BindTexture(1, GL_TEXTURE_2D, 0);
deviceCommandContext->BindTexture(0, GL_TEXTURE_2D, 0);
}
void OverlayRenderer::RenderForegroundOverlays(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CCamera& viewCamera)
{
PROFILE3_GPU("overlays (fg)");
const CVector3D right = -viewCamera.GetOrientation().GetLeft();
const CVector3D up = viewCamera.GetOrientation().GetUp();
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_foreground_overlay);
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
tech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthTestEnabled = false;
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;
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
deviceCommandContext->BeginPass();
Renderer::Backend::GL::CShaderProgram* shader = tech->GetShader();
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
const CVector2D uvs[6] =
{
{0.0f, 1.0f},
{1.0f, 1.0f},
{1.0f, 0.0f},
{0.0f, 1.0f},
{1.0f, 0.0f},
{0.0f, 0.0f},
};
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT, 0, 0, 1);
deviceCommandContext->SetVertexBufferData(1, &uvs[0]);
for (size_t i = 0; i < m->sprites.size(); ++i)
{
SOverlaySprite* sprite = m->sprites[i];
if (!i || sprite->m_Texture != m->sprites[i - 1]->m_Texture)
{
sprite->m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext);
shader->BindTexture(str_baseTex, sprite->m_Texture->GetBackendTexture());
}
shader->Uniform(str_colorMul, sprite->m_Color);
const CVector3D position[6] =
{
sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0,
sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y0,
sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1,
sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y0,
sprite->m_Position + right*sprite->m_X1 + up*sprite->m_Y1,
sprite->m_Position + right*sprite->m_X0 + up*sprite->m_Y1
};
deviceCommandContext->SetVertexBufferData(0, &position[0].X);
deviceCommandContext->Draw(0, 6);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += 2;
}
deviceCommandContext->EndPass();
}
static void TessellateSphereFace(const CVector3D& a, u16 ai,
const CVector3D& b, u16 bi,
const CVector3D& c, u16 ci,
std::vector& vertexes, std::vector& indexes, int level)
{
if (level == 0)
{
indexes.push_back(ai);
indexes.push_back(bi);
indexes.push_back(ci);
}
else
{
CVector3D d = (a + b).Normalized();
CVector3D e = (b + c).Normalized();
CVector3D f = (c + a).Normalized();
int di = vertexes.size() / 3; vertexes.push_back(d.X); vertexes.push_back(d.Y); vertexes.push_back(d.Z);
int ei = vertexes.size() / 3; vertexes.push_back(e.X); vertexes.push_back(e.Y); vertexes.push_back(e.Z);
int fi = vertexes.size() / 3; vertexes.push_back(f.X); vertexes.push_back(f.Y); vertexes.push_back(f.Z);
TessellateSphereFace(a,ai, d,di, f,fi, vertexes, indexes, level-1);
TessellateSphereFace(d,di, b,bi, e,ei, vertexes, indexes, level-1);
TessellateSphereFace(f,fi, e,ei, c,ci, vertexes, indexes, level-1);
TessellateSphereFace(d,di, e,ei, f,fi, vertexes, indexes, level-1);
}
}
static void TessellateSphere(std::vector& vertexes, std::vector& indexes, int level)
{
/* Start with a tetrahedron, then tessellate */
float s = sqrtf(0.5f);
#define VERT(a,b,c) vertexes.push_back(a); vertexes.push_back(b); vertexes.push_back(c);
VERT(-s, 0, -s);
VERT( s, 0, -s);
VERT( s, 0, s);
VERT(-s, 0, s);
VERT( 0, -1, 0);
VERT( 0, 1, 0);
#define FACE(a,b,c) \
TessellateSphereFace( \
CVector3D(vertexes[a*3], vertexes[a*3+1], vertexes[a*3+2]), a, \
CVector3D(vertexes[b*3], vertexes[b*3+1], vertexes[b*3+2]), b, \
CVector3D(vertexes[c*3], vertexes[c*3+1], vertexes[c*3+2]), c, \
vertexes, indexes, level);
FACE(0,4,1);
FACE(1,4,2);
FACE(2,4,3);
FACE(3,4,0);
FACE(1,5,0);
FACE(2,5,1);
FACE(3,5,2);
FACE(0,5,3);
#undef FACE
#undef VERT
}
void OverlayRendererInternals::GenerateSphere()
{
if (sphereVertexes.empty())
TessellateSphere(sphereVertexes, sphereIndexes, 3);
}
void OverlayRenderer::RenderSphereOverlays(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
PROFILE3_GPU("overlays (spheres)");
if (m->spheres.empty())
return;
Renderer::Backend::GL::CShaderProgram* shader;
CShaderTechniquePtr tech;
tech = g_Renderer.GetShaderManager().LoadEffect(str_overlay_solid);
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
tech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthWriteEnabled = false;
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;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
deviceCommandContext->BeginPass();
shader = tech->GetShader();
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
m->GenerateSphere();
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32B32_SFLOAT, 0, 0, 0);
deviceCommandContext->SetVertexBufferData(0, m->sphereVertexes.data());
deviceCommandContext->SetIndexBufferData(m->sphereIndexes.data());
for (size_t i = 0; i < m->spheres.size(); ++i)
{
SOverlaySphere* sphere = m->spheres[i];
CMatrix3D transform;
transform.SetIdentity();
transform.Scale(sphere->m_Radius, sphere->m_Radius, sphere->m_Radius);
transform.Translate(sphere->m_Center);
shader->Uniform(str_instancingTransform, transform);
shader->Uniform(str_color, sphere->m_Color);
deviceCommandContext->DrawIndexed(0, m->sphereIndexes.size(), 0);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3;
}
deviceCommandContext->EndPass();
}
Index: ps/trunk/source/renderer/OverlayRenderer.h
===================================================================
--- ps/trunk/source/renderer/OverlayRenderer.h (revision 26834)
+++ ps/trunk/source/renderer/OverlayRenderer.h (revision 26835)
@@ -1,159 +1,159 @@
/* Copyright (C) 2022 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 .
*/
#ifndef INCLUDED_OVERLAYRENDERER
#define INCLUDED_OVERLAYRENDERER
#include "graphics/ShaderProgram.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
struct SOverlayLine;
struct SOverlayTexturedLine;
struct SOverlaySprite;
struct SOverlayQuad;
struct SOverlaySphere;
class CCamera;
struct OverlayRendererInternals;
/**
* Class OverlayRenderer: Render various bits of data that overlay the
* game world (selection circles, health bars, etc).
*/
class OverlayRenderer
{
NONCOPYABLE(OverlayRenderer);
public:
OverlayRenderer();
~OverlayRenderer();
/**
* Performs one-time initialization. Called by CRenderer::Open after graphics
- * capabilities and the shader path have been determined (notably VBO support).
+ * capabilities and the shader path have been determined.
*/
void Initialize();
/**
* Add a line overlay for rendering in this frame.
* @param overlay Must be non-null. The pointed-to object must remain valid at least
* until the end of the frame.
*/
void Submit(SOverlayLine* overlay);
/**
* Add a textured line overlay for rendering in this frame.
* @param overlay Must be non-null. The pointed-to object must remain valid at least
* until the end of the frame.
*/
void Submit(SOverlayTexturedLine* overlay);
/**
* Add a sprite overlay for rendering in this frame.
* @param overlay Must be non-null. The pointed-to object must remain valid at least
* until the end of the frame.
*/
void Submit(SOverlaySprite* overlay);
/**
* Add a textured quad overlay for rendering in this frame.
* @param overlay Must be non-null. The pointed-to object must remain valid at least
* until the end of the frame.
*/
void Submit(SOverlayQuad* overlay);
/**
* Add a sphere overlay for rendering in this frame.
* @param overlay Must be non-null. The pointed-to object must remain valid at least
* until the end of the frame.
*/
void Submit(SOverlaySphere* overlay);
/**
* Prepare internal data structures for rendering.
* Must be called after all Submit calls for a frame, and before
* any rendering calls.
*/
void PrepareForRendering();
/**
* Reset the list of submitted overlays.
*/
void EndFrame();
/**
* Render all the submitted overlays that are embedded in the world
* (i.e. rendered behind other objects in the normal 3D way)
* and should be drawn before water (i.e. may be visible under the water)
*/
void RenderOverlaysBeforeWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Render all the submitted overlays that are embedded in the world
* (i.e. rendered behind other objects in the normal 3D way)
* and should be drawn after water (i.e. may be visible on top of the water)
*/
void RenderOverlaysAfterWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Render all the submitted overlays that should appear on top of everything
* in the world.
* @param viewCamera camera to be used for billboard computations
*/
void RenderForegroundOverlays(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CCamera& viewCamera);
/// Small vertical offset of overlays from terrain to prevent visual glitches
static const float OVERLAY_VOFFSET;
private:
/**
* Helper method; renders all overlay lines currently registered in the internals. Batch-
* renders textured overlay lines batched according to their visibility status by delegating
* to RenderTexturedOverlayLines(CShaderProgramPtr, bool).
*/
void RenderTexturedOverlayLines(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Helper method; renders those overlay lines currently registered in the internals (i.e.
* in m->texlines) for which the 'always visible' flag equals @p alwaysVisible. Used for
* batch rendering the overlay lines according to their alwaysVisible status, as this
* requires a separate shader to be used.
*/
void RenderTexturedOverlayLines(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
Renderer::Backend::GL::CShaderProgram* shader, bool alwaysVisible);
/**
* Helper method; batch-renders all registered quad overlays, batched by their texture for effiency.
*/
void RenderQuadOverlays(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Helper method; batch-renders all sphere quad overlays.
*/
void RenderSphereOverlays(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
private:
OverlayRendererInternals* m;
};
#endif // INCLUDED_OVERLAYRENDERER
Index: ps/trunk/source/renderer/TexturedLineRData.cpp
===================================================================
--- ps/trunk/source/renderer/TexturedLineRData.cpp (revision 26834)
+++ ps/trunk/source/renderer/TexturedLineRData.cpp (revision 26835)
@@ -1,460 +1,462 @@
/* Copyright (C) 2022 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 .
*/
#include "precompiled.h"
#include "TexturedLineRData.h"
#include "graphics/ShaderProgram.h"
#include "graphics/Terrain.h"
#include "maths/Frustum.h"
#include "maths/MathUtil.h"
#include "maths/Quaternion.h"
#include "ps/CStrInternStatic.h"
#include "renderer/OverlayRenderer.h"
#include "renderer/Renderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/system/SimContext.h"
#include "simulation2/components/ICmpWaterManager.h"
/* Note: this implementation uses g_VBMan directly rather than access it through the nicer VertexArray interface,
* because it allows you to work with variable amounts of vertices and indices more easily. New code should prefer
* to use VertexArray where possible, though. */
void CTexturedLineRData::Render(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const SOverlayTexturedLine& line, Renderer::Backend::GL::CShaderProgram* shader)
{
if (!m_VB || !m_VBIndices)
return; // might have failed to allocate
// -- render main line quad strip ----------------------
line.m_TextureBase->UploadBackendTextureIfNeeded(deviceCommandContext);
line.m_TextureMask->UploadBackendTextureIfNeeded(deviceCommandContext);
m_VB->m_Owner->UploadIfNeeded(deviceCommandContext);
m_VBIndices->m_Owner->UploadIfNeeded(deviceCommandContext);
shader->BindTexture(str_baseTex, line.m_TextureBase->GetBackendTexture());
shader->BindTexture(str_maskTex, line.m_TextureMask->GetBackendTexture());
shader->Uniform(str_objectColor, line.m_Color);
const uint32_t stride = sizeof(CTexturedLineRData::SVertex);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::POSITION,
Renderer::Backend::Format::R32G32B32_SFLOAT,
offsetof(CTexturedLineRData::SVertex, m_Position), stride, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV0,
Renderer::Backend::Format::R32G32_SFLOAT,
offsetof(CTexturedLineRData::SVertex, m_UVs), stride, 0);
deviceCommandContext->SetVertexAttributeFormat(
Renderer::Backend::VertexAttributeStream::UV1,
Renderer::Backend::Format::R32G32_SFLOAT,
offsetof(CTexturedLineRData::SVertex, m_UVs), stride, 0);
deviceCommandContext->SetVertexBuffer(0, m_VB->m_Owner->GetBuffer());
deviceCommandContext->SetIndexBuffer(m_VBIndices->m_Owner->GetBuffer());
deviceCommandContext->DrawIndexed(m_VBIndices->m_Index, m_VBIndices->m_Count, 0);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += m_VBIndices->m_Count/3;
}
void CTexturedLineRData::Update(const SOverlayTexturedLine& line)
{
m_VBIndices.Reset();
m_VB.Reset();
if (!line.m_SimContext)
{
debug_warn(L"[TexturedLineRData] No SimContext set for textured overlay line, cannot render (no terrain data)");
return;
}
float v = 0.f;
std::vector vertices;
std::vector indices;
const size_t n = line.m_Coords.size(); // number of line points
bool closed = line.m_Closed;
ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point)
// In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
// To avoid slightly expensive terrain computations we cycle these around and
// recompute p2 at the end of each iteration.
CVector3D p0;
CVector3D p1(line.m_Coords[0].X, 0, line.m_Coords[0].Y);
CVector3D p2(line.m_Coords[1].X, 0, line.m_Coords[1].Y);
if (closed)
// grab the ending point so as to close the loop
p0 = CVector3D(line.m_Coords[n - 1].X, 0, line.m_Coords[n - 1].Y);
else
// we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
// extends the p2 -> p1 direction, and use that point instead
p0 = p1 + (p1 - p2);
bool p1floating = false;
bool p2floating = false;
// Compute terrain heights, clamped to the water height (and remember whether
// each point was floating on water, for normal computation later)
// TODO: if we ever support more than one water level per map, recompute this per point
CmpPtr cmpWaterManager(*line.m_SimContext, SYSTEM_ENTITY);
float w = cmpWaterManager ? cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z) : 0.f;
const CTerrain& terrain = line.m_SimContext->GetTerrain();
p0.Y = terrain.GetExactGroundLevel(p0.X, p0.Z);
if (p0.Y < w)
p0.Y = w;
p1.Y = terrain.GetExactGroundLevel(p1.X, p1.Z);
if (p1.Y < w)
{
p1.Y = w;
p1floating = true;
}
p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z);
if (p2.Y < w)
{
p2.Y = w;
p2floating = true;
}
for (size_t i = 0; i < n; ++i)
{
// For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1)
// perpendicular to terrain normal
// Normal is vertical if on water, else computed from terrain
CVector3D norm;
if (p1floating)
norm = CVector3D(0, 1, 0);
else
norm = terrain.CalcExactNormal(p1.X, p1.Z);
CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm);
// Adjust bisector length to match the line thickness, along the line's width
float l = b.Dot((p2 - p1).Normalized().Cross(norm));
if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero
b *= line.m_Thickness / l;
// Push vertices and indices for each quad in GL_TRIANGLES order. The two triangles of each quad are indexed using
// the winding orders (BR, BL, TR) and (TR, BL, TL) (where BR is bottom-right of this iteration's quad, TR top-right etc).
SVertex vertex1(p1 + b + norm*OverlayRenderer::OVERLAY_VOFFSET, 0.f, v);
SVertex vertex2(p1 - b + norm*OverlayRenderer::OVERLAY_VOFFSET, 1.f, v);
vertices.push_back(vertex1);
vertices.push_back(vertex2);
u16 vertexCount = static_cast(vertices.size());
u16 index1 = vertexCount - 2; // index of vertex1 in this iteration (TR of this quad)
u16 index2 = vertexCount - 1; // index of the vertex2 in this iteration (TL of this quad)
if (i == 0)
{
// initial two vertices to continue building triangles from (n must be >= 2 for this to work)
indices.push_back(index1);
indices.push_back(index2);
}
else
{
u16 index1Prev = vertexCount - 4; // index of the vertex1 in the previous iteration (BR of this quad)
u16 index2Prev = vertexCount - 3; // index of the vertex2 in the previous iteration (BL of this quad)
ENSURE(index1Prev < vertexCount);
ENSURE(index2Prev < vertexCount);
// Add two corner points from last iteration and join with one of our own corners to create triangle 1
// (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied)
if (i > 1)
{
indices.push_back(index1Prev);
indices.push_back(index2Prev);
}
indices.push_back(index1); // complete triangle 1
// create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1
indices.push_back(index1);
indices.push_back(index2Prev);
indices.push_back(index2);
}
// alternate V coordinate for debugging
v = 1 - v;
// cycle the p's and compute the new p2
p0 = p1;
p1 = p2;
p1floating = p2floating;
// if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
if (!closed && i == n-2)
// next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
p2 = p1 + (p1 - p0);
else
p2 = CVector3D(line.m_Coords[(i + 2) % n].X, 0, line.m_Coords[(i + 2) % n].Y);
p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z);
if (p2.Y < w)
{
p2.Y = w;
p2floating = true;
}
else
p2floating = false;
}
if (closed)
{
// close the path
if (n % 2 == 0)
{
u16 vertexCount = static_cast(vertices.size());
indices.push_back(vertexCount - 2);
indices.push_back(vertexCount - 1);
indices.push_back(0);
indices.push_back(0);
indices.push_back(vertexCount - 1);
indices.push_back(1);
}
else
{
// add two vertices to have the good UVs for the last quad
SVertex vertex1(vertices[0].m_Position, 0.f, 1.f);
SVertex vertex2(vertices[1].m_Position, 1.f, 1.f);
vertices.push_back(vertex1);
vertices.push_back(vertex2);
u16 vertexCount = static_cast(vertices.size());
indices.push_back(vertexCount - 4);
indices.push_back(vertexCount - 3);
indices.push_back(vertexCount - 2);
indices.push_back(vertexCount - 2);
indices.push_back(vertexCount - 3);
indices.push_back(vertexCount - 1);
}
}
else
{
// Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of
// vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector
// between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector.
std::vector capIndices;
std::vector capVertices;
// create end cap
CreateLineCap(
line,
// the order of these vertices is important here, swapping them produces caps at the wrong side
vertices[vertices.size()-2].m_Position, // top-right vertex of last quad
vertices[vertices.size()-1].m_Position, // top-left vertex of last quad
// directional vector between centroids of last vertex pair and second-to-last vertex pair
(Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(),
line.m_EndCapType,
capVertices,
capIndices
);
for (unsigned i = 0; i < capIndices.size(); i++)
capIndices[i] += static_cast(vertices.size());
vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
indices.insert(indices.end(), capIndices.begin(), capIndices.end());
capIndices.clear();
capVertices.clear();
// create start cap
CreateLineCap(
line,
// the order of these vertices is important here, swapping them produces caps at the wrong side
vertices[1].m_Position,
vertices[0].m_Position,
// directional vector between centroids of first vertex pair and second vertex pair
(Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(),
line.m_StartCapType,
capVertices,
capIndices
);
for (unsigned i = 0; i < capIndices.size(); i++)
capIndices[i] += static_cast(vertices.size());
vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
indices.insert(indices.end(), capIndices.begin(), capIndices.end());
}
if (vertices.empty() || indices.empty())
return;
// Indices for triangles, so must be multiple of 3.
ENSURE(indices.size() % 3 == 0);
m_BoundingBox = CBoundingBoxAligned();
for (const SVertex& vertex : vertices)
m_BoundingBox += vertex.m_Position;
m_VB = g_VBMan.AllocateChunk(
sizeof(SVertex), vertices.size(), Renderer::Backend::GL::CBuffer::Type::VERTEX, false);
- if (m_VB) // allocation might fail (e.g. due to too many vertices)
+ // Allocation might fail (e.g. due to too many vertices).
+ if (m_VB)
{
- m_VB->m_Owner->UpdateChunkVertices(m_VB.Get(), &vertices[0]); // copy data into VBO
+ // Copy data into backend buffer.
+ m_VB->m_Owner->UpdateChunkVertices(m_VB.Get(), &vertices[0]);
for (size_t k = 0; k < indices.size(); ++k)
indices[k] += static_cast(m_VB->m_Index);
m_VBIndices = g_VBMan.AllocateChunk(
sizeof(u16), indices.size(), Renderer::Backend::GL::CBuffer::Type::INDEX, false);
if (m_VBIndices)
m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices.Get(), &indices[0]);
}
}
void CTexturedLineRData::CreateLineCap(const SOverlayTexturedLine& line, const CVector3D& corner1, const CVector3D& corner2,
const CVector3D& lineDirectionNormal, SOverlayTexturedLine::LineCapType endCapType, std::vector& verticesOut,
std::vector& indicesOut)
{
if (endCapType == SOverlayTexturedLine::LINECAP_FLAT)
return; // no action needed, this is the default
// When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
// direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
// butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
// That is to say, when viewed from the top, we will have something like
// .
// this: and not like this: /|
// ----+ / |
// | / .
// | /
// ----+ /
//
int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points)
float radius = line.m_Thickness;
CVector3D centerPoint = (corner1 + corner2) * 0.5f;
SVertex centerVertex(centerPoint, 0.5f, 0.5f);
u16 indexOffset = static_cast(verticesOut.size()); // index offset in verticesOut from where we start adding our vertices
switch (endCapType)
{
case SOverlayTexturedLine::LINECAP_SHARP:
{
roundCapPoints = 3; // creates only one point directly ahead
radius *= 1.5f; // make it a bit sharper (note that we don't use the radius for the butt-end corner points so it should be ok)
centerVertex.m_UVs[0] = 0.480f; // slight visual correction to make the texture match up better at the corner points
}
FALLTHROUGH;
case SOverlayTexturedLine::LINECAP_ROUND:
{
// Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the
// line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane.
// The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then
// of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in
// the line's plane, producing the desired rounded cap.
// To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from
// the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use
// the negated angle.
float stepAngle = -(float)(M_PI/(roundCapPoints-1));
// Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards)
// Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector.
// This is because we want to support an overly large radius to make the sharp line ending look sharper.
verticesOut.push_back(centerVertex);
verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
// Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points.
// Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom
// radii to support tuning the 'sharpness' of sharp end caps (see above)
CVector3D rotationBaseVector = (corner2 - centerPoint).Normalized() * radius;
// Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that
// is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line.
// Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water,
// then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky).
CVector3D capPlaneNormal = lineDirectionNormal.Cross(rotationBaseVector).Normalized();
for (int i = 1; i < roundCapPoints - 1; ++i)
{
// Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point.
CQuaternion quatRotation;
quatRotation.FromAxisAngle(capPlaneNormal, i * stepAngle);
CVector3D worldPos3D = centerPoint + quatRotation.Rotate(rotationBaseVector);
// Let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge
// of the texture around the edge of the semicircle)
float u = 0.f;
float v = Clamp((i / static_cast(roundCapPoints - 1)), 0.f, 1.f); // pos, u, v
verticesOut.push_back(SVertex(worldPos3D, u, v));
}
// connect back to the other butt-end corner point to complete the semicircle
verticesOut.push_back(SVertex(corner1, 0.f, 1.f));
// now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the
// first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So:
for (int i=1; i < roundCapPoints; ++i)
{
indicesOut.push_back(indexOffset); // center vertex
indicesOut.push_back(indexOffset + i);
indicesOut.push_back(indexOffset + i + 1);
}
}
break;
case SOverlayTexturedLine::LINECAP_SQUARE:
{
// Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of
// three triangles (sort of like a triangle fan)
// NOTE: The order in which the vertices are pushed out determines the visibility, as they
// are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom.
verticesOut.push_back(centerVertex);
verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
verticesOut.push_back(SVertex(corner2 + (lineDirectionNormal * (line.m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector
verticesOut.push_back(SVertex(corner1 + (lineDirectionNormal * (line.m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector
verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1
for (int i=1; i < 4; ++i)
{
indicesOut.push_back(indexOffset); // center point
indicesOut.push_back(indexOffset + i);
indicesOut.push_back(indexOffset + i + 1);
}
}
break;
default:
break;
}
}
bool CTexturedLineRData::IsVisibleInFrustum(const CFrustum& frustum) const
{
return frustum.IsBoxVisible(m_BoundingBox);
}
Index: ps/trunk/source/renderer/VertexArray.cpp
===================================================================
--- ps/trunk/source/renderer/VertexArray.cpp (revision 26834)
+++ ps/trunk/source/renderer/VertexArray.cpp (revision 26835)
@@ -1,311 +1,311 @@
/* Copyright (C) 2022 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 .
*/
#include "precompiled.h"
#include "lib/alignment.h"
#include "lib/sysdep/rtl.h"
#include "maths/Vector3D.h"
#include "maths/Vector4D.h"
#include "ps/CLogger.h"
#include "graphics/Color.h"
#include "graphics/SColor.h"
#include "renderer/VertexArray.h"
#include "renderer/VertexBuffer.h"
#include "renderer/VertexBufferManager.h"
namespace
{
size_t GetAttributeSize(const Renderer::Backend::Format format)
{
switch (format)
{
case Renderer::Backend::Format::R8G8B8A8_UNORM: FALLTHROUGH;
case Renderer::Backend::Format::R8G8B8A8_UINT:
return sizeof(u8) * 4;
case Renderer::Backend::Format::A8_UNORM:
return sizeof(u8);
case Renderer::Backend::Format::R16_UNORM: FALLTHROUGH;
case Renderer::Backend::Format::R16_UINT: FALLTHROUGH;
case Renderer::Backend::Format::R16_SINT:
return sizeof(u16);
case Renderer::Backend::Format::R16G16_UNORM: FALLTHROUGH;
case Renderer::Backend::Format::R16G16_UINT: FALLTHROUGH;
case Renderer::Backend::Format::R16G16_SINT:
return sizeof(u16) * 2;
case Renderer::Backend::Format::R32_SFLOAT:
return sizeof(float);
case Renderer::Backend::Format::R32G32_SFLOAT:
return sizeof(float) * 2;
case Renderer::Backend::Format::R32G32B32_SFLOAT:
return sizeof(float) * 3;
case Renderer::Backend::Format::R32G32B32A32_SFLOAT:
return sizeof(float) * 4;
default:
break;
};
return 0;
}
} // anonymous namespace
VertexArray::VertexArray(
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic)
: m_Type(type), m_Dynamic(dynamic)
{
m_NumberOfVertices = 0;
m_BackingStore = 0;
m_Stride = 0;
}
VertexArray::~VertexArray()
{
Free();
}
// Free all resources on destruction or when a layout parameter changes
void VertexArray::Free()
{
rtl_FreeAligned(m_BackingStore);
m_BackingStore = 0;
m_VB.Reset();
}
// Set the number of vertices stored in the array
void VertexArray::SetNumberOfVertices(const size_t numberOfVertices)
{
if (numberOfVertices == m_NumberOfVertices)
return;
Free();
m_NumberOfVertices = numberOfVertices;
}
// Add vertex attributes like Position, Normal, UV
void VertexArray::AddAttribute(Attribute* attr)
{
// Attribute is supported is its size is greater than zero.
ENSURE(GetAttributeSize(attr->format) > 0 && "Unsupported attribute.");
attr->vertexArray = this;
m_Attributes.push_back(attr);
Free();
}
// Template specialization for GetIterator().
// We can put this into the source file because only a fixed set of types
// is supported for type safety.
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(
format == Renderer::Backend::Format::R32G32B32_SFLOAT ||
format == Renderer::Backend::Format::R32G32B32A32_SFLOAT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::R32G32B32A32_SFLOAT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::R32G32_SFLOAT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
format == Renderer::Backend::Format::R8G8B8A8_UINT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::R16_UINT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::R16G16_UINT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::A8_UNORM);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(
format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
format == Renderer::Backend::Format::R8G8B8A8_UINT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::R16_SINT);
return vertexArray->MakeIterator(this);
}
template<>
VertexArrayIterator VertexArray::Attribute::GetIterator() const
{
ENSURE(vertexArray);
ENSURE(format == Renderer::Backend::Format::R16G16_SINT);
return vertexArray->MakeIterator(this);
}
static size_t RoundStride(size_t stride)
{
if (stride <= 0)
return 0;
if (stride <= 4)
return 4;
if (stride <= 8)
return 8;
if (stride <= 16)
return 16;
return Align<32>(stride);
}
// Re-layout by assigning offsets on a first-come first-serve basis,
// then round up to a reasonable stride.
-// Backing store is also created here, VBOs are created on upload.
+// Backing store is also created here, backend buffers are created on upload.
void VertexArray::Layout()
{
Free();
m_Stride = 0;
for (ssize_t idx = m_Attributes.size()-1; idx >= 0; --idx)
{
Attribute* attr = m_Attributes[idx];
if (attr->format == Renderer::Backend::Format::UNDEFINED)
continue;
const size_t attrSize = GetAttributeSize(attr->format);
ENSURE(attrSize > 0);
attr->offset = m_Stride;
m_Stride += attrSize;
if (m_Type == Renderer::Backend::GL::CBuffer::Type::VERTEX)
m_Stride = Align<4>(m_Stride);
}
if (m_Type == Renderer::Backend::GL::CBuffer::Type::VERTEX)
m_Stride = RoundStride(m_Stride);
if (m_Stride)
m_BackingStore = (char*)rtl_AllocateAligned(m_Stride * m_NumberOfVertices, 16);
}
void VertexArray::PrepareForRendering()
{
m_VB->m_Owner->PrepareForRendering(m_VB.Get());
}
// (Re-)Upload the attributes.
-// Create the VBO if necessary.
+// Create the backend buffer if necessary.
void VertexArray::Upload()
{
ENSURE(m_BackingStore);
if (!m_VB)
{
m_VB = g_VBMan.AllocateChunk(
m_Stride, m_NumberOfVertices, m_Type, m_Dynamic, m_BackingStore);
}
if (!m_VB)
{
- LOGERROR("Failed to allocate VBO for vertex array");
+ LOGERROR("Failed to allocate backend buffer for vertex array");
return;
}
m_VB->m_Owner->UpdateChunkVertices(m_VB.Get(), m_BackingStore);
}
void VertexArray::UploadIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
m_VB->m_Owner->UploadIfNeeded(deviceCommandContext);
}
// Free the backing store to save some memory
void VertexArray::FreeBackingStore()
{
// In streaming modes, the backing store must be retained
ENSURE(!CVertexBuffer::UseStreaming(m_Dynamic));
rtl_FreeAligned(m_BackingStore);
m_BackingStore = 0;
}
VertexIndexArray::VertexIndexArray(const bool dynamic) :
VertexArray(Renderer::Backend::GL::CBuffer::Type::INDEX, dynamic)
{
m_Attr.format = Renderer::Backend::Format::R16_UINT;
AddAttribute(&m_Attr);
}
VertexArrayIterator VertexIndexArray::GetIterator() const
{
return m_Attr.GetIterator();
}
Index: ps/trunk/source/renderer/VertexBuffer.cpp
===================================================================
--- ps/trunk/source/renderer/VertexBuffer.cpp (revision 26834)
+++ ps/trunk/source/renderer/VertexBuffer.cpp (revision 26835)
@@ -1,325 +1,325 @@
/* Copyright (C) 2022 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 .
*/
#include "precompiled.h"
#include "VertexBuffer.h"
#include "lib/ogl.h"
#include "lib/sysdep/cpu.h"
#include "ps/CLogger.h"
#include "ps/Errors.h"
#include "ps/VideoMode.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include
#include
#include
// Absolute maximum (bytewise) size of each GL vertex buffer object.
// Make it large enough for the maximum feasible mesh size (64K vertexes,
// 64 bytes per vertex in InstancingModelRenderer).
// TODO: measure what influence this has on performance
constexpr std::size_t MAX_VB_SIZE_BYTES = 4 * 1024 * 1024;
CVertexBuffer::CVertexBuffer(
const char* name, const size_t vertexSize,
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic)
: CVertexBuffer(name, vertexSize, type, dynamic, MAX_VB_SIZE_BYTES)
{
}
CVertexBuffer::CVertexBuffer(
const char* name, const size_t vertexSize,
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic,
const size_t maximumBufferSize)
: m_VertexSize(vertexSize), m_HasNeededChunks(false)
{
size_t size = maximumBufferSize;
if (type == Renderer::Backend::GL::CBuffer::Type::VERTEX)
{
// We want to store 16-bit indices to any vertex in a buffer, so the
// buffer must never be bigger than vertexSize*64K bytes since we can
// address at most 64K of them with 16-bit indices
size = std::min(size, vertexSize * 65536);
}
else if (type == Renderer::Backend::GL::CBuffer::Type::INDEX)
{
ENSURE(vertexSize == sizeof(u16));
}
// store max/free vertex counts
m_MaxVertices = m_FreeVertices = size / vertexSize;
m_Buffer = g_VideoMode.GetBackendDevice()->CreateBuffer(
name, type, m_MaxVertices * m_VertexSize, dynamic);
// create sole free chunk
VBChunk* chunk = new VBChunk;
chunk->m_Owner = this;
chunk->m_Count = m_FreeVertices;
chunk->m_Index = 0;
m_FreeList.emplace_back(chunk);
}
CVertexBuffer::~CVertexBuffer()
{
// Must have released all chunks before destroying the buffer
ENSURE(m_AllocList.empty());
m_Buffer.reset();
for (VBChunk* const& chunk : m_FreeList)
delete chunk;
}
bool CVertexBuffer::CompatibleVertexType(
const size_t vertexSize, const Renderer::Backend::GL::CBuffer::Type type,
const bool dynamic) const
{
ENSURE(m_Buffer);
return type == m_Buffer->GetType() && dynamic == m_Buffer->IsDynamic() && vertexSize == m_VertexSize;
}
///////////////////////////////////////////////////////////////////////////////
// Allocate: try to allocate a buffer of given number of vertices (each of
// given size), with the given type, and using the given texture - return null
// if no free chunks available
CVertexBuffer::VBChunk* CVertexBuffer::Allocate(
const size_t vertexSize, const size_t numberOfVertices,
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic,
void* backingStore)
{
// check this is the right kind of buffer
if (!CompatibleVertexType(vertexSize, type, dynamic))
return nullptr;
if (UseStreaming(dynamic))
ENSURE(backingStore != nullptr);
// quick check there's enough vertices spare to allocate
if (numberOfVertices > m_FreeVertices)
return nullptr;
// trawl free list looking for first free chunk with enough space
std::vector::iterator best_iter = m_FreeList.end();
for (std::vector::iterator iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter)
{
if (numberOfVertices == (*iter)->m_Count)
{
best_iter = iter;
break;
}
else if (numberOfVertices < (*iter)->m_Count && (best_iter == m_FreeList.end() || (*best_iter)->m_Count < (*iter)->m_Count))
best_iter = iter;
}
// We could not find a large enough chunk.
if (best_iter == m_FreeList.end())
return nullptr;
VBChunk* chunk = *best_iter;
m_FreeList.erase(best_iter);
m_FreeVertices -= chunk->m_Count;
chunk->m_BackingStore = backingStore;
chunk->m_Dirty = false;
chunk->m_Needed = false;
// split chunk into two; - allocate a new chunk using all unused vertices in the
// found chunk, and add it to the free list
if (chunk->m_Count > numberOfVertices)
{
VBChunk* newchunk = new VBChunk;
newchunk->m_Owner = this;
newchunk->m_Count = chunk->m_Count - numberOfVertices;
newchunk->m_Index = chunk->m_Index + numberOfVertices;
m_FreeList.emplace_back(newchunk);
m_FreeVertices += newchunk->m_Count;
// resize given chunk
chunk->m_Count = numberOfVertices;
}
// return found chunk
m_AllocList.push_back(chunk);
return chunk;
}
///////////////////////////////////////////////////////////////////////////////
// Release: return given chunk to this buffer
void CVertexBuffer::Release(VBChunk* chunk)
{
// Update total free count before potentially modifying this chunk's count
m_FreeVertices += chunk->m_Count;
m_AllocList.erase(std::find(m_AllocList.begin(), m_AllocList.end(), chunk));
// Sorting O(nlogn) shouldn't be too far from O(n) by performance, because
// the container is partly sorted already.
std::sort(
m_FreeList.begin(), m_FreeList.end(),
[](const VBChunk* chunk1, const VBChunk* chunk2) -> bool
{
return chunk1->m_Index < chunk2->m_Index;
});
// Coalesce with any free-list items that are adjacent to this chunk;
// merge the found chunk with the new one, and remove the old one
// from the list.
for (std::vector::iterator iter = m_FreeList.begin(); iter != m_FreeList.end();)
{
if ((*iter)->m_Index == chunk->m_Index + chunk->m_Count
|| (*iter)->m_Index + (*iter)->m_Count == chunk->m_Index)
{
chunk->m_Index = std::min(chunk->m_Index, (*iter)->m_Index);
chunk->m_Count += (*iter)->m_Count;
delete *iter;
iter = m_FreeList.erase(iter);
if (!m_FreeList.empty() && iter != m_FreeList.begin())
iter = std::prev(iter);
}
else
{
++iter;
}
}
m_FreeList.emplace_back(chunk);
}
///////////////////////////////////////////////////////////////////////////////
// UpdateChunkVertices: update vertex data for given chunk
void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk, void* data)
{
ENSURE(m_Buffer);
if (UseStreaming(m_Buffer->IsDynamic()))
{
- // The VBO is now out of sync with the backing store
+ // The backend buffer is now out of sync with the backing store.
chunk->m_Dirty = true;
// Sanity check: Make sure the caller hasn't tried to reallocate
- // their backing store
+ // their backing store.
ENSURE(data == chunk->m_BackingStore);
}
else
{
ENSURE(data);
g_Renderer.GetDeviceCommandContext()->UploadBufferRegion(
m_Buffer.get(), data, chunk->m_Index * m_VertexSize, chunk->m_Count * m_VertexSize);
}
}
void CVertexBuffer::UploadIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (UseStreaming(m_Buffer->IsDynamic()))
{
if (!m_HasNeededChunks)
return;
- // If any chunks are out of sync with the current VBO, and are
- // needed for rendering this frame, we'll need to re-upload the VBO
+ // If any chunks are out of sync with the current backend buffer, and are
+ // needed for rendering this frame, we'll need to re-upload the backend buffer.
bool needUpload = false;
for (VBChunk* const& chunk : m_AllocList)
{
if (chunk->m_Dirty && chunk->m_Needed)
{
needUpload = true;
break;
}
}
if (needUpload)
{
deviceCommandContext->UploadBuffer(m_Buffer.get(), [&](u8* mappedData)
{
#ifndef NDEBUG
// To help detect bugs where PrepareForRendering() was not called,
// force all not-needed data to 0, so things won't get rendered
// with undefined (but possibly still correct-looking) data.
memset(mappedData, 0, m_MaxVertices * m_VertexSize);
#endif
// Copy only the chunks we need. (This condition is helpful when
- // the VBO contains data for every unit in the world, but only a
- // handful are visible on screen and we don't need to bother copying
- // the rest.)
+ // the backend buffer contains data for every unit in the world,
+ // but only a handful are visible on screen and we don't need to
+ // bother copying the rest.)
for (VBChunk* const& chunk : m_AllocList)
if (chunk->m_Needed)
std::memcpy(mappedData + chunk->m_Index * m_VertexSize, chunk->m_BackingStore, chunk->m_Count * m_VertexSize);
});
// Anything we just uploaded is clean; anything else is dirty
- // since the rest of the VBO content is now undefined
+ // since the rest of the backend buffer content is now undefined
for (VBChunk* const& chunk : m_AllocList)
{
if (chunk->m_Needed)
{
chunk->m_Dirty = false;
chunk->m_Needed = false;
}
else
chunk->m_Dirty = true;
}
}
else
{
// Reset the flags for the next phase.
for (VBChunk* const& chunk : m_AllocList)
chunk->m_Needed = false;
}
m_HasNeededChunks = false;
}
}
size_t CVertexBuffer::GetBytesReserved() const
{
return MAX_VB_SIZE_BYTES;
}
size_t CVertexBuffer::GetBytesAllocated() const
{
return (m_MaxVertices - m_FreeVertices) * m_VertexSize;
}
void CVertexBuffer::DumpStatus() const
{
debug_printf("freeverts = %d\n", static_cast(m_FreeVertices));
size_t maxSize = 0;
for (VBChunk* const& chunk : m_FreeList)
{
debug_printf("free chunk %p: size=%d\n", static_cast(chunk), static_cast(chunk->m_Count));
maxSize = std::max(chunk->m_Count, maxSize);
}
debug_printf("max size = %d\n", static_cast(maxSize));
}
bool CVertexBuffer::UseStreaming(const bool dynamic)
{
return dynamic;
}
void CVertexBuffer::PrepareForRendering(VBChunk* chunk)
{
chunk->m_Needed = true;
m_HasNeededChunks = true;
}
Index: ps/trunk/source/renderer/VertexBuffer.h
===================================================================
--- ps/trunk/source/renderer/VertexBuffer.h (revision 26834)
+++ ps/trunk/source/renderer/VertexBuffer.h (revision 26835)
@@ -1,162 +1,163 @@
/* Copyright (C) 2022 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 .
*/
/*
- * encapsulation of VBOs with batching and sharing
+ * Encapsulation of backend buffers with batching and sharing.
*/
#ifndef INCLUDED_VERTEXBUFFER
#define INCLUDED_VERTEXBUFFER
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include
#include
/**
- * CVertexBuffer: encapsulation of ARB_vertex_buffer_object, also supplying
+ * CVertexBuffer: encapsulation of backend buffers, also supplying
* some additional functionality for sharing buffers between multiple objects.
*
* The class can be used in two modes, depending on the usage parameter:
*
* Static buffer: Call Allocate() with backingStore = nullptr. Then call
* UpdateChunkVertices() with any pointer - the data will be immediately copied
- * to the VBO. This should be used for vertex data that rarely changes.
+ * to the buffer. This should be used for vertex data that rarely changes.
*
* Dynamic buffer: Call Allocate() with backingStore pointing
* at some memory that will remain valid for the lifetime of the CVertexBuffer.
* This should be used for vertex data that may change every frame.
* Rendering is expected to occur in two phases:
* - "Prepare" phase:
* If this chunk is going to be used for rendering during the next rendering phase,
* you must call PrepareForRendering().
* If the vertex data in backingStore has been modified since the last uploading phase,
* you must call UpdateChunkVertices().
* - "Upload" phase:
* UploadedIfNeeded() can be called (multiple times). The vertex data will be uploaded
* to the GPU if necessary.
* It is okay to have multiple prepare/upload cycles per frame (though slightly less
* efficient), but they must occur sequentially.
*/
class CVertexBuffer
{
NONCOPYABLE(CVertexBuffer);
public:
- /// VBChunk: describes a portion of this vertex buffer
+ // VBChunk: describes a portion of this vertex buffer
struct VBChunk
{
- /// Owning (parent) vertex buffer
+ // Owning (parent) vertex buffer
CVertexBuffer* m_Owner;
- /// Start index of this chunk in owner
+ // Start index of this chunk in owner
size_t m_Index;
- /// Number of vertices used by chunk
+ // Number of vertices used by chunk
size_t m_Count;
- /// If UseStreaming() is true, points at the data for this chunk
+ // If UseStreaming() is true, points at the data for this chunk
void* m_BackingStore;
- /// If true, the VBO is not consistent with the chunk's backing store
- /// (and will need to be re-uploaded before rendering with this chunk)
+ // If true, the backend buffer is not consistent with the chunk's
+ // backing store (and will need to be re-uploaded before rendering with
+ // this chunk).
bool m_Dirty;
- /// If true, we have been told this chunk is going to be used for
- /// rendering in the next uploading phase and will need to be uploaded
+ // If true, we have been told this chunk is going to be used for
+ // rendering in the next uploading phase and will need to be uploaded
bool m_Needed;
private:
// Only CVertexBuffer can construct/delete these
- // (Other people should use g_VBMan.Allocate, g_VBMan.Release)
+ // (Other people should use g_VBMan.AllocateChunk)
friend class CVertexBuffer;
VBChunk() {}
~VBChunk() {}
};
public:
// constructor, destructor
CVertexBuffer(
const char* name, const size_t vertexSize,
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic);
CVertexBuffer(
const char* name, const size_t vertexSize,
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic,
const size_t maximumBufferSize);
~CVertexBuffer();
void UploadIfNeeded(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/// Make the vertex data available for the next usage.
void PrepareForRendering(VBChunk* chunk);
/// Update vertex data for given chunk. Transfers the provided data to the actual OpenGL vertex buffer.
void UpdateChunkVertices(VBChunk* chunk, void* data);
size_t GetVertexSize() const { return m_VertexSize; }
size_t GetBytesReserved() const;
size_t GetBytesAllocated() const;
/// Returns true if this vertex buffer is compatible with the specified vertex type and intended usage.
bool CompatibleVertexType(
const size_t vertexSize, const Renderer::Backend::GL::CBuffer::Type type,
const bool dynamic) const;
void DumpStatus() const;
/**
* Given the usage flags of a buffer that has been (or will be) allocated:
*
* If true, we assume the buffer is going to be modified on every frame,
* so we will re-upload the entire buffer every frame using glMapBuffer.
* This requires the buffer's owner to hold onto its backing store.
*
* If false, we assume it will change rarely, and use direct upload to
* update it incrementally. The backing store can be freed to save memory.
*/
static bool UseStreaming(const bool dynamic);
Renderer::Backend::GL::CBuffer* GetBuffer() { return m_Buffer.get(); }
private:
friend class CVertexBufferManager; // allow allocate only via CVertexBufferManager
/// Try to allocate a buffer of given number of vertices (each of given size),
/// and with the given type - return null if no free chunks available
VBChunk* Allocate(
const size_t vertexSize, const size_t numberOfVertices,
const Renderer::Backend::GL::CBuffer::Type type, const bool dynamic,
void* backingStore);
/// Return given chunk to this buffer
void Release(VBChunk* chunk);
/// Vertex size of this vertex buffer
size_t m_VertexSize;
/// Number of vertices of above size in this buffer
size_t m_MaxVertices;
/// List of free chunks in this buffer
std::vector m_FreeList;
/// List of allocated chunks
std::vector m_AllocList;
/// Available free vertices - total of all free vertices in the free list
size_t m_FreeVertices;
std::unique_ptr m_Buffer;
bool m_HasNeededChunks;
};
#endif // INCLUDED_VERTEXBUFFER
Index: ps/trunk/source/renderer/VertexBufferManager.cpp
===================================================================
--- ps/trunk/source/renderer/VertexBufferManager.cpp (revision 26834)
+++ ps/trunk/source/renderer/VertexBufferManager.cpp (revision 26835)
@@ -1,209 +1,208 @@
/* Copyright (C) 2022 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 .
*/
#include "precompiled.h"
#include "VertexBufferManager.h"
#include "lib/ogl.h"
#include "ps/CLogger.h"
-#include "ps/CStr.h"
#define DUMP_VB_STATS 0 // for debugging
namespace
{
const char* GetBufferTypeName(
const Renderer::Backend::GL::CBuffer::Type type)
{
const char* name = "UnknownBuffer";
switch (type)
{
case Renderer::Backend::GL::CBuffer::Type::VERTEX:
name = "VertexBuffer";
break;
case Renderer::Backend::GL::CBuffer::Type::INDEX:
name = "IndexBuffer";
break;
default:
debug_warn("Unknown buffer type");
break;
}
return name;
}
const char* GetGroupName(
const CVertexBufferManager::Group group)
{
const char* name = "Unknown";
switch (group)
{
case CVertexBufferManager::Group::DEFAULT:
name = "Default";
break;
case CVertexBufferManager::Group::TERRAIN:
name = "Terrain";
break;
case CVertexBufferManager::Group::WATER:
name = "Water";
break;
default:
debug_warn("Unknown buffer group");
break;
}
return name;
}
} // anonymous namespace
CVertexBufferManager g_VBMan;
CVertexBufferManager::Handle::Handle(Handle&& other)
: m_Chunk(other.m_Chunk)
{
other.m_Chunk = nullptr;
}
CVertexBufferManager::Handle& CVertexBufferManager::Handle::operator=(Handle&& other)
{
if (&other == this)
return *this;
if (IsValid())
Reset();
Handle tmp(std::move(other));
swap(*this, tmp);
return *this;
}
CVertexBufferManager::Handle::Handle(CVertexBuffer::VBChunk* chunk)
: m_Chunk(chunk)
{
}
void CVertexBufferManager::Handle::Reset()
{
if (!IsValid())
return;
g_VBMan.Release(m_Chunk);
m_Chunk = nullptr;
}
// Explicit shutdown of the vertex buffer subsystem.
// This avoids the ordering issues that arise when using destructors of
// global instances.
void CVertexBufferManager::Shutdown()
{
for (int group = static_cast(Group::DEFAULT); group < static_cast(Group::COUNT); ++group)
m_Buffers[group].clear();
}
/**
* AllocateChunk: try to allocate a buffer of given number of vertices (each of
* given size), with the given type, and using the given texture - return null
* if no free chunks available
*/
CVertexBufferManager::Handle CVertexBufferManager::AllocateChunk(
const size_t vertexSize, const size_t numberOfVertices,
const Renderer::Backend::GL::CBuffer::Type type,
const bool dynamic, void* backingStore, Group group)
{
ENSURE(vertexSize > 0);
ENSURE(numberOfVertices > 0);
CVertexBuffer::VBChunk* result = nullptr;
if (CVertexBuffer::UseStreaming(dynamic))
ENSURE(backingStore != NULL);
// TODO, RC - run some sanity checks on allocation request
std::vector>& buffers = m_Buffers[static_cast(group)];
#if DUMP_VB_STATS
debug_printf("\n============================\n# allocate vsize=%zu nverts=%zu\n\n", vertexSize, numVertices);
for (const std::unique_ptr& buffer : buffers)
{
if (buffer->CompatibleVertexType(vertexSize, type, dynamic))
{
debug_printf("%p\n", buffer.get());
buffer->DumpStatus();
}
}
#endif
// iterate through all existing buffers testing for one that'll
// satisfy the allocation
for (const std::unique_ptr& buffer : buffers)
{
result = buffer->Allocate(vertexSize, numberOfVertices, type, dynamic, backingStore);
if (result)
return Handle(result);
}
char bufferName[64] = {0};
snprintf(
bufferName, std::size(bufferName), "%s (%s, %zu%s)",
GetBufferTypeName(type), GetGroupName(group), vertexSize, (dynamic ? ", dynamic" : ""));
// got this far; need to allocate a new buffer
buffers.emplace_back(
group == Group::DEFAULT
? std::make_unique(bufferName, vertexSize, type, dynamic)
// Reduces the buffer size for not so frequent buffers.
: std::make_unique(bufferName, vertexSize, type, dynamic, 1024 * 1024));
result = buffers.back()->Allocate(vertexSize, numberOfVertices, type, dynamic, backingStore);
if (!result)
{
- LOGERROR("Failed to create VBOs (%zu*%zu)", vertexSize, numberOfVertices);
+ LOGERROR("Failed to create backend buffer (%zu*%zu)", vertexSize, numberOfVertices);
return Handle();
}
return Handle(result);
}
void CVertexBufferManager::Release(CVertexBuffer::VBChunk* chunk)
{
ENSURE(chunk);
#if DUMP_VB_STATS
debug_printf("\n============================\n# release %p nverts=%zu\n\n", chunk, chunk->m_Count);
#endif
chunk->m_Owner->Release(chunk);
}
size_t CVertexBufferManager::GetBytesReserved() const
{
size_t total = 0;
for (int group = static_cast(Group::DEFAULT); group < static_cast(Group::COUNT); ++group)
for (const std::unique_ptr& buffer : m_Buffers[static_cast(group)])
total += buffer->GetBytesReserved();
return total;
}
size_t CVertexBufferManager::GetBytesAllocated() const
{
size_t total = 0;
for (int group = static_cast(Group::DEFAULT); group < static_cast(Group::COUNT); ++group)
for (const std::unique_ptr& buffer : m_Buffers[static_cast(group)])
total += buffer->GetBytesAllocated();
return total;
}