Index: ps/trunk/source/graphics/LOSTexture.cpp
===================================================================
--- ps/trunk/source/graphics/LOSTexture.cpp (revision 26198)
+++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26199)
@@ -1,427 +1,424 @@
/* 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 "LOSTexture.h"
#include "graphics/ShaderManager.h"
#include "lib/bits.h"
#include "lib/config2.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/TimeManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/helpers/Los.h"
/*
The LOS bitmap is computed with one value per LOS vertex, based on
CCmpRangeManager's visibility information.
The bitmap is then blurred using an NxN filter (in particular a
7-tap Binomial filter as an efficient integral approximation of a Gaussian).
To implement the blur efficiently without using extra memory for a second copy
of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side,
then the blur shifts the image back into the corner.
The blurred bitmap is then uploaded into a GL texture for use by the renderer.
*/
// Blur with a NxN filter, where N = g_BlurSize must be an odd number.
// Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES.
static const size_t g_BlurSize = 7;
// Alignment (in bytes) of the pixel data passed into texture uploading.
// This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since
// that's what we set it to) but in some weird cases appears to have a different
// value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway.
static const size_t g_SubTextureAlignment = 4;
CLOSTexture::CLOSTexture(CSimulation2& simulation)
: m_Simulation(simulation)
{
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
CreateShader();
}
CLOSTexture::~CLOSTexture()
{
if (m_SmoothFBO1)
glDeleteFramebuffersEXT(1, &m_SmoothFBO1);
if (m_SmoothFBO2)
glDeleteFramebuffersEXT(1, &m_SmoothFBO2);
if (m_Texture)
DeleteTexture();
}
// Create the LOS texture engine. Should be ran only once.
bool CLOSTexture::CreateShader()
{
m_SmoothTech = g_Renderer.GetShaderManager().LoadEffect(str_los_interp);
CShaderProgramPtr shader = m_SmoothTech->GetShader();
m_ShaderInitialized = m_SmoothTech && shader;
if (!m_ShaderInitialized)
{
LOGERROR("Failed to load SmoothLOS shader, disabling.");
g_RenderingOptions.SetSmoothLOS(false);
return false;
}
glGenFramebuffersEXT(1, &m_SmoothFBO1);
glGenFramebuffersEXT(1, &m_SmoothFBO2);
return true;
}
void CLOSTexture::DeleteTexture()
{
m_Texture.reset();
m_TextureSmooth1.reset();
m_TextureSmooth2.reset();
}
void CLOSTexture::MakeDirty()
{
m_Dirty = true;
}
Renderer::Backend::GL::CTexture* CLOSTexture::GetTextureSmooth()
{
if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS())
return GetTexture();
else
return (m_WhichTex ? m_TextureSmooth1 : m_TextureSmooth2).get();
}
void CLOSTexture::InterpolateLOS(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
const bool skipSmoothLOS = CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS();
if (!skipSmoothLOS && !m_ShaderInitialized)
{
if (!CreateShader())
return;
// RecomputeTexture will not cause the ConstructTexture to run.
// Force the textures to be created.
DeleteTexture();
ConstructTexture(deviceCommandContext);
m_Dirty = true;
}
if (m_Dirty)
{
RecomputeTexture(deviceCommandContext);
m_Dirty = false;
}
if (skipSmoothLOS)
return;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, (m_WhichTex ? m_SmoothFBO2 : m_SmoothFBO1));
m_SmoothTech->BeginPass();
CShaderProgramPtr shader = m_SmoothTech->GetShader();
glDisable(GL_BLEND);
- shader->Bind();
-
shader->BindTexture(str_losTex1, m_Texture.get());
shader->BindTexture(str_losTex2, (m_WhichTex ? m_TextureSmooth1 : m_TextureSmooth2).get());
shader->Uniform(str_delta, (float)g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f, 0.0f, 0.0f, 0.0f);
const SViewPort oldVp = g_Renderer.GetViewport();
const SViewPort vp =
{
0, 0,
static_cast(m_Texture->GetWidth()),
static_cast(m_Texture->GetHeight())
};
g_Renderer.SetViewport(vp);
float quadVerts[] =
{
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] =
{
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
g_Renderer.SetViewport(oldVp);
- shader->Unbind();
m_SmoothTech->EndPass();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
m_WhichTex = !m_WhichTex;
}
Renderer::Backend::GL::CTexture* CLOSTexture::GetTexture()
{
ENSURE(!m_Dirty);
return m_Texture.get();
}
const CMatrix3D& CLOSTexture::GetTextureMatrix()
{
ENSURE(!m_Dirty);
return m_TextureMatrix;
}
const CMatrix3D& CLOSTexture::GetMinimapTextureMatrix()
{
ENSURE(!m_Dirty);
return m_MinimapTextureMatrix;
}
void CLOSTexture::ConstructTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
m_MapSize = cmpRangeManager->GetVerticesPerSide();
const size_t textureSize = round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment));
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
m_Texture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
// Initialise texture with SoD color, for the areas we don't
// overwrite with uploading later.
std::unique_ptr texData = std::make_unique(textureSize * textureSize);
memset(texData.get(), 0x00, textureSize * textureSize);
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
{
m_TextureSmooth1 = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
m_TextureSmooth2 = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_SmoothFBO1);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
m_TextureSmooth1->GetHandle(), 0);
GLenum status1 = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_SmoothFBO2);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
m_TextureSmooth2->GetHandle(), 0);
GLenum status2 = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status1 != GL_FRAMEBUFFER_COMPLETE_EXT || status2 != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("LOS framebuffer object incomplete: 0x%04X 0x%04X", status1, status2);
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
deviceCommandContext->UploadTexture(m_TextureSmooth1.get(), Renderer::Backend::Format::A8, texData.get(), textureSize * textureSize);
deviceCommandContext->UploadTexture(m_TextureSmooth2.get(), Renderer::Backend::Format::A8, texData.get(), textureSize * textureSize);
}
deviceCommandContext->UploadTexture(m_Texture.get(), Renderer::Backend::Format::A8, texData.get(), textureSize * textureSize);
texData.reset();
{
// Texture matrix: We want to map
// world pos (0, y, 0) (i.e. first vertex)
// onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel);
// world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex)
// onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel)
float s = (m_MapSize-1) / static_cast(textureSize * (m_MapSize-1) * LOS_TILE_SIZE);
float t = 0.5f / textureSize;
m_TextureMatrix.SetZero();
m_TextureMatrix._11 = s;
m_TextureMatrix._23 = s;
m_TextureMatrix._14 = t;
m_TextureMatrix._24 = t;
m_TextureMatrix._44 = 1;
}
{
// Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize)
float s = m_MapSize / (float)textureSize;
m_MinimapTextureMatrix.SetZero();
m_MinimapTextureMatrix._11 = s;
m_MinimapTextureMatrix._22 = s;
m_MinimapTextureMatrix._44 = 1;
}
}
void CLOSTexture::RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
// If the map was resized, delete and regenerate the texture
if (m_Texture)
{
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager || m_MapSize != cmpRangeManager->GetVerticesPerSide())
DeleteTexture();
}
bool recreated = false;
if (!m_Texture)
{
ConstructTexture(deviceCommandContext);
recreated = true;
}
PROFILE("recompute LOS texture");
size_t pitch;
const size_t dataSize = GetBitmapSize(m_MapSize, m_MapSize, &pitch);
ENSURE(pitch * m_MapSize <= dataSize);
std::unique_ptr losData = std::make_unique(dataSize);
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()));
GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch);
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS() && recreated)
{
deviceCommandContext->UploadTextureRegion(
m_TextureSmooth1.get(), Renderer::Backend::Format::A8, losData.get(),
pitch * m_MapSize, 0, 0, pitch, m_MapSize);
deviceCommandContext->UploadTextureRegion(
m_TextureSmooth2.get(), Renderer::Backend::Format::A8, losData.get(),
pitch * m_MapSize, 0, 0, pitch, m_MapSize);
}
deviceCommandContext->UploadTextureRegion(
m_Texture.get(), Renderer::Backend::Format::A8, losData.get(),
pitch * m_MapSize, 0, 0, pitch, m_MapSize);
}
size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch)
{
*pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment);
return *pitch * (h + g_BlurSize - 1);
}
void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch)
{
u8 *dataPtr = losData;
// Initialise the top padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
for (size_t j = 0; j < h; ++j)
{
// Initialise the left padding
for (size_t i = 0; i < g_BlurSize/2; ++i)
*dataPtr++ = 0;
// Fill in the visibility data
for (size_t i = 0; i < w; ++i)
{
if (los.IsVisible_UncheckedRange(i, j))
*dataPtr++ = 255;
else if (los.IsExplored_UncheckedRange(i, j))
*dataPtr++ = 127;
else
*dataPtr++ = 0;
}
// Initialise the right padding
for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i)
*dataPtr++ = 0;
}
// Initialise the bottom padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
// Horizontal blur:
for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0] +
6*d[1] +
15*d[2] +
20*d[3] +
15*d[4] +
6*d[5] +
1*d[6]
) / 64;
}
}
// Vertical blur:
for (size_t j = 0; j < h; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0*pitch] +
6*d[1*pitch] +
15*d[2*pitch] +
20*d[3*pitch] +
15*d[4*pitch] +
6*d[5*pitch] +
1*d[6*pitch]
) / 64;
}
}
}
Index: ps/trunk/source/renderer/OverlayRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26198)
+++ ps/trunk/source/renderer/OverlayRenderer.cpp (revision 26199)
@@ -1,767 +1,764 @@
/* 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).
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.
void Initialize();
};
const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f;
OverlayRendererInternals::OverlayRendererInternals()
: quadVertices(GL_DYNAMIC_DRAW), quadIndices(GL_STATIC_DRAW)
{
quadAttributePos.elems = 3;
quadAttributePos.type = GL_FLOAT;
quadVertices.AddAttribute(&quadAttributePos);
quadAttributeColor.elems = 4;
quadAttributeColor.type = GL_FLOAT;
quadVertices.AddAttribute(&quadAttributeColor);
quadAttributeUV.elems = 2;
quadAttributeUV.type = GL_SHORT; // don't use GL_UNSIGNED_SHORT here, TexCoordPointer won't accept it
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),
// because their creation depends on the shader path, which is not reliably set before this point.
quadVertices.SetNumVertices(MAX_QUAD_OVERLAYS * 4);
quadVertices.Layout(); // allocate backing store
quadIndices.SetNumVertices(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];
// TODO: this is kind of ugly, the iterator should use a type that can have quad->m_Color assigned
// to it directly
const CVector4D quadColor(quad->m_Color.r, quad->m_Color.g, quad->m_Color.b, quad->m_Color.a);
*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()
{
PROFILE3_GPU("overlays (before)");
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderOverlaysBeforeWater for GLES
#else
glEnable(GL_BLEND);
// Ignore z so that we draw behind terrain (but don't disable GL_DEPTH_TEST
// since we still want to write to the z buffer)
glDepthFunc(GL_ALWAYS);
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));
}
glDepthFunc(GL_LEQUAL);
glDisable(GL_BLEND);
#endif
}
void OverlayRenderer::RenderOverlaysAfterWater()
{
PROFILE3_GPU("overlays (after)");
RenderTexturedOverlayLines();
RenderQuadOverlays();
RenderSphereOverlays();
}
void OverlayRenderer::RenderTexturedOverlayLines()
{
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderTexturedOverlayLines for GLES
return;
#endif
if (m->texlines.empty())
return;
ogl_WarnIfError();
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_BLEND);
glDepthMask(0);
CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
// ----------------------------------------------------------------------------------------
CShaderTechniquePtr shaderTechTexLineNormal = GetOverlayLineShaderTechnique(m->defsOverlayLineNormal);
if (shaderTechTexLineNormal)
{
shaderTechTexLineNormal->BeginPass();
CShaderProgramPtr shaderTexLineNormal = shaderTechTexLineNormal->GetShader();
- shaderTexLineNormal->Bind();
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(shaderTexLineNormal, false);
shaderTechTexLineNormal->EndPass();
}
// ----------------------------------------------------------------------------------------
CShaderTechniquePtr shaderTechTexLineAlwaysVisible = GetOverlayLineShaderTechnique(m->defsOverlayLineAlwaysVisible);
if (shaderTechTexLineAlwaysVisible)
{
shaderTechTexLineAlwaysVisible->BeginPass();
CShaderProgramPtr shaderTexLineAlwaysVisible = shaderTechTexLineAlwaysVisible->GetShader();
- shaderTexLineAlwaysVisible->Bind();
// 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(shaderTexLineAlwaysVisible, true);
shaderTechTexLineAlwaysVisible->EndPass();
}
// ----------------------------------------------------------------------------------------
// TODO: the shaders should probably be responsible for unbinding their textures
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(0, 0);
CVertexBuffer::Unbind();
glDepthMask(1);
glDisable(GL_BLEND);
}
void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible)
{
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
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(*line, shader);
}
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
void OverlayRenderer::RenderQuadOverlays()
{
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderQuadOverlays for GLES
return;
#endif
if (m->quadBatchMap.empty())
return;
CShaderTechniquePtr shaderTech = GetOverlayLineShaderTechnique(m->defsQuadOverlay);
if (!shaderTech)
return;
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_BLEND);
glDepthMask(0);
CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
- shader->Bind();
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());
// Base offsets (in bytes) of the two backing stores relative to their owner VBO
u8* indexBase = m->quadIndices.Bind();
u8* vertexBase = m->quadVertices.Bind();
GLsizei indexStride = m->quadIndices.GetStride();
GLsizei vertexStride = m->quadVertices.GetStride();
for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
{
QuadBatchData& batchRenderData = it->second;
const size_t batchNumQuads = batchRenderData.m_NumRenderQuads;
// Careful; some drivers don't like drawing calls with 0 stuff to draw.
if (batchNumQuads == 0)
continue;
const QuadBatchKey& maskPair = it->first;
shader->BindTexture(str_baseTex, maskPair.m_Texture);
shader->BindTexture(str_maskTex, maskPair.m_TextureMask);
int streamflags = shader->GetStreamFlags();
if (streamflags & STREAM_POS)
shader->VertexPointer(m->quadAttributePos.elems, m->quadAttributePos.type, vertexStride, vertexBase + m->quadAttributePos.offset);
if (streamflags & STREAM_UV0)
shader->TexCoordPointer(GL_TEXTURE0, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset);
if (streamflags & STREAM_UV1)
shader->TexCoordPointer(GL_TEXTURE1, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset);
if (streamflags & STREAM_COLOR)
shader->ColorPointer(m->quadAttributeColor.elems, m->quadAttributeColor.type, vertexStride, vertexBase + m->quadAttributeColor.offset);
shader->AssertPointersBound();
glDrawElements(GL_TRIANGLES, (GLsizei)(batchNumQuads * 6), GL_UNSIGNED_SHORT, indexBase + indexStride * batchRenderData.m_IndicesBase);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2;
}
shaderTech->EndPass();
// TODO: the shader should probably be responsible for unbinding its textures
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(0, 0);
CVertexBuffer::Unbind();
glDepthMask(1);
glDisable(GL_BLEND);
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
{
PROFILE3_GPU("overlays (fg)");
#if CONFIG2_GLES
UNUSED2(viewCamera);
#warning TODO: implement OverlayRenderer::RenderForegroundOverlays for GLES
#else
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
CVector3D right = -viewCamera.GetOrientation().GetLeft();
CVector3D up = viewCamera.GetOrientation().GetUp();
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_foreground_overlay);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
float uvs[8] = { 0,1, 1,1, 1,0, 0,0 };
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, sizeof(float)*2, &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)
shader->BindTexture(str_baseTex, sprite->m_Texture);
shader->Uniform(str_colorMul, sprite->m_Color);
CVector3D pos[4] = {
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_Y1
};
shader->VertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X);
glDrawArrays(GL_QUADS, 0, (GLsizei)4);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris += 2;
}
tech->EndPass();
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
if (g_Renderer.GetSceneRenderer().GetOverlayRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
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()
{
PROFILE3_GPU("overlays (spheres)");
#if CONFIG2_GLES
#warning TODO: implement OverlayRenderer::RenderSphereOverlays for GLES
#else
if (m->spheres.empty())
return;
glEnable(GL_BLEND);
glDepthMask(0);
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
tech = g_Renderer.GetShaderManager().LoadEffect(str_overlay_solid);
tech->BeginPass();
shader = tech->GetShader();
m->GenerateSphere();
shader->VertexPointer(3, GL_FLOAT, 0, &m->sphereVertexes[0]);
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_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
shader->Uniform(str_instancingTransform, transform);
shader->Uniform(str_color, sphere->m_Color);
glDrawElements(GL_TRIANGLES, m->sphereIndexes.size(), GL_UNSIGNED_SHORT, &m->sphereIndexes[0]);
g_Renderer.GetStats().m_DrawCalls++;
g_Renderer.GetStats().m_OverlayTris = m->sphereIndexes.size()/3;
}
tech->EndPass();
glDepthMask(1);
glDisable(GL_BLEND);
#endif
}
Index: ps/trunk/source/renderer/PostprocManager.cpp
===================================================================
--- ps/trunk/source/renderer/PostprocManager.cpp (revision 26198)
+++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26199)
@@ -1,834 +1,832 @@
/* 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 "renderer/PostprocManager.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
#include "lib/bits.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "ps/ConfigDB.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#if !CONFIG2_GLES
CPostprocManager::CPostprocManager()
: m_IsInitialized(false), m_PingFbo(0), m_PongFbo(0), m_PostProcEffect(L"default"),
m_BloomFbo(0), m_WhichBuffer(true), m_Sharpness(0.3f), m_UsingMultisampleBuffer(false),
m_MultisampleFBO(0), m_MultisampleCount(0)
{
}
CPostprocManager::~CPostprocManager()
{
Cleanup();
}
void CPostprocManager::Cleanup()
{
if (!m_IsInitialized) // Only cleanup if previously used
return;
if (m_PingFbo) glDeleteFramebuffersEXT(1, &m_PingFbo);
if (m_PongFbo) glDeleteFramebuffersEXT(1, &m_PongFbo);
if (m_BloomFbo) glDeleteFramebuffersEXT(1, &m_BloomFbo);
m_PingFbo = m_PongFbo = m_BloomFbo = 0;
m_ColorTex1.reset();
m_ColorTex2.reset();
m_DepthTex.reset();
m_BlurTex2a.reset();
m_BlurTex2b.reset();
m_BlurTex4a.reset();
m_BlurTex4b.reset();
m_BlurTex8a.reset();
m_BlurTex8b.reset();
}
void CPostprocManager::Initialize()
{
if (m_IsInitialized)
return;
GLint maxSamples = 0;
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
const GLsizei possibleSampleCounts[] = {2, 4, 8, 16};
std::copy_if(
std::begin(possibleSampleCounts), std::end(possibleSampleCounts),
std::back_inserter(m_AllowedSampleCounts),
[maxSamples](const GLsizei sampleCount) { return sampleCount <= maxSamples; } );
// The screen size starts out correct and then must be updated with Resize()
m_Width = g_Renderer.GetWidth();
m_Height = g_Renderer.GetHeight();
RecreateBuffers();
m_IsInitialized = true;
// Once we have initialised the buffers, we can update the techniques.
UpdateAntiAliasingTechnique();
UpdateSharpeningTechnique();
UpdateSharpnessFactor();
// This might happen after the map is loaded and the effect chosen
SetPostEffect(m_PostProcEffect);
}
void CPostprocManager::Resize()
{
m_Width = g_Renderer.GetWidth();
m_Height = g_Renderer.GetHeight();
// If the buffers were intialized, recreate them to the new size.
if (m_IsInitialized)
RecreateBuffers();
}
void CPostprocManager::RecreateBuffers()
{
Cleanup();
#define GEN_BUFFER_RGBA(name, w, h) \
name = Renderer::Backend::GL::CTexture::Create2D( \
Renderer::Backend::Format::R8G8B8A8, w, h, \
Renderer::Backend::Sampler::MakeDefaultSampler( \
Renderer::Backend::Sampler::Filter::LINEAR, \
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
// Two fullscreen ping-pong textures.
GEN_BUFFER_RGBA(m_ColorTex1, m_Width, m_Height);
GEN_BUFFER_RGBA(m_ColorTex2, m_Width, m_Height);
// Textures for several blur sizes. It would be possible to reuse
// m_BlurTex2b, thus avoiding the need for m_BlurTex4b and m_BlurTex8b, though given
// that these are fairly small it's probably not worth complicating the coordinates passed
// to the blur helper functions.
GEN_BUFFER_RGBA(m_BlurTex2a, m_Width / 2, m_Height / 2);
GEN_BUFFER_RGBA(m_BlurTex2b, m_Width / 2, m_Height / 2);
GEN_BUFFER_RGBA(m_BlurTex4a, m_Width / 4, m_Height / 4);
GEN_BUFFER_RGBA(m_BlurTex4b, m_Width / 4, m_Height / 4);
GEN_BUFFER_RGBA(m_BlurTex8a, m_Width / 8, m_Height / 8);
GEN_BUFFER_RGBA(m_BlurTex8b, m_Width / 8, m_Height / 8);
#undef GEN_BUFFER_RGBA
// Allocate the Depth/Stencil texture.
m_DepthTex = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D24_S8, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
glBindTexture(GL_TEXTURE_2D, m_DepthTex->GetHandle());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glBindTexture(GL_TEXTURE_2D, 0);
// Set up the framebuffers with some initial textures.
glGenFramebuffersEXT(1, &m_PingFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, m_ColorTex1->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Framebuffer object incomplete (A): 0x%04X", status);
}
glGenFramebuffersEXT(1, &m_PongFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, m_ColorTex2->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0);
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Framebuffer object incomplete (B): 0x%04X", status);
}
glGenFramebuffersEXT(1, &m_BloomFbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo);
/*
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, m_BloomTex1, 0);
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Framebuffer object incomplete (B): 0x%04X", status);
}
*/
if (m_UsingMultisampleBuffer)
{
DestroyMultisampleBuffer();
CreateMultisampleBuffer();
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
void CPostprocManager::ApplyBlurDownscale2x(
Renderer::Backend::GL::CTexture* inTex, Renderer::Backend::GL::CTexture* outTex, int inWidth, int inHeight)
{
// Bind inTex to framebuffer for rendering.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, outTex->GetHandle(), 0);
// Get bloom shader with instructions to simply copy texels.
CShaderDefines defines;
defines.Add(str_BLOOM_NOP, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->BindTexture(str_renderedTex, inTex);
const SViewPort oldVp = g_Renderer.GetViewport();
const SViewPort vp = { 0, 0, inWidth / 2, inHeight / 2 };
g_Renderer.SetViewport(vp);
float quadVerts[] =
{
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] =
{
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
g_Renderer.SetViewport(oldVp);
tech->EndPass();
}
void CPostprocManager::ApplyBlurGauss(
Renderer::Backend::GL::CTexture* inOutTex, Renderer::Backend::GL::CTexture* tempTex, int inWidth, int inHeight)
{
// Set tempTex as our rendering target.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tempTex->GetHandle(), 0);
// Get bloom shader, for a horizontal Gaussian blur pass.
CShaderDefines defines2;
defines2.Add(str_BLOOM_PASS_H, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines2);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->BindTexture(str_renderedTex, inOutTex);
shader->Uniform(str_texSize, inWidth, inHeight, 0.0f, 0.0f);
const SViewPort oldVp = g_Renderer.GetViewport();
const SViewPort vp = { 0, 0, inWidth, inHeight };
g_Renderer.SetViewport(vp);
float quadVerts[] =
{
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] =
{
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
g_Renderer.SetViewport(oldVp);
tech->EndPass();
// Set result texture as our render target.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_BloomFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, inOutTex->GetHandle(), 0);
// Get bloom shader, for a vertical Gaussian blur pass.
CShaderDefines defines3;
defines3.Add(str_BLOOM_PASS_V, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_bloom, defines3);
tech->BeginPass();
shader = tech->GetShader();
// Our input texture to the shader is the output of the horizontal pass.
shader->BindTexture(str_renderedTex, tempTex);
shader->Uniform(str_texSize, inWidth, inHeight, 0.0f, 0.0f);
g_Renderer.SetViewport(vp);
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
g_Renderer.SetViewport(oldVp);
tech->EndPass();
}
void CPostprocManager::ApplyBlur()
{
glDisable(GL_BLEND);
int width = m_Width, height = m_Height;
#define SCALE_AND_BLUR(tex1, tex2, temptex) \
ApplyBlurDownscale2x((tex1).get(), (tex2).get(), width, height); \
width /= 2; \
height /= 2; \
ApplyBlurGauss((tex2).get(), (temptex).get(), width, height);
// We do the same thing for each scale, incrementally adding more and more blur.
SCALE_AND_BLUR(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2, m_BlurTex2a, m_BlurTex2b);
SCALE_AND_BLUR(m_BlurTex2a, m_BlurTex4a, m_BlurTex4b);
SCALE_AND_BLUR(m_BlurTex4a, m_BlurTex8a, m_BlurTex8b);
#undef SCALE_AND_BLUR
}
void CPostprocManager::CaptureRenderOutput()
{
ENSURE(m_IsInitialized);
// Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };
glDrawBuffers(1, buffers);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
glDrawBuffers(1, buffers);
m_WhichBuffer = true;
if (m_UsingMultisampleBuffer)
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO);
glDrawBuffers(1, buffers);
}
}
void CPostprocManager::ReleaseRenderOutput()
{
ENSURE(m_IsInitialized);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// we blit to screen from the previous active buffer
if (m_WhichBuffer)
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_PingFbo);
else
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, m_PongFbo);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
glBlitFramebufferEXT(0, 0, m_Width, m_Height, 0, 0, m_Width, m_Height,
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
void CPostprocManager::ApplyEffect(CShaderTechniquePtr &shaderTech1, int pass)
{
// select the other FBO for rendering
if (!m_WhichBuffer)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
else
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
shaderTech1->BeginPass(pass);
CShaderProgramPtr shader = shaderTech1->GetShader(pass);
- shader->Bind();
-
// Use the textures from the current FBO as input to the shader.
// We also bind a bunch of other textures and parameters, but since
// this only happens once per frame the overhead is negligible.
if (m_WhichBuffer)
shader->BindTexture(str_renderedTex, m_ColorTex1.get());
else
shader->BindTexture(str_renderedTex, m_ColorTex2.get());
shader->BindTexture(str_depthTex, m_DepthTex.get());
shader->BindTexture(str_blurTex2, m_BlurTex2a.get());
shader->BindTexture(str_blurTex4, m_BlurTex4a.get());
shader->BindTexture(str_blurTex8, m_BlurTex8a.get());
shader->Uniform(str_width, m_Width);
shader->Uniform(str_height, m_Height);
shader->Uniform(str_zNear, m_NearPlane);
shader->Uniform(str_zFar, m_FarPlane);
shader->Uniform(str_sharpness, m_Sharpness);
shader->Uniform(str_brightness, g_LightEnv.m_Brightness);
shader->Uniform(str_hdr, g_LightEnv.m_Contrast);
shader->Uniform(str_saturation, g_LightEnv.m_Saturation);
shader->Uniform(str_bloom, g_LightEnv.m_Bloom);
- float quadVerts[] = {
+ float quadVerts[] =
+ {
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
- float quadTex[] = {
+ float quadTex[] =
+ {
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
- shader->Unbind();
-
shaderTech1->EndPass(pass);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
m_WhichBuffer = !m_WhichBuffer;
}
void CPostprocManager::ApplyPostproc()
{
ENSURE(m_IsInitialized);
// Don't do anything if we are using the default effect and no AA.
const bool hasEffects = m_PostProcEffect != L"default";
const bool hasARB = g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB;
const bool hasAA = m_AATech && !hasARB;
const bool hasSharp = m_SharpTech && !hasARB;
if (!hasEffects && !hasAA && !hasSharp)
return;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0);
GLenum buffers[] = { GL_COLOR_ATTACHMENT0_EXT };
glDrawBuffers(1, buffers);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, 0, 0);
glDrawBuffers(1, buffers);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
if (hasEffects)
{
// First render blur textures. Note that this only happens ONLY ONCE, before any effects are applied!
// (This may need to change depending on future usage, however that will have a fps hit)
ApplyBlur();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass)
ApplyEffect(m_PostProcTech, pass);
}
if (hasAA)
{
for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass)
ApplyEffect(m_AATech, pass);
}
if (hasSharp)
{
for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
ApplyEffect(m_SharpTech, pass);
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_DepthTex->GetHandle(), 0);
}
// Generate list of available effect-sets
std::vector CPostprocManager::GetPostEffects()
{
std::vector effects;
const VfsPath folder(L"shaders/effects/postproc/");
VfsPaths pathnames;
if (vfs::GetPathnames(g_VFS, folder, 0, pathnames) < 0)
LOGERROR("Error finding Post effects in '%s'", folder.string8());
for (const VfsPath& path : pathnames)
if (path.Extension() == L".xml")
effects.push_back(path.Basename().string());
// Add the default "null" effect to the list.
effects.push_back(L"default");
sort(effects.begin(), effects.end());
return effects;
}
void CPostprocManager::SetPostEffect(const CStrW& name)
{
if (m_IsInitialized)
{
if (name != L"default")
{
CStrW n = L"postproc/" + name;
m_PostProcTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(n.ToUTF8()));
}
}
m_PostProcEffect = name;
}
void CPostprocManager::UpdateAntiAliasingTechnique()
{
if (g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB || !m_IsInitialized)
return;
CStr newAAName;
CFG_GET_VAL("antialiasing", newAAName);
if (m_AAName == newAAName)
return;
m_AAName = newAAName;
m_AATech.reset();
if (m_UsingMultisampleBuffer)
{
m_UsingMultisampleBuffer = false;
DestroyMultisampleBuffer();
}
// We have to hardcode names in the engine, because anti-aliasing
// techinques strongly depend on the graphics pipeline.
// We might use enums in future though.
const CStr msaaPrefix = "msaa";
if (m_AAName == "fxaa")
{
m_AATech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern("fxaa"));
}
else if (m_AAName.size() > msaaPrefix.size() && m_AAName.substr(0, msaaPrefix.size()) == msaaPrefix)
{
#if !CONFIG2_GLES
// We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas.
if (g_AtlasGameLoop && g_AtlasGameLoop->running)
return;
const bool is_msaa_supported =
ogl_HaveVersion(3, 3) &&
ogl_HaveExtension("GL_ARB_multisample") &&
ogl_HaveExtension("GL_ARB_texture_multisample") &&
!m_AllowedSampleCounts.empty();
if (!is_msaa_supported)
{
LOGWARNING("MSAA is unsupported.");
return;
}
std::stringstream ss(m_AAName.substr(msaaPrefix.size()));
ss >> m_MultisampleCount;
if (std::find(std::begin(m_AllowedSampleCounts), std::end(m_AllowedSampleCounts), m_MultisampleCount) ==
std::end(m_AllowedSampleCounts))
{
m_MultisampleCount = 4;
LOGWARNING("Wrong MSAA sample count: %s.", m_AAName.EscapeToPrintableASCII().c_str());
}
m_UsingMultisampleBuffer = true;
CreateMultisampleBuffer();
#else
#warning TODO: implement and test MSAA for GLES
LOGWARNING("MSAA is unsupported.");
#endif
}
}
void CPostprocManager::UpdateSharpeningTechnique()
{
if (g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB || !m_IsInitialized)
return;
CStr newSharpName;
CFG_GET_VAL("sharpening", newSharpName);
if (m_SharpName == newSharpName)
return;
m_SharpName = newSharpName;
m_SharpTech.reset();
if (m_SharpName == "cas")
{
m_SharpTech = g_Renderer.GetShaderManager().LoadEffect(CStrIntern(m_SharpName));
}
}
void CPostprocManager::UpdateSharpnessFactor()
{
CFG_GET_VAL("sharpness", m_Sharpness);
}
void CPostprocManager::SetDepthBufferClipPlanes(float nearPlane, float farPlane)
{
m_NearPlane = nearPlane;
m_FarPlane = farPlane;
}
void CPostprocManager::CreateMultisampleBuffer()
{
glEnable(GL_MULTISAMPLE);
m_MultisampleColorTex = Renderer::Backend::GL::CTexture::Create(
Renderer::Backend::GL::CTexture::Type::TEXTURE_2D_MULTISAMPLE,
Renderer::Backend::Format::R8G8B8A8, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
// Allocate the Depth/Stencil texture.
m_MultisampleDepthTex = Renderer::Backend::GL::CTexture::Create(
Renderer::Backend::GL::CTexture::Type::TEXTURE_2D_MULTISAMPLE,
Renderer::Backend::Format::D24_S8, m_Width, m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, m_MultisampleCount);
// Set up the framebuffers with some initial textures.
glGenFramebuffersEXT(1, &m_MultisampleFBO);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleColorTex->GetHandle(), 0);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D_MULTISAMPLE, m_MultisampleDepthTex->GetHandle(), 0);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Multisample framebuffer object incomplete (A): 0x%04X", status);
m_UsingMultisampleBuffer = false;
DestroyMultisampleBuffer();
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
void CPostprocManager::DestroyMultisampleBuffer()
{
if (m_UsingMultisampleBuffer)
return;
if (m_MultisampleFBO)
glDeleteFramebuffersEXT(1, &m_MultisampleFBO);
m_MultisampleColorTex.reset();
m_MultisampleDepthTex.reset();
glDisable(GL_MULTISAMPLE);
}
bool CPostprocManager::IsMultisampleEnabled() const
{
return m_UsingMultisampleBuffer;
}
void CPostprocManager::ResolveMultisampleFramebuffer()
{
if (!m_UsingMultisampleBuffer)
return;
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, m_PingFbo);
glBlitFramebufferEXT(0, 0, m_Width, m_Height, 0, 0, m_Width, m_Height,
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
}
#else
#warning TODO: implement PostprocManager for GLES
void ApplyBlurDownscale2x(
Renderer::Backend::GL::CTexture* UNUSED(inTex),
Renderer::Backend::GL::CTexture* UNUSED(outTex),
int UNUSED(inWidth), int UNUSED(inHeight))
{
}
void CPostprocManager::ApplyBlurGauss(
Renderer::Backend::GL::CTexture* UNUSED(inOutTex),
Renderer::Backend::GL::CTexture* UNUSED(tempTex),
int UNUSED(inWidth), int UNUSED(inHeight))
{
}
void CPostprocManager::ApplyEffect(CShaderTechniquePtr &UNUSED(shaderTech1), int UNUSED(pass))
{
}
CPostprocManager::CPostprocManager()
{
}
CPostprocManager::~CPostprocManager()
{
}
void CPostprocManager::Initialize()
{
}
void CPostprocManager::Resize()
{
}
void CPostprocManager::Cleanup()
{
}
void CPostprocManager::RecreateBuffers()
{
}
std::vector CPostprocManager::GetPostEffects()
{
return std::vector();
}
void CPostprocManager::SetPostEffect(const CStrW& UNUSED(name))
{
}
void CPostprocManager::SetDepthBufferClipPlanes(float UNUSED(nearPlane), float UNUSED(farPlane))
{
}
void CPostprocManager::UpdateAntiAliasingTechnique()
{
}
void CPostprocManager::UpdateSharpeningTechnique()
{
}
void CPostprocManager::UpdateSharpnessFactor()
{
}
void CPostprocManager::CaptureRenderOutput()
{
}
void CPostprocManager::ApplyPostproc()
{
}
void CPostprocManager::ReleaseRenderOutput()
{
}
void CPostprocManager::CreateMultisampleBuffer()
{
}
void CPostprocManager::DestroyMultisampleBuffer()
{
}
bool CPostprocManager::IsMultisampleEnabled() const
{
return false;
}
void CPostprocManager::ResolveMultisampleFramebuffer()
{
}
#endif
Index: ps/trunk/source/renderer/TerrainOverlay.cpp
===================================================================
--- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26198)
+++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26199)
@@ -1,393 +1,389 @@
/* 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 "TerrainOverlay.h"
#include "graphics/Color.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "graphics/Terrain.h"
#include "lib/bits.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TerrainRenderer.h"
#include "simulation2/system/SimContext.h"
#include
// Global overlay list management:
static std::vector > g_TerrainOverlayList;
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& a, const std::pair& b) {
return a.second < b.second;
});
}
ITerrainOverlay::~ITerrainOverlay()
{
std::vector >::iterator newEnd =
std::remove_if(g_TerrainOverlayList.begin(), g_TerrainOverlayList.end(),
[this](const std::pair& a) { return a.first == this; });
g_TerrainOverlayList.erase(newEnd, g_TerrainOverlayList.end());
}
void ITerrainOverlay::RenderOverlaysBeforeWater()
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (before)");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderBeforeWater();
}
void ITerrainOverlay::RenderOverlaysAfterWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, int cullGroup)
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (after)");
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())
{
}
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()
{
if (!m_Terrain)
return; // should never happen, but let's play it safe
#if CONFIG2_GLES
#warning TODO: implement TerrainOverlay::RenderOverlays for GLES
#else
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(GL_FALSE);
// To ensure that outlines are drawn on top of the terrain correctly (and
// don't Z-fight and flicker nastily), draw them as QUADS with the LINE
// PolygonMode, and use PolygonOffset to pull them towards the camera.
// (See e.g. http://www.opengl.org/resources/faq/technical/polygonoffset.htm)
glPolygonOffset(-1.f, -1.f);
//glEnable(GL_POLYGON_OFFSET_LINE);
glEnable(GL_POLYGON_OFFSET_FILL);
glActiveTextureARB(GL_TEXTURE0);
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(min_i, 0, m_Terrain->GetTilesPerSide());
min_j = Clamp(min_j, 0, m_Terrain->GetTilesPerSide());
max_i = Clamp(max_i, -1, m_Terrain->GetTilesPerSide()-1);
max_j = Clamp(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(m_i, m_j);
EndRender();
// Clean up state changes
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
//glDisable(GL_POLYGON_OFFSET_LINE);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
#endif
}
void TerrainOverlay::RenderTile(const CColor& color, bool draw_hidden)
{
RenderTile(color, draw_hidden, m_i, m_j);
}
void TerrainOverlay::RenderTile(const CColor& color, bool draw_hidden, 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.
if (draw_hidden)
{
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}
else
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
#if CONFIG2_GLES
UNUSED2(color); UNUSED2(i); UNUSED2(j);
#warning TODO: implement TerrainOverlay::RenderTile for GLES
#else
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 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
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
CShaderProgramPtr overlayShader = overlayTech->GetShader();
- overlayShader->Bind();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 3);
- overlayShader->Unbind();
overlayTech->EndPass();
#endif
}
void TerrainOverlay::RenderTileOutline(const CColor& color, int line_width, bool draw_hidden)
{
RenderTileOutline(color, line_width, draw_hidden, m_i, m_j);
}
void TerrainOverlay::RenderTileOutline(const CColor& color, int line_width, bool draw_hidden, ssize_t i, ssize_t j)
{
if (draw_hidden)
{
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
}
else
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
}
#if CONFIG2_GLES
UNUSED2(color); UNUSED2(line_width); UNUSED2(i); UNUSED2(j);
#warning TODO: implement TerrainOverlay::RenderTileOutline for GLES
#else
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
if (line_width != 1)
glLineWidth((float)line_width);
std::vector 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+1);
#undef ADD
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
CShaderProgramPtr overlayShader = overlayTech->GetShader();
- overlayShader->Bind();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUADS, 0, vertices.size() / 3);
- overlayShader->Unbind();
overlayTech->EndPass();
if (line_width != 1)
glLineWidth(1.0f);
#endif
}
//////////////////////////////////////////////////////////////////////////
TerrainTextureOverlay::TerrainTextureOverlay(float texelsPerTile, int priority) :
ITerrainOverlay(priority), m_TexelsPerTile(texelsPerTile)
{
}
TerrainTextureOverlay::~TerrainTextureOverlay() = default;
void TerrainTextureOverlay::RenderAfterWater(
Renderer::Backend::GL::CDeviceCommandContext* 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);
glActiveTextureARB(GL_TEXTURE0);
// Recreate the texture with new size if necessary
if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight)
{
m_Texture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, 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, data, w * h * 4, 0, 0, w, h);
free(data);
CMatrix3D matrix;
matrix.SetZero();
matrix._11 = m_TexelsPerTile / (m_Texture->GetWidth() * TERRAIN_TILE_SIZE);
matrix._23 = m_TexelsPerTile / (m_Texture->GetHeight() * TERRAIN_TILE_SIZE);
matrix._44 = 1;
g_Renderer.GetSceneRenderer().GetTerrainRenderer().RenderTerrainOverlayTexture(cullGroup, matrix, 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);
}
Index: ps/trunk/source/renderer/TerrainRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26198)
+++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26199)
@@ -1,606 +1,602 @@
/* 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 "renderer/TerrainRenderer.h"
#include "graphics/Camera.h"
#include "graphics/Canvas2D.h"
#include "graphics/Decal.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/LOSTexture.h"
#include "graphics/Patch.h"
#include "graphics/Model.h"
#include "graphics/ShaderManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/TextRenderer.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "renderer/DecalRData.h"
#include "renderer/PatchRData.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include "renderer/ShadowMap.h"
#include "renderer/SkyManager.h"
#include "renderer/VertexArray.h"
#include "renderer/WaterManager.h"
/**
* TerrainRenderer keeps track of which phase it is in, to detect
* when Submit, PrepareForRendering etc. are called in the wrong order.
*/
enum Phase
{
Phase_Submit,
Phase_Render
};
/**
* Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class.
*/
struct TerrainRendererInternals
{
/// Which phase (submitting or rendering patches) are we in right now?
Phase phase;
/// Patches that were submitted for this frame
std::vector visiblePatches[CSceneRenderer::CULL_MAX];
/// Decals that were submitted for this frame
std::vector visibleDecals[CSceneRenderer::CULL_MAX];
/// Fancy water shader
CShaderTechniquePtr fancyWaterTech;
CSimulation2* simulation;
};
///////////////////////////////////////////////////////////////////
// Construction/Destruction
TerrainRenderer::TerrainRenderer()
{
m = new TerrainRendererInternals();
m->phase = Phase_Submit;
}
TerrainRenderer::~TerrainRenderer()
{
delete m;
}
void TerrainRenderer::SetSimulation(CSimulation2* simulation)
{
m->simulation = simulation;
}
///////////////////////////////////////////////////////////////////
// Submit a patch for rendering
void TerrainRenderer::Submit(int cullGroup, CPatch* patch)
{
ENSURE(m->phase == Phase_Submit);
CPatchRData* data = (CPatchRData*)patch->GetRenderData();
if (data == 0)
{
// no renderdata for patch, create it now
data = new CPatchRData(patch, m->simulation);
patch->SetRenderData(data);
}
data->Update(m->simulation);
m->visiblePatches[cullGroup].push_back(data);
}
///////////////////////////////////////////////////////////////////
// Submit a decal for rendering
void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal)
{
ENSURE(m->phase == Phase_Submit);
CDecalRData* data = (CDecalRData*)decal->GetRenderData();
if (data == 0)
{
// no renderdata for decal, create it now
data = new CDecalRData(decal, m->simulation);
decal->SetRenderData(data);
}
data->Update(m->simulation);
m->visibleDecals[cullGroup].push_back(data);
}
///////////////////////////////////////////////////////////////////
// Prepare for rendering
void TerrainRenderer::PrepareForRendering()
{
ENSURE(m->phase == Phase_Submit);
m->phase = Phase_Render;
}
///////////////////////////////////////////////////////////////////
// Clear submissions lists
void TerrainRenderer::EndFrame()
{
ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit);
for (int i = 0; i < CSceneRenderer::CULL_MAX; ++i)
{
m->visiblePatches[i].clear();
m->visibleDecals[i].clear();
}
m->phase = Phase_Submit;
}
void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix,
Renderer::Backend::GL::CTexture* texture)
{
#if CONFIG2_GLES
#warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES
UNUSED2(cullGroup);
UNUSED2(textureMatrix);
UNUSED2(texture);
#else
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthMask(0);
glDisable(GL_DEPTH_TEST);
CShaderTechniquePtr debugOverlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_overlay);
debugOverlayTech->BeginPass();
CShaderProgramPtr debugOverlayShader = debugOverlayTech->GetShader();
- debugOverlayShader->Bind();
debugOverlayShader->BindTexture(str_baseTex, texture);
debugOverlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
debugOverlayShader->Uniform(str_textureTransform, textureMatrix);
CPatchRData::RenderStreams(visiblePatches, debugOverlayShader, STREAM_POS | STREAM_POSTOUV0);
glEnable(GL_DEPTH_TEST);
// To make the overlay visible over water, render an additional map-sized
// water-height patch.
CBoundingBoxAligned waterBounds;
for (CPatchRData* data : visiblePatches)
waterBounds += data->GetWaterBounds();
if (!waterBounds.IsEmpty())
{
// Add a delta to avoid z-fighting.
const float height = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight + 0.05f;
const float waterPos[] = {
waterBounds[0].X, height, waterBounds[0].Z,
waterBounds[1].X, height, waterBounds[0].Z,
waterBounds[0].X, height, waterBounds[1].Z,
waterBounds[1].X, height, waterBounds[1].Z
};
const GLsizei stride = sizeof(float) * 3;
debugOverlayShader->VertexPointer(3, GL_FLOAT, stride, waterPos);
debugOverlayShader->TexCoordPointer(GL_TEXTURE0, 3, GL_FLOAT, stride, waterPos);
debugOverlayShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
- debugOverlayShader->Unbind();
debugOverlayTech->EndPass();
glDepthMask(1);
glDisable(GL_BLEND);
#endif
}
///////////////////////////////////////////////////////////////////
/**
* Set up all the uniforms for a shader pass.
*/
void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow)
{
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
shader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection());
shader->Uniform(str_cameraPos, sceneRenderer.GetViewCamera().GetOrientation().GetTranslation());
const CLightEnv& lightEnv = sceneRenderer.GetLightEnv();
if (shadow)
shadow->BindTo(shader);
CLOSTexture& los = sceneRenderer.GetScene().GetLOSTexture();
shader->BindTexture(str_losTex, los.GetTextureSmooth());
shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
shader->Uniform(str_ambient, lightEnv.m_AmbientColor);
shader->Uniform(str_sunColor, lightEnv.m_SunColor);
shader->Uniform(str_sunDir, lightEnv.GetSunDir());
shader->Uniform(str_fogColor, lightEnv.m_FogColor);
shader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f);
}
void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
std::vector& visibleDecals = m->visibleDecals[cullGroup];
if (visiblePatches.empty() && visibleDecals.empty())
return;
// render the solid black sides of the map first
CShaderTechniquePtr techSolid = g_Renderer.GetShaderManager().LoadEffect(str_solid);
techSolid->BeginPass();
CShaderProgramPtr shaderSolid = techSolid->GetShader();
shaderSolid->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
shaderSolid->Uniform(str_color, 0.0f, 0.0f, 0.0f, 1.0f);
CPatchRData::RenderSides(visiblePatches, shaderSolid);
techSolid->EndPass();
CPatchRData::RenderBases(visiblePatches, context, shadow);
// no need to write to the depth buffer a second time
glDepthMask(0);
// render blend passes for each patch
CPatchRData::RenderBlends(visiblePatches, context, shadow);
CDecalRData::RenderDecals(visibleDecals, context, shadow);
// restore OpenGL state
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(2, 0);
g_Renderer.BindTexture(3, 0);
glDepthMask(1);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_BLEND);
}
///////////////////////////////////////////////////////////////////
// Render un-textured patches as polygons
void TerrainRenderer::RenderPatches(int cullGroup, const CColor& color)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
if (visiblePatches.empty())
return;
#if CONFIG2_GLES
UNUSED2(color);
#warning TODO: implement TerrainRenderer::RenderPatches for GLES
#else
CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_dummy);
dummyTech->BeginPass();
CShaderProgramPtr dummyShader = dummyTech->GetShader();
dummyShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
dummyShader->Uniform(str_color, color);
CPatchRData::RenderStreams(visiblePatches, dummyShader, STREAM_POS);
dummyTech->EndPass();
#endif
}
///////////////////////////////////////////////////////////////////
// Render outlines of submitted patches as lines
void TerrainRenderer::RenderOutlines(int cullGroup)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
if (visiblePatches.empty())
return;
for (size_t i = 0; i < visiblePatches.size(); ++i)
visiblePatches[i]->RenderOutline();
}
///////////////////////////////////////////////////////////////////
// Scissor rectangle of water patches
CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CCamera& camera)
{
CBoundingBoxAligned scissor;
for (const CPatchRData* data : m->visiblePatches[cullGroup])
{
const CBoundingBoxAligned& waterBounds = data->GetWaterBounds();
if (waterBounds.IsEmpty())
continue;
const CBoundingBoxAligned waterBoundsInViewPort =
camera.GetBoundsInViewPort(waterBounds);
if (!waterBoundsInViewPort.IsEmpty())
scissor += waterBoundsInViewPort;
}
return CBoundingBoxAligned(
CVector3D(Clamp(scissor[0].X, -1.0f, 1.0f), Clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f),
CVector3D(Clamp(scissor[1].X, -1.0f, 1.0f), Clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f));
}
// Render fancy water
bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
PROFILE3_GPU("fancy water");
OGL_SCOPED_DEBUG_GROUP("Render Fancy Water");
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
WaterManager& waterManager = sceneRenderer.GetWaterManager();
CShaderDefines defines = context;
// If we're using fancy water, make sure its shader is loaded
if (!m->fancyWaterTech || waterManager.m_NeedsReloading)
{
if (waterManager.m_WaterRealDepth)
defines.Add(str_USE_REAL_DEPTH, str_1);
if (waterManager.m_WaterFancyEffects)
defines.Add(str_USE_FANCY_EFFECTS, str_1);
if (waterManager.m_WaterRefraction)
defines.Add(str_USE_REFRACTION, str_1);
if (waterManager.m_WaterReflection)
defines.Add(str_USE_REFLECTION, str_1);
m->fancyWaterTech = g_Renderer.GetShaderManager().LoadEffect(str_water_high, defines);
if (!m->fancyWaterTech)
{
LOGERROR("Failed to load water shader. Falling back to a simple water.\n");
waterManager.m_RenderWater = false;
return false;
}
waterManager.m_NeedsReloading = false;
}
CLOSTexture& losTexture = sceneRenderer.GetScene().GetLOSTexture();
// Calculating the advanced informations about Foam and all if the quality calls for it.
/*if (WaterMgr->m_NeedInfoUpdate && (WaterMgr->m_WaterFoam || WaterMgr->m_WaterCoastalWaves))
{
WaterMgr->m_NeedInfoUpdate = false;
WaterMgr->CreateSuperfancyInfo();
}*/
const double time = waterManager.m_WaterTexTimer;
const float repeatPeriod = waterManager.m_RepeatPeriod;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
m->fancyWaterTech->BeginPass();
CShaderProgramPtr fancyWaterShader = m->fancyWaterTech->GetShader();
const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera();
const double period = 8.0;
fancyWaterShader->BindTexture(str_normalMap, waterManager.m_NormalMap[waterManager.GetCurrentTextureIndex(period)]);
fancyWaterShader->BindTexture(str_normalMap2, waterManager.m_NormalMap[waterManager.GetNextTextureIndex(period)]);
if (waterManager.m_WaterFancyEffects)
{
fancyWaterShader->BindTexture(str_waterEffectsTex, waterManager.m_FancyTexture.get());
}
if (waterManager.m_WaterRefraction && waterManager.m_WaterRealDepth)
{
fancyWaterShader->BindTexture(str_depthTex, waterManager.m_RefrFboDepthTexture.get());
fancyWaterShader->Uniform(str_projInvTransform, waterManager.m_RefractionProjInvMatrix);
fancyWaterShader->Uniform(str_viewInvTransform, waterManager.m_RefractionViewInvMatrix);
}
if (waterManager.m_WaterRefraction)
fancyWaterShader->BindTexture(str_refractionMap, waterManager.m_RefractionTexture.get());
if (waterManager.m_WaterReflection)
fancyWaterShader->BindTexture(str_reflectionMap, waterManager.m_ReflectionTexture.get());
fancyWaterShader->BindTexture(str_losTex, losTexture.GetTextureSmooth());
const CLightEnv& lightEnv = sceneRenderer.GetLightEnv();
fancyWaterShader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection());
fancyWaterShader->BindTexture(str_skyCube, sceneRenderer.GetSkyManager().GetSkyCube());
// TODO: check that this rotates in the right direction.
CMatrix3D skyBoxRotation;
skyBoxRotation.SetIdentity();
skyBoxRotation.RotateY(M_PI + lightEnv.GetRotation());
fancyWaterShader->Uniform(str_skyBoxRot, skyBoxRotation);
if (waterManager.m_WaterRefraction)
fancyWaterShader->Uniform(str_refractionMatrix, waterManager.m_RefractionMatrix);
if (waterManager.m_WaterReflection)
fancyWaterShader->Uniform(str_reflectionMatrix, waterManager.m_ReflectionMatrix);
fancyWaterShader->Uniform(str_ambient, lightEnv.m_AmbientColor);
fancyWaterShader->Uniform(str_sunDir, lightEnv.GetSunDir());
fancyWaterShader->Uniform(str_sunColor, lightEnv.m_SunColor);
fancyWaterShader->Uniform(str_color, waterManager.m_WaterColor);
fancyWaterShader->Uniform(str_tint, waterManager.m_WaterTint);
fancyWaterShader->Uniform(str_waviness, waterManager.m_Waviness);
fancyWaterShader->Uniform(str_murkiness, waterManager.m_Murkiness);
fancyWaterShader->Uniform(str_windAngle, waterManager.m_WindAngle);
fancyWaterShader->Uniform(str_repeatScale, 1.0f / repeatPeriod);
fancyWaterShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f);
fancyWaterShader->Uniform(str_cameraPos, camera.GetOrientation().GetTranslation());
fancyWaterShader->Uniform(str_fogColor, lightEnv.m_FogColor);
fancyWaterShader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f);
fancyWaterShader->Uniform(str_time, (float)time);
fancyWaterShader->Uniform(str_screenSize, (float)g_Renderer.GetWidth(), (float)g_Renderer.GetHeight(), 0.0f, 0.0f);
if (waterManager.m_WaterType == L"clap")
{
fancyWaterShader->Uniform(str_waveParams1, 30.0f,1.5f,20.0f,0.03f);
fancyWaterShader->Uniform(str_waveParams2, 0.5f,0.0f,0.0f,0.0f);
}
else if (waterManager.m_WaterType == L"lake")
{
fancyWaterShader->Uniform(str_waveParams1, 8.5f,1.5f,15.0f,0.03f);
fancyWaterShader->Uniform(str_waveParams2, 0.2f,0.0f,0.0f,0.07f);
}
else
{
fancyWaterShader->Uniform(str_waveParams1, 15.0f,0.8f,10.0f,0.1f);
fancyWaterShader->Uniform(str_waveParams2, 0.3f,0.0f,0.1f,0.3f);
}
if (shadow)
shadow->BindTo(fancyWaterShader);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
{
CPatchRData* data = visiblePatches[i];
data->RenderWater(fancyWaterShader);
}
m->fancyWaterTech->EndPass();
glDepthFunc(GL_LEQUAL);
glDisable(GL_BLEND);
return true;
}
void TerrainRenderer::RenderSimpleWater(int cullGroup)
{
#if CONFIG2_GLES
UNUSED2(cullGroup);
#else
PROFILE3_GPU("simple water");
OGL_SCOPED_DEBUG_GROUP("Render Simple Water");
const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager();
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
const double time = waterManager.m_WaterTexTimer;
CShaderTechniquePtr waterSimpleTech =
g_Renderer.GetShaderManager().LoadEffect(str_water_simple);
waterSimpleTech->BeginPass();
CShaderProgramPtr waterSimpleShader = waterSimpleTech->GetShader();
- waterSimpleShader->Bind();
waterSimpleShader->BindTexture(str_baseTex, waterManager.m_WaterTexture[waterManager.GetCurrentTextureIndex(1.6)]);
waterSimpleShader->BindTexture(str_losTex, losTexture.GetTextureSmooth());
waterSimpleShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
waterSimpleShader->Uniform(str_losTransform, losTexture.GetTextureMatrix()[0], losTexture.GetTextureMatrix()[12], 0.f, 0.f);
waterSimpleShader->Uniform(str_time, static_cast(time));
waterSimpleShader->Uniform(str_color, waterManager.m_WaterColor);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
{
CPatchRData* data = visiblePatches[i];
data->RenderWater(waterSimpleShader, false, true);
}
- waterSimpleShader->Unbind();
g_Renderer.BindTexture(1, 0);
glActiveTextureARB(GL_TEXTURE0_ARB);
waterSimpleTech->EndPass();
#endif
}
///////////////////////////////////////////////////////////////////
// Render water that is part of the terrain
void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager();
if (!waterManager.WillRenderFancyWater())
RenderSimpleWater(cullGroup);
else
RenderFancyWater(context, cullGroup, shadow);
}
void TerrainRenderer::RenderWaterFoamOccluders(int cullGroup)
{
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
const WaterManager& waterManager = sceneRenderer.GetWaterManager();
if (!waterManager.WillRenderFancyWater())
return;
// Render normals and foam to a framebuffer if we're using fancy effects.
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, waterManager.m_FancyEffectsFBO);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glDisable(GL_CULL_FACE);
// Overwrite waves that would be behind the ground.
CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
dummyTech->BeginPass();
CShaderProgramPtr dummyShader = dummyTech->GetShader();
dummyShader->Uniform(str_transform, sceneRenderer.GetViewCamera().GetViewProjection());
dummyShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.0f);
for (CPatchRData* data : m->visiblePatches[cullGroup])
data->RenderWater(dummyShader, true, true);
dummyTech->EndPass();
glEnable(GL_CULL_FACE);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
void TerrainRenderer::RenderPriorities(int cullGroup)
{
PROFILE("priorities");
ENSURE(m->phase == Phase_Render);
CCanvas2D canvas;
CTextRenderer textRenderer;
textRenderer.SetCurrentFont(CStrIntern("mono-stroke-10"));
textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 1.0f));
std::vector& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
visiblePatches[i]->RenderPriorities(textRenderer);
canvas.DrawText(textRenderer);
}