Index: ps/trunk/source/graphics/LOSTexture.cpp
===================================================================
--- ps/trunk/source/graphics/LOSTexture.cpp (revision 26301)
+++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26302)
@@ -1,425 +1,419 @@
/* 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/backend/gl/Device.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);
+ m_SmoothFramebuffers[0].reset();
+ m_SmoothFramebuffers[1].reset();
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();
+ m_SmoothTextures[0].reset();
+ m_SmoothTextures[1].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();
+ return m_SmoothTextures[m_WhichTexture].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));
+ deviceCommandContext->SetFramebuffer(m_SmoothFramebuffers[m_WhichTexture].get());
m_SmoothTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
m_SmoothTech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& shader = m_SmoothTech->GetShader();
shader->BindTexture(str_losTex1, m_Texture.get());
- shader->BindTexture(str_losTex2, (m_WhichTex ? m_TextureSmooth1 : m_TextureSmooth2).get());
+ shader->BindTexture(str_losTex2, m_SmoothTextures[m_WhichTexture].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);
m_SmoothTech->EndPass();
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
- m_WhichTex = !m_WhichTex;
+ m_WhichTexture = 1u - m_WhichTexture;
}
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(
+ m_SmoothTextures[0] = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
- m_TextureSmooth2 = Renderer::Backend::GL::CTexture::Create2D(
+ m_SmoothTextures[1] = 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)
+ m_SmoothFramebuffers[0] = Renderer::Backend::GL::CFramebuffer::Create(
+ m_SmoothTextures[0].get(), nullptr);
+ m_SmoothFramebuffers[1] = Renderer::Backend::GL::CFramebuffer::Create(
+ m_SmoothTextures[1].get(), nullptr);
+ if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1])
{
- LOGWARNING("LOS framebuffer object incomplete: 0x%04X 0x%04X", status1, status2);
+ LOGERROR("Failed to create LOS framebuffers");
+ g_RenderingOptions.SetSmoothLOS(false);
}
- 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_SmoothTextures[0].get(), Renderer::Backend::Format::A8, texData.get(), textureSize * textureSize);
+ deviceCommandContext->UploadTexture(m_SmoothTextures[1].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(),
+ m_SmoothTextures[0].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(),
+ m_SmoothTextures[1].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/graphics/LOSTexture.h
===================================================================
--- ps/trunk/source/graphics/LOSTexture.h (revision 26301)
+++ ps/trunk/source/graphics/LOSTexture.h (revision 26302)
@@ -1,108 +1,108 @@
/* 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_LOSTEXTURE
#define INCLUDED_LOSTEXTURE
-#include "lib/ogl.h"
-
#include "graphics/ShaderTechniquePtr.h"
#include "maths/Matrix3D.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
+#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Texture.h"
#include
class CLosQuerier;
class CSimulation2;
/**
* Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for
* rendering and for the minimap.
*/
class CLOSTexture
{
NONCOPYABLE(CLOSTexture);
friend class TestLOSTexture;
public:
CLOSTexture(CSimulation2& simulation);
~CLOSTexture();
/**
* Marks the LOS texture as needing recomputation. Call this after each
* simulation update, to ensure responsive updates.
*/
void MakeDirty();
/**
* Recomputes the LOS texture if necessary, and returns the texture handle.
* Also potentially switches the current active texture unit, and enables texturing on it.
* The texture is in 8-bit ALPHA format.
*/
Renderer::Backend::GL::CTexture* GetTexture();
Renderer::Backend::GL::CTexture* GetTextureSmooth();
void InterpolateLOS(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
/**
* Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture
* coordinates, in the form expected by a matrix uniform.
* This must only be called after InterpolateLOS.
*/
const CMatrix3D& GetTextureMatrix();
/**
* Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture
* coordinates, in the form expected by a matrix uniform.
* This must only be called after InterpolateLOS.
*/
const CMatrix3D& GetMinimapTextureMatrix();
private:
void DeleteTexture();
bool CreateShader();
void ConstructTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
void RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
size_t GetBitmapSize(size_t w, size_t h, size_t* pitch);
void GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch);
CSimulation2& m_Simulation;
bool m_Dirty = true;
bool m_ShaderInitialized = false;
std::unique_ptr
- m_Texture, m_TextureSmooth1, m_TextureSmooth2;
+ m_Texture, m_SmoothTextures[2];
- bool m_WhichTex = true;
+ uint32_t m_WhichTexture = 0;
- // We update textures once a frame, so we change a FBO once a frame. That
- // allows us to use two ping-pong FBOs instead of checking completeness of
- // FBO each frame.
- GLuint m_SmoothFBO1 = 0, m_SmoothFBO2 = 0;
+ // We update textures once a frame, so we change a Framebuffer once a frame.
+ // That allows us to use two ping-pong FBOs instead of checking completeness
+ // of Framebuffer each frame.
+ std::unique_ptr
+ m_SmoothFramebuffers[2];
CShaderTechniquePtr m_SmoothTech;
size_t m_MapSize = 0; // vertexes per side
CMatrix3D m_TextureMatrix;
CMatrix3D m_MinimapTextureMatrix;
};
#endif // INCLUDED_LOSTEXTURE
Index: ps/trunk/source/graphics/MiniMapTexture.cpp
===================================================================
--- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26301)
+++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26302)
@@ -1,543 +1,536 @@
/* 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 "MiniMapTexture.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniPatch.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgramPtr.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/TextureManager.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
+#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/Object.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/system/ParamNode.h"
namespace
{
// Set max drawn entities to UINT16_MAX for now, which is more than enough
// TODO: we should be cleverer about drawing them to reduce clutter
const u16 MAX_ENTITIES_DRAWN = 65535;
const size_t FINAL_TEXTURE_SIZE = 512;
unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color >> 8) & 0xff) * x);
unsigned int b = unsigned(float((color >> 16) & 0xff) * x);
return (0xff000000 | b | g << 8 | r << 16);
}
void DrawTexture(CShaderProgramPtr shader)
{
const float quadUVs[] =
{
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f
};
const float quadVertices[] =
{
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadUVs);
shader->VertexPointer(3, GL_FLOAT, 0, quadVertices);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
}
struct MinimapUnitVertex
{
// This struct is copyable for convenience and because to move is to copy for primitives.
u8 r, g, b, a;
float x, y;
};
// Adds a vertex to the passed VertexArray
static void inline addVertex(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.x;
(*attrPos)[1] = v.y;
++attrPos;
}
} // anonymous namespace
CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
: m_Simulation(simulation), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW)
{
// Register Relax NG validator.
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
m_ShallowPassageHeight = GetShallowPassageHeight();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
}
m_HalfBlinkDuration = blinkDuration / 2.0;
m_AttributePos.type = GL_FLOAT;
m_AttributePos.elems = 2;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.type = GL_UNSIGNED_BYTE;
m_AttributeColor.elems = 4;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_VertexArray.Layout();
m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
*index++ = i;
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
}
CMiniMapTexture::~CMiniMapTexture()
{
DestroyTextures();
}
void CMiniMapTexture::Update(const float UNUSED(deltaRealTime))
{
if (m_WaterHeight != g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight)
{
m_TerrainTextureDirty = true;
m_FinalTextureDirty = true;
}
}
void CMiniMapTexture::Render(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
const CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain)
return;
if (!m_TerrainTexture)
CreateTextures(deviceCommandContext, terrain);
if (m_TerrainTextureDirty)
RebuildTerrainTexture(deviceCommandContext, terrain);
RenderFinalTexture(deviceCommandContext);
}
void CMiniMapTexture::CreateTextures(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, const CTerrain* terrain)
{
DestroyTextures();
m_MapSize = terrain->GetVerticesPerSide();
const size_t textureSize = round_up_to_pow2(static_cast(m_MapSize));
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
// Create terrain texture
m_TerrainTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, textureSize, textureSize, defaultSamplerDesc);
// Initialise texture with solid black, for the areas we don't
// overwrite with uploading later.
std::unique_ptr texData = std::make_unique(textureSize * textureSize);
for (size_t i = 0; i < textureSize * textureSize; ++i)
texData[i] = 0xFF000000;
deviceCommandContext->UploadTexture(
m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8,
texData.get(), textureSize * textureSize * 4);
texData.reset();
m_TerrainData = std::make_unique((m_MapSize - 1) * (m_MapSize - 1));
m_FinalTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc);
- glGenFramebuffersEXT(1, &m_FinalTextureFBO);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FinalTextureFBO);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FinalTexture->GetHandle(), 0);
-
- GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
- if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
- {
- LOGWARNING("MiniMapTexture Framebuffer object incomplete (A): 0x%04X", status);
- }
-
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ m_FinalTextureFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_FinalTexture.get(), nullptr);
+ ENSURE(m_FinalTextureFramebuffer);
}
void CMiniMapTexture::DestroyTextures()
{
m_TerrainTexture.reset();
m_FinalTexture.reset();
m_TerrainData.reset();
}
void CMiniMapTexture::RebuildTerrainTexture(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CTerrain* terrain)
{
const u32 x = 0;
const u32 y = 0;
const u32 width = m_MapSize - 1;
const u32 height = m_MapSize - 1;
m_WaterHeight = g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight;
m_TerrainTextureDirty = false;
for (u32 j = 0; j < height; ++j)
{
u32* dataPtr = m_TerrainData.get() + ((y + j) * width) + x;
for (u32 i = 0; i < width; ++i)
{
const float avgHeight = ( terrain->GetVertexGroundLevel((int)i, (int)j)
+ terrain->GetVertexGroundLevel((int)i+1, (int)j)
+ terrain->GetVertexGroundLevel((int)i, (int)j+1)
+ terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
) / 4.0f;
if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
{
// shallow water
*dataPtr++ = 0xffc09870;
}
else if (avgHeight < m_WaterHeight)
{
// Set water as constant color for consistency on different maps
*dataPtr++ = 0xffa07850;
}
else
{
int hmap = ((int)terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
int val = (hmap / 3) + 170;
u32 color = 0xFFFFFFFF;
CMiniPatch* mp = terrain->GetTile(x + i, y + j);
if (mp)
{
CTerrainTextureEntry* tex = mp->GetTextureEntry();
if (tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if(!tex->GetTexture()->TryLoad())
m_TerrainTextureDirty = true;
color = tex->GetBaseColor();
}
}
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
}
}
}
// Upload the texture
deviceCommandContext->UploadTextureRegion(
m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8,
m_TerrainData.get(), width * height * 4, 0, 0, width, height);
}
void CMiniMapTexture::RenderFinalTexture(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
const double currentTime = timer_Time();
const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty;
if (doUpdate)
m_LastFinalTextureUpdate = currentTime;
else
return;
m_FinalTextureDirty = false;
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FinalTextureFBO);
+ deviceCommandContext->SetFramebuffer(m_FinalTextureFramebuffer.get());
const SViewPort oldViewPort = g_Renderer.GetViewport();
const SViewPort viewPort = { 0, 0, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE };
g_Renderer.SetViewport(viewPort);
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
const float invTileMapSize = 1.0f / static_cast(TERRAIN_TILE_SIZE * m_MapSize);
const float texCoordMax = m_TerrainTexture ? static_cast(m_MapSize - 1) / m_TerrainTexture->GetWidth() : 1.0f;
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines);
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
tech->GetGraphicsPipelineStateDesc();
tech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
shader = tech->GetShader();
if (m_TerrainTexture)
shader->BindTexture(str_baseTex, m_TerrainTexture.get());
CMatrix3D baseTransform;
baseTransform.SetIdentity();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
CMatrix3D terrainTransform;
terrainTransform.SetIdentity();
terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f);
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, terrainTransform);
if (m_TerrainTexture)
DrawTexture(shader);
pipelineStateDesc.blendState.enabled = true;
pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
Renderer::Backend::BlendFactor::SRC_ALPHA;
pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
Renderer::Backend::BlendOp::ADD;
pipelineStateDesc.blendState.colorWriteMask =
Renderer::Backend::ColorWriteMask::RED |
Renderer::Backend::ColorWriteMask::GREEN |
Renderer::Backend::ColorWriteMask::BLUE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
// Draw territory boundaries
CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, territoryTexture.GetMinimapTextureMatrix());
DrawTexture(shader);
pipelineStateDesc.blendState.enabled = false;
pipelineStateDesc.blendState.colorWriteMask =
Renderer::Backend::ColorWriteMask::ALPHA;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
shader->BindTexture(str_baseTex, losTexture.GetTexture());
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, losTexture.GetMinimapTextureMatrix());
DrawTexture(shader);
tech->EndPass();
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines);
tech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
shader = tech->GetShader();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_pointSize, 9.0f);
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Convert world space coordinates into [0, 2].
const float unitScale = invTileMapSize;
unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f);
// Offset the coordinates to [-1, 1].
unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f));
shader->Uniform(str_transform, unitMatrix);
CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap);
if (doUpdate)
{
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
if (currentTime > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = currentTime + m_HalfBlinkDuration;
}
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer());
if (vis != LosVisibility::HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat();
v.y = posZ.ToFloat();
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
addVertex(v, attrColor, attrPos);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (const MinimapUnitVertex& vertex : pingingVertices)
{
addVertex(vertex, attrColor, attrPos);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
m_VertexArray.Upload();
}
m_VertexArray.PrepareForRendering();
if (m_EntitiesDrawn > 0)
{
Renderer::Backend::GL::CDeviceCommandContext::ScissorRect scissorRect;
scissorRect.x = scissorRect.y = 1;
scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2;
deviceCommandContext->SetScissors(1, &scissorRect);
#if !CONFIG2_GLES
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
u8* indexBase = m_IndexArray.Bind();
u8* base = m_VertexArray.Bind();
const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
shader->AssertPointersBound();
glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind();
#if !CONFIG2_GLES
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
deviceCommandContext->SetScissors(0, nullptr);
}
tech->EndPass();
-
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
g_Renderer.SetViewport(oldViewPort);
}
// static
float CMiniMapTexture::GetShallowPassageHeight()
{
float shallowPassageHeight = 0.0f;
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
return shallowPassageHeight;
}
Index: ps/trunk/source/graphics/MiniMapTexture.h
===================================================================
--- ps/trunk/source/graphics/MiniMapTexture.h (revision 26301)
+++ ps/trunk/source/graphics/MiniMapTexture.h (revision 26302)
@@ -1,101 +1,101 @@
/* 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_MINIMAPTEXTURE
#define INCLUDED_MINIMAPTEXTURE
-#include "lib/ogl.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Texture.h"
#include "renderer/VertexArray.h"
#include
class CSimulation2;
class CTerrain;
class CMiniMapTexture
{
NONCOPYABLE(CMiniMapTexture);
public:
CMiniMapTexture(CSimulation2& simulation);
~CMiniMapTexture();
/**
* Marks the texture as dirty if it's old enough to redraw it on Render.
*/
void Update(const float deltaRealTime);
/**
* Redraws the texture if it's dirty.
*/
void Render(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
Renderer::Backend::GL::CTexture* GetTexture() const { return m_FinalTexture.get(); }
/**
* @return The maximum height for unit passage in water.
*/
static float GetShallowPassageHeight();
private:
void CreateTextures(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CTerrain* terrain);
void DestroyTextures();
void RebuildTerrainTexture(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CTerrain* terrain);
void RenderFinalTexture(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
CSimulation2& m_Simulation;
bool m_TerrainTextureDirty = true;
bool m_FinalTextureDirty = true;
double m_LastFinalTextureUpdate = 0.0;
// minimap texture handles
std::unique_ptr
m_TerrainTexture, m_FinalTexture;
- GLuint m_FinalTextureFBO = 0;
+ std::unique_ptr
+ m_FinalTextureFramebuffer;
// texture data
std::unique_ptr m_TerrainData;
// map size
ssize_t m_MapSize = 0;
// Maximal water height to allow the passage of a unit (for underwater shallows).
float m_ShallowPassageHeight = 0.0f;
float m_WaterHeight = 0.0f;
VertexIndexArray m_IndexArray;
VertexArray m_VertexArray;
VertexArray::Attribute m_AttributePos;
VertexArray::Attribute m_AttributeColor;
size_t m_EntitiesDrawn = 0;
double m_PingDuration = 25.0;
double m_HalfBlinkDuration = 0.0;
double m_NextBlinkTime = 0.0;
bool m_BlinkState = false;
};
#endif // INCLUDED_MINIMAPTEXTURE
Index: ps/trunk/source/renderer/DebugRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/DebugRenderer.cpp (revision 26301)
+++ ps/trunk/source/renderer/DebugRenderer.cpp (revision 26302)
@@ -1,451 +1,451 @@
/* 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/DebugRenderer.h"
#include "graphics/Camera.h"
#include "graphics/Color.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "lib/ogl.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Brush.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CStrInternStatic.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include
namespace
{
void SetGraphicsPipelineStateFromTechAndColor(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderTechniquePtr& tech, const CColor& color, const bool depthTestEnabled = true)
{
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc = tech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthTestEnabled = depthTestEnabled;
if (color.a != 1.0f)
{
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;
}
else
pipelineStateDesc.blendState.enabled = false;
pipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
}
} // anonymous namespace
void CDebugRenderer::DrawLine(
const CVector3D& from, const CVector3D& to, const CColor& color,
const float width, const bool depthTestEnabled)
{
if (from == to)
return;
DrawLine({from, to}, color, width, depthTestEnabled);
}
void CDebugRenderer::DrawLine(
const std::vector& line, const CColor& color,
const float width, const bool depthTestEnabled)
{
#if CONFIG2_GLES
- UNUSED2(line); UNUSED2(color); UNUSED2(width);
+ UNUSED2(line); UNUSED2(color); UNUSED2(width); UNUSED2(depthTestEnabled);
#warning TODO: implement drawing line for GLES
#else
CShaderTechniquePtr debugLineTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
debugLineTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), debugLineTech, color, depthTestEnabled);
const CCamera& viewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
CShaderProgramPtr debugLineShader = debugLineTech->GetShader();
debugLineShader->Uniform(str_transform, viewCamera.GetViewProjection());
debugLineShader->Uniform(str_color, color);
const CVector3D cameraIn = viewCamera.GetOrientation().GetIn();
std::vector vertices;
vertices.reserve(line.size() * 6 * 3);
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
for (size_t idx = 1; idx < line.size(); ++idx)
{
const CVector3D from = line[idx - 1];
const CVector3D to = line[idx];
const CVector3D direction = (to - from).Normalized();
const CVector3D view = direction.Dot(cameraIn) > 0.9f ?
CVector3D(0.0f, 1.0f, 0.0f) :
cameraIn;
const CVector3D offset = view.Cross(direction).Normalized() * width;
ADD(from + offset)
ADD(to - offset)
ADD(to + offset)
ADD(from + offset)
ADD(from - offset)
ADD(to - offset)
}
#undef ADD
debugLineShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
debugLineShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, vertices.size() / 3);
debugLineTech->EndPass();
#endif
}
void CDebugRenderer::DrawCircle(const CVector3D& origin, const float radius, const CColor& color)
{
#if CONFIG2_GLES
UNUSED2(origin); UNUSED2(radius); UNUSED2(color);
#warning TODO: implement drawing circle for GLES
#else
CShaderTechniquePtr debugCircleTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
debugCircleTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), debugCircleTech, color);
const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera();
CShaderProgramPtr debugCircleShader = debugCircleTech->GetShader();
debugCircleShader->Uniform(str_transform, camera.GetViewProjection());
debugCircleShader->Uniform(str_color, color);
const CVector3D cameraUp = camera.GetOrientation().GetUp();
const CVector3D cameraLeft = camera.GetOrientation().GetLeft();
std::vector vertices;
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
ADD(origin)
constexpr size_t segments = 16;
for (size_t idx = 0; idx <= segments; ++idx)
{
const float angle = M_PI * 2.0f * idx / segments;
const CVector3D offset = cameraUp * sin(angle) - cameraLeft * cos(angle);
ADD(origin + offset * radius)
}
#undef ADD
debugCircleShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
debugCircleShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size() / 3);
debugCircleTech->EndPass();
#endif
}
void CDebugRenderer::DrawCameraFrustum(const CCamera& camera, const CColor& color, int intermediates)
{
#if CONFIG2_GLES
UNUSED2(camera); UNUSED2(color); UNUSED2(intermediates);
#warning TODO: implement camera frustum for GLES
#else
CCamera::Quad nearPoints;
CCamera::Quad farPoints;
camera.GetViewQuad(camera.GetNearPlane(), nearPoints);
camera.GetViewQuad(camera.GetFarPlane(), farPoints);
for(int i = 0; i < 4; i++)
{
nearPoints[i] = camera.m_Orientation.Transform(nearPoints[i]);
farPoints[i] = camera.m_Orientation.Transform(farPoints[i]);
}
CShaderTechniquePtr overlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_line);
overlayTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), overlayTech, color);
CShaderProgramPtr overlayShader = overlayTech->GetShader();
overlayShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
overlayShader->Uniform(str_color, color);
std::vector vertices;
#define ADD(position) \
vertices.emplace_back((position).X); \
vertices.emplace_back((position).Y); \
vertices.emplace_back((position).Z);
// Near plane.
ADD(nearPoints[0]);
ADD(nearPoints[1]);
ADD(nearPoints[2]);
ADD(nearPoints[3]);
// Far plane.
ADD(farPoints[0]);
ADD(farPoints[1]);
ADD(farPoints[2]);
ADD(farPoints[3]);
// Intermediate planes.
CVector3D intermediatePoints[4];
for(int i = 0; i < intermediates; ++i)
{
const float t = (i + 1.0f) / (intermediates + 1.0f);
for(int j = 0; j < 4; ++j)
intermediatePoints[j] = nearPoints[j] * t + farPoints[j] * (1.0f - t);
ADD(intermediatePoints[0]);
ADD(intermediatePoints[1]);
ADD(intermediatePoints[2]);
ADD(intermediatePoints[3]);
}
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUADS, 0, vertices.size() / 3);
vertices.clear();
// Connection lines.
ADD(nearPoints[0]);
ADD(farPoints[0]);
ADD(nearPoints[1]);
ADD(farPoints[1]);
ADD(nearPoints[2]);
ADD(farPoints[2]);
ADD(nearPoints[3]);
ADD(farPoints[3]);
ADD(nearPoints[0]);
ADD(farPoints[0]);
overlayShader->VertexPointer(3, GL_FLOAT, 0, vertices.data());
overlayShader->AssertPointersBound();
glDrawArrays(GL_QUAD_STRIP, 0, vertices.size() / 3);
#undef ADD
overlayTech->EndPass();
#endif
}
void CDebugRenderer::DrawBoundingBox(const CBoundingBoxAligned& boundingBox, const CColor& color)
{
DrawBoundingBox(boundingBox, color, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
}
void CDebugRenderer::DrawBoundingBox(const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), shaderTech, color);
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, transform);
std::vector data;
#define ADD_FACE(x, y, z) \
ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \
ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z);
#define ADD_PT(u_, v_, x, y, z) \
STMT(int u = u_; int v = v_; \
data.push_back(u); \
data.push_back(v); \
data.push_back(boundingBox[x].X); \
data.push_back(boundingBox[y].Y); \
data.push_back(boundingBox[z].Z); \
)
ADD_FACE(u, v, 0);
ADD_FACE(0, u, v);
ADD_FACE(u, 0, 1-v);
ADD_FACE(u, 1-v, 1);
ADD_FACE(1, u, 1-v);
ADD_FACE(u, 1, v);
#undef ADD_FACE
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6*6);
shaderTech->EndPass();
}
void CDebugRenderer::DrawBoundingBoxOutline(const CBoundingBoxAligned& boundingBox, const CColor& color)
{
DrawBoundingBoxOutline(boundingBox, color, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
}
void CDebugRenderer::DrawBoundingBoxOutline(const CBoundingBoxAligned& boundingBox, const CColor& color, const CMatrix3D& transform)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), shaderTech, color);
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, transform);
std::vector data;
#define ADD_FACE(x, y, z) \
ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); \
ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \
ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); \
ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z);
#define ADD_PT(u_, v_, x, y, z) \
STMT(int u = u_; int v = v_; \
data.push_back(u); \
data.push_back(v); \
data.push_back(boundingBox[x].X); \
data.push_back(boundingBox[y].Y); \
data.push_back(boundingBox[z].Z); \
)
ADD_FACE(u, v, 0);
ADD_FACE(0, u, v);
ADD_FACE(u, 0, 1-v);
ADD_FACE(u, 1-v, 1);
ADD_FACE(1, u, 1-v);
ADD_FACE(u, 1, v);
#undef ADD_FACE
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_LINES, 0, 6*8);
shaderTech->EndPass();
}
void CDebugRenderer::DrawBrush(const CBrush& brush, const CColor& color)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), shaderTech, color);
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
std::vector data;
std::vector> faces;
brush.GetFaces(faces);
#define ADD_VERT(a) \
STMT( \
data.push_back(u); \
data.push_back(v); \
data.push_back(brush.GetVertices()[faces[i][a]].X); \
data.push_back(brush.GetVertices()[faces[i][a]].Y); \
data.push_back(brush.GetVertices()[faces[i][a]].Z); \
)
for (size_t i = 0; i < faces.size(); ++i)
{
// Triangulate into (0,1,2), (0,2,3), ...
for (size_t j = 1; j < faces[i].size() - 2; ++j)
{
float u = 0;
float v = 0;
ADD_VERT(0);
ADD_VERT(j);
ADD_VERT(j+1);
}
}
#undef ADD_VERT
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, data.size() / 5);
shaderTech->EndPass();
}
void CDebugRenderer::DrawBrushOutline(const CBrush& brush, const CColor& color)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
shaderTech->BeginPass();
SetGraphicsPipelineStateFromTechAndColor(g_Renderer.GetDeviceCommandContext(), shaderTech, color);
CShaderProgramPtr shader = shaderTech->GetShader();
shader->Uniform(str_color, color);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
std::vector data;
std::vector> faces;
brush.GetFaces(faces);
#define ADD_VERT(a) \
STMT( \
data.push_back(u); \
data.push_back(v); \
data.push_back(brush.GetVertices()[faces[i][a]].X); \
data.push_back(brush.GetVertices()[faces[i][a]].Y); \
data.push_back(brush.GetVertices()[faces[i][a]].Z); \
)
for (size_t i = 0; i < faces.size(); ++i)
{
for (size_t j = 0; j < faces[i].size() - 1; ++j)
{
float u = 0;
float v = 0;
ADD_VERT(j);
ADD_VERT(j+1);
}
}
#undef ADD_VERT
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
shader->AssertPointersBound();
glDrawArrays(GL_LINES, 0, data.size() / 5);
shaderTech->EndPass();
}
Index: ps/trunk/source/renderer/PostprocManager.cpp
===================================================================
--- ps/trunk/source/renderer/PostprocManager.cpp (revision 26301)
+++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26302)
@@ -1,857 +1,777 @@
/* 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/backend/gl/Device.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)
+ : m_IsInitialized(false), m_PostProcEffect(L"default"), m_WhichBuffer(true),
+ m_Sharpness(0.3f), m_UsingMultisampleBuffer(false), m_MultisampleCount(0)
{
}
CPostprocManager::~CPostprocManager()
{
Cleanup();
}
bool CPostprocManager::IsEnabled() const
{
return g_RenderingOptions.GetPostProc() &&
g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB;
}
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_CaptureFramebuffer.reset();
+
+ m_PingFramebuffer.reset();
+ m_PongFramebuffer.reset();
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();
+ for (BlurScale& scale : m_BlurScales)
+ {
+ for (BlurScale::Step& step : scale.steps)
+ {
+ step.framebuffer.reset();
+ step.texture.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);
+ uint32_t width = m_Width / 2, height = m_Height / 2;
+ for (BlurScale& scale : m_BlurScales)
+ {
+ for (BlurScale::Step& step : scale.steps)
+ {
+ GEN_BUFFER_RGBA(step.texture, width, height);
+ step.framebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ step.texture.get(), nullptr);
+ }
+ width /= 2;
+ height /= 2;
+ }
#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.
+ m_CaptureFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_ColorTex1.get(), m_DepthTex.get(),
+ g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor());
+
+ m_PingFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_ColorTex1.get(), nullptr);
+ m_PongFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_ColorTex2.get(), nullptr);
- 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)
+ if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer)
{
- LOGWARNING("Framebuffer object incomplete (A): 0x%04X", status);
+ LOGWARNING("Failed to create postproc framebuffers");
+ g_RenderingOptions.SetPostProc(false);
}
- 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::CDeviceCommandContext* deviceCommandContext,
- Renderer::Backend::GL::CTexture* inTex, Renderer::Backend::GL::CTexture* outTex, int inWidth, int inHeight)
+ Renderer::Backend::GL::CFramebuffer* framebuffer,
+ Renderer::Backend::GL::CTexture* inTex, 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);
+ deviceCommandContext->SetFramebuffer(framebuffer);
// 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();
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
const 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::CDeviceCommandContext* deviceCommandContext,
- Renderer::Backend::GL::CTexture* inOutTex, Renderer::Backend::GL::CTexture* tempTex, int inWidth, int inHeight)
+ Renderer::Backend::GL::CTexture* inTex,
+ Renderer::Backend::GL::CTexture* tempTex,
+ Renderer::Backend::GL::CFramebuffer* tempFramebuffer,
+ Renderer::Backend::GL::CFramebuffer* outFramebuffer,
+ 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);
+ deviceCommandContext->SetFramebuffer(tempFramebuffer);
// 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();
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
CShaderProgramPtr shader = tech->GetShader();
- shader->BindTexture(str_renderedTex, inOutTex);
+ shader->BindTexture(str_renderedTex, inTex);
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);
+ deviceCommandContext->SetFramebuffer(outFramebuffer);
// 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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
- int width = m_Width, height = m_Height;
-
- #define SCALE_AND_BLUR(tex1, tex2, temptex) \
- ApplyBlurDownscale2x(deviceCommandContext, (tex1).get(), (tex2).get(), width, height); \
- width /= 2; \
- height /= 2; \
- ApplyBlurGauss(deviceCommandContext, (tex2).get(), (temptex).get(), width, height);
+ uint32_t width = m_Width, height = m_Height;
+ Renderer::Backend::GL::CTexture* previousTexture =
+ (m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
- // 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
+ for (BlurScale& scale : m_BlurScales)
+ {
+ ApplyBlurDownscale2x(deviceCommandContext, scale.steps[0].framebuffer.get(), previousTexture, width, height);
+ width /= 2;
+ height /= 2;
+ ApplyBlurGauss(deviceCommandContext, scale.steps[0].texture.get(),
+ scale.steps[1].texture.get(), scale.steps[1].framebuffer.get(),
+ scale.steps[0].framebuffer.get(), width, height);
+ }
}
-void CPostprocManager::CaptureRenderOutput()
+void CPostprocManager::CaptureRenderOutput(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
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);
+ if (m_UsingMultisampleBuffer)
+ deviceCommandContext->SetFramebuffer(m_MultisampleFramebuffer.get());
+ else
+ deviceCommandContext->SetFramebuffer(m_CaptureFramebuffer.get());
m_WhichBuffer = true;
-
- if (m_UsingMultisampleBuffer)
- {
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_MultisampleFBO);
- glDrawBuffers(1, buffers);
- }
}
void CPostprocManager::ReleaseRenderOutput(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
ENSURE(m_IsInitialized);
- const Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
- Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc();
- deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ // We blit to the backbuffer from the previous active buffer.
+ deviceCommandContext->BlitFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer(),
+ (m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get());
- // 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);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
}
void CPostprocManager::ApplyEffect(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
- const CShaderTechniquePtr& shaderTech1, int pass)
+ const CShaderTechniquePtr& shaderTech, int pass)
{
// select the other FBO for rendering
- if (!m_WhichBuffer)
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
- else
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PongFbo);
+ deviceCommandContext->SetFramebuffer(
+ (m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get());
- shaderTech1->BeginPass(pass);
+ shaderTech->BeginPass(pass);
deviceCommandContext->SetGraphicsPipelineState(
- shaderTech1->GetGraphicsPipelineStateDesc(pass));
- const CShaderProgramPtr& shader = shaderTech1->GetShader(pass);
+ shaderTech->GetGraphicsPipelineStateDesc(pass));
+ const CShaderProgramPtr& shader = shaderTech->GetShader(pass);
// 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->BindTexture(str_blurTex2, m_BlurScales[0].steps[0].texture.get());
+ shader->BindTexture(str_blurTex4, m_BlurScales[1].steps[0].texture.get());
+ shader->BindTexture(str_blurTex8, m_BlurScales[2].steps[0].texture.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[] =
{
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);
- shaderTech1->EndPass(pass);
+ shaderTech->EndPass(pass);
m_WhichBuffer = !m_WhichBuffer;
}
void CPostprocManager::ApplyPostproc(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
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(deviceCommandContext);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_PingFbo);
for (int pass = 0; pass < m_PostProcTech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_PostProcTech, pass);
}
if (hasAA)
{
for (int pass = 0; pass < m_AATech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, m_AATech, pass);
}
if (hasSharp)
{
for (int pass = 0; pass < m_SharpTech->GetNumPasses(); ++pass)
ApplyEffect(deviceCommandContext, 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);
+ m_MultisampleFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_MultisampleColorTex.get(), m_MultisampleDepthTex.get(),
+ g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor());
- 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)
+ if (!m_MultisampleFramebuffer)
{
- LOGWARNING("Multisample framebuffer object incomplete (A): 0x%04X", status);
+ LOGERROR("Failed to create postproc multisample framebuffer");
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_MultisampleFramebuffer.reset();
m_MultisampleColorTex.reset();
m_MultisampleDepthTex.reset();
glDisable(GL_MULTISAMPLE);
}
bool CPostprocManager::IsMultisampleEnabled() const
{
return m_UsingMultisampleBuffer;
}
-void CPostprocManager::ResolveMultisampleFramebuffer()
+void CPostprocManager::ResolveMultisampleFramebuffer(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
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);
+ deviceCommandContext->BlitFramebuffer(
+ m_PingFramebuffer.get(), m_MultisampleFramebuffer.get());
+ deviceCommandContext->SetFramebuffer(m_PingFramebuffer.get());
}
#else
#warning TODO: implement PostprocManager for GLES
void ApplyBlurDownscale2x(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext),
+ Renderer::Backend::GL::CFramebuffer* UNUSED(framebuffer),
Renderer::Backend::GL::CTexture* UNUSED(inTex),
- Renderer::Backend::GL::CTexture* UNUSED(outTex),
int UNUSED(inWidth), int UNUSED(inHeight))
{
}
void CPostprocManager::ApplyBlurGauss(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext),
- Renderer::Backend::GL::CTexture* UNUSED(inOutTex),
+ Renderer::Backend::GL::CTexture* UNUSED(inTex),
Renderer::Backend::GL::CTexture* UNUSED(tempTex),
+ Renderer::Backend::GL::CFramebuffer* UNUSED(tempFramebuffer),
+ Renderer::Backend::GL::CFramebuffer* UNUSED(outFramebuffer),
int UNUSED(inWidth), int UNUSED(inHeight))
{
}
void CPostprocManager::ApplyEffect(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext),
- const CShaderTechniquePtr& UNUSED(shaderTech1), int UNUSED(pass))
+ const CShaderTechniquePtr& UNUSED(shaderTech), int UNUSED(pass))
{
}
CPostprocManager::CPostprocManager()
{
}
CPostprocManager::~CPostprocManager()
{
}
bool CPostprocManager::IsEnabled() const
{
return false;
}
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::CaptureRenderOutput(
+ Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext))
{
}
void CPostprocManager::ApplyPostproc(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext))
{
}
void CPostprocManager::ReleaseRenderOutput(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext))
{
}
void CPostprocManager::CreateMultisampleBuffer()
{
}
void CPostprocManager::DestroyMultisampleBuffer()
{
}
bool CPostprocManager::IsMultisampleEnabled() const
{
return false;
}
-void CPostprocManager::ResolveMultisampleFramebuffer()
+void CPostprocManager::ResolveMultisampleFramebuffer(
+ Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext))
{
}
#endif
Index: ps/trunk/source/renderer/PostprocManager.h
===================================================================
--- ps/trunk/source/renderer/PostprocManager.h (revision 26301)
+++ ps/trunk/source/renderer/PostprocManager.h (revision 26302)
@@ -1,165 +1,182 @@
/* 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_POSTPROCMANAGER
#define INCLUDED_POSTPROCMANAGER
#include "graphics/ShaderTechniquePtr.h"
-#include "lib/ogl.h"
#include "ps/CStr.h"
+#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Texture.h"
+#include
#include
class CPostprocManager
{
public:
CPostprocManager();
~CPostprocManager();
// Returns true if the the manager can be used.
bool IsEnabled() const;
// Create all buffers/textures in GPU memory and set default effect.
// @note Must be called before using in the renderer. May be called multiple times.
void Initialize();
// Update the size of the screen
void Resize();
// Returns a list of xml files found in shaders/effects/postproc.
static std::vector GetPostEffects();
// Returns the name of the current effect.
const CStrW& GetPostEffect() const
{
return m_PostProcEffect;
}
// Sets the current effect.
void SetPostEffect(const CStrW& name);
// Triggers update of shaders and FBO if needed.
void UpdateAntiAliasingTechnique();
void UpdateSharpeningTechnique();
void UpdateSharpnessFactor();
void SetDepthBufferClipPlanes(float nearPlane, float farPlane);
// Clears the two color buffers and depth buffer, and redirects all rendering
// to our textures instead of directly to the system framebuffer.
// @note CPostprocManager must be initialized first
- void CaptureRenderOutput();
+ void CaptureRenderOutput(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
// First renders blur textures, then calls ApplyEffect for each effect pass,
// ping-ponging the buffers at each step.
// @note CPostprocManager must be initialized first
void ApplyPostproc(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
// Blits the final postprocessed texture to the system framebuffer. The system framebuffer
// is selected as the output buffer. Should be called before silhouette rendering.
// @note CPostprocManager must be initialized first
void ReleaseRenderOutput(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
// Returns true if we render main scene in the MSAA framebuffer.
bool IsMultisampleEnabled() const;
// Resolves the MSAA framebuffer into the regular one.
- void ResolveMultisampleFramebuffer();
+ void ResolveMultisampleFramebuffer(
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
private:
void CreateMultisampleBuffer();
void DestroyMultisampleBuffer();
// Two framebuffers, that we flip between at each shader pass.
- GLuint m_PingFbo, m_PongFbo;
+ std::unique_ptr
+ m_CaptureFramebuffer, m_PingFramebuffer, m_PongFramebuffer;
// Unique color textures for the framebuffers.
std::unique_ptr m_ColorTex1, m_ColorTex2;
// The framebuffers share a depth/stencil texture.
std::unique_ptr m_DepthTex;
float m_NearPlane, m_FarPlane;
// A framebuffer and textures x2 for each blur level we render.
- GLuint m_BloomFbo;
- std::unique_ptr
- m_BlurTex2a, m_BlurTex2b, m_BlurTex4a, m_BlurTex4b, m_BlurTex8a, m_BlurTex8b;
+ struct BlurScale
+ {
+ struct Step
+ {
+ std::unique_ptr framebuffer;
+ std::unique_ptr texture;
+ };
+ std::array steps;
+ };
+ std::array m_BlurScales;
// Indicates which of the ping-pong buffers is used for reading and which for drawing.
bool m_WhichBuffer;
// The name and shader technique we are using. "default" name means no technique is used
// (i.e. while we do allocate the buffers, no effects are rendered).
CStrW m_PostProcEffect;
CShaderTechniquePtr m_PostProcTech;
CStr m_SharpName;
CShaderTechniquePtr m_SharpTech;
float m_Sharpness;
CStr m_AAName;
CShaderTechniquePtr m_AATech;
bool m_UsingMultisampleBuffer;
- GLuint m_MultisampleFBO;
+ std::unique_ptr m_MultisampleFramebuffer;
std::unique_ptr
m_MultisampleColorTex, m_MultisampleDepthTex;
GLsizei m_MultisampleCount;
std::vector m_AllowedSampleCounts;
// The current screen dimensions in pixels.
int m_Width, m_Height;
// Is the postproc manager initialized? Buffers created? Default effect loaded?
bool m_IsInitialized;
// Creates blur textures at various scales, for bloom, DOF, etc.
void ApplyBlur(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext);
// High quality GPU image scaling to half size. outTex must have exactly half the size
// of inTex. inWidth and inHeight are the dimensions of inTex in texels.
void ApplyBlurDownscale2x(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
- Renderer::Backend::GL::CTexture* inTex, Renderer::Backend::GL::CTexture* outTex, int inWidth, int inHeight);
+ Renderer::Backend::GL::CFramebuffer* framebuffer,
+ Renderer::Backend::GL::CTexture* inTex,
+ int inWidth, int inHeight);
// GPU-based Gaussian blur in two passes. inOutTex contains the input image and will be filled
// with the blurred image. tempTex must have the same size as inOutTex.
// inWidth and inHeight are the dimensions of the images in texels.
void ApplyBlurGauss(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
- Renderer::Backend::GL::CTexture* inOutTex, Renderer::Backend::GL::CTexture* tempTex, int inWidth, int inHeight);
+ Renderer::Backend::GL::CTexture* inTex,
+ Renderer::Backend::GL::CTexture* tempTex,
+ Renderer::Backend::GL::CFramebuffer* tempFramebuffer,
+ Renderer::Backend::GL::CFramebuffer* outFramebuffer,
+ int inWidth, int inHeight);
// Applies a pass of a given effect to the entire current framebuffer. The shader is
// provided with a number of general-purpose variables, including the rendered screen so far,
// the depth buffer, a number of blur textures, the screen size, the zNear/zFar planes and
// some other parameters used by the optional bloom/HDR pass.
void ApplyEffect(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
- const CShaderTechniquePtr& shaderTech1, int pass);
+ const CShaderTechniquePtr& shaderTech, int pass);
// Delete all allocated buffers/textures from GPU memory.
void Cleanup();
// Delete existing buffers/textures and create them again, using a new screen size if needed.
// (the textures are also attached to the framebuffers)
void RecreateBuffers();
};
#endif // INCLUDED_POSTPROCMANAGER
Index: ps/trunk/source/renderer/Renderer.cpp
===================================================================
--- ps/trunk/source/renderer/Renderer.cpp (revision 26301)
+++ ps/trunk/source/renderer/Renderer.cpp (revision 26302)
@@ -1,832 +1,835 @@
/* 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.h"
#include "graphics/Canvas2D.h"
#include "graphics/CinemaManager.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/ModelDef.h"
#include "graphics/TerrainTextureManager.h"
#include "i18n/L10n.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/ogl.h"
#include "lib/tex/tex.h"
#include "gui/GUIManager.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Globals.h"
#include "ps/Loader.h"
#include "ps/Profile.h"
#include "ps/Filesystem.h"
#include "ps/World.h"
#include "ps/ProfileViewer.h"
#include "graphics/Camera.h"
#include "graphics/FontManager.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/DebugRenderer.h"
#include "renderer/PostprocManager.h"
#include "renderer/RenderingOptions.h"
#include "renderer/RenderModifiers.h"
#include "renderer/SceneRenderer.h"
#include "renderer/TimeManager.h"
#include "renderer/VertexBufferManager.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "tools/atlas/GameInterface/View.h"
#include
namespace
{
size_t g_NextScreenShotNumber = 0;
///////////////////////////////////////////////////////////////////////////////////
// CRendererStatsTable - Profile display of rendering stats
/**
* Class CRendererStatsTable: Implementation of AbstractProfileTable to
* display the renderer stats in-game.
*
* Accesses CRenderer::m_Stats by keeping the reference passed to the
* constructor.
*/
class CRendererStatsTable : public AbstractProfileTable
{
NONCOPYABLE(CRendererStatsTable);
public:
CRendererStatsTable(const CRenderer::Stats& st);
// Implementation of AbstractProfileTable interface
CStr GetName();
CStr GetTitle();
size_t GetNumberRows();
const std::vector& GetColumns();
CStr GetCellText(size_t row, size_t col);
AbstractProfileTable* GetChild(size_t row);
private:
/// Reference to the renderer singleton's stats
const CRenderer::Stats& Stats;
/// Column descriptions
std::vector columnDescriptions;
enum
{
Row_DrawCalls = 0,
Row_TerrainTris,
Row_WaterTris,
Row_ModelTris,
Row_OverlayTris,
Row_BlendSplats,
Row_Particles,
Row_VBReserved,
Row_VBAllocated,
Row_TextureMemory,
Row_ShadersLoaded,
// Must be last to count number of rows
NumberRows
};
};
// Construction
CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st)
: Stats(st)
{
columnDescriptions.push_back(ProfileColumn("Name", 230));
columnDescriptions.push_back(ProfileColumn("Value", 100));
}
// Implementation of AbstractProfileTable interface
CStr CRendererStatsTable::GetName()
{
return "renderer";
}
CStr CRendererStatsTable::GetTitle()
{
return "Renderer statistics";
}
size_t CRendererStatsTable::GetNumberRows()
{
return NumberRows;
}
const std::vector& CRendererStatsTable::GetColumns()
{
return columnDescriptions;
}
CStr CRendererStatsTable::GetCellText(size_t row, size_t col)
{
char buf[256];
switch(row)
{
case Row_DrawCalls:
if (col == 0)
return "# draw calls";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls);
return buf;
case Row_TerrainTris:
if (col == 0)
return "# terrain tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris);
return buf;
case Row_WaterTris:
if (col == 0)
return "# water tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris);
return buf;
case Row_ModelTris:
if (col == 0)
return "# model tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris);
return buf;
case Row_OverlayTris:
if (col == 0)
return "# overlay tris";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris);
return buf;
case Row_BlendSplats:
if (col == 0)
return "# blend splats";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats);
return buf;
case Row_Particles:
if (col == 0)
return "# particles";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles);
return buf;
case Row_VBReserved:
if (col == 0)
return "VB reserved";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024);
return buf;
case Row_VBAllocated:
if (col == 0)
return "VB allocated";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024);
return buf;
case Row_TextureMemory:
if (col == 0)
return "textures uploaded";
sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024);
return buf;
case Row_ShadersLoaded:
if (col == 0)
return "shader effects loaded";
sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded());
return buf;
default:
return "???";
}
}
AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row))
{
return 0;
}
} // anonymous namespace
///////////////////////////////////////////////////////////////////////////////////
// CRenderer implementation
/**
* Struct CRendererInternals: Truly hide data that is supposed to be hidden
* in this structure so it won't even appear in header files.
*/
class CRenderer::Internals
{
NONCOPYABLE(Internals);
public:
/// true if CRenderer::Open has been called
bool IsOpen;
/// true if shaders need to be reloaded
bool ShadersDirty;
/// Table to display renderer stats in-game via profile system
CRendererStatsTable profileTable;
/// Shader manager
CShaderManager shaderManager;
/// Texture manager
CTextureManager textureManager;
/// Time manager
CTimeManager timeManager;
/// Postprocessing effect manager
CPostprocManager postprocManager;
CSceneRenderer sceneRenderer;
CDebugRenderer debugRenderer;
CFontManager fontManager;
std::unique_ptr deviceCommandContext;
Internals() :
IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false)
{
}
};
CRenderer::CRenderer()
{
TIMER(L"InitRenderer");
m = std::make_unique();
g_ProfileViewer.AddRootTable(&m->profileTable);
m_Width = 0;
m_Height = 0;
m_Stats.Reset();
// Create terrain related stuff.
new CTerrainTextureManager;
Open(g_xres, g_yres);
// Setup lighting environment. Since the Renderer accesses the
// lighting environment through a pointer, this has to be done before
// the first Frame.
GetSceneRenderer().SetLightEnv(&g_LightEnv);
// I haven't seen the camera affecting GUI rendering and such, but the
// viewport has to be updated according to the video mode
SViewPort vp;
vp.m_X = 0;
vp.m_Y = 0;
vp.m_Width = g_xres;
vp.m_Height = g_yres;
SetViewport(vp);
ModelDefActivateFastImpl();
ColorActivateFastImpl();
ModelRenderer::Init();
}
CRenderer::~CRenderer()
{
delete &g_TexMan;
// We no longer UnloadWaterTextures here -
// that is the responsibility of the module that asked for
// them to be loaded (i.e. CGameView).
m.reset();
}
void CRenderer::EnumCaps()
{
// assume support for nothing
m_Caps.m_ARBProgram = false;
m_Caps.m_ARBProgramShadow = false;
m_Caps.m_VertexShader = false;
m_Caps.m_FragmentShader = false;
m_Caps.m_Shadows = false;
m_Caps.m_PrettyWater = false;
// now start querying extensions
if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL))
{
m_Caps.m_ARBProgram = true;
if (ogl_HaveExtension("GL_ARB_fragment_program_shadow"))
m_Caps.m_ARBProgramShadow = true;
}
// GLSL shaders are in core since GL2.0.
if (ogl_HaveVersion(2, 0))
m_Caps.m_VertexShader = m_Caps.m_FragmentShader = true;
#if CONFIG2_GLES
m_Caps.m_Shadows = true;
#else
if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", NULL))
{
if (ogl_max_tex_units >= 4)
m_Caps.m_Shadows = true;
}
#endif
#if CONFIG2_GLES
m_Caps.m_PrettyWater = true;
#else
if (m_Caps.m_VertexShader && m_Caps.m_FragmentShader)
m_Caps.m_PrettyWater = true;
#endif
}
void CRenderer::ReloadShaders()
{
ENSURE(m->IsOpen);
m->sceneRenderer.ReloadShaders();
m->ShadersDirty = false;
}
bool CRenderer::Open(int width, int height)
{
m->IsOpen = true;
// Must query card capabilities before creating renderers that depend
// on card capabilities.
EnumCaps();
// Dimensions
m_Width = width;
m_Height = height;
// Validate the currently selected render path
SetRenderPath(g_RenderingOptions.GetRenderPath());
- m->deviceCommandContext = Renderer::Backend::GL::CDeviceCommandContext::Create();
+ m->deviceCommandContext = g_VideoMode.GetBackendDevice()->CreateCommandContext();
if (m->postprocManager.IsEnabled())
m->postprocManager.Initialize();
m->sceneRenderer.Initialize();
return true;
}
void CRenderer::Resize(int width, int height)
{
m_Width = width;
m_Height = height;
m->postprocManager.Resize();
m->sceneRenderer.Resize(width, height);
}
void CRenderer::SetRenderPath(RenderPath rp)
{
if (!m->IsOpen)
{
// Delay until Open() is called.
return;
}
// Renderer has been opened, so validate the selected renderpath
if (rp == RenderPath::DEFAULT)
{
if (m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB))
rp = RenderPath::SHADER;
else
rp = RenderPath::FIXED;
}
if (rp == RenderPath::SHADER)
{
if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB)))
{
LOGWARNING("Falling back to fixed function\n");
rp = RenderPath::FIXED;
}
}
// TODO: remove this once capabilities have been properly extracted and the above checks have been moved elsewhere.
g_RenderingOptions.m_RenderPath = rp;
MakeShadersDirty();
}
bool CRenderer::ShouldRender() const
{
return !g_app_minimized && (g_app_has_focus || !g_VideoMode.IsInFullscreen());
}
void CRenderer::RenderFrame(const bool needsPresent)
{
// Do not render if not focused while in fullscreen or minimised,
// as that triggers a difficult-to-reproduce crash on some graphic cards.
if (!ShouldRender())
return;
if (m_ShouldPreloadResourcesBeforeNextFrame)
{
m_ShouldPreloadResourcesBeforeNextFrame = false;
// We don't meed to render logger for the preload.
RenderFrameImpl(true, false);
}
if (m_ScreenShotType == ScreenShotType::BIG)
{
RenderBigScreenShot(needsPresent);
}
else
{
if (m_ScreenShotType == ScreenShotType::DEFAULT)
RenderScreenShot();
else
RenderFrameImpl(true, true);
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
}
}
void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
{
PROFILE3("render");
g_Profiler2.RecordGPUFrameStart();
ogl_WarnIfError();
g_TexMan.UploadResourcesIfNeeded(m->deviceCommandContext.get());
// prepare before starting the renderer frame
if (g_Game && g_Game->IsGameStarted())
g_Game->GetView()->BeginFrame();
if (g_Game)
m->sceneRenderer.SetSimulation(g_Game->GetSimulation2());
// start new frame
BeginFrame();
ogl_WarnIfError();
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->Render();
ogl_WarnIfError();
}
+ m->deviceCommandContext->SetFramebuffer(
+ m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
+
m->sceneRenderer.RenderTextOverlays();
// If we're in Atlas game view, render special tools
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
g_AtlasGameLoop->view->DrawCinemaPathTool();
ogl_WarnIfError();
}
if (g_Game && g_Game->IsGameStarted())
{
g_Game->GetView()->GetCinema()->Render();
ogl_WarnIfError();
}
if (renderGUI)
{
OGL_SCOPED_DEBUG_GROUP("Draw GUI");
// All GUI elements are drawn in Z order to render semi-transparent
// objects correctly.
g_GUI->Draw();
ogl_WarnIfError();
}
// If we're in Atlas game view, render special overlays (e.g. editor bandbox).
if (g_AtlasGameLoop && g_AtlasGameLoop->view)
{
CCanvas2D canvas;
g_AtlasGameLoop->view->DrawOverlays(canvas);
ogl_WarnIfError();
}
g_Console->Render();
ogl_WarnIfError();
if (renderLogger)
{
g_Logger->Render();
ogl_WarnIfError();
}
// Profile information
g_ProfileViewer.RenderProfile();
ogl_WarnIfError();
EndFrame();
const Stats& stats = GetStats();
PROFILE2_ATTR("draw calls: %zu", stats.m_DrawCalls);
PROFILE2_ATTR("terrain tris: %zu", stats.m_TerrainTris);
PROFILE2_ATTR("water tris: %zu", stats.m_WaterTris);
PROFILE2_ATTR("model tris: %zu", stats.m_ModelTris);
PROFILE2_ATTR("overlay tris: %zu", stats.m_OverlayTris);
PROFILE2_ATTR("blend splats: %zu", stats.m_BlendSplats);
PROFILE2_ATTR("particles: %zu", stats.m_Particles);
ogl_WarnIfError();
g_Profiler2.RecordGPUFrameEnd();
ogl_WarnIfError();
}
void CRenderer::RenderScreenShot()
{
m_ScreenShotType = ScreenShotType::NONE;
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath filenameFormat(L"screenshots/screenshot%04d.png");
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
const size_t w = (size_t)g_xres, h = (size_t)g_yres;
const size_t bpp = 24;
GLenum fmt = GL_RGB;
int flags = TEX_BOTTOM_UP;
// Hide log messages and re-render
RenderFrameImpl(true, false);
const size_t img_size = w * h * bpp / 8;
const size_t hdr_size = tex_hdr_size(filename);
std::shared_ptr buf;
AllocateAligned(buf, hdr_size + img_size, maxSectorSize);
GLvoid* img = buf.get() + hdr_size;
Tex t;
if (t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
return;
glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
}
void CRenderer::RenderBigScreenShot(const bool needsPresent)
{
m_ScreenShotType = ScreenShotType::NONE;
// If the game hasn't started yet then use WriteScreenshot to generate the image.
if (!g_Game)
return RenderScreenShot();
int tiles = 4, tileWidth = 256, tileHeight = 256;
CFG_GET_VAL("screenshot.tiles", tiles);
CFG_GET_VAL("screenshot.tilewidth", tileWidth);
CFG_GET_VAL("screenshot.tileheight", tileHeight);
if (tiles <= 0 || tileWidth <= 0 || tileHeight <= 0 || tileWidth * tiles % 4 != 0 || tileHeight * tiles % 4 != 0)
{
LOGWARNING("Invalid big screenshot size: tiles=%d tileWidth=%d tileHeight=%d", tiles, tileWidth, tileHeight);
return;
}
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath filenameFormat(L"screenshots/screenshot%04d.bmp");
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, g_NextScreenShotNumber, filename);
// Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
// hope the screen is actually large enough for that.
ENSURE(g_xres >= tileWidth && g_yres >= tileHeight);
const int imageWidth = tileWidth * tiles, imageHeight = tileHeight * tiles;
const int bpp = 24;
// we want writing BMP to be as fast as possible,
// so read data from OpenGL in BMP format to obviate conversion.
#if CONFIG2_GLES // GLES doesn't support BGR
const GLenum fmt = GL_RGB;
const int flags = TEX_BOTTOM_UP;
#else
const GLenum fmt = GL_BGR;
const int flags = TEX_BOTTOM_UP | TEX_BGR;
#endif
const size_t imageSize = imageWidth * imageHeight * bpp / 8;
const size_t tileSize = tileWidth * tileHeight * bpp / 8;
const size_t headerSize = tex_hdr_size(filename);
void* tileData = malloc(tileSize);
if (!tileData)
{
WARN_IF_ERR(ERR::NO_MEM);
return;
}
std::shared_ptr imageBuffer;
AllocateAligned(imageBuffer, headerSize + imageSize, maxSectorSize);
Tex t;
GLvoid* img = imageBuffer.get() + headerSize;
if (t.wrap(imageWidth, imageHeight, bpp, flags, imageBuffer, headerSize) < 0)
{
free(tileData);
return;
}
ogl_WarnIfError();
CCamera oldCamera = *g_Game->GetView()->GetCamera();
// Resize various things so that the sizes and aspect ratios are correct
{
g_Renderer.Resize(tileWidth, tileHeight);
SViewPort vp = { 0, 0, tileWidth, tileHeight };
g_Game->GetView()->SetViewport(vp);
}
// Render each tile
CMatrix3D projection;
projection.SetIdentity();
const float aspectRatio = 1.0f * tileWidth / tileHeight;
for (int tileY = 0; tileY < tiles; ++tileY)
{
for (int tileX = 0; tileX < tiles; ++tileX)
{
// Adjust the camera to render the appropriate region
if (oldCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
projection.SetPerspectiveTile(oldCamera.GetFOV(), aspectRatio, oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tileX, tileY);
}
g_Game->GetView()->GetCamera()->SetProjection(projection);
RenderFrameImpl(false, false);
// Copy the tile pixels into the main image
glReadPixels(0, 0, tileWidth, tileHeight, fmt, GL_UNSIGNED_BYTE, tileData);
for (int y = 0; y < tileHeight; ++y)
{
void* dest = static_cast(img) + ((tileY * tileHeight + y) * imageWidth + (tileX * tileWidth)) * bpp / 8;
void* src = static_cast(tileData) + y * tileWidth * bpp / 8;
memcpy(dest, src, tileWidth * bpp / 8);
}
m->deviceCommandContext->Flush();
if (needsPresent)
g_VideoMode.GetBackendDevice()->Present();
}
}
// Restore the viewport settings
{
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->SetViewport(vp);
g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
}
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
free(tileData);
}
void CRenderer::BeginFrame()
{
PROFILE("begin frame");
// Zero out all the per-frame stats.
m_Stats.Reset();
if (m->ShadersDirty)
ReloadShaders();
m->sceneRenderer.BeginFrame();
}
void CRenderer::EndFrame()
{
PROFILE3("end frame");
m->sceneRenderer.EndFrame();
BindTexture(0, 0);
}
void CRenderer::SetViewport(const SViewPort &vp)
{
m_Viewport = vp;
glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height);
}
SViewPort CRenderer::GetViewport()
{
return m_Viewport;
}
void CRenderer::BindTexture(int unit, GLuint tex)
{
glActiveTextureARB(GL_TEXTURE0+unit);
glBindTexture(GL_TEXTURE_2D, tex);
}
void CRenderer::MakeShadersDirty()
{
m->ShadersDirty = true;
m->sceneRenderer.MakeShadersDirty();
}
CTextureManager& CRenderer::GetTextureManager()
{
return m->textureManager;
}
CShaderManager& CRenderer::GetShaderManager()
{
return m->shaderManager;
}
CTimeManager& CRenderer::GetTimeManager()
{
return m->timeManager;
}
CPostprocManager& CRenderer::GetPostprocManager()
{
return m->postprocManager;
}
CSceneRenderer& CRenderer::GetSceneRenderer()
{
return m->sceneRenderer;
}
CDebugRenderer& CRenderer::GetDebugRenderer()
{
return m->debugRenderer;
}
CFontManager& CRenderer::GetFontManager()
{
return m->fontManager;
}
void CRenderer::PreloadResourcesBeforeNextFrame()
{
m_ShouldPreloadResourcesBeforeNextFrame = true;
}
void CRenderer::MakeScreenShotOnNextFrame(ScreenShotType screenShotType)
{
m_ScreenShotType = screenShotType;
}
Renderer::Backend::GL::CDeviceCommandContext* CRenderer::GetDeviceCommandContext()
{
return m->deviceCommandContext.get();
}
Index: ps/trunk/source/renderer/SceneRenderer.cpp
===================================================================
--- ps/trunk/source/renderer/SceneRenderer.cpp (revision 26301)
+++ ps/trunk/source/renderer/SceneRenderer.cpp (revision 26302)
@@ -1,1297 +1,1280 @@
/* 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 "SceneRenderer.h"
#include "graphics/Camera.h"
#include "graphics/Decal.h"
#include "graphics/GameView.h"
#include "graphics/LightEnv.h"
#include "graphics/LOSTexture.h"
#include "graphics/MaterialManager.h"
#include "graphics/MiniMapTexture.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"
#include "graphics/ParticleManager.h"
#include "graphics/Patch.h"
#include "graphics/ShaderManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/Terrain.h"
#include "graphics/Texture.h"
#include "graphics/TextureManager.h"
#include "maths/Matrix3D.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
+#include "renderer/backend/gl/Device.h"
#include "renderer/DebugRenderer.h"
#include "renderer/HWLightingModelRenderer.h"
#include "renderer/InstancingModelRenderer.h"
#include "renderer/ModelRenderer.h"
#include "renderer/OverlayRenderer.h"
#include "renderer/ParticleRenderer.h"
#include "renderer/PostprocManager.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/RenderModifiers.h"
#include "renderer/ShadowMap.h"
#include "renderer/SilhouetteRenderer.h"
#include "renderer/SkyManager.h"
#include "renderer/TerrainOverlay.h"
#include "renderer/TerrainRenderer.h"
#include "renderer/WaterManager.h"
#include
struct SScreenRect
{
GLint x1, y1, x2, y2;
};
/**
* Struct CSceneRendererInternals: Truly hide data that is supposed to be hidden
* in this structure so it won't even appear in header files.
*/
class CSceneRenderer::Internals
{
NONCOPYABLE(Internals);
public:
Internals() = default;
~Internals() = default;
/// Water manager
WaterManager waterManager;
/// Sky manager
SkyManager skyManager;
/// Terrain renderer
TerrainRenderer terrainRenderer;
/// Overlay renderer
OverlayRenderer overlayRenderer;
/// Particle manager
CParticleManager particleManager;
/// Particle renderer
ParticleRenderer particleRenderer;
/// Material manager
CMaterialManager materialManager;
/// Shadow map
ShadowMap shadow;
SilhouetteRenderer silhouetteRenderer;
/// Various model renderers
struct Models
{
// NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer,
// RenderModifier, etc) is mostly a relic of an older design that implemented
// the different materials and rendering modes through extensive subclassing
// and hooking objects together in various combinations.
// The new design uses the CShaderManager API to abstract away the details
// of rendering, and uses a data-driven approach to materials, so there are
// now a small number of generic subclasses instead of many specialised subclasses,
// but most of the old infrastructure hasn't been refactored out yet and leads to
// some unwanted complexity.
// Submitted models are split on two axes:
// - Normal vs Transp[arent] - alpha-blended models are stored in a separate
// list so we can draw them above/below the alpha-blended water plane correctly
// - Skinned vs Unskinned - with hardware lighting we don't need to
// duplicate mesh data per model instance (except for skinned models),
// so non-skinned models get different ModelVertexRenderers
ModelRendererPtr NormalSkinned;
ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported
ModelRendererPtr TranspSkinned;
ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported
ModelVertexRendererPtr VertexRendererShader;
ModelVertexRendererPtr VertexInstancingShader;
ModelVertexRendererPtr VertexGPUSkinningShader;
LitRenderModifierPtr ModShader;
} Model;
CShaderDefines globalContext;
/**
* Renders all non-alpha-blended models with the given context.
*/
void CallModelRenderers(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags)
{
CShaderDefines contextSkinned = context;
if (g_RenderingOptions.GetGPUSkinning())
{
contextSkinned.Add(str_USE_INSTANCING, str_1);
contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
}
Model.NormalSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags);
if (Model.NormalUnskinned != Model.NormalSkinned)
{
CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.NormalUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags);
}
}
/**
* Renders all alpha-blended models with the given context.
*/
void CallTranspModelRenderers(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, int flags)
{
CShaderDefines contextSkinned = context;
if (g_RenderingOptions.GetGPUSkinning())
{
contextSkinned.Add(str_USE_INSTANCING, str_1);
contextSkinned.Add(str_USE_GPU_SKINNING, str_1);
}
Model.TranspSkinned->Render(deviceCommandContext, Model.ModShader, contextSkinned, cullGroup, flags);
if (Model.TranspUnskinned != Model.TranspSkinned)
{
CShaderDefines contextUnskinned = context;
contextUnskinned.Add(str_USE_INSTANCING, str_1);
Model.TranspUnskinned->Render(deviceCommandContext, Model.ModShader, contextUnskinned, cullGroup, flags);
}
}
};
CSceneRenderer::CSceneRenderer()
{
m = std::make_unique();
m_TerrainRenderMode = SOLID;
m_WaterRenderMode = SOLID;
m_ModelRenderMode = SOLID;
m_OverlayRenderMode = SOLID;
- m_ClearColor[0] = m_ClearColor[1] = m_ClearColor[2] = m_ClearColor[3] = 0;
m_DisplayTerrainPriorities = false;
- CStr skystring = "0 0 0";
- CColor skycolor;
- CFG_GET_VAL("skycolor", skystring);
- if (skycolor.ParseString(skystring, 255.f))
- {
- m_ClearColor[0] = skycolor.r;
- m_ClearColor[1] = skycolor.g;
- m_ClearColor[2] = skycolor.b;
- m_ClearColor[3] = skycolor.a;
- }
-
m_LightEnv = nullptr;
m_CurrentScene = nullptr;
}
CSceneRenderer::~CSceneRenderer()
{
// We no longer UnloadWaterTextures here -
// that is the responsibility of the module that asked for
// them to be loaded (i.e. CGameView).
m.reset();
}
void CSceneRenderer::ReloadShaders()
{
m->globalContext = CShaderDefines();
const CRenderer::Caps& capabilities = g_Renderer.GetCapabilities();
if (capabilities.m_Shadows && g_RenderingOptions.GetShadows())
{
m->globalContext.Add(str_USE_SHADOW, str_1);
if (capabilities.m_ARBProgramShadow && g_RenderingOptions.GetARBProgramShadow())
m->globalContext.Add(str_USE_FP_SHADOW, str_1);
if (g_RenderingOptions.GetShadowPCF())
m->globalContext.Add(str_USE_SHADOW_PCF, str_1);
const int cascadeCount = m->shadow.GetCascadeCount();
ENSURE(1 <= cascadeCount && cascadeCount <= 4);
const CStrIntern cascadeCountStr[5] = {str_0, str_1, str_2, str_3, str_4};
m->globalContext.Add(str_SHADOWS_CASCADE_COUNT, cascadeCountStr[cascadeCount]);
#if !CONFIG2_GLES
m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1);
#endif
}
m->globalContext.Add(str_RENDER_DEBUG_MODE,
RenderDebugModeEnum::ToString(g_RenderingOptions.GetRenderDebugMode()));
if (g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB && g_RenderingOptions.GetFog())
m->globalContext.Add(str_USE_FOG, str_1);
m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier());
ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED);
m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer());
m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB));
if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too
{
m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB));
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader));
}
else
{
m->Model.VertexGPUSkinningShader.reset();
m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader));
}
m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader));
}
void CSceneRenderer::Initialize()
{
// Let component renderers perform one-time initialization after graphics capabilities and
// the shader path have been determined.
m->overlayRenderer.Initialize();
}
// resize renderer view
void CSceneRenderer::Resize(int UNUSED(width), int UNUSED(height))
{
// need to recreate the shadow map object to resize the shadow texture
m->shadow.RecreateTexture();
m->waterManager.Resize();
}
void CSceneRenderer::BeginFrame()
{
// choose model renderers for this frame
m->Model.ModShader->SetShadowMap(&m->shadow);
m->Model.ModShader->SetLightEnv(m_LightEnv);
}
void CSceneRenderer::SetSimulation(CSimulation2* simulation)
{
// set current simulation context for terrain renderer
m->terrainRenderer.SetSimulation(simulation);
}
void CSceneRenderer::RenderShadowMap(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context)
{
PROFILE3_GPU("shadow map");
OGL_SCOPED_DEBUG_GROUP("Render shadow map");
CShaderDefines shadowsContext = context;
shadowsContext.Add(str_PASS_SHADOWS, str_1);
CShaderDefines contextCast = shadowsContext;
contextCast.Add(str_MODE_SHADOWCAST, str_1);
m->shadow.BeginRender();
const int cascadeCount = m->shadow.GetCascadeCount();
ENSURE(0 <= cascadeCount && cascadeCount <= 4);
for (int cascade = 0; cascade < cascadeCount; ++cascade)
{
m->shadow.PrepareCamera(cascade);
const int cullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
{
PROFILE("render patches");
m->terrainRenderer.RenderPatches(deviceCommandContext, cullGroup, shadowsContext);
}
{
PROFILE("render models");
m->CallModelRenderers(deviceCommandContext, contextCast, cullGroup, MODELFLAG_CASTSHADOWS);
}
{
PROFILE("render transparent models");
m->CallTranspModelRenderers(deviceCommandContext, contextCast, cullGroup, MODELFLAG_CASTSHADOWS);
}
}
m->shadow.EndRender();
g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
}
void CSceneRenderer::RenderPatches(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup)
{
PROFILE3_GPU("patches");
OGL_SCOPED_DEBUG_GROUP("Render patches");
#if CONFIG2_GLES
#warning TODO: implement wireface/edged rendering mode GLES
#else
// switch on wireframe if we need it
if (m_TerrainRenderMode == WIREFRAME)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
#endif
// render all the patches, including blend pass
const CRenderer::Caps& capabilities = g_Renderer.GetCapabilities();
m->terrainRenderer.RenderTerrainShader(deviceCommandContext, context, cullGroup,
(capabilities.m_Shadows && g_RenderingOptions.GetShadows()) ? &m->shadow : 0);
#if !CONFIG2_GLES
if (m_TerrainRenderMode == WIREFRAME)
{
// switch wireframe off again
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
else if (m_TerrainRenderMode == EDGED_FACES)
{
// edged faces: need to make a second pass over the data:
// first switch on wireframe
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// setup some renderstate ..
glActiveTextureARB(GL_TEXTURE0);
glLineWidth(2.0f);
// render tiles edges
m->terrainRenderer.RenderPatches(deviceCommandContext, cullGroup, context, CColor(0.5f, 0.5f, 1.0f, 1.0f));
glLineWidth(4.0f);
// render outline of each patch
m->terrainRenderer.RenderOutlines(cullGroup);
// .. and restore the renderstates
glLineWidth(1.0f);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif
}
void CSceneRenderer::RenderModels(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup)
{
PROFILE3_GPU("models");
OGL_SCOPED_DEBUG_GROUP("Render models");
int flags = 0;
#if !CONFIG2_GLES
if (m_ModelRenderMode == WIREFRAME)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
#endif
m->CallModelRenderers(deviceCommandContext, context, cullGroup, flags);
#if !CONFIG2_GLES
if (m_ModelRenderMode == WIREFRAME)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
else if (m_ModelRenderMode == EDGED_FACES)
{
CShaderDefines contextWireframe = context;
contextWireframe.Add(str_MODE_WIREFRAME, str_1);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
m->CallModelRenderers(deviceCommandContext, contextWireframe, cullGroup, flags);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif
}
void CSceneRenderer::RenderTransparentModels(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode)
{
PROFILE3_GPU("transparent models");
OGL_SCOPED_DEBUG_GROUP("Render transparent models");
int flags = 0;
#if !CONFIG2_GLES
// switch on wireframe if we need it
if (m_ModelRenderMode == WIREFRAME)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
#endif
CShaderDefines contextOpaque = context;
contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1);
CShaderDefines contextBlend = context;
contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1);
if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE)
m->CallTranspModelRenderers(deviceCommandContext, contextOpaque, cullGroup, flags);
if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND)
m->CallTranspModelRenderers(deviceCommandContext, contextBlend, cullGroup, flags);
#if !CONFIG2_GLES
if (m_ModelRenderMode == WIREFRAME)
{
// switch wireframe off again
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
else if (m_ModelRenderMode == EDGED_FACES)
{
CShaderDefines contextWireframe = contextOpaque;
contextWireframe.Add(str_MODE_WIREFRAME, str_1);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
m->CallTranspModelRenderers(deviceCommandContext, contextWireframe, cullGroup, flags);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif
}
// SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space)
// Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html
// - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test)
void CSceneRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const
{
// First, we'll convert the given clip plane to camera space, then we'll
// Get the view matrix and normal matrix (top 3x3 part of view matrix)
CMatrix3D normalMatrix = camera.GetOrientation().GetTranspose();
CVector4D camPlane = normalMatrix.Transform(worldPlane);
CMatrix3D matrix = camera.GetProjection();
// Calculate the clip-space corner point opposite the clipping plane
// as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and
// transform it into camera space by multiplying it
// by the inverse of the projection matrix
CVector4D q;
q.X = (Sign(camPlane.X) - matrix[8] / matrix[11]) / matrix[0];
q.Y = (Sign(camPlane.Y) - matrix[9] / matrix[11]) / matrix[5];
q.Z = 1.0f / matrix[11];
q.W = (1.0f - matrix[10] / matrix[11]) / matrix[14];
// Calculate the scaled plane vector
CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q));
// Replace the third row of the projection matrix
matrix[2] = c.X;
matrix[6] = c.Y;
matrix[10] = c.Z - matrix[11];
matrix[14] = c.W;
// Load it back into the camera
camera.SetProjection(matrix);
}
void CSceneRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
{
WaterManager& wm = m->waterManager;
CMatrix3D projection;
if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
const float aspectRatio = 1.0f;
// Expand fov slightly since ripples can reflect parts of the scene that
// are slightly outside the normal camera view, and we want to avoid any
// noticeable edge-filtering artifacts
projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane());
}
else
projection = m_ViewCamera.GetProjection();
camera = m_ViewCamera;
// Temporarily change the camera to one that is reflected.
// Also, for texturing purposes, make it render to a view port the size of the
// water texture, stretch the image according to our aspect ratio so it covers
// the whole screen despite being rendered into a square, and cover slightly more
// of the view so we can see wavy reflections of slightly off-screen objects.
camera.m_Orientation.Scale(1, -1, 1);
camera.m_Orientation.Translate(0, 2 * wm.m_WaterHeight, 0);
camera.UpdateFrustum(scissor);
// Clip slightly above the water to improve reflections of objects on the water
// when the reflections are distorted.
camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight + 2.0f));
SViewPort vp;
vp.m_Height = wm.m_RefTextureSize;
vp.m_Width = wm.m_RefTextureSize;
vp.m_X = 0;
vp.m_Y = 0;
camera.SetViewPort(vp);
camera.SetProjection(projection);
CMatrix3D scaleMat;
scaleMat.SetScaling(g_Renderer.GetHeight() / static_cast(std::max(1, g_Renderer.GetWidth())), 1.0f, 1.0f);
camera.SetProjection(scaleMat * camera.GetProjection());
CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f);
SetObliqueFrustumClipping(camera, camPlane);
}
void CSceneRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const
{
WaterManager& wm = m->waterManager;
CMatrix3D projection;
if (m_ViewCamera.GetProjectionType() == CCamera::ProjectionType::PERSPECTIVE)
{
const float aspectRatio = 1.0f;
// Expand fov slightly since ripples can reflect parts of the scene that
// are slightly outside the normal camera view, and we want to avoid any
// noticeable edge-filtering artifacts
projection.SetPerspective(m_ViewCamera.GetFOV() * 1.05f, aspectRatio, m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane());
}
else
projection = m_ViewCamera.GetProjection();
camera = m_ViewCamera;
// Temporarily change the camera to make it render to a view port the size of the
// water texture, stretch the image according to our aspect ratio so it covers
// the whole screen despite being rendered into a square, and cover slightly more
// of the view so we can see wavy refractions of slightly off-screen objects.
camera.UpdateFrustum(scissor);
camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores.
SViewPort vp;
vp.m_Height = wm.m_RefTextureSize;
vp.m_Width = wm.m_RefTextureSize;
vp.m_X = 0;
vp.m_Y = 0;
camera.SetViewPort(vp);
camera.SetProjection(projection);
CMatrix3D scaleMat;
scaleMat.SetScaling(g_Renderer.GetHeight() / static_cast(std::max(1, g_Renderer.GetWidth())), 1.0f, 1.0f);
camera.SetProjection(scaleMat * camera.GetProjection());
}
// RenderReflections: render the water reflections to the reflection texture
void CSceneRenderer::RenderReflections(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, const CBoundingBoxAligned& scissor)
{
PROFILE3_GPU("water reflections");
OGL_SCOPED_DEBUG_GROUP("Render water reflections");
WaterManager& wm = m->waterManager;
// Remember old camera
CCamera normalCamera = m_ViewCamera;
ComputeReflectionCamera(m_ViewCamera, scissor);
const CBoundingBoxAligned reflectionScissor =
m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
// Save the model-view-projection matrix so the shaders can use it for projective texturing
wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection();
float vpHeight = wm.m_RefTextureSize;
float vpWidth = wm.m_RefTextureSize;
SScreenRect screenScissor;
screenScissor.x1 = (GLint)floor((reflectionScissor[0].X*0.5f+0.5f)*vpWidth);
screenScissor.y1 = (GLint)floor((reflectionScissor[0].Y*0.5f+0.5f)*vpHeight);
screenScissor.x2 = (GLint)ceil((reflectionScissor[1].X*0.5f+0.5f)*vpWidth);
screenScissor.y2 = (GLint)ceil((reflectionScissor[1].Y*0.5f+0.5f)*vpHeight);
Renderer::Backend::GL::CDeviceCommandContext::ScissorRect scissorRect;
scissorRect.x = screenScissor.x1;
scissorRect.y = screenScissor.y1;
scissorRect.width = screenScissor.x2 - screenScissor.x1;
scissorRect.height = screenScissor.y2 - screenScissor.y1;
deviceCommandContext->SetScissors(1, &scissorRect);
- // try binding the framebuffer
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_ReflectionFbo);
-
- const Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
- Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc();
- deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
- glClearColor(0.5f, 0.5f, 1.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ deviceCommandContext->SetFramebuffer(wm.m_ReflectionFramebuffer.get());
+ deviceCommandContext->ClearFramebuffer();
if (!g_RenderingOptions.GetWaterReflection())
{
m->skyManager.RenderSky(deviceCommandContext);
ogl_WarnIfError();
}
else
{
CShaderDefines reflectionsContext = context;
reflectionsContext.Add(str_PASS_REFLECTIONS, str_1);
// Render terrain and models
RenderPatches(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS);
ogl_WarnIfError();
RenderModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS);
ogl_WarnIfError();
RenderTransparentModels(deviceCommandContext, reflectionsContext, CULL_REFLECTIONS, TRANSPARENT);
ogl_WarnIfError();
}
// Particles are always oriented to face the camera in the vertex shader,
// so they don't need the inverted cull face.
if (g_RenderingOptions.GetParticles())
{
RenderParticles(deviceCommandContext, CULL_REFLECTIONS);
ogl_WarnIfError();
}
deviceCommandContext->SetScissors(0, nullptr);
// Reset old camera
m_ViewCamera = normalCamera;
g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
}
// RenderRefractions: render the water refractions to the refraction texture
void CSceneRenderer::RenderRefractions(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, const CBoundingBoxAligned &scissor)
{
PROFILE3_GPU("water refractions");
OGL_SCOPED_DEBUG_GROUP("Render water refractions");
WaterManager& wm = m->waterManager;
// Remember old camera
CCamera normalCamera = m_ViewCamera;
ComputeRefractionCamera(m_ViewCamera, scissor);
const CBoundingBoxAligned refractionScissor =
m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f);
SetObliqueFrustumClipping(m_ViewCamera, camPlane);
g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
// Save the model-view-projection matrix so the shaders can use it for projective texturing
wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection();
wm.m_RefractionProjInvMatrix = m_ViewCamera.GetProjection().GetInverse();
wm.m_RefractionViewInvMatrix = m_ViewCamera.GetOrientation();
float vpHeight = wm.m_RefTextureSize;
float vpWidth = wm.m_RefTextureSize;
SScreenRect screenScissor;
screenScissor.x1 = (GLint)floor((refractionScissor[0].X*0.5f+0.5f)*vpWidth);
screenScissor.y1 = (GLint)floor((refractionScissor[0].Y*0.5f+0.5f)*vpHeight);
screenScissor.x2 = (GLint)ceil((refractionScissor[1].X*0.5f+0.5f)*vpWidth);
screenScissor.y2 = (GLint)ceil((refractionScissor[1].Y*0.5f+0.5f)*vpHeight);
Renderer::Backend::GL::CDeviceCommandContext::ScissorRect scissorRect;
scissorRect.x = screenScissor.x1;
scissorRect.y = screenScissor.y1;
scissorRect.width = screenScissor.x2 - screenScissor.x1;
scissorRect.height = screenScissor.y2 - screenScissor.y1;
deviceCommandContext->SetScissors(1, &scissorRect);
- // try binding the framebuffer
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo);
-
- const Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
- Renderer::Backend::MakeDefaultGraphicsPipelineStateDesc();
- deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
- glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ deviceCommandContext->SetFramebuffer(wm.m_RefractionFramebuffer.get());
+ deviceCommandContext->ClearFramebuffer();
// Render terrain and models
RenderPatches(deviceCommandContext, context, CULL_REFRACTIONS);
ogl_WarnIfError();
RenderModels(deviceCommandContext, context, CULL_REFRACTIONS);
ogl_WarnIfError();
RenderTransparentModels(deviceCommandContext, context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE);
ogl_WarnIfError();
deviceCommandContext->SetScissors(0, nullptr);
// Reset old camera
m_ViewCamera = normalCamera;
g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
}
void CSceneRenderer::RenderSilhouettes(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context)
{
PROFILE3_GPU("silhouettes");
OGL_SCOPED_DEBUG_GROUP("Render water silhouettes");
CShaderDefines contextOccluder = context;
contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1);
CShaderDefines contextDisplay = context;
contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1);
// Render silhouettes of units hidden behind terrain or occluders.
// To avoid breaking the standard rendering of alpha-blended objects, this
// has to be done in a separate pass.
// First we render all occluders into depth, then render all units with
// inverted depth test so any behind an occluder will get drawn in a constant
// color.
- glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ deviceCommandContext->ClearFramebuffer(false, true, true);
// Render occluders:
{
PROFILE("render patches");
m->terrainRenderer.RenderPatches(deviceCommandContext, CULL_SILHOUETTE_OCCLUDER, contextOccluder);
}
{
PROFILE("render model occluders");
m->CallModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
}
{
PROFILE("render transparent occluders");
m->CallTranspModelRenderers(deviceCommandContext, contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
}
// Since we can't sort, we'll use the stencil buffer to ensure we only draw
// a pixel once (using the color of whatever model happens to be drawn first).
{
PROFILE("render model casters");
m->CallModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0);
}
{
PROFILE("render transparent casters");
m->CallTranspModelRenderers(deviceCommandContext, contextDisplay, CULL_SILHOUETTE_CASTER, 0);
}
}
void CSceneRenderer::RenderParticles(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
int cullGroup)
{
PROFILE3_GPU("particles");
OGL_SCOPED_DEBUG_GROUP("Render particles");
m->particleRenderer.RenderParticles(
deviceCommandContext, cullGroup);
#if !CONFIG2_GLES
if (m_ModelRenderMode == EDGED_FACES)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
m->particleRenderer.RenderParticles(
deviceCommandContext, cullGroup, true);
m->particleRenderer.RenderBounds(cullGroup);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif
}
// RenderSubmissions: force rendering of any batched objects
void CSceneRenderer::RenderSubmissions(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CBoundingBoxAligned& waterScissor)
{
PROFILE3("render submissions");
OGL_SCOPED_DEBUG_GROUP("Render submissions");
m->skyManager.LoadAndUploadSkyTexturesIfNeeded(deviceCommandContext);
GetScene().GetLOSTexture().InterpolateLOS(deviceCommandContext);
GetScene().GetTerritoryTexture().UpdateIfNeeded(deviceCommandContext);
GetScene().GetMiniMapTexture().Render(deviceCommandContext);
CShaderDefines context = m->globalContext;
int cullGroup = CULL_DEFAULT;
ogl_WarnIfError();
// Set the camera
g_Renderer.SetViewport(m_ViewCamera.GetViewPort());
// Prepare model renderers
{
PROFILE3("prepare models");
m->Model.NormalSkinned->PrepareModels();
m->Model.TranspSkinned->PrepareModels();
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->PrepareModels();
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->PrepareModels();
}
m->terrainRenderer.PrepareForRendering();
m->overlayRenderer.PrepareForRendering();
m->particleRenderer.PrepareForRendering(context);
const CRenderer::Caps& capabilities = g_Renderer.GetCapabilities();
if (capabilities.m_Shadows && g_RenderingOptions.GetShadows())
{
RenderShadowMap(deviceCommandContext, context);
}
ogl_WarnIfError();
if (m->waterManager.m_RenderWater)
{
if (waterScissor.GetVolume() > 0 && m->waterManager.WillRenderFancyWater())
{
m->waterManager.UpdateQuality();
PROFILE3_GPU("water scissor");
RenderReflections(deviceCommandContext, context, waterScissor);
if (g_RenderingOptions.GetWaterRefraction())
RenderRefractions(deviceCommandContext, context, waterScissor);
m->terrainRenderer.RenderWaterFoamOccluders(deviceCommandContext, cullGroup);
}
}
CPostprocManager& postprocManager = g_Renderer.GetPostprocManager();
if (postprocManager.IsEnabled())
{
// We have to update the post process manager with real near/far planes
// that we use for the scene rendering.
postprocManager.SetDepthBufferClipPlanes(
m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane()
);
postprocManager.Initialize();
- postprocManager.CaptureRenderOutput();
+ postprocManager.CaptureRenderOutput(deviceCommandContext);
+ }
+ else
+ {
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
}
{
PROFILE3_GPU("clear buffers");
- glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ deviceCommandContext->ClearFramebuffer();
}
m->skyManager.RenderSky(deviceCommandContext);
// render submitted patches and models
RenderPatches(deviceCommandContext, context, cullGroup);
ogl_WarnIfError();
// render debug-related terrain overlays
ITerrainOverlay::RenderOverlaysBeforeWater(deviceCommandContext);
ogl_WarnIfError();
// render other debug-related overlays before water (so they can be seen when underwater)
m->overlayRenderer.RenderOverlaysBeforeWater();
ogl_WarnIfError();
RenderModels(deviceCommandContext, context, cullGroup);
ogl_WarnIfError();
// render water
if (m->waterManager.m_RenderWater && g_Game && waterScissor.GetVolume() > 0)
{
if (m->waterManager.WillRenderFancyWater())
{
// Render transparent stuff, but only the solid parts that can occlude block water.
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT_OPAQUE);
ogl_WarnIfError();
m->terrainRenderer.RenderWater(deviceCommandContext, context, cullGroup, &m->shadow);
ogl_WarnIfError();
// Render transparent stuff again, but only the blended parts that overlap water.
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT_BLEND);
ogl_WarnIfError();
}
else
{
m->terrainRenderer.RenderWater(deviceCommandContext, context, cullGroup, &m->shadow);
ogl_WarnIfError();
// Render transparent stuff, so it can overlap models/terrain.
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT);
ogl_WarnIfError();
}
}
else
{
// render transparent stuff, so it can overlap models/terrain
RenderTransparentModels(deviceCommandContext, context, cullGroup, TRANSPARENT);
ogl_WarnIfError();
}
// render debug-related terrain overlays
ITerrainOverlay::RenderOverlaysAfterWater(deviceCommandContext, cullGroup);
ogl_WarnIfError();
// render some other overlays after water (so they can be displayed on top of water)
m->overlayRenderer.RenderOverlaysAfterWater(deviceCommandContext);
ogl_WarnIfError();
// particles are transparent so render after water
if (g_RenderingOptions.GetParticles())
{
RenderParticles(deviceCommandContext, cullGroup);
ogl_WarnIfError();
}
if (postprocManager.IsEnabled())
{
if (g_Renderer.GetPostprocManager().IsMultisampleEnabled())
- g_Renderer.GetPostprocManager().ResolveMultisampleFramebuffer();
+ g_Renderer.GetPostprocManager().ResolveMultisampleFramebuffer(deviceCommandContext);
postprocManager.ApplyPostproc(deviceCommandContext);
postprocManager.ReleaseRenderOutput(deviceCommandContext);
}
if (g_RenderingOptions.GetSilhouettes())
{
RenderSilhouettes(deviceCommandContext, context);
}
// render debug lines
if (g_RenderingOptions.GetDisplayFrustum())
DisplayFrustum();
if (g_RenderingOptions.GetDisplayShadowsFrustum())
{
m->shadow.RenderDebugBounds();
m->shadow.RenderDebugTexture(deviceCommandContext);
}
m->silhouetteRenderer.RenderDebugOverlays(deviceCommandContext, m_ViewCamera);
// render overlays that should appear on top of all other objects
m->overlayRenderer.RenderForegroundOverlays(deviceCommandContext, m_ViewCamera);
ogl_WarnIfError();
}
void CSceneRenderer::EndFrame()
{
// empty lists
m->terrainRenderer.EndFrame();
m->overlayRenderer.EndFrame();
m->particleRenderer.EndFrame();
m->silhouetteRenderer.EndFrame();
// Finish model renderers
m->Model.NormalSkinned->EndFrame();
m->Model.TranspSkinned->EndFrame();
if (m->Model.NormalUnskinned != m->Model.NormalSkinned)
m->Model.NormalUnskinned->EndFrame();
if (m->Model.TranspUnskinned != m->Model.TranspSkinned)
m->Model.TranspUnskinned->EndFrame();
}
// DisplayFrustum: debug displays
// - white: cull camera frustum
// - red: bounds of shadow casting objects
void CSceneRenderer::DisplayFrustum()
{
#if CONFIG2_GLES
#warning TODO: implement CSceneRenderer::DisplayFrustum for GLES
#else
g_Renderer.GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 0.25f), 2);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
g_Renderer.GetDebugRenderer().DrawCameraFrustum(m_CullCamera, CColor(1.0f, 1.0f, 1.0f, 1.0f), 2);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
ogl_WarnIfError();
}
// Text overlay rendering
void CSceneRenderer::RenderTextOverlays()
{
PROFILE3_GPU("text overlays");
if (m_DisplayTerrainPriorities)
m->terrainRenderer.RenderPriorities(CULL_DEFAULT);
ogl_WarnIfError();
}
// SetSceneCamera: setup projection and transform of camera and adjust viewport to current view
// The camera always represents the actual camera used to render a scene, not any virtual camera
// used for shadow rendering or reflections.
void CSceneRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera)
{
m_ViewCamera = viewCamera;
m_CullCamera = cullCamera;
const CRenderer::Caps& capabilities = g_Renderer.GetCapabilities();
if (capabilities.m_Shadows && g_RenderingOptions.GetShadows())
m->shadow.SetupFrame(m_CullCamera, m_LightEnv->GetSunDir());
}
void CSceneRenderer::Submit(CPatch* patch)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
{
m->shadow.AddShadowReceiverBound(patch->GetWorldBounds());
m->silhouetteRenderer.AddOccluder(patch);
}
if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
{
const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
m->shadow.AddShadowCasterBound(cascade, patch->GetWorldBounds());
}
m->terrainRenderer.Submit(m_CurrentCullGroup, patch);
}
void CSceneRenderer::Submit(SOverlayLine* overlay)
{
// Overlays are only needed in the default cull group for now,
// so just ignore submissions to any other group
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlayTexturedLine* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlaySprite* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlayQuad* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(SOverlaySphere* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CSceneRenderer::Submit(CModelDecal* decal)
{
// Decals can't cast shadows since they're flat on the terrain.
// They can receive shadows, but the terrain under them will have
// already been passed to AddShadowCasterBound, so don't bother
// doing it again here.
m->terrainRenderer.Submit(m_CurrentCullGroup, decal);
}
void CSceneRenderer::Submit(CParticleEmitter* emitter)
{
m->particleRenderer.Submit(m_CurrentCullGroup, emitter);
}
void CSceneRenderer::SubmitNonRecursive(CModel* model)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
{
m->shadow.AddShadowReceiverBound(model->GetWorldBounds());
if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER)
m->silhouetteRenderer.AddOccluder(model);
if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY)
m->silhouetteRenderer.AddCaster(model);
}
if (CULL_SHADOWS_CASCADE_0 <= m_CurrentCullGroup && m_CurrentCullGroup <= CULL_SHADOWS_CASCADE_3)
{
if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS))
return;
const int cascade = m_CurrentCullGroup - CULL_SHADOWS_CASCADE_0;
m->shadow.AddShadowCasterBound(cascade, model->GetWorldBounds());
}
bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0);
if (model->GetMaterial().UsesAlphaBlending())
{
if (requiresSkinning)
m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model);
else
m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model);
}
else
{
if (requiresSkinning)
m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model);
else
m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model);
}
}
// Render the given scene
void CSceneRenderer::RenderScene(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, Scene& scene)
{
m_CurrentScene = &scene;
CFrustum frustum = m_CullCamera.GetFrustum();
m_CurrentCullGroup = CULL_DEFAULT;
scene.EnumerateObjects(frustum, this);
m->particleManager.RenderSubmit(*this, frustum);
if (g_RenderingOptions.GetSilhouettes())
{
m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera);
m_CurrentCullGroup = CULL_DEFAULT;
m->silhouetteRenderer.RenderSubmitOverlays(*this);
m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER;
m->silhouetteRenderer.RenderSubmitOccluders(*this);
m_CurrentCullGroup = CULL_SILHOUETTE_CASTER;
m->silhouetteRenderer.RenderSubmitCasters(*this);
}
const CRenderer::Caps& capabilities = g_Renderer.GetCapabilities();
if (capabilities.m_Shadows && g_RenderingOptions.GetShadows())
{
for (int cascade = 0; cascade <= m->shadow.GetCascadeCount(); ++cascade)
{
m_CurrentCullGroup = CULL_SHADOWS_CASCADE_0 + cascade;
const CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(cascade);
scene.EnumerateObjects(shadowFrustum, this);
}
}
CBoundingBoxAligned waterScissor;
if (m->waterManager.m_RenderWater)
{
waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera);
if (waterScissor.GetVolume() > 0 && m->waterManager.WillRenderFancyWater())
{
if (g_RenderingOptions.GetWaterReflection())
{
m_CurrentCullGroup = CULL_REFLECTIONS;
CCamera reflectionCamera;
ComputeReflectionCamera(reflectionCamera, waterScissor);
scene.EnumerateObjects(reflectionCamera.GetFrustum(), this);
}
if (g_RenderingOptions.GetWaterRefraction())
{
m_CurrentCullGroup = CULL_REFRACTIONS;
CCamera refractionCamera;
ComputeRefractionCamera(refractionCamera, waterScissor);
scene.EnumerateObjects(refractionCamera.GetFrustum(), this);
}
// Render the waves to the Fancy effects texture
m->waterManager.RenderWaves(deviceCommandContext, frustum);
}
}
m_CurrentCullGroup = -1;
ogl_WarnIfError();
RenderSubmissions(deviceCommandContext, waterScissor);
m_CurrentScene = NULL;
}
Scene& CSceneRenderer::GetScene()
{
ENSURE(m_CurrentScene);
return *m_CurrentScene;
}
void CSceneRenderer::MakeShadersDirty()
{
m->waterManager.m_NeedsReloading = true;
}
WaterManager& CSceneRenderer::GetWaterManager()
{
return m->waterManager;
}
SkyManager& CSceneRenderer::GetSkyManager()
{
return m->skyManager;
}
CParticleManager& CSceneRenderer::GetParticleManager()
{
return m->particleManager;
}
TerrainRenderer& CSceneRenderer::GetTerrainRenderer()
{
return m->terrainRenderer;
}
CMaterialManager& CSceneRenderer::GetMaterialManager()
{
return m->materialManager;
}
ShadowMap& CSceneRenderer::GetShadowMap()
{
return m->shadow;
}
void CSceneRenderer::ResetState()
{
// Clear all emitters, that were created in previous games
GetParticleManager().ClearUnattachedEmitters();
}
Index: ps/trunk/source/renderer/SceneRenderer.h
===================================================================
--- ps/trunk/source/renderer/SceneRenderer.h (revision 26301)
+++ ps/trunk/source/renderer/SceneRenderer.h (revision 26302)
@@ -1,285 +1,283 @@
/* 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_RENDERER_SCENERENDERER
#define INCLUDED_RENDERER_SCENERENDERER
#include "graphics/Camera.h"
#include "graphics/ShaderDefines.h"
#include "graphics/ShaderProgramPtr.h"
#include "ps/Singleton.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/RenderingOptions.h"
#include "renderer/Scene.h"
#include
class CLightEnv;
class CMaterial;
class CMaterialManager;
class CModel;
class CParticleManager;
class CPatch;
class CSimulation2;
class ShadowMap;
class SkyManager;
class TerrainRenderer;
class WaterManager;
// rendering modes
enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES };
// transparency modes
enum ETransparentMode { TRANSPARENT, TRANSPARENT_OPAQUE, TRANSPARENT_BLEND };
class CSceneRenderer : public SceneCollector
{
public:
enum CullGroup
{
CULL_DEFAULT,
CULL_SHADOWS_CASCADE_0,
CULL_SHADOWS_CASCADE_1,
CULL_SHADOWS_CASCADE_2,
CULL_SHADOWS_CASCADE_3,
CULL_REFLECTIONS,
CULL_REFRACTIONS,
CULL_SILHOUETTE_OCCLUDER,
CULL_SILHOUETTE_CASTER,
CULL_MAX
};
CSceneRenderer();
~CSceneRenderer();
void Initialize();
void Resize(int width, int height);
void BeginFrame();
void EndFrame();
/**
* Set simulation context for rendering purposes.
* Must be called at least once when the game has started and before
* frames are rendered.
*/
void SetSimulation(CSimulation2* simulation);
// trigger a reload of shaders (when parameters they depend on have changed)
void MakeShadersDirty();
/**
* Set up the camera used for rendering the next scene; this includes
* setting OpenGL state like viewport, projection and modelview matrices.
*
* @param viewCamera this camera determines the eye position for rendering
* @param cullCamera this camera determines the frustum for culling in the renderer and
* for shadow calculations
*/
void SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera);
/**
* Render the given scene immediately.
* @param scene a Scene object describing what should be rendered.
*/
void RenderScene(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, Scene& scene);
/**
* Return the scene that is currently being rendered.
* Only valid when the renderer is in a RenderScene call.
*/
Scene& GetScene();
/**
* Render text overlays on top of the scene.
* Assumes the caller has set up the GL environment for orthographic rendering
* with texturing and blending.
*/
void RenderTextOverlays();
// set the current lighting environment; (note: the passed pointer is just copied to a variable within the renderer,
// so the lightenv passed must be scoped such that it is not destructed until after the renderer is no longer rendering)
void SetLightEnv(CLightEnv* lightenv)
{
m_LightEnv = lightenv;
}
// set the mode to render subsequent terrain patches
void SetTerrainRenderMode(ERenderMode mode) { m_TerrainRenderMode = mode; }
// get the mode to render subsequent terrain patches
ERenderMode GetTerrainRenderMode() const { return m_TerrainRenderMode; }
// set the mode to render subsequent water patches
void SetWaterRenderMode(ERenderMode mode) { m_WaterRenderMode = mode; }
// get the mode to render subsequent water patches
ERenderMode GetWaterRenderMode() const { return m_WaterRenderMode; }
// set the mode to render subsequent models
void SetModelRenderMode(ERenderMode mode) { m_ModelRenderMode = mode; }
// get the mode to render subsequent models
ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; }
// Get the mode to render subsequent overlays.
ERenderMode GetOverlayRenderMode() const { return m_OverlayRenderMode; }
// Set the mode to render subsequent overlays.
void SetOverlayRenderMode(ERenderMode mode) { m_OverlayRenderMode = mode; }
// debugging
void SetDisplayTerrainPriorities(bool enabled) { m_DisplayTerrainPriorities = enabled; }
// return the current light environment
const CLightEnv &GetLightEnv() { return *m_LightEnv; }
// return the current view camera
const CCamera& GetViewCamera() const { return m_ViewCamera; }
// replace the current view camera
void SetViewCamera(const CCamera& camera) { m_ViewCamera = camera; }
// return the current cull camera
const CCamera& GetCullCamera() const { return m_CullCamera; }
/**
* GetWaterManager: Return the renderer's water manager.
*
* @return the WaterManager object used by the renderer
*/
WaterManager& GetWaterManager();
/**
* GetSkyManager: Return the renderer's sky manager.
*
* @return the SkyManager object used by the renderer
*/
SkyManager& GetSkyManager();
CParticleManager& GetParticleManager();
TerrainRenderer& GetTerrainRenderer();
CMaterialManager& GetMaterialManager();
ShadowMap& GetShadowMap();
/**
* Resets the render state to default, that was before a game started
*/
void ResetState();
void ReloadShaders();
protected:
void Submit(CPatch* patch) override;
void Submit(SOverlayLine* overlay) override;
void Submit(SOverlayTexturedLine* overlay) override;
void Submit(SOverlaySprite* overlay) override;
void Submit(SOverlayQuad* overlay) override;
void Submit(CModelDecal* decal) override;
void Submit(CParticleEmitter* emitter) override;
void Submit(SOverlaySphere* overlay) override;
void SubmitNonRecursive(CModel* model) override;
// render any batched objects
void RenderSubmissions(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CBoundingBoxAligned& waterScissor);
// patch rendering stuff
void RenderPatches(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup);
// model rendering stuff
void RenderModels(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup);
void RenderTransparentModels(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode);
void RenderSilhouettes(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context);
void RenderParticles(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
int cullGroup);
// shadow rendering stuff
void RenderShadowMap(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context);
// render water reflection and refraction textures
void RenderReflections(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, const CBoundingBoxAligned& scissor);
void RenderRefractions(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, const CBoundingBoxAligned& scissor);
void ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const;
void ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const;
// debugging
void DisplayFrustum();
// enable oblique frustum clipping with the given clip plane
void SetObliqueFrustumClipping(CCamera& camera, const CVector4D& clipPlane) const;
// Private data that is not needed by inline functions.
class Internals;
std::unique_ptr m;
// Current terrain rendering mode.
ERenderMode m_TerrainRenderMode;
// Current water rendering mode.
ERenderMode m_WaterRenderMode;
// Current model rendering mode.
ERenderMode m_ModelRenderMode;
// Current overlay rendering mode.
ERenderMode m_OverlayRenderMode;
/**
* m_ViewCamera: determines the eye position for rendering
*
* @see CGameView::m_ViewCamera
*/
CCamera m_ViewCamera;
/**
* m_CullCamera: determines the frustum for culling and shadowmap calculations
*
* @see CGameView::m_ViewCamera
*/
CCamera m_CullCamera;
// only valid inside a call to RenderScene
Scene* m_CurrentScene;
int m_CurrentCullGroup;
- // color used to clear screen in BeginFrame
- float m_ClearColor[4];
// current lighting setup
CLightEnv* m_LightEnv;
/**
* Enable rendering of terrain tile priority text overlay, for debugging.
*/
bool m_DisplayTerrainPriorities;
};
#endif // INCLUDED_RENDERER_SCENERENDERER
Index: ps/trunk/source/renderer/ShadowMap.cpp
===================================================================
--- ps/trunk/source/renderer/ShadowMap.cpp (revision 26301)
+++ ps/trunk/source/renderer/ShadowMap.cpp (revision 26302)
@@ -1,811 +1,773 @@
/* 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 "ShadowMap.h"
#include "graphics/Camera.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
#include "gui/GUIMatrix.h"
#include "lib/bits.h"
#include "lib/ogl.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Brush.h"
#include "maths/Frustum.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
+#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/Texture.h"
#include "renderer/DebugRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
#include
namespace
{
constexpr int MAX_CASCADE_COUNT = 4;
constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f;
constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f;
} // anonymous namespace
/**
* Struct ShadowMapInternals: Internal data for the ShadowMap implementation
*/
struct ShadowMapInternals
{
- // the EXT_framebuffer_object framebuffer
- GLuint Framebuffer;
- // handle of shadow map
+ std::unique_ptr Framebuffer;
std::unique_ptr Texture;
// bit depth for the depth texture
int DepthTextureBits;
// width, height of shadow map
int Width, Height;
// Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
int QualityLevel;
// used width, height of shadow map
int EffectiveWidth, EffectiveHeight;
// Transform world space into light space; calculated on SetupFrame
CMatrix3D LightTransform;
// transform light space into world space
CMatrix3D InvLightTransform;
CBoundingBoxAligned ShadowReceiverBound;
int CascadeCount;
float CascadeDistanceRatio;
float ShadowsCutoffDistance;
bool ShadowsCoverMap;
struct Cascade
{
// transform light space into projected light space
// in projected light space, the shadowbound box occupies the [-1..1] cube
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D LightProjection;
float Distance;
CBoundingBoxAligned FrustumBBAA;
CBoundingBoxAligned ConvexBounds;
CBoundingBoxAligned ShadowRenderBound;
// Bounding box of shadowed objects in the light space.
CBoundingBoxAligned ShadowCasterBound;
// Transform world space into texture space of the shadow map;
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D TextureMatrix;
// View port of the shadow texture where the cascade should be rendered.
SViewPort ViewPort;
};
std::array Cascades;
// Camera transformed into light space
CCamera LightspaceCamera;
// Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
// incorrectly when the FBO has only a depth attachment.
// When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
// alpha texture which is attached to the FBO as a workaround.
std::unique_ptr DummyTexture;
// Copy of renderer's standard view camera, saved between
// BeginRender and EndRender while we replace it with the shadow camera
CCamera SavedViewCamera;
void CalculateShadowMatrices(const int cascade);
void CreateTexture();
void UpdateCascadesParameters();
};
void ShadowMapInternals::UpdateCascadesParameters()
{
CascadeCount = 1;
CFG_GET_VAL("shadowscascadecount", CascadeCount);
if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || g_VideoMode.GetBackend() == CVideoMode::Backend::GL_ARB)
CascadeCount = 1;
ShadowsCoverMap = false;
CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
}
void CalculateBoundsForCascade(
const CCamera& camera, const CMatrix3D& lightTransform,
const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
CBoundingBoxAligned* frustumBBAA)
{
frustumBBAA->SetEmpty();
// We need to calculate a circumscribed sphere for the camera to
// create a rotation stable bounding box.
const CVector3D cameraIn = camera.m_Orientation.GetIn();
const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation();
const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane;
const CVector3D centerDist = cameraTranslation + cameraIn * farPlane;
// We can solve 3D problem in 2D space, because the frustum is
// symmetric by 2 planes. Than means we can use only one corner
// to find a circumscribed sphere.
CCamera::Quad corners;
camera.GetViewQuad(nearPlane, corners);
for (CVector3D& corner : corners)
corner = camera.GetOrientation().Transform(corner);
const CVector3D cornerNear = corners[0];
for (const CVector3D& corner : corners)
*frustumBBAA += lightTransform.Transform(corner);
camera.GetViewQuad(farPlane, corners);
for (CVector3D& corner : corners)
corner = camera.GetOrientation().Transform(corner);
const CVector3D cornerDist = corners[0];
for (const CVector3D& corner : corners)
*frustumBBAA += lightTransform.Transform(corner);
// We solve 2D case for the right trapezoid.
const float firstBase = (cornerNear - centerNear).Length();
const float secondBase = (cornerDist - centerDist).Length();
const float height = (centerDist - centerNear).Length();
const float distanceToCenter =
(height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
const float radius = (cornerNear - position).Length();
// We need to convert the bounding box to the light space.
position = lightTransform.Rotate(position);
const float insets = 0.2f;
*bbaa = CBoundingBoxAligned(position, position);
bbaa->Expand(radius);
bbaa->Expand(insets);
}
ShadowMap::ShadowMap()
{
m = new ShadowMapInternals;
m->Framebuffer = 0;
m->Width = 0;
m->Height = 0;
m->QualityLevel = 0;
m->EffectiveWidth = 0;
m->EffectiveHeight = 0;
m->DepthTextureBits = 0;
// DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
// but they're very much slower on Radeon 9800.
// In both cases, the default (no specified depth) is fast, so we just use
// that by default and hope it's alright. (Otherwise, we'd probably need to
// do some kind of hardware detection to work out what to use.)
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
m->LightTransform.SetIdentity();
m->UpdateCascadesParameters();
}
ShadowMap::~ShadowMap()
{
+ m->Framebuffer.reset();
m->Texture.reset();
m->DummyTexture.reset();
- if (m->Framebuffer)
- glDeleteFramebuffersEXT(1, &m->Framebuffer);
-
delete m;
}
// Force the texture/buffer/etc to be recreated, particularly when the renderer's
// size has changed
void ShadowMap::RecreateTexture()
{
+ m->Framebuffer.reset();
m->Texture.reset();
m->DummyTexture.reset();
- if (m->Framebuffer)
- glDeleteFramebuffersEXT(1, &m->Framebuffer);
-
- m->Framebuffer = 0;
-
m->UpdateCascadesParameters();
// (Texture will be constructed in next SetupFrame)
}
// SetupFrame: camera and light direction for this frame
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
{
if (!m->Texture)
m->CreateTexture();
CVector3D x(0, 1, 0), eyepos;
CVector3D z = lightdir;
z.Normalize();
x -= z * z.Dot(x);
if (x.Length() < 0.001)
{
// this is invoked if the camera and light directions almost coincide
// assumption: light direction has a significant Z component
x = CVector3D(1.0, 0.0, 0.0);
x -= z * z.Dot(x);
}
x.Normalize();
CVector3D y = z.Cross(x);
// X axis perpendicular to light direction, flowing along with view direction
m->LightTransform._11 = x.X;
m->LightTransform._12 = x.Y;
m->LightTransform._13 = x.Z;
// Y axis perpendicular to light and view direction
m->LightTransform._21 = y.X;
m->LightTransform._22 = y.Y;
m->LightTransform._23 = y.Z;
// Z axis is in direction of light
m->LightTransform._31 = z.X;
m->LightTransform._32 = z.Y;
m->LightTransform._33 = z.Z;
// eye is at the origin of the coordinate system
m->LightTransform._14 = -x.Dot(eyepos);
m->LightTransform._24 = -y.Dot(eyepos);
m->LightTransform._34 = -z.Dot(eyepos);
m->LightTransform._41 = 0.0;
m->LightTransform._42 = 0.0;
m->LightTransform._43 = 0.0;
m->LightTransform._44 = 1.0;
m->LightTransform.GetInverse(m->InvLightTransform);
m->ShadowReceiverBound.SetEmpty();
m->LightspaceCamera = camera;
m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
m->LightspaceCamera.UpdateFrustum();
m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE;
m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO;
CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance);
CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio);
m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f);
m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance;
for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade)
m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio;
if (GetCascadeCount() == 1 || m->ShadowsCoverMap)
{
m->Cascades[0].ViewPort =
SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2};
if (m->ShadowsCoverMap)
m->Cascades[0].Distance = camera.GetFarPlane();
}
else
{
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
{
const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0;
const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0;
m->Cascades[cascade].ViewPort =
SViewPort{offsetX + 1, offsetY + 1,
m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2};
}
}
for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx)
{
ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx];
const float nearPlane = cascadeIdx > 0 ?
m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane();
const float farPlane = cascade.Distance;
CalculateBoundsForCascade(camera, m->LightTransform,
nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA);
cascade.ShadowCasterBound.SetEmpty();
}
}
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
// objects
void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
}
void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->ShadowReceiverBound += lightspacebounds;
}
CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
{
// Get the bounds of all objects that can receive shadows
CBoundingBoxAligned bound = m->ShadowReceiverBound;
// Intersect with the camera frustum, so the shadow map doesn't have to get
// stretched to cover the off-screen parts of large models
bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
// ShadowBound might have been empty to begin with, producing an empty result
if (bound.IsEmpty())
{
// CFrustum can't easily represent nothingness, so approximate it with
// a single point which won't match many objects
bound += CVector3D(0.0f, 0.0f, 0.0f);
return bound.ToFrustum();
}
// Extend the bounds a long way towards the light source, to encompass
// all objects that might cast visible shadows.
// (The exact constant was picked entirely arbitrarily.)
bound[0].Z -= 1000.f;
CFrustum frustum = bound.ToFrustum();
frustum.Transform(m->InvLightTransform);
return frustum;
}
// CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
// projection and transformation matrices
void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
{
CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
shadowRenderBound = Cascades[cascade].ConvexBounds;
if (ShadowsCoverMap)
{
// Start building the shadow map to cover all objects that will receive shadows
CBoundingBoxAligned receiverBound = ShadowReceiverBound;
// Intersect with the camera frustum, so the shadow map doesn't have to get
// stretched to cover the off-screen parts of large models
receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
// Intersect with the shadow caster bounds, because there's no point
// wasting space around the edges of the shadow map that we're not going
// to draw into
shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X);
shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y);
shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X);
shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y);
}
else if (CascadeCount > 1)
{
// We need to offset the cascade to its place on the texture.
const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f;
if (!(cascade & 0x1))
shadowRenderBound[1].X += size.X * 2.0f;
else
shadowRenderBound[0].X -= size.X * 2.0f;
if (!(cascade & 0x2))
shadowRenderBound[1].Y += size.Y * 2.0f;
else
shadowRenderBound[0].Y -= size.Y * 2.0f;
}
// Set the near and far planes to include just the shadow casters,
// so we make full use of the depth texture's range. Add a bit of a
// delta so we don't accidentally clip objects that are directly on
// the planes.
shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f;
shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f;
// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
if (scale.X < 1.0)
scale.X = 1.0;
if (scale.Y < 1.0)
scale.Y = 1.0;
if (scale.Z < 1.0)
scale.Z = 1.0;
scale.X = 2.0 / scale.X;
scale.Y = 2.0 / scale.Y;
scale.Z = 2.0 / scale.Z;
// make sure a given world position falls on a consistent shadowmap texel fractional offset
float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
CMatrix3D& lightProjection = Cascades[cascade].LightProjection;
lightProjection.SetZero();
lightProjection._11 = scale.X;
lightProjection._14 = (shift.X + offsetX) * scale.X;
lightProjection._22 = scale.Y;
lightProjection._24 = (shift.Y + offsetY) * scale.Y;
lightProjection._33 = scale.Z;
lightProjection._34 = shift.Z * scale.Z;
lightProjection._44 = 1.0;
// Calculate texture matrix by creating the clip space to texture coordinate matrix
// and then concatenating all matrices that have been calculated so far
float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
float texscalez = scale.Z * 0.5f;
CMatrix3D lightToTex;
lightToTex.SetZero();
lightToTex._11 = texscalex;
lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
lightToTex._22 = texscaley;
lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
lightToTex._33 = texscalez;
lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
lightToTex._44 = 1.0;
Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
}
// Create the shadow map
void ShadowMapInternals::CreateTexture()
{
// Cleanup
+ Framebuffer.reset();
Texture.reset();
DummyTexture.reset();
- if (Framebuffer)
- {
- glDeleteFramebuffersEXT(1, &Framebuffer);
- Framebuffer = 0;
- }
-
- glGenFramebuffersEXT(1, &Framebuffer);
-
CFG_GET_VAL("shadowquality", QualityLevel);
// Get shadow map size as next power of two up from view width/height.
int shadowMapSize;
switch (QualityLevel)
{
// Low
case -1:
shadowMapSize = 512;
break;
// High
case 1:
shadowMapSize = 2048;
break;
// Ultra
case 2:
shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())) * 4, 4096);
break;
// Medium as is
default:
shadowMapSize = 1024;
break;
}
// Clamp to the maximum texture size.
shadowMapSize = std::min(shadowMapSize, static_cast(ogl_max_tex_size));
Width = Height = shadowMapSize;
// Since we're using a framebuffer object, the whole texture is available
EffectiveWidth = Width;
EffectiveHeight = Height;
const char* formatName;
Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED;
#if CONFIG2_GLES
formatName = "DEPTH_COMPONENT";
backendFormat = Renderer::Backend::Format::D24;
#else
switch (DepthTextureBits)
{
case 16: formatName = "Format::D16"; backendFormat = Renderer::Backend::Format::D16; break;
case 24: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break;
case 32: formatName = "Format::D32"; backendFormat = Renderer::Backend::Format::D32; break;
default: formatName = "Format::D24"; backendFormat = Renderer::Backend::Format::D24; break;
}
#endif
ENSURE(formatName);
LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
Width, Height, formatName);
if (g_RenderingOptions.GetShadowAlphaFix())
{
DummyTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, Width, Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
}
Texture = Renderer::Backend::GL::CTexture::Create2D(
backendFormat, Width, Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
#if CONFIG2_GLES
// GLES doesn't do depth comparisons, so treat it as a
// basic unfiltered depth texture
Renderer::Backend::Sampler::Filter::NEAREST,
#else
// Use GL_LINEAR to trigger automatic PCF on some devices
Renderer::Backend::Sampler::Filter::LINEAR,
#endif
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
#if !CONFIG2_GLES
g_Renderer.BindTexture(0, Texture->GetHandle());
// Enable automatic depth comparisons
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glBindTexture(GL_TEXTURE_2D, 0);
#endif
ogl_WarnIfError();
- // bind to framebuffer object
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Framebuffer);
-
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Texture->GetHandle(), 0);
-
- if (g_RenderingOptions.GetShadowAlphaFix())
- {
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture->GetHandle(), 0);
- }
- else
- {
-#if CONFIG2_GLES
-#warning TODO: figure out whether the glDrawBuffer/glReadBuffer stuff is needed, since it is not supported by GLES
-#else
- glDrawBuffer(GL_NONE);
-#endif
- }
-
-#if !CONFIG2_GLES
- glReadBuffer(GL_NONE);
-#endif
-
- ogl_WarnIfError();
-
- GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+ Framebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ g_RenderingOptions.GetShadowAlphaFix() ? DummyTexture.get() : nullptr, Texture.get());
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
-
- if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
+ if (!Framebuffer)
{
- LOGWARNING("Framebuffer object incomplete: 0x%04X", status);
+ LOGERROR("Failed to create shadows framebuffer");
- // Disable shadow rendering (but let the user try again if they want)
+ // Disable shadow rendering (but let the user try again if they want).
g_RenderingOptions.SetShadows(false);
}
}
// Set up to render into shadow map texture
void ShadowMap::BeginRender()
{
+ Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext =
+ g_Renderer.GetDeviceCommandContext();
+
{
PROFILE("bind framebuffer");
glBindTexture(GL_TEXTURE_2D, 0);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->Framebuffer);
+ deviceCommandContext->SetFramebuffer(m->Framebuffer.get());
}
// clear buffers
{
PROFILE("clear depth texture");
// In case we used m_ShadowAlphaFix, we ought to clear the unused
// color buffer too, else Mali 400 drivers get confused.
// Might as well clear stencil too for completeness.
- if (g_RenderingOptions.GetShadowAlphaFix())
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
- else
- glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ deviceCommandContext->ClearFramebuffer();
}
m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
}
void ShadowMap::PrepareCamera(const int cascade)
{
m->CalculateShadowMatrices(cascade);
const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
g_Renderer.SetViewport(vp);
CCamera camera = m->SavedViewCamera;
camera.SetProjection(m->Cascades[cascade].LightProjection);
camera.GetOrientation() = m->InvLightTransform;
g_Renderer.GetSceneRenderer().SetViewCamera(camera);
const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
Renderer::Backend::GL::CDeviceCommandContext::ScissorRect scissorRect;
scissorRect.x = cascadeViewPort.m_X;
scissorRect.y = cascadeViewPort.m_Y;
scissorRect.width = cascadeViewPort.m_Width;
scissorRect.height = cascadeViewPort.m_Height;
g_Renderer.GetDeviceCommandContext()->SetScissors(1, &scissorRect);
}
// Finish rendering into shadow map texture
void ShadowMap::EndRender()
{
g_Renderer.GetDeviceCommandContext()->SetScissors(0, nullptr);
g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera);
{
PROFILE("unbind framebuffer");
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ g_Renderer.GetDeviceCommandContext()->SetFramebuffer(
+ g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer());
}
const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() };
g_Renderer.SetViewport(vp);
}
void ShadowMap::BindTo(const CShaderProgramPtr& shader) const
{
if (!shader->GetTextureBinding(str_shadowTex).Active() || !m->Texture)
return;
shader->BindTexture(str_shadowTex, m->Texture.get());
shader->Uniform(str_shadowScale, m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
const CVector3D cameraForward = g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn();
shader->Uniform(str_cameraForward, cameraForward.X, cameraForward.Y, cameraForward.Z,
cameraForward.Dot(g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetTranslation()));
if (GetCascadeCount() == 1)
{
shader->Uniform(str_shadowTransform, m->Cascades[0].TextureMatrix);
shader->Uniform(str_shadowDistance, m->Cascades[0].Distance);
}
else
{
std::vector shadowDistances;
std::vector shadowTransforms;
for (const ShadowMapInternals::Cascade& cascade : m->Cascades)
{
shadowDistances.emplace_back(cascade.Distance);
shadowTransforms.emplace_back(cascade.TextureMatrix);
}
shader->Uniform(str_shadowTransforms_0, GetCascadeCount(), shadowTransforms.data());
shader->Uniform(str_shadowTransforms, GetCascadeCount(), shadowTransforms.data());
shader->Uniform(str_shadowDistances_0, GetCascadeCount(), shadowDistances.data());
shader->Uniform(str_shadowDistances, GetCascadeCount(), shadowDistances.data());
}
}
// Depth texture bits
int ShadowMap::GetDepthTextureBits() const
{
return m->DepthTextureBits;
}
void ShadowMap::SetDepthTextureBits(int bits)
{
if (bits != m->DepthTextureBits)
{
m->Texture.reset();
m->Width = m->Height = 0;
m->DepthTextureBits = bits;
}
}
void ShadowMap::RenderDebugBounds()
{
// Render various shadow bounds:
// Yellow = bounds of objects in view frustum that receive shadows
// Red = culling frustum used to find potential shadow casters
// Blue = frustum used for rendering the shadow map
const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform;
g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform);
for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
{
g_Renderer.GetDebugRenderer().DrawBoundingBox(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
g_Renderer.GetDebugRenderer().DrawBoundingBoxOutline(m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform);
const CFrustum frustum = GetShadowCasterCullFrustum(cascade);
// We don't have a function to create a brush directly from a frustum, so use
// the ugly approach of creating a large cube and then intersecting with the frustum
const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
CBrush brush(dummy);
CBrush frustumBrush;
brush.Intersect(frustum, frustumBrush);
g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
g_Renderer.GetDebugRenderer().DrawBrushOutline(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.5f));
}
ogl_WarnIfError();
}
void ShadowMap::RenderDebugTexture(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!m->Texture)
return;
#if !CONFIG2_GLES
g_Renderer.BindTexture(0, m->Texture->GetHandle());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
#endif
CShaderTechniquePtr texTech = g_Renderer.GetShaderManager().LoadEffect(str_canvas2d);
texTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
texTech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& texShader = texTech->GetShader();
texShader->Uniform(str_transform, GetDefaultGuiMatrix());
texShader->BindTexture(str_tex, m->Texture.get());
texShader->Uniform(str_colorAdd, CColor(0.0f, 0.0f, 0.0f, 1.0f));
texShader->Uniform(str_colorMul, CColor(1.0f, 1.0f, 1.0f, 0.0f));
texShader->Uniform(str_grayscaleFactor, 0.0f);
float s = 256.f;
float boxVerts[] =
{
0,0, 0,s, s,0,
s,0, 0,s, s,s
};
float boxUV[] =
{
0,0, 0,1, 1,0,
1,0, 0,1, 1,1
};
texShader->VertexPointer(2, GL_FLOAT, 0, boxVerts);
texShader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, boxUV);
texShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
texTech->EndPass();
#if !CONFIG2_GLES
g_Renderer.BindTexture(0, m->Texture->GetHandle());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
#endif
ogl_WarnIfError();
}
int ShadowMap::GetCascadeCount() const
{
#if CONFIG2_GLES
return 1;
#else
return m->ShadowsCoverMap ? 1 : m->CascadeCount;
#endif
}
Index: ps/trunk/source/renderer/TerrainOverlay.cpp
===================================================================
--- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26301)
+++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26302)
@@ -1,406 +1,408 @@
/* 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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
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(deviceCommandContext);
}
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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!m_Terrain)
return; // should never happen, but let's play it safe
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
#warning TODO: implement TerrainOverlay::RenderOverlays for GLES
#else
// 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(deviceCommandContext, m_i, m_j);
EndRender();
//glDisable(GL_POLYGON_OFFSET_LINE);
glDisable(GL_POLYGON_OFFSET_FILL);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
void TerrainOverlay::RenderTile(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden)
{
RenderTile(deviceCommandContext, color, drawHidden, m_i, m_j);
}
void TerrainOverlay::RenderTile(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden, ssize_t i, ssize_t j)
{
// TODO: unnecessary computation calls has been removed but we should use
// a vertex buffer or a vertex shader with a texture.
// Not sure if it's possible on old OpenGL.
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
UNUSED2(color);
+ UNUSED2(drawHidden);
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);
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
overlayTech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden;
pipelineStateDesc.blendState.enabled = true;
pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
Renderer::Backend::BlendFactor::SRC_ALPHA;
pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
Renderer::Backend::BlendOp::ADD;
pipelineStateDesc.rasterizationState.cullMode =
drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK;
overlayTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
CShaderProgramPtr overlayShader = overlayTech->GetShader();
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);
overlayTech->EndPass();
#endif
}
void TerrainOverlay::RenderTileOutline(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CColor& color, int lineWidth, bool drawHidden)
{
RenderTileOutline(deviceCommandContext, color, lineWidth, drawHidden, m_i, m_j);
}
void TerrainOverlay::RenderTileOutline(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CColor& color, int lineWidth, bool drawHidden, ssize_t i, ssize_t j)
{
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
UNUSED2(color);
UNUSED2(lineWidth);
+ UNUSED2(drawHidden);
UNUSED2(i);
UNUSED2(j);
#warning TODO: implement TerrainOverlay::RenderTileOutline for GLES
#else
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
if (lineWidth != 1)
glLineWidth(static_cast(lineWidth));
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);
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
overlayTech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthTestEnabled = !drawHidden;
pipelineStateDesc.blendState.enabled = true;
pipelineStateDesc.blendState.srcColorBlendFactor = pipelineStateDesc.blendState.srcAlphaBlendFactor =
Renderer::Backend::BlendFactor::SRC_ALPHA;
pipelineStateDesc.blendState.dstColorBlendFactor = pipelineStateDesc.blendState.dstAlphaBlendFactor =
Renderer::Backend::BlendFactor::ONE_MINUS_SRC_ALPHA;
pipelineStateDesc.blendState.colorBlendOp = pipelineStateDesc.blendState.alphaBlendOp =
Renderer::Backend::BlendOp::ADD;
pipelineStateDesc.rasterizationState.cullMode =
drawHidden ? Renderer::Backend::CullMode::NONE : Renderer::Backend::CullMode::BACK;
overlayTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
const CShaderProgramPtr& overlayShader = overlayTech->GetShader();
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);
overlayTech->EndPass();
if (lineWidth != 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(
deviceCommandContext, 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 26301)
+++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 26302)
@@ -1,621 +1,623 @@
/* 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/backend/gl/Device.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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
int cullGroup, CMatrix3D& textureMatrix,
Renderer::Backend::GL::CTexture* texture)
{
#if CONFIG2_GLES
#warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES
UNUSED2(deviceCommandContext);
UNUSED2(cullGroup);
UNUSED2(textureMatrix);
UNUSED2(texture);
#else
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
CShaderTechniquePtr debugOverlayTech =
g_Renderer.GetShaderManager().LoadEffect(str_debug_overlay);
debugOverlayTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
debugOverlayTech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& debugOverlayShader = debugOverlayTech->GetShader();
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);
// 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);
}
debugOverlayTech->EndPass();
#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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
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();
Renderer::Backend::GraphicsPipelineStateDesc solidPipelineStateDesc =
techSolid->GetGraphicsPipelineStateDesc();
solidPipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE;
deviceCommandContext->SetGraphicsPipelineState(solidPipelineStateDesc);
const 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(deviceCommandContext, visiblePatches, context, shadow);
// render blend passes for each patch
CPatchRData::RenderBlends(deviceCommandContext, visiblePatches, context, shadow);
CDecalRData::RenderDecals(deviceCommandContext, visibleDecals, context, shadow);
// restore OpenGL state
g_Renderer.BindTexture(1, 0);
g_Renderer.BindTexture(2, 0);
g_Renderer.BindTexture(3, 0);
}
///////////////////////////////////////////////////////////////////
// Render un-textured patches as polygons
void TerrainRenderer::RenderPatches(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
int cullGroup, const CShaderDefines& defines, const CColor& color)
{
ENSURE(m->phase == Phase_Render);
std::vector& visiblePatches = m->visiblePatches[cullGroup];
if (visiblePatches.empty())
return;
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
UNUSED2(defines);
UNUSED2(color);
#warning TODO: implement TerrainRenderer::RenderPatches for GLES
#else
CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_terrain_solid, defines);
solidTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
solidTech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& solidShader = solidTech->GetShader();
solidShader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
solidShader->Uniform(str_color, color);
CPatchRData::RenderStreams(visiblePatches, solidShader, STREAM_POS);
solidTech->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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
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;
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#endif
m->fancyWaterTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
m->fancyWaterTech->GetGraphicsPipelineStateDesc());
const 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);
for (CPatchRData* data : m->visiblePatches[cullGroup])
{
data->RenderWaterSurface(fancyWaterShader, true);
if (waterManager.m_WaterFancyEffects)
data->RenderWaterShore(fancyWaterShader);
}
m->fancyWaterTech->EndPass();
#if !CONFIG2_GLES
if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
return true;
}
void TerrainRenderer::RenderSimpleWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
int cullGroup)
{
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
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();
if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
const double time = waterManager.m_WaterTexTimer;
CShaderTechniquePtr waterSimpleTech =
g_Renderer.GetShaderManager().LoadEffect(str_water_simple);
waterSimpleTech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
waterSimpleTech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& waterSimpleShader = waterSimpleTech->GetShader();
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->RenderWaterSurface(waterSimpleShader, false);
}
g_Renderer.BindTexture(1, 0);
glActiveTextureARB(GL_TEXTURE0_ARB);
waterSimpleTech->EndPass();
if (g_Renderer.GetSceneRenderer().GetWaterRenderMode() == WIREFRAME)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif
}
///////////////////////////////////////////////////////////////////
// Render water that is part of the terrain
void TerrainRenderer::RenderWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderDefines& context, int cullGroup, ShadowMap* shadow)
{
const WaterManager& waterManager = g_Renderer.GetSceneRenderer().GetWaterManager();
if (!waterManager.WillRenderFancyWater())
RenderSimpleWater(deviceCommandContext, cullGroup);
else
RenderFancyWater(deviceCommandContext, context, cullGroup, shadow);
}
void TerrainRenderer::RenderWaterFoamOccluders(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
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);
+ deviceCommandContext->SetFramebuffer(waterManager.m_FancyEffectsFramebuffer.get());
// Overwrite waves that would be behind the ground.
CShaderTechniquePtr dummyTech = g_Renderer.GetShaderManager().LoadEffect(str_solid);
dummyTech->BeginPass();
Renderer::Backend::GraphicsPipelineStateDesc pipelineStateDesc =
dummyTech->GetGraphicsPipelineStateDesc();
pipelineStateDesc.depthStencilState.depthTestEnabled = true;
pipelineStateDesc.rasterizationState.cullMode = Renderer::Backend::CullMode::NONE;
deviceCommandContext->SetGraphicsPipelineState(pipelineStateDesc);
const 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->RenderWaterShore(dummyShader);
dummyTech->EndPass();
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
}
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);
}
Index: ps/trunk/source/renderer/WaterManager.cpp
===================================================================
--- ps/trunk/source/renderer/WaterManager.cpp (revision 26301)
+++ ps/trunk/source/renderer/WaterManager.cpp (revision 26302)
@@ -1,1062 +1,1025 @@
/* 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 "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "graphics/ShaderManager.h"
#include "graphics/ShaderProgram.h"
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/ogl.h"
#include "lib/tex/tex.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
-#include "renderer/WaterManager.h"
+#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/SceneRenderer.h"
+#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/components/ICmpRangeManager.h"
#include
struct CoastalPoint
{
CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {};
int index;
CVector2D position;
};
struct SWavesVertex
{
// vertex position
CVector3D m_BasePosition;
CVector3D m_ApexPosition;
CVector3D m_SplashPosition;
CVector3D m_RetreatPosition;
CVector2D m_PerpVect;
u8 m_UV[3];
// pad to a power of two
u8 m_Padding[5];
};
cassert(sizeof(SWavesVertex) == 64);
struct WaveObject
{
CVertexBufferManager::Handle m_VBVertices;
CBoundingBoxAligned m_AABB;
size_t m_Width;
float m_TimeDiff;
};
WaterManager::WaterManager()
{
// water
m_RenderWater = false; // disabled until textures are successfully loaded
m_WaterHeight = 5.0f;
m_RefTextureSize = 0;
- m_ReflectionFbo = 0;
- m_RefractionFbo = 0;
- m_FancyEffectsFBO = 0;
-
m_WaterTexTimer = 0.0;
m_WindAngle = 0.0f;
m_Waviness = 8.0f;
m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f);
m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f);
m_Murkiness = 0.45f;
m_RepeatPeriod = 16.0f;
m_WaterEffects = true;
m_WaterFancyEffects = false;
m_WaterRealDepth = false;
m_WaterRefraction = false;
m_WaterReflection = false;
m_WaterType = L"ocean";
m_NeedsReloading = false;
m_NeedInfoUpdate = true;
m_MapSize = 0;
m_updatei0 = 0;
m_updatej0 = 0;
m_updatei1 = 0;
m_updatej1 = 0;
}
WaterManager::~WaterManager()
{
// Cleanup if the caller messed up
UnloadWaterTextures();
m_ShoreWaves.clear();
m_ShoreWavesVBIndices.Reset();
m_DistanceHeightmap.reset();
m_WindStrength.reset();
- if (!g_Renderer.GetCapabilities().m_PrettyWater)
- return;
+ m_FancyEffectsFramebuffer.reset();
+ m_RefractionFramebuffer.reset();
+ m_ReflectionFramebuffer.reset();
m_FancyTexture.reset();
m_FancyTextureDepth.reset();
m_ReflFboDepthTexture.reset();
m_RefrFboDepthTexture.reset();
-
- glDeleteFramebuffersEXT(1, &m_FancyEffectsFBO);
- glDeleteFramebuffersEXT(1, &m_RefractionFbo);
- glDeleteFramebuffersEXT(1, &m_ReflectionFbo);
}
///////////////////////////////////////////////////////////////////
// Progressive load of water textures
int WaterManager::LoadWaterTextures()
{
// TODO: this doesn't need to be progressive-loading any more
// (since texture loading is async now)
wchar_t pathname[PATH_MAX];
// Load diffuse grayscale images (for non-fancy water)
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i)
{
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1);
CTextureProperties textureProps(pathname);
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaterTexture[i] = texture;
}
if (!g_Renderer.GetCapabilities().m_PrettyWater)
{
// Enable rendering, now that we've succeeded this far
m_RenderWater = true;
return 0;
}
#if CONFIG2_GLES
#warning Fix WaterManager::LoadWaterTextures on GLES
#else
// Load normalmaps (for fancy water)
ReloadWaterNormalTextures();
// Load CoastalWaves
{
CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png");
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaveTex = texture;
}
// Load Foam
{
CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png");
textureProps.SetWrap(GL_REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_FoamTex = texture;
}
// Use screen-sized textures for minimum artifacts.
- m_RefTextureSize = g_Renderer.GetHeight();
-
- m_RefTextureSize = round_up_to_pow2(m_RefTextureSize);
+ m_RefTextureSize = round_up_to_pow2(g_Renderer.GetHeight());
// Create reflection texture
m_ReflectionTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
// Create refraction texture
m_RefractionTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
// Create depth textures
m_ReflFboDepthTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
m_RefrFboDepthTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
Resize();
// Create the water framebuffers
- m_ReflectionFbo = 0;
- glGenFramebuffersEXT(1, &m_ReflectionFbo);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_ReflectionFbo);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_ReflectionTexture->GetHandle(), 0);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_ReflFboDepthTexture->GetHandle(), 0);
-
- ogl_WarnIfError();
-
- GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
- if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
+ m_ReflectionFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_ReflectionTexture.get(), m_ReflFboDepthTexture.get(), CColor(0.5f, 0.5f, 1.0f, 0.0f));
+ if (!m_ReflectionFramebuffer)
{
- LOGWARNING("Reflection framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterReflection(false);
UpdateQuality();
}
- m_RefractionFbo = 0;
- glGenFramebuffersEXT(1, &m_RefractionFbo);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_RefractionFbo);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_RefractionTexture->GetHandle(), 0);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_RefrFboDepthTexture->GetHandle(), 0);
-
- ogl_WarnIfError();
-
- status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
- if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
+ m_RefractionFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_RefractionTexture.get(), m_RefrFboDepthTexture.get(), CColor(1.0f, 0.0f, 0.0f, 0.0f));
+ if (!m_RefractionFramebuffer)
{
- LOGWARNING("Refraction framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
- glGenFramebuffersEXT(1, &m_FancyEffectsFBO);
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FancyTexture->GetHandle(), 0);
- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, m_FancyTextureDepth->GetHandle(), 0);
-
- ogl_WarnIfError();
-
- status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
- if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
+ m_FancyEffectsFramebuffer = Renderer::Backend::GL::CFramebuffer::Create(
+ m_FancyTexture.get(), m_FancyTextureDepth.get());
+ if (!m_FancyEffectsFramebuffer)
{
- LOGWARNING("Fancy Effects framebuffer object incomplete: 0x%04X", status);
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
-
// Enable rendering, now that we've succeeded this far
m_RenderWater = true;
#endif
return 0;
}
///////////////////////////////////////////////////////////////////
// Resize: Updates the fancy water textures.
void WaterManager::Resize()
{
// Create the Fancy Effects texture
m_FancyTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
m_FancyTextureDepth = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::D32, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
}
void WaterManager::ReloadWaterNormalTextures()
{
wchar_t pathname[PATH_MAX];
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i)
{
swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), static_cast(i) + 1);
CTextureProperties textureProps(pathname);
textureProps.SetWrap(GL_REPEAT);
textureProps.SetMaxAnisotropy(4);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_NormalMap[i] = texture;
}
}
///////////////////////////////////////////////////////////////////
// Unload water textures
void WaterManager::UnloadWaterTextures()
{
for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
m_WaterTexture[i].reset();
if (!g_Renderer.GetCapabilities().m_PrettyWater)
return;
for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++)
m_NormalMap[i].reset();
+ m_RefractionFramebuffer.reset();
+ m_ReflectionFramebuffer.reset();
m_ReflectionTexture.reset();
m_RefractionTexture.reset();
- glDeleteFramebuffersEXT(1, &m_RefractionFbo);
- glDeleteFramebuffersEXT(1, &m_ReflectionFbo);
}
template
static inline void ComputeDirection(float* distanceMap, const u16* heightmap, float waterHeight, size_t SideSize, size_t maxLevel)
{
#define ABOVEWATER(x, z) (HEIGHT_SCALE * heightmap[z*SideSize + x] >= waterHeight)
#define UPDATELOOKAHEAD \
for (; lookahead <= id2+maxLevel && lookahead < SideSize && \
((!Transpose && !ABOVEWATER(lookahead, id1)) || (Transpose && !ABOVEWATER(id1, lookahead))); ++lookahead)
// Algorithm:
// We want to know the distance to the closest shore point. Go through each line/column,
// keep track of when we encountered the last shore point and how far ahead the next one is.
for (size_t id1 = 0; id1 < SideSize; ++id1)
{
size_t id2 = 0;
const size_t& x = Transpose ? id1 : id2;
const size_t& z = Transpose ? id2 : id1;
size_t level = ABOVEWATER(x, z) ? 0 : maxLevel;
size_t lookahead = (size_t)(level > 0);
UPDATELOOKAHEAD;
// start moving
for (; id2 < SideSize; ++id2)
{
// update current level
if (ABOVEWATER(x, z))
level = 0;
else
level = std::min(level+1, maxLevel);
// move lookahead
if (lookahead == id2)
++lookahead;
UPDATELOOKAHEAD;
// This is the important bit: set the distance to either:
// - the distance to the previous shore point (level)
// - the distance to the next shore point (lookahead-id2)
distanceMap[z*SideSize + x] = std::min(distanceMap[z*SideSize + x], (float)std::min(lookahead-id2, level));
}
}
#undef ABOVEWATER
#undef UPDATELOOKAHEAD
}
///////////////////////////////////////////////////////////////////
// Calculate our binary heightmap from the terrain heightmap.
void WaterManager::RecomputeDistanceHeightmap()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
size_t SideSize = m_MapSize;
// we want to look ahead some distance, but not too much (less efficient and not interesting). This is our lookahead.
const size_t maxLevel = 5;
if (!m_DistanceHeightmap)
{
m_DistanceHeightmap = std::make_unique(SideSize * SideSize);
std::fill(m_DistanceHeightmap.get(), m_DistanceHeightmap.get() + SideSize * SideSize, static_cast(maxLevel));
}
// Create a manhattan-distance heightmap.
// This could be refined to only be done near the coast itself, but it's probably not necessary.
u16* heightmap = terrain->GetHeightMap();
ComputeDirection(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel);
ComputeDirection(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel);
}
// This requires m_DistanceHeightmap to be defined properly.
void WaterManager::CreateWaveMeshes()
{
OGL_SCOPED_DEBUG_GROUP("Create Wave Meshes");
if (m_MapSize == 0)
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
m_ShoreWaves.clear();
m_ShoreWavesVBIndices.Reset();
if (m_Waviness < 5.0f && m_WaterType != L"ocean")
return;
size_t SideSize = m_MapSize;
// First step: get the points near the coast.
std::set CoastalPointsSet;
for (size_t z = 1; z < SideSize-1; ++z)
for (size_t x = 1; x < SideSize-1; ++x)
// get the points not on the shore but near it, ocean-side
if (m_DistanceHeightmap[z*m_MapSize + x] > 0.5f && m_DistanceHeightmap[z*m_MapSize + x] < 1.5f)
CoastalPointsSet.insert((z)*SideSize + x);
// Second step: create chains out of those coastal points.
static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } };
std::vector > CoastalPointsChains;
while (!CoastalPointsSet.empty())
{
int index = *(CoastalPointsSet.begin());
int x = index % SideSize;
int y = (index - x ) / SideSize;
std::deque Chain;
Chain.push_front(CoastalPoint(index,CVector2D(x*4,y*4)));
// Erase us.
CoastalPointsSet.erase(CoastalPointsSet.begin());
// We're our starter points. At most we can have 2 points close to us.
// We'll pick the first one and look for its neighbors (he can only have one new)
// Up until we either reach the end of the chain, or ourselves.
// Then go down the other direction if there is any.
int neighbours[2] = { -1, -1 };
int nbNeighb = 0;
for (int i = 0; i < 8; ++i)
{
if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize))
{
if (nbNeighb < 2)
neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize;
++nbNeighb;
}
}
if (nbNeighb > 2)
continue;
for (int i = 0; i < 2; ++i)
{
if (neighbours[i] == -1)
continue;
// Move to our neighboring point
int xx = neighbours[i] % SideSize;
int yy = (neighbours[i] - xx ) / SideSize;
int indexx = xx + yy*SideSize;
int endedChain = false;
if (i == 0)
Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
else
Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
// If there's a loop we'll be the "other" neighboring point already so check for that.
// We'll readd at the end/front the other one to have full squares.
if (CoastalPointsSet.count(indexx) == 0)
break;
CoastalPointsSet.erase(indexx);
// Start checking from there.
while(!endedChain)
{
bool found = false;
nbNeighb = 0;
for (int p = 0; p < 8; ++p)
{
if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize))
{
if (nbNeighb >= 2)
{
CoastalPointsSet.erase(xx + yy*SideSize);
continue;
}
++nbNeighb;
// We've found a new point around us.
// Move there
xx = xx + around[p][0];
yy = yy + around[p][1];
indexx = xx + yy*SideSize;
if (i == 0)
Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
else
Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
CoastalPointsSet.erase(xx + yy*SideSize);
found = true;
break;
}
}
if (!found)
endedChain = true;
}
}
if (Chain.size() > 10)
CoastalPointsChains.push_back(Chain);
}
// (optional) third step: Smooth chains out.
// This is also really dumb.
for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
{
// Bump 1 for smoother.
for (int p = 0; p < 3; ++p)
{
for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j)
{
CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position;
CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f;
}
}
}
// Fourth step: create waves themselves, using those chains. We basically create subchains.
GLushort waveSizes = 14; // maximal size in width.
// Construct indices buffer (we can afford one for all of them)
std::vector water_indices;
for (GLushort a = 0; a < waveSizes - 1; ++a)
{
for (GLushort rect = 0; rect < 7; ++rect)
{
water_indices.push_back(a * 9 + rect);
water_indices.push_back(a * 9 + 9 + rect);
water_indices.push_back(a * 9 + 1 + rect);
water_indices.push_back(a * 9 + 9 + rect);
water_indices.push_back(a * 9 + 10 + rect);
water_indices.push_back(a * 9 + 1 + rect);
}
}
// Generic indexes, max-length
m_ShoreWavesVBIndices = g_VBMan.AllocateChunk(
sizeof(GLushort), water_indices.size(),
GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER,
nullptr, CVertexBufferManager::Group::WATER);
m_ShoreWavesVBIndices->m_Owner->UpdateChunkVertices(m_ShoreWavesVBIndices.Get(), &water_indices[0]);
float diff = (rand() % 50) / 5.0f;
std::vector vertices, reversed;
for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
{
for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j)
{
if (CoastalPointsChains[i].size()- 1 - j < waveSizes)
break;
GLushort width = waveSizes;
// First pass to get some parameters out.
float outmost = 0.0f; // how far to move on the shore.
float avgDepth = 0.0f;
int sign = 1;
CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0);
for (GLushort a = 0; a < waveSizes;++a)
{
lastPerp = perp;
perp = CVector2D(0,0);
int nb = 0;
CVector2D pos = CoastalPointsChains[i][j+a].position;
CVector2D posPlus;
CVector2D posMinus;
if (a > 0)
{
++nb;
posMinus = CoastalPointsChains[i][j+a-1].position;
perp += pos-posMinus;
}
if (a < waveSizes-1)
{
++nb;
posPlus = CoastalPointsChains[i][j+a+1].position;
perp += posPlus-pos;
}
perp /= nb;
perp = CVector2D(-perp.Y,perp.X).Normalized();
if (a == 0)
firstPerp = perp;
if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f)
{
width = a+1;
break;
}
if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight)
sign = -1;
avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight;
float localOutmost = -2.0f;
while (localOutmost < 0.0f)
{
float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight;
if (depth < 0.0f || depth > 0.6f)
localOutmost += 0.2f;
else
break;
}
outmost += localOutmost;
}
if (width < 5)
{
j += 6;
continue;
}
outmost /= width;
if (outmost > -0.5f)
{
j += 3;
continue;
}
outmost = -2.5f + outmost * m_Waviness/10.0f;
avgDepth /= width;
if (avgDepth > -1.3f)
{
j += 3;
continue;
}
// we passed the checks, we can create a wave of size "width".
std::unique_ptr shoreWave = std::make_unique();
vertices.clear();
vertices.reserve(9 * width);
shoreWave->m_Width = width;
shoreWave->m_TimeDiff = diff;
diff += (rand() % 100) / 25.0f + 4.0f;
for (GLushort a = 0; a < width;++a)
{
perp = CVector2D(0,0);
int nb = 0;
CVector2D pos = CoastalPointsChains[i][j+a].position;
CVector2D posPlus;
CVector2D posMinus;
if (a > 0)
{
++nb;
posMinus = CoastalPointsChains[i][j+a-1].position;
perp += pos-posMinus;
}
if (a < waveSizes-1)
{
++nb;
posPlus = CoastalPointsChains[i][j+a+1].position;
perp += posPlus-pos;
}
perp /= nb;
perp = CVector2D(-perp.Y,perp.X).Normalized();
SWavesVertex point[9];
float baseHeight = 0.04f;
float halfWidth = (width-1.0f)/2.0f;
float sideNess = sqrtf(Clamp( (halfWidth - fabsf(a - halfWidth)) / 3.0f, 0.0f, 1.0f));
point[0].m_UV[0] = a; point[0].m_UV[1] = 8;
point[1].m_UV[0] = a; point[1].m_UV[1] = 7;
point[2].m_UV[0] = a; point[2].m_UV[1] = 6;
point[3].m_UV[0] = a; point[3].m_UV[1] = 5;
point[4].m_UV[0] = a; point[4].m_UV[1] = 4;
point[5].m_UV[0] = a; point[5].m_UV[1] = 3;
point[6].m_UV[0] = a; point[6].m_UV[1] = 2;
point[7].m_UV[0] = a; point[7].m_UV[1] = 1;
point[8].m_UV[0] = a; point[8].m_UV[1] = 0;
point[0].m_PerpVect = perp;
point[1].m_PerpVect = perp;
point[2].m_PerpVect = perp;
point[3].m_PerpVect = perp;
point[4].m_PerpVect = perp;
point[5].m_PerpVect = perp;
point[6].m_PerpVect = perp;
point[7].m_PerpVect = perp;
point[8].m_PerpVect = perp;
static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f };
static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f };
static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f };
static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f };
static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 };
static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 };
static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 };
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost),
pos.Y+sign*perp.Y*(perpT1[t]+outmost));
point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT1[t]+outmost));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost),
pos.Y+sign*perp.Y*(perpT2[t]+outmost));
point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT2[t]+outmost));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess),
pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
}
for (size_t t = 0; t < 9; ++t)
{
float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost),
pos.Y+sign*perp.Y*(perpT4[t]+outmost));
point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight),
pos.Y+sign*perp.Y*(perpT4[t]+outmost));
}
vertices.push_back(point[8]);
vertices.push_back(point[7]);
vertices.push_back(point[6]);
vertices.push_back(point[5]);
vertices.push_back(point[4]);
vertices.push_back(point[3]);
vertices.push_back(point[2]);
vertices.push_back(point[1]);
vertices.push_back(point[0]);
shoreWave->m_AABB += point[8].m_SplashPosition;
shoreWave->m_AABB += point[8].m_BasePosition;
shoreWave->m_AABB += point[0].m_SplashPosition;
shoreWave->m_AABB += point[0].m_BasePosition;
shoreWave->m_AABB += point[4].m_ApexPosition;
}
if (sign == 1)
{
// Let's do some fancy reversing.
reversed.clear();
reversed.reserve(vertices.size());
for (int a = width - 1; a >= 0; --a)
{
for (size_t t = 0; t < 9; ++t)
reversed.push_back(vertices[a * 9 + t]);
}
std::swap(vertices, reversed);
}
j += width/2-1;
shoreWave->m_VBVertices = g_VBMan.AllocateChunk(
sizeof(SWavesVertex), vertices.size(),
GL_STATIC_DRAW, GL_ARRAY_BUFFER,
nullptr, CVertexBufferManager::Group::WATER);
shoreWave->m_VBVertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBVertices.Get(), &vertices[0]);
m_ShoreWaves.emplace_back(std::move(shoreWave));
}
}
}
void WaterManager::RenderWaves(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CFrustum& frustrum)
{
OGL_SCOPED_DEBUG_GROUP("Render Waves");
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
UNUSED2(frustrum);
#warning Fix WaterManager::RenderWaves on GLES
#else
if (!m_WaterFancyEffects)
return;
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FancyEffectsFBO);
-
- GLuint attachments[1] = { GL_COLOR_ATTACHMENT0_EXT };
- glDrawBuffers(1, attachments);
-
- glClearColor(0.0f,0.0f, 0.0f,0.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ deviceCommandContext->SetFramebuffer(m_FancyEffectsFramebuffer.get());
+ deviceCommandContext->ClearFramebuffer();
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_water_waves);
tech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
tech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& shader = tech->GetShader();
shader->BindTexture(str_waveTex, m_WaveTex);
shader->BindTexture(str_foamTex, m_FoamTex);
shader->Uniform(str_time, (float)m_WaterTexTimer);
shader->Uniform(str_transform, g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection());
for (size_t a = 0; a < m_ShoreWaves.size(); ++a)
{
if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB))
continue;
CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBVertices.Get();
SWavesVertex* base = (SWavesVertex*)VBchunk->m_Owner->Bind();
// setup data pointers
GLsizei stride = sizeof(SWavesVertex);
shader->VertexPointer(3, GL_FLOAT, stride, &base[VBchunk->m_Index].m_BasePosition);
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_UNSIGNED_BYTE, stride, &base[VBchunk->m_Index].m_UV);
// NormalPointer(gl_FLOAT, stride, &base[m_VBWater->m_Index].m_UV)
glVertexAttribPointerARB(2, 2, GL_FLOAT, GL_FALSE, stride, &base[VBchunk->m_Index].m_PerpVect); // replaces commented above because my normal is vec2
shader->VertexAttribPointer(str_a_apexPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_ApexPosition);
shader->VertexAttribPointer(str_a_splashPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_SplashPosition);
shader->VertexAttribPointer(str_a_retreatPosition, 3, GL_FLOAT, false, stride, &base[VBchunk->m_Index].m_RetreatPosition);
shader->AssertPointersBound();
shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff);
shader->Uniform(str_width, (int)m_ShoreWaves[a]->m_Width);
u8* indexBase = m_ShoreWavesVBIndices->m_Owner->Bind();
glDrawElements(GL_TRIANGLES, (GLsizei) (m_ShoreWaves[a]->m_Width-1)*(7*6),
GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(m_ShoreWavesVBIndices->m_Index));
shader->Uniform(str_translation, m_ShoreWaves[a]->m_TimeDiff + 6.0f);
// TODO: figure out why this doesn't work.
//g_Renderer.m_Stats.m_DrawCalls++;
//g_Renderer.m_Stats.m_WaterTris += m_ShoreWaves_VBIndices->m_Count / 3;
CVertexBuffer::Unbind();
}
tech->EndPass();
- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ deviceCommandContext->SetFramebuffer(
+ deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
#endif
}
void WaterManager::RecomputeWaterData()
{
if (!m_MapSize)
return;
RecomputeDistanceHeightmap();
RecomputeWindStrength();
CreateWaveMeshes();
}
///////////////////////////////////////////////////////////////////
// Calculate the strength of the wind at a given point on the map.
void WaterManager::RecomputeWindStrength()
{
if (m_MapSize <= 0)
return;
if (!m_WindStrength)
m_WindStrength = std::make_unique(m_MapSize * m_MapSize);
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain || !terrain->GetHeightMap())
return;
CVector2D windDir = CVector2D(cos(m_WindAngle), sin(m_WindAngle));
int stepSize = 10;
ssize_t windX = -round(stepSize * windDir.X);
ssize_t windY = -round(stepSize * windDir.Y);
struct SWindPoint {
SWindPoint(size_t x, size_t y, float strength) : X(x), Y(y), windStrength(strength) {}
ssize_t X;
ssize_t Y;
float windStrength;
};
std::vector startingPoints;
std::vector> movement; // Every increment, move each starting point by all of these.
// Compute starting points (one or two edges of the map) and how much to move each computation increment.
if (fabs(windDir.X) < 0.01f)
{
movement.emplace_back(0, windY > 0.f ? 1 : -1);
startingPoints.reserve(m_MapSize);
size_t start = windY > 0 ? 0 : m_MapSize - 1;
for (size_t x = 0; x < m_MapSize; ++x)
startingPoints.emplace_back(x, start, 0.f);
}
else if (fabs(windDir.Y) < 0.01f)
{
movement.emplace_back(windX > 0.f ? 1 : - 1, 0);
startingPoints.reserve(m_MapSize);
size_t start = windX > 0 ? 0 : m_MapSize - 1;
for (size_t z = 0; z < m_MapSize; ++z)
startingPoints.emplace_back(start, z, 0.f);
}
else
{
startingPoints.reserve(m_MapSize * 2);
// Points along X.
size_t start = windY > 0 ? 0 : m_MapSize - 1;
for (size_t x = 0; x < m_MapSize; ++x)
startingPoints.emplace_back(x, start, 0.f);
// Points along Z, avoid repeating the corner point.
start = windX > 0 ? 0 : m_MapSize - 1;
if (windY > 0)
for (size_t z = 1; z < m_MapSize; ++z)
startingPoints.emplace_back(start, z, 0.f);
else
for (size_t z = 0; z < m_MapSize-1; ++z)
startingPoints.emplace_back(start, z, 0.f);
// Compute movement array.
movement.reserve(std::max(std::abs(windX),std::abs(windY)));
while (windX != 0 || windY != 0)
{
std::pair move = {
windX == 0 ? 0 : windX > 0 ? +1 : -1,
windY == 0 ? 0 : windY > 0 ? +1 : -1
};
windX -= move.first;
windY -= move.second;
movement.push_back(move);
}
}
// We have all starting points ready, move them all until the map is covered.
for (SWindPoint& point : startingPoints)
{
// Starting velocity is 1.0 unless in shallow water.
m_WindStrength[point.Y * m_MapSize + point.X] = 1.f;
float depth = m_WaterHeight - terrain->GetVertexGroundLevel(point.X, point.Y);
if (depth > 0.f && depth < 2.f)
m_WindStrength[point.Y * m_MapSize + point.X] = depth / 2.f;
point.windStrength = m_WindStrength[point.Y * m_MapSize + point.X];
bool onMap = true;
while (onMap)
for (size_t step = 0; step < movement.size(); ++step)
{
// Move wind speed towards the mean.
point.windStrength = 0.15f + point.windStrength * 0.85f;
// Adjust speed based on height difference, a positive height difference slowly increases speed (simulate venturi effect)
// and a lower height reduces speed (wind protection from hills/...)
float heightDiff = std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X + movement[step].first, point.Y + movement[step].second)) -
std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X, point.Y));
if (heightDiff > 0.f)
point.windStrength = std::min(2.f, point.windStrength + std::min(4.f, heightDiff) / 40.f);
else
point.windStrength = std::max(0.f, point.windStrength + std::max(-4.f, heightDiff) / 5.f);
point.X += movement[step].first;
point.Y += movement[step].second;
if (point.X < 0 || point.X >= static_cast(m_MapSize) || point.Y < 0 || point.Y >= static_cast(m_MapSize))
{
onMap = false;
break;
}
m_WindStrength[point.Y * m_MapSize + point.X] = point.windStrength;
}
}
// TODO: should perhaps blur a little, or change the above code to incorporate neighboring tiles a bit.
}
////////////////////////////////////////////////////////////////////////
// TODO: This will always recalculate for now
void WaterManager::SetMapSize(size_t size)
{
// TODO: Im' blindly trusting the user here.
m_MapSize = size;
m_NeedInfoUpdate = true;
m_updatei0 = 0;
m_updatei1 = size;
m_updatej0 = 0;
m_updatej1 = size;
m_DistanceHeightmap.reset();
m_WindStrength.reset();
}
////////////////////////////////////////////////////////////////////////
// This will set the bools properly
void WaterManager::UpdateQuality()
{
if (g_RenderingOptions.GetWaterEffects() != m_WaterEffects)
{
m_WaterEffects = g_RenderingOptions.GetWaterEffects();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterFancyEffects() != m_WaterFancyEffects)
{
m_WaterFancyEffects = g_RenderingOptions.GetWaterFancyEffects();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterRealDepth() != m_WaterRealDepth)
{
m_WaterRealDepth = g_RenderingOptions.GetWaterRealDepth();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterRefraction() != m_WaterRefraction)
{
m_WaterRefraction = g_RenderingOptions.GetWaterRefraction();
m_NeedsReloading = true;
}
if (g_RenderingOptions.GetWaterReflection() != m_WaterReflection)
{
m_WaterReflection = g_RenderingOptions.GetWaterReflection();
m_NeedsReloading = true;
}
}
bool WaterManager::WillRenderFancyWater() const
{
return
m_RenderWater && g_VideoMode.GetBackend() != CVideoMode::Backend::GL_ARB &&
g_RenderingOptions.GetWaterEffects() && g_Renderer.GetCapabilities().m_PrettyWater;
}
size_t WaterManager::GetCurrentTextureIndex(const double& period) const
{
ENSURE(period > 0.0);
return static_cast(m_WaterTexTimer * ARRAY_SIZE(m_WaterTexture) / period) % ARRAY_SIZE(m_WaterTexture);
}
size_t WaterManager::GetNextTextureIndex(const double& period) const
{
ENSURE(period > 0.0);
return (GetCurrentTextureIndex(period) + 1) % ARRAY_SIZE(m_WaterTexture);
}
Index: ps/trunk/source/renderer/WaterManager.h
===================================================================
--- ps/trunk/source/renderer/WaterManager.h (revision 26301)
+++ ps/trunk/source/renderer/WaterManager.h (revision 26302)
@@ -1,207 +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 .
*/
/*
* Water settings (speed, height) and texture management
*/
#ifndef INCLUDED_WATERMANAGER
#define INCLUDED_WATERMANAGER
#include "graphics/Color.h"
#include "graphics/Texture.h"
#include "maths/Matrix3D.h"
#include "maths/Vector2D.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
+#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Texture.h"
#include "renderer/VertexBufferManager.h"
#include
#include
class CFrustum;
struct WaveObject;
/**
* Class WaterManager: Maintain rendering-related water settings and textures
* Anything that affects gameplay should go in CcmpWaterManager.cpp and passed to this (possibly as copy).
*/
class WaterManager
{
public:
CTexturePtr m_WaterTexture[60];
CTexturePtr m_NormalMap[60];
// How strong the waves are at point X. % of waviness.
std::unique_ptr m_WindStrength;
// How far from the shore a point is. Manhattan.
std::unique_ptr m_DistanceHeightmap;
// Waves vertex buffers
// TODO: measure storing value instead of pointer.
std::vector> m_ShoreWaves;
// Waves indices buffer. Only one since All Wave Objects have the same.
CVertexBufferManager::Handle m_ShoreWavesVBIndices;
size_t m_MapSize;
ssize_t m_TexSize;
CTexturePtr m_WaveTex;
CTexturePtr m_FoamTex;
std::unique_ptr m_FancyTexture;
std::unique_ptr m_FancyTextureDepth;
std::unique_ptr m_ReflFboDepthTexture;
std::unique_ptr m_RefrFboDepthTexture;
// used to know what to update when updating parts of the terrain only.
u32 m_updatei0;
u32 m_updatej0;
u32 m_updatei1;
u32 m_updatej1;
bool m_RenderWater;
// If disabled, force the use of the simple water shader for rendering.
bool m_WaterEffects;
// Those variables register the current quality level. If there is a change, I have to recompile the shader.
// Use real depth or use the fake precomputed one.
bool m_WaterRealDepth;
// Use fancy shore effects and show trails behind ships
bool m_WaterFancyEffects;
// Use refractions instead of simply making the water more or less transparent.
bool m_WaterRefraction;
// Use complete reflections instead of showing merely the sky.
bool m_WaterReflection;
bool m_NeedsReloading;
// requires also recreating the super fancy information.
bool m_NeedInfoUpdate;
float m_WaterHeight;
double m_WaterTexTimer;
float m_RepeatPeriod;
// Reflection and refraction textures for fancy water
std::unique_ptr m_ReflectionTexture;
std::unique_ptr m_RefractionTexture;
size_t m_RefTextureSize;
// framebuffer objects
- GLuint m_RefractionFbo;
- GLuint m_ReflectionFbo;
- GLuint m_FancyEffectsFBO;
+ std::unique_ptr m_RefractionFramebuffer;
+ std::unique_ptr m_ReflectionFramebuffer;
+ std::unique_ptr m_FancyEffectsFramebuffer;
// Model-view-projection matrices for reflected & refracted cameras
// (used to let the vertex shader do projective texturing)
CMatrix3D m_ReflectionMatrix;
CMatrix3D m_RefractionMatrix;
CMatrix3D m_RefractionProjInvMatrix;
CMatrix3D m_RefractionViewInvMatrix;
// Water parameters
std::wstring m_WaterType; // Which texture to use.
CColor m_WaterColor; // Color of the water without refractions. This is what you're seeing when the water's deep or murkiness high.
CColor m_WaterTint; // Tint of refraction in the water.
float m_Waviness; // How big the waves are.
float m_Murkiness; // How murky the water is.
float m_WindAngle; // In which direction the water waves go.
public:
WaterManager();
~WaterManager();
/**
* LoadWaterTextures: Load water textures from within the
* progressive load framework.
*
* @return 0 if loading has completed, a value from 1 to 100 (in percent of completion)
* if more textures need to be loaded and a negative error value on failure.
*/
int LoadWaterTextures();
/**
* Resize: Updates the fancy water textures so that water will render correctly
* with fancy water.
*/
void Resize();
/**
* ReloadWaterNormalTextures: Reload the normal textures so that changing
* water type in Atlas will actually do the right thing.
*/
void ReloadWaterNormalTextures();
/**
* UnloadWaterTextures: Free any loaded water textures and reset the internal state
* so that another call to LoadWaterTextures will begin progressive loading.
*/
void UnloadWaterTextures();
/**
* RecomputeWaterData: calculates all derived data from the waterheight
*/
void RecomputeWaterData();
/**
* RecomputeWindStrength: calculates the intensity of waves
*/
void RecomputeWindStrength();
/**
* RecomputeDistanceHeightmap: recalculates (or calculates) the distance heightmap.
*/
void RecomputeDistanceHeightmap();
/**
* CreateWaveMeshes: Creates the waves objects (and meshes).
*/
void CreateWaveMeshes();
/**
* Updates the map size. Will trigger a complete recalculation of fancy water information the next turn.
*/
void SetMapSize(size_t size);
/**
* Updates the settings to the one from the renderer, and sets m_NeedsReloading.
*/
void UpdateQuality();
/**
* Returns true if fancy water shaders will be used (i.e. the hardware is capable
* and it hasn't been configured off)
*/
bool WillRenderFancyWater() const;
void RenderWaves(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CFrustum& frustrum);
/**
* Returns an index of the current texture that should be used for rendering
* water.
*/
size_t GetCurrentTextureIndex(const double& period) const;
size_t GetNextTextureIndex(const double& period) const;
};
#endif // INCLUDED_WATERMANAGER
Index: ps/trunk/source/renderer/backend/gl/Device.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26301)
+++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26302)
@@ -1,623 +1,631 @@
/* 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 "Device.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Profile.h"
+#include "renderer/backend/gl/DeviceCommandContext.h"
#include "scriptinterface/JSON.h"
#include "scriptinterface/Object.h"
#include "scriptinterface/ScriptInterface.h"
#include "scriptinterface/ScriptRequest.h"
#if OS_WIN
#include "lib/sysdep/os/win/wgfx.h"
// We can't include wutil directly because GL headers conflict with Windows
// until we use a proper GL loader.
extern void* wutil_GetAppHDC();
#endif
#include
#include
#include
// TODO: Support OpenGL platforms which don't use GLX as well.
#if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
#include
#include
#endif
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
std::string GetNameImpl()
{
// GL_VENDOR+GL_RENDERER are good enough here, so we don't use WMI to detect the cards.
// On top of that WMI can cause crashes with Nvidia Optimus and some netbooks
// see http://trac.wildfiregames.com/ticket/1952
// http://trac.wildfiregames.com/ticket/1575
char cardName[128];
const char* vendor = reinterpret_cast(glGetString(GL_VENDOR));
const char* renderer = reinterpret_cast(glGetString(GL_RENDERER));
// Happens if called before GL initialization.
if (!vendor || !renderer)
return {};
sprintf_s(cardName, std::size(cardName), "%s %s", vendor, renderer);
// Remove crap from vendor names. (don't dare touch the model name -
// it's too risky, there are too many different strings).
#define SHORTEN(what, charsToKeep) \
if (!strncmp(cardName, what, std::size(what) - 1)) \
memmove(cardName + charsToKeep, cardName + std::size(what) - 1, (strlen(cardName) - (std::size(what) - 1) + 1) * sizeof(char));
SHORTEN("ATI Technologies Inc.", 3);
SHORTEN("NVIDIA Corporation", 6);
SHORTEN("S3 Graphics", 2); // returned by EnumDisplayDevices
SHORTEN("S3 Graphics, Incorporated", 2); // returned by GL_VENDOR
#undef SHORTEN
return cardName;
}
std::string GetVersionImpl()
{
return reinterpret_cast(glGetString(GL_VERSION));
}
std::string GetDriverInformationImpl()
{
const std::string version = GetVersionImpl();
std::string driverInfo;
#if OS_WIN
driverInfo = CStrW(wgfx_DriverInfo()).ToUTF8();
if (driverInfo.empty())
#endif
{
if (!version.empty())
{
// Add "OpenGL" to differentiate this from the real driver version
// (returned by platform-specific detect routines).
driverInfo = std::string("OpenGL ") + version;
}
}
if (driverInfo.empty())
return version;
return version + " " + driverInfo;
}
std::vector GetExtensionsImpl()
{
std::vector extensions;
const std::string exts = ogl_ExtensionString();
boost::split(extensions, exts, boost::algorithm::is_space(), boost::token_compress_on);
std::sort(extensions.begin(), extensions.end());
return extensions;
}
} // anonymous namespace
// static
std::unique_ptr CDevice::Create(SDL_Window* window, const bool arb)
{
std::unique_ptr device(new CDevice());
if (window)
{
// According to https://wiki.libsdl.org/SDL_CreateWindow we don't need to
// call SDL_GL_LoadLibrary if we have a window with SDL_WINDOW_OPENGL,
// because it'll be called internally for the first created window.
device->m_Window = window;
device->m_Context = SDL_GL_CreateContext(device->m_Window);
if (!device->m_Context)
{
LOGERROR("SDL_GL_CreateContext failed: '%s'", SDL_GetError());
return nullptr;
}
#if OS_WIN
ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC());
#elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
ogl_Init(SDL_GL_GetProcAddress, GetX11Display(device->m_Window));
#else
ogl_Init(SDL_GL_GetProcAddress);
#endif
}
else
{
#if OS_WIN
ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC());
#elif defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
ogl_Init(SDL_GL_GetProcAddress, XOpenDisplay(NULL));
#else
ogl_Init(SDL_GL_GetProcAddress);
#endif
#if OS_WIN || defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
// Hack to stop things looking very ugly when scrolling in Atlas.
ogl_SetVsyncEnabled(true);
#endif
}
// If we don't have GL2.0 then we don't have GLSL in core.
if (!arb && !ogl_HaveVersion(2, 0))
return nullptr;
if ((ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr) // ARB
&& !ogl_HaveVersion(2, 0)) // GLSL
|| !ogl_HaveExtension("GL_ARB_vertex_buffer_object") // VBO
|| ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", nullptr)
|| (!ogl_HaveExtension("GL_EXT_framebuffer_object") && !ogl_HaveExtension("GL_ARB_framebuffer_object")))
{
// It doesn't make sense to continue working here, because we're not
// able to display anything.
DEBUG_DISPLAY_FATAL_ERROR(
L"Your graphics card doesn't appear to be fully compatible with OpenGL shaders."
L" The game does not support pre-shader graphics cards."
L" You are advised to try installing newer drivers and/or upgrade your graphics card."
L" For more information, please see http://www.wildfiregames.com/forum/index.php?showtopic=16734"
);
}
device->m_Name = GetNameImpl();
device->m_Version = GetVersionImpl();
device->m_DriverInformation = GetDriverInformationImpl();
device->m_Extensions = GetExtensionsImpl();
// Set packing parameters for uploading and downloading data.
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ device->m_Backbuffer = CFramebuffer::CreateBackbuffer();
+
return device;
}
CDevice::CDevice() = default;
CDevice::~CDevice()
{
if (m_Context)
SDL_GL_DeleteContext(m_Context);
}
void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
{
const char* errstr = "(error)";
#define INTEGER(id) do { \
GLint i = -1; \
glGetIntegerv(GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #id, i); \
} while (false)
#define INTEGER2(id) do { \
GLint i[2] = { -1, -1 }; \
glGetIntegerv(GL_##id, i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \
} else { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", i[0]); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", i[1]); \
} \
} while (false)
#define FLOAT(id) do { \
GLfloat f = std::numeric_limits::quiet_NaN(); \
glGetFloatv(GL_##id, &f); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #id, f); \
} while (false)
#define FLOAT2(id) do { \
GLfloat f[2] = { std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN() }; \
glGetFloatv(GL_##id, f); \
if (ogl_SquelchError(GL_INVALID_ENUM)) { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", errstr); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", errstr); \
} else { \
Script::SetProperty(rq, settings, "GL_" #id "[0]", f[0]); \
Script::SetProperty(rq, settings, "GL_" #id "[1]", f[1]); \
} \
} while (false)
#define STRING(id) do { \
const char* c = (const char*)glGetString(GL_##id); \
if (!c) c = ""; \
if (ogl_SquelchError(GL_INVALID_ENUM)) c = errstr; \
Script::SetProperty(rq, settings, "GL_" #id, std::string(c)); \
} while (false)
#define QUERY(target, pname) do { \
GLint i = -1; \
glGetQueryivARB(GL_##target, GL_##pname, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, errstr); \
else \
Script::SetProperty(rq, settings, "GL_" #target ".GL_" #pname, i); \
} while (false)
#define VERTEXPROGRAM(id) do { \
GLint i = -1; \
glGetProgramivARB(GL_VERTEX_PROGRAM_ARB, GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_VERTEX_PROGRAM_ARB.GL_" #id, i); \
} while (false)
#define FRAGMENTPROGRAM(id) do { \
GLint i = -1; \
glGetProgramivARB(GL_FRAGMENT_PROGRAM_ARB, GL_##id, &i); \
if (ogl_SquelchError(GL_INVALID_ENUM)) \
Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, errstr); \
else \
Script::SetProperty(rq, settings, "GL_FRAGMENT_PROGRAM_ARB.GL_" #id, i); \
} while (false)
#define BOOL(id) INTEGER(id)
ogl_WarnIfError();
// Core OpenGL 1.3:
// (We don't bother checking extension strings for anything older than 1.3;
// it'll just produce harmless warnings)
STRING(VERSION);
STRING(VENDOR);
STRING(RENDERER);
STRING(EXTENSIONS);
#if !CONFIG2_GLES
INTEGER(MAX_CLIP_PLANES);
#endif
INTEGER(SUBPIXEL_BITS);
#if !CONFIG2_GLES
INTEGER(MAX_3D_TEXTURE_SIZE);
#endif
INTEGER(MAX_TEXTURE_SIZE);
INTEGER(MAX_CUBE_MAP_TEXTURE_SIZE);
INTEGER2(MAX_VIEWPORT_DIMS);
#if !CONFIG2_GLES
BOOL(RGBA_MODE);
BOOL(INDEX_MODE);
BOOL(DOUBLEBUFFER);
BOOL(STEREO);
#endif
FLOAT2(ALIASED_POINT_SIZE_RANGE);
FLOAT2(ALIASED_LINE_WIDTH_RANGE);
#if !CONFIG2_GLES
INTEGER(MAX_ELEMENTS_INDICES);
INTEGER(MAX_ELEMENTS_VERTICES);
INTEGER(MAX_TEXTURE_UNITS);
#endif
INTEGER(SAMPLE_BUFFERS);
INTEGER(SAMPLES);
// TODO: compressed texture formats
INTEGER(RED_BITS);
INTEGER(GREEN_BITS);
INTEGER(BLUE_BITS);
INTEGER(ALPHA_BITS);
#if !CONFIG2_GLES
INTEGER(INDEX_BITS);
#endif
INTEGER(DEPTH_BITS);
INTEGER(STENCIL_BITS);
#if !CONFIG2_GLES
// Core OpenGL 2.0 (treated as extensions):
if (ogl_HaveExtension("GL_EXT_texture_lod_bias"))
{
FLOAT(MAX_TEXTURE_LOD_BIAS_EXT);
}
if (ogl_HaveExtension("GL_ARB_occlusion_query"))
{
QUERY(SAMPLES_PASSED, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_ARB_shading_language_100"))
{
STRING(SHADING_LANGUAGE_VERSION_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_shader"))
{
INTEGER(MAX_VERTEX_ATTRIBS_ARB);
INTEGER(MAX_VERTEX_UNIFORM_COMPONENTS_ARB);
INTEGER(MAX_VARYING_FLOATS_ARB);
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB);
}
if (ogl_HaveExtension("GL_ARB_fragment_shader"))
{
INTEGER(MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_shader") || ogl_HaveExtension("GL_ARB_fragment_shader") ||
ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
{
INTEGER(MAX_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_TEXTURE_COORDS_ARB);
}
if (ogl_HaveExtension("GL_ARB_draw_buffers"))
{
INTEGER(MAX_DRAW_BUFFERS_ARB);
}
// Core OpenGL 3.0:
if (ogl_HaveExtension("GL_EXT_gpu_shader4"))
{
INTEGER(MIN_PROGRAM_TEXEL_OFFSET_EXT); // no _EXT version of these in glext.h
INTEGER(MAX_PROGRAM_TEXEL_OFFSET_EXT);
}
if (ogl_HaveExtension("GL_EXT_framebuffer_object"))
{
INTEGER(MAX_COLOR_ATTACHMENTS_EXT);
INTEGER(MAX_RENDERBUFFER_SIZE_EXT);
}
if (ogl_HaveExtension("GL_EXT_framebuffer_multisample"))
{
INTEGER(MAX_SAMPLES_EXT);
}
if (ogl_HaveExtension("GL_EXT_texture_array"))
{
INTEGER(MAX_ARRAY_TEXTURE_LAYERS_EXT);
}
if (ogl_HaveExtension("GL_EXT_transform_feedback"))
{
INTEGER(MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS_EXT);
INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS_EXT);
INTEGER(MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS_EXT);
}
// Other interesting extensions:
if (ogl_HaveExtension("GL_EXT_timer_query") || ogl_HaveExtension("GL_ARB_timer_query"))
{
QUERY(TIME_ELAPSED, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_ARB_timer_query"))
{
QUERY(TIMESTAMP, QUERY_COUNTER_BITS);
}
if (ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"))
{
FLOAT(MAX_TEXTURE_MAX_ANISOTROPY_EXT);
}
if (ogl_HaveExtension("GL_ARB_texture_rectangle"))
{
INTEGER(MAX_RECTANGLE_TEXTURE_SIZE_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_program") || ogl_HaveExtension("GL_ARB_fragment_program"))
{
INTEGER(MAX_PROGRAM_MATRICES_ARB);
INTEGER(MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB);
}
if (ogl_HaveExtension("GL_ARB_vertex_program"))
{
VERTEXPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
VERTEXPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
if (ogl_HaveExtension("GL_ARB_fragment_program"))
{
// The spec seems to say these should be supported, but
// Mesa complains about them so let's not bother
/*
VERTEXPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
VERTEXPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
*/
}
}
if (ogl_HaveExtension("GL_ARB_fragment_program"))
{
FRAGMENTPROGRAM(MAX_PROGRAM_ENV_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_LOCAL_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_ALU_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEX_INDIRECTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_TEMPORARIES_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_ATTRIBS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_TEMPORARIES_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_PARAMETERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ATTRIBS_ARB);
if (ogl_HaveExtension("GL_ARB_vertex_program"))
{
// The spec seems to say these should be supported, but
// Intel drivers on Windows complain about them so let's not bother
/*
FRAGMENTPROGRAM(MAX_PROGRAM_ADDRESS_REGISTERS_ARB);
FRAGMENTPROGRAM(MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB);
*/
}
}
if (ogl_HaveExtension("GL_ARB_geometry_shader4"))
{
INTEGER(MAX_GEOMETRY_TEXTURE_IMAGE_UNITS_ARB);
INTEGER(MAX_GEOMETRY_OUTPUT_VERTICES_ARB);
INTEGER(MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS_ARB);
INTEGER(MAX_GEOMETRY_UNIFORM_COMPONENTS_ARB);
INTEGER(MAX_GEOMETRY_VARYING_COMPONENTS_ARB);
INTEGER(MAX_VERTEX_VARYING_COMPONENTS_ARB);
}
#else // CONFIG2_GLES
// Core OpenGL ES 2.0:
STRING(SHADING_LANGUAGE_VERSION);
INTEGER(MAX_VERTEX_ATTRIBS);
INTEGER(MAX_VERTEX_UNIFORM_VECTORS);
INTEGER(MAX_VARYING_VECTORS);
INTEGER(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_VERTEX_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_FRAGMENT_UNIFORM_VECTORS);
INTEGER(MAX_TEXTURE_IMAGE_UNITS);
INTEGER(MAX_RENDERBUFFER_SIZE);
#endif // CONFIG2_GLES
// TODO: Support OpenGL platforms which don't use GLX as well.
#if defined(SDL_VIDEO_DRIVER_X11) && !CONFIG2_GLES
#define GLXQCR_INTEGER(id) do { \
unsigned int i = UINT_MAX; \
if (glXQueryCurrentRendererIntegerMESA(id, &i)) \
Script::SetProperty(rq, settings, #id, i); \
} while (false)
#define GLXQCR_INTEGER2(id) do { \
unsigned int i[2] = { UINT_MAX, UINT_MAX }; \
if (glXQueryCurrentRendererIntegerMESA(id, i)) { \
Script::SetProperty(rq, settings, #id "[0]", i[0]); \
Script::SetProperty(rq, settings, #id "[1]", i[1]); \
} \
} while (false)
#define GLXQCR_INTEGER3(id) do { \
unsigned int i[3] = { UINT_MAX, UINT_MAX, UINT_MAX }; \
if (glXQueryCurrentRendererIntegerMESA(id, i)) { \
Script::SetProperty(rq, settings, #id "[0]", i[0]); \
Script::SetProperty(rq, settings, #id "[1]", i[1]); \
Script::SetProperty(rq, settings, #id "[2]", i[2]); \
} \
} while (false)
#define GLXQCR_STRING(id) do { \
const char* str = glXQueryCurrentRendererStringMESA(id); \
if (str) \
Script::SetProperty(rq, settings, #id ".string", str); \
} while (false)
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
const int ret = SDL_GetWindowWMInfo(m_Window, &wminfo);
if (ret && wminfo.subsystem == SDL_SYSWM_X11)
{
Display* dpy = wminfo.info.x11.display;
int scrnum = DefaultScreen(dpy);
const char* glxexts = glXQueryExtensionsString(dpy, scrnum);
Script::SetProperty(rq, settings, "glx_extensions", glxexts);
if (strstr(glxexts, "GLX_MESA_query_renderer") && glXQueryCurrentRendererIntegerMESA && glXQueryCurrentRendererStringMESA)
{
GLXQCR_INTEGER(GLX_RENDERER_VENDOR_ID_MESA);
GLXQCR_INTEGER(GLX_RENDERER_DEVICE_ID_MESA);
GLXQCR_INTEGER3(GLX_RENDERER_VERSION_MESA);
GLXQCR_INTEGER(GLX_RENDERER_ACCELERATED_MESA);
GLXQCR_INTEGER(GLX_RENDERER_VIDEO_MEMORY_MESA);
GLXQCR_INTEGER(GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA);
GLXQCR_INTEGER(GLX_RENDERER_PREFERRED_PROFILE_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA);
GLXQCR_INTEGER2(GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA);
GLXQCR_STRING(GLX_RENDERER_VENDOR_ID_MESA);
GLXQCR_STRING(GLX_RENDERER_DEVICE_ID_MESA);
}
}
#endif // SDL_VIDEO_DRIVER_X11
}
+std::unique_ptr CDevice::CreateCommandContext()
+{
+ return CDeviceCommandContext::Create(this);
+}
+
void CDevice::Present()
{
if (m_Window)
{
PROFILE3("swap buffers");
SDL_GL_SwapWindow(m_Window);
ogl_WarnIfError();
}
bool checkGLErrorAfterSwap = false;
CFG_GET_VAL("gl.checkerrorafterswap", checkGLErrorAfterSwap);
#if defined(NDEBUG)
if (!checkGLErrorAfterSwap)
return;
#endif
PROFILE3("error check");
// We have to check GL errors after SwapBuffer to avoid possible
// synchronizations during rendering.
if (GLenum err = glGetError())
ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err));
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Device.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/Device.h (revision 26301)
+++ ps/trunk/source/renderer/backend/gl/Device.h (revision 26302)
@@ -1,76 +1,85 @@
/* 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_RENDERER_BACKEND_GL_DEVICE
#define INCLUDED_RENDERER_BACKEND_GL_DEVICE
+#include "renderer/backend/gl/Framebuffer.h"
#include "scriptinterface/ScriptForward.h"
#include
#include
#include
typedef struct SDL_Window SDL_Window;
typedef void* SDL_GLContext;
namespace Renderer
{
namespace Backend
{
namespace GL
{
+class CDeviceCommandContext;
+
class CDevice
{
public:
~CDevice();
/**
* Creates the GL device and the GL context for the window if it presents.
*/
static std::unique_ptr Create(SDL_Window* window, const bool arb);
const std::string& GetName() const { return m_Name; }
const std::string& GetVersion() const { return m_Version; }
const std::string& GetDriverInformation() const { return m_DriverInformation; }
const std::vector& GetExtensions() const { return m_Extensions; }
void Report(const ScriptRequest& rq, JS::HandleValue settings);
+ CFramebuffer* GetCurrentBackbuffer() { return m_Backbuffer.get(); }
+
+ std::unique_ptr CreateCommandContext();
+
void Present();
private:
CDevice();
SDL_Window* m_Window = nullptr;
SDL_GLContext m_Context = nullptr;
std::string m_Name;
std::string m_Version;
std::string m_DriverInformation;
std::vector m_Extensions;
+
+ std::unique_ptr m_Backbuffer;
};
} // namespace GL
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_GL_DEVICE
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26301)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26302)
@@ -1,383 +1,479 @@
/* 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 "DeviceCommandContext.h"
+#include "renderer/backend/gl/Device.h"
+#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Mapping.h"
#include "renderer/backend/gl/Texture.h"
+#include
+
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
bool operator==(const StencilOpState& lhs, const StencilOpState& rhs)
{
return
lhs.failOp == rhs.failOp &&
lhs.passOp == rhs.passOp &&
lhs.depthFailOp == rhs.depthFailOp &&
lhs.compareOp == rhs.compareOp;
}
bool operator!=(const StencilOpState& lhs, const StencilOpState& rhs)
{
return !operator==(lhs, rhs);
}
bool operator==(
const CDeviceCommandContext::ScissorRect& lhs,
const CDeviceCommandContext::ScissorRect& rhs)
{
return
lhs.x == rhs.x && lhs.y == rhs.y &&
lhs.width == rhs.width && lhs.height == rhs.height;
}
bool operator!=(
const CDeviceCommandContext::ScissorRect& lhs,
const CDeviceCommandContext::ScissorRect& rhs)
{
return !operator==(lhs, rhs);
}
+void ApplyDepthMask(const bool depthWriteEnabled)
+{
+ glDepthMask(depthWriteEnabled ? GL_TRUE : GL_FALSE);
+}
+
+void ApplyColorMask(const uint8_t colorWriteMask)
+{
+ glColorMask(
+ (colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE,
+ (colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE,
+ (colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE,
+ (colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE);
+}
+
+void ApplyStencilMask(const uint32_t stencilWriteMask)
+{
+ glStencilMask(stencilWriteMask);
+}
+
} // anonymous namespace
// static
-std::unique_ptr CDeviceCommandContext::Create()
+std::unique_ptr CDeviceCommandContext::Create(CDevice* device)
{
- std::unique_ptr deviceCommandContext(new CDeviceCommandContext());
+ std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device));
+ deviceCommandContext->m_Framebuffer = device->GetCurrentBackbuffer();
deviceCommandContext->ResetStates();
return deviceCommandContext;
}
-CDeviceCommandContext::CDeviceCommandContext() = default;
+CDeviceCommandContext::CDeviceCommandContext(CDevice* device)
+ : m_Device(device)
+{
+}
CDeviceCommandContext::~CDeviceCommandContext() = default;
void CDeviceCommandContext::SetGraphicsPipelineState(
const GraphicsPipelineStateDesc& pipelineStateDesc)
{
SetGraphicsPipelineStateImpl(pipelineStateDesc, false);
}
void CDeviceCommandContext::UploadTexture(
CTexture* texture, const Format format,
const void* data, const size_t dataSize,
const uint32_t level, const uint32_t layer)
{
UploadTextureRegion(texture, format, data, dataSize,
0, 0, texture->GetWidth(), texture->GetHeight(), level, layer);
}
void CDeviceCommandContext::UploadTextureRegion(
CTexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t xOffset, const uint32_t yOffset,
const uint32_t width, const uint32_t height,
const uint32_t level, const uint32_t layer)
{
+ ENSURE(texture);
if (texture->GetType() == CTexture::Type::TEXTURE_2D)
{
ENSURE(level == 0 && layer == 0);
if (texture->GetFormat() == Format::R8G8B8A8 || texture->GetFormat() == Format::A8)
{
ENSURE(width > 0 && height > 0);
ENSURE(texture->GetFormat() == dataFormat);
const size_t bpp = dataFormat == Format::R8G8B8A8 ? 4 : 1;
ENSURE(dataSize == width * height * bpp);
ENSURE(xOffset + width <= texture->GetWidth());
ENSURE(yOffset + height <= texture->GetHeight());
glBindTexture(GL_TEXTURE_2D, texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, level,
xOffset, yOffset, width, height,
dataFormat == Format::R8G8B8A8 ? GL_RGBA : GL_ALPHA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE)
{
if (texture->GetFormat() == Format::R8G8B8A8)
{
ENSURE(texture->GetFormat() == dataFormat);
ENSURE(level == 0 && layer < 6);
ENSURE(xOffset == 0 && yOffset == 0 && texture->GetWidth() == width && texture->GetHeight() == height);
const size_t bpp = 4;
ENSURE(dataSize == width * height * bpp);
// The order of layers should be the following:
// front, back, top, bottom, right, left
static const GLenum targets[6] =
{
GL_TEXTURE_CUBE_MAP_POSITIVE_X,
GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
};
glBindTexture(GL_TEXTURE_CUBE_MAP, texture->GetHandle());
glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else
debug_warn("Unsupported type");
}
void CDeviceCommandContext::Flush()
{
ResetStates();
}
void CDeviceCommandContext::ResetStates()
{
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true);
SetScissors(0, nullptr);
+ SetFramebuffer(m_Device->GetCurrentBackbuffer());
}
void CDeviceCommandContext::SetGraphicsPipelineStateImpl(
const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force)
{
const DepthStencilStateDesc& currentDepthStencilStateDesc = m_GraphicsPipelineStateDesc.depthStencilState;
const DepthStencilStateDesc& nextDepthStencilStateDesc = pipelineStateDesc.depthStencilState;
if (force || currentDepthStencilStateDesc.depthTestEnabled != nextDepthStencilStateDesc.depthTestEnabled)
{
if (nextDepthStencilStateDesc.depthTestEnabled)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
}
if (force || currentDepthStencilStateDesc.depthCompareOp != nextDepthStencilStateDesc.depthCompareOp)
{
glDepthFunc(Mapping::FromCompareOp(nextDepthStencilStateDesc.depthCompareOp));
}
if (force || currentDepthStencilStateDesc.depthWriteEnabled != nextDepthStencilStateDesc.depthWriteEnabled)
{
- glDepthMask(nextDepthStencilStateDesc.depthWriteEnabled ? GL_TRUE : GL_FALSE);
+ ApplyDepthMask(nextDepthStencilStateDesc.depthWriteEnabled);
}
if (force || currentDepthStencilStateDesc.stencilTestEnabled != nextDepthStencilStateDesc.stencilTestEnabled)
{
if (nextDepthStencilStateDesc.stencilTestEnabled)
glEnable(GL_STENCIL_TEST);
else
glDisable(GL_STENCIL_TEST);
}
if (force ||
currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace ||
currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
if (nextDepthStencilStateDesc.stencilFrontFace == nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOp(
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
else
{
if (force || currentDepthStencilStateDesc.stencilFrontFace != nextDepthStencilStateDesc.stencilFrontFace)
{
glStencilOpSeparate(
GL_FRONT,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilFrontFace.passOp));
}
if (force || currentDepthStencilStateDesc.stencilBackFace != nextDepthStencilStateDesc.stencilBackFace)
{
glStencilOpSeparate(
GL_BACK,
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.failOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.depthFailOp),
Mapping::FromStencilOp(nextDepthStencilStateDesc.stencilBackFace.passOp));
}
}
}
if (force || currentDepthStencilStateDesc.stencilWriteMask != nextDepthStencilStateDesc.stencilWriteMask)
{
- glStencilMask(nextDepthStencilStateDesc.stencilWriteMask);
+ ApplyStencilMask(nextDepthStencilStateDesc.stencilWriteMask);
}
if (force ||
currentDepthStencilStateDesc.stencilReference != nextDepthStencilStateDesc.stencilReference ||
currentDepthStencilStateDesc.stencilReadMask != nextDepthStencilStateDesc.stencilReadMask ||
currentDepthStencilStateDesc.stencilFrontFace.compareOp != nextDepthStencilStateDesc.stencilFrontFace.compareOp ||
currentDepthStencilStateDesc.stencilBackFace.compareOp != nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
if (nextDepthStencilStateDesc.stencilFrontFace.compareOp == nextDepthStencilStateDesc.stencilBackFace.compareOp)
{
glStencilFunc(
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
else
{
glStencilFuncSeparate(GL_FRONT,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilFrontFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
glStencilFuncSeparate(GL_BACK,
Mapping::FromCompareOp(nextDepthStencilStateDesc.stencilBackFace.compareOp),
nextDepthStencilStateDesc.stencilReference,
nextDepthStencilStateDesc.stencilReadMask);
}
}
const BlendStateDesc& currentBlendStateDesc = m_GraphicsPipelineStateDesc.blendState;
const BlendStateDesc& nextBlendStateDesc = pipelineStateDesc.blendState;
if (force || currentBlendStateDesc.enabled != nextBlendStateDesc.enabled)
{
if (nextBlendStateDesc.enabled)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
}
if (force ||
currentBlendStateDesc.srcColorBlendFactor != nextBlendStateDesc.srcColorBlendFactor ||
currentBlendStateDesc.srcAlphaBlendFactor != nextBlendStateDesc.srcAlphaBlendFactor ||
currentBlendStateDesc.dstColorBlendFactor != nextBlendStateDesc.dstColorBlendFactor ||
currentBlendStateDesc.dstAlphaBlendFactor != nextBlendStateDesc.dstAlphaBlendFactor)
{
if (nextBlendStateDesc.srcColorBlendFactor == nextBlendStateDesc.srcAlphaBlendFactor &&
nextBlendStateDesc.dstColorBlendFactor == nextBlendStateDesc.dstAlphaBlendFactor)
{
glBlendFunc(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor));
}
else
{
glBlendFuncSeparate(
Mapping::FromBlendFactor(nextBlendStateDesc.srcColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstColorBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.srcAlphaBlendFactor),
Mapping::FromBlendFactor(nextBlendStateDesc.dstAlphaBlendFactor));
}
}
if (force ||
currentBlendStateDesc.colorBlendOp != nextBlendStateDesc.colorBlendOp ||
currentBlendStateDesc.alphaBlendOp != nextBlendStateDesc.alphaBlendOp)
{
if (nextBlendStateDesc.colorBlendOp == nextBlendStateDesc.alphaBlendOp)
{
glBlendEquation(Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp));
}
else
{
glBlendEquationSeparate(
Mapping::FromBlendOp(nextBlendStateDesc.colorBlendOp),
Mapping::FromBlendOp(nextBlendStateDesc.alphaBlendOp));
}
}
if (force ||
currentBlendStateDesc.constant != nextBlendStateDesc.constant)
{
glBlendColor(
nextBlendStateDesc.constant.r,
nextBlendStateDesc.constant.g,
nextBlendStateDesc.constant.b,
nextBlendStateDesc.constant.a);
}
if (force ||
currentBlendStateDesc.colorWriteMask != nextBlendStateDesc.colorWriteMask)
{
- glColorMask(
- (nextBlendStateDesc.colorWriteMask & ColorWriteMask::RED) != 0 ? GL_TRUE : GL_FALSE,
- (nextBlendStateDesc.colorWriteMask & ColorWriteMask::GREEN) != 0 ? GL_TRUE : GL_FALSE,
- (nextBlendStateDesc.colorWriteMask & ColorWriteMask::BLUE) != 0 ? GL_TRUE : GL_FALSE,
- (nextBlendStateDesc.colorWriteMask & ColorWriteMask::ALPHA) != 0 ? GL_TRUE : GL_FALSE);
+ ApplyColorMask(nextBlendStateDesc.colorWriteMask);
}
const RasterizationStateDesc& currentRasterizationStateDesc = m_GraphicsPipelineStateDesc.rasterizationState;
const RasterizationStateDesc& nextRasterizationStateDesc = pipelineStateDesc.rasterizationState;
if (force ||
currentRasterizationStateDesc.cullMode != nextRasterizationStateDesc.cullMode)
{
if (nextRasterizationStateDesc.cullMode == CullMode::NONE)
{
glDisable(GL_CULL_FACE);
}
else
{
if (force || currentRasterizationStateDesc.cullMode == CullMode::NONE)
glEnable(GL_CULL_FACE);
glCullFace(nextRasterizationStateDesc.cullMode == CullMode::FRONT ? GL_FRONT : GL_BACK);
}
}
if (force ||
currentRasterizationStateDesc.frontFace != nextRasterizationStateDesc.frontFace)
{
if (nextRasterizationStateDesc.frontFace == FrontFace::CLOCKWISE)
glFrontFace(GL_CW);
else
glFrontFace(GL_CCW);
}
m_GraphicsPipelineStateDesc = pipelineStateDesc;
}
+void CDeviceCommandContext::BlitFramebuffer(
+ CFramebuffer* destinationFramebuffer, CFramebuffer* sourceFramebuffer)
+{
+#if CONFIG2_GLES
+ UNUSED2(destinationFramebuffer);
+ UNUSED2(sourceFramebuffer);
+ debug_warn("CDeviceCommandContext::BlitFramebuffer is not implemented for GLES");
+#else
+ // Source framebuffer should not be backbuffer.
+ ENSURE( sourceFramebuffer->GetHandle() != 0);
+ ENSURE( destinationFramebuffer != sourceFramebuffer );
+ glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, sourceFramebuffer->GetHandle());
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, destinationFramebuffer->GetHandle());
+ // TODO: add more check for internal formats. And currently we don't support
+ // scaling inside blit.
+ glBlitFramebufferEXT(
+ 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
+ 0, 0, sourceFramebuffer->GetWidth(), sourceFramebuffer->GetHeight(),
+ (sourceFramebuffer->GetAttachmentMask() & destinationFramebuffer->GetAttachmentMask()),
+ GL_NEAREST);
+#endif
+}
+
+void CDeviceCommandContext::ClearFramebuffer()
+{
+ ClearFramebuffer(true, true, true);
+}
+
+void CDeviceCommandContext::ClearFramebuffer(const bool color, const bool depth, const bool stencil)
+{
+ const bool needsColor = color && (m_Framebuffer->GetAttachmentMask() & GL_COLOR_BUFFER_BIT) != 0;
+ const bool needsDepth = depth && (m_Framebuffer->GetAttachmentMask() & GL_DEPTH_BUFFER_BIT) != 0;
+ const bool needsStencil = stencil && (m_Framebuffer->GetAttachmentMask() & GL_STENCIL_BUFFER_BIT) != 0;
+ GLbitfield mask = 0;
+ if (needsColor)
+ {
+ ApplyColorMask(ColorWriteMask::RED | ColorWriteMask::GREEN | ColorWriteMask::BLUE | ColorWriteMask::ALPHA);
+ glClearColor(
+ m_Framebuffer->GetClearColor().r,
+ m_Framebuffer->GetClearColor().g,
+ m_Framebuffer->GetClearColor().b,
+ m_Framebuffer->GetClearColor().a);
+ mask |= GL_COLOR_BUFFER_BIT;
+ }
+ if (needsDepth)
+ {
+ ApplyDepthMask(true);
+ mask |= GL_DEPTH_BUFFER_BIT;
+ }
+ if (needsStencil)
+ {
+ ApplyStencilMask(std::numeric_limits::max());
+ mask |= GL_STENCIL_BUFFER_BIT;
+ }
+ glClear(mask);
+ if (needsColor)
+ ApplyColorMask(m_GraphicsPipelineStateDesc.blendState.colorWriteMask);
+ if (needsDepth)
+ ApplyDepthMask(m_GraphicsPipelineStateDesc.depthStencilState.depthWriteEnabled);
+ if (needsStencil)
+ ApplyStencilMask(m_GraphicsPipelineStateDesc.depthStencilState.stencilWriteMask);
+}
+
+void CDeviceCommandContext::SetFramebuffer(CFramebuffer* framebuffer)
+{
+ ENSURE(framebuffer);
+ ENSURE(framebuffer->GetHandle() == 0 || (framebuffer->GetWidth() > 0 && framebuffer->GetHeight() > 0));
+ m_Framebuffer = framebuffer;
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->GetHandle());
+}
+
void CDeviceCommandContext::SetScissors(const uint32_t scissorCount, const ScissorRect* scissors)
{
ENSURE(scissorCount <= 1);
if (scissorCount == 0)
{
if (m_ScissorCount != scissorCount)
glDisable(GL_SCISSOR_TEST);
}
else
{
if (m_ScissorCount != scissorCount)
glEnable(GL_SCISSOR_TEST);
+ ENSURE(scissors);
if (m_ScissorCount != scissorCount || m_Scissors[0] != scissors[0])
{
- ENSURE(scissors);
m_Scissors[0] = scissors[0];
glScissor(m_Scissors[0].x, m_Scissors[0].y, m_Scissors[0].width, m_Scissors[0].height);
}
}
m_ScissorCount = scissorCount;
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26301)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.h (revision 26302)
@@ -1,87 +1,102 @@
/* 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_RENDERER_GL_DEVICECOMMANDCONTEXT
#define INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
#include "renderer/backend/Format.h"
#include "renderer/backend/PipelineState.h"
#include
#include
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
+class CDevice;
+class CFramebuffer;
class CTexture;
class CDeviceCommandContext
{
public:
~CDeviceCommandContext();
- static std::unique_ptr Create();
+ CDevice* GetDevice() { return m_Device; }
void SetGraphicsPipelineState(const GraphicsPipelineStateDesc& pipelineStateDesc);
+ void BlitFramebuffer(CFramebuffer* destinationFramebuffer, CFramebuffer* sourceFramebuffer);
+
+ void ClearFramebuffer();
+ void ClearFramebuffer(const bool color, const bool depth, const bool stencil);
+ void SetFramebuffer(CFramebuffer* framebuffer);
+
void UploadTexture(CTexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t level = 0, const uint32_t layer = 0);
void UploadTextureRegion(CTexture* texture, const Format dataFormat,
const void* data, const size_t dataSize,
const uint32_t xOffset, const uint32_t yOffset,
const uint32_t width, const uint32_t height,
const uint32_t level = 0, const uint32_t layer = 0);
// TODO: maybe we should add a more common type, like CRectI.
struct ScissorRect
{
int32_t x, y;
int32_t width, height;
};
void SetScissors(const uint32_t scissorCount, const ScissorRect* scissors);
void Flush();
private:
- CDeviceCommandContext();
+ friend class CDevice;
+
+ static std::unique_ptr Create(CDevice* device);
+
+ CDeviceCommandContext(CDevice* device);
void ResetStates();
void SetGraphicsPipelineStateImpl(
const GraphicsPipelineStateDesc& pipelineStateDesc, const bool force);
+ CDevice* m_Device = nullptr;
+
GraphicsPipelineStateDesc m_GraphicsPipelineStateDesc{};
+ CFramebuffer* m_Framebuffer = nullptr;
uint32_t m_ScissorCount = 0;
// GL2.1 doesn't support more than 1 scissor.
std::array m_Scissors;
};
} // namespace GL
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_GL_DEVICECOMMANDCONTEXT
Index: ps/trunk/source/renderer/backend/gl/Framebuffer.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Framebuffer.cpp (nonexistent)
+++ ps/trunk/source/renderer/backend/gl/Framebuffer.cpp (revision 26302)
@@ -0,0 +1,169 @@
+/* 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 "Framebuffer.h"
+
+#include "lib/code_annotation.h"
+#include "lib/config2.h"
+#include "lib/res/graphics/ogl_tex.h"
+#include "ps/CLogger.h"
+#include "ps/ConfigDB.h"
+#include "renderer/backend/gl/Device.h"
+#include "renderer/backend/gl/Texture.h"
+
+namespace Renderer
+{
+
+namespace Backend
+{
+
+namespace GL
+{
+
+// static
+std::unique_ptr CFramebuffer::Create(
+ CTexture* colorAttachment, CTexture* depthStencilAttachment)
+{
+ return Create(colorAttachment, depthStencilAttachment, CColor(0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+// static
+std::unique_ptr CFramebuffer::Create(
+ CTexture* colorAttachment, CTexture* depthStencilAttachment,
+ const CColor& clearColor)
+{
+ ENSURE(colorAttachment || depthStencilAttachment);
+
+ std::unique_ptr framebuffer(new CFramebuffer());
+ framebuffer->m_ClearColor = clearColor;
+
+ glGenFramebuffersEXT(1, &framebuffer->m_Handle);
+ if (!framebuffer->m_Handle)
+ {
+ LOGERROR("Failed to create CFramebuffer object");
+ return nullptr;
+ }
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->m_Handle);
+
+ if (colorAttachment)
+ {
+ framebuffer->m_AttachmentMask |= GL_COLOR_BUFFER_BIT;
+
+#if CONFIG2_GLES
+ ENSURE(colorAttachment->GetType() == CTexture::Type::TEXTURE_2D);
+ const GLenum textureTarget = GL_TEXTURE_2D;
+#else
+ const GLenum textureTarget = colorAttachment->GetType() == CTexture::Type::TEXTURE_2D_MULTISAMPLE ?
+ GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
+#endif
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ textureTarget, colorAttachment->GetHandle(), 0);
+ }
+ if (depthStencilAttachment)
+ {
+ framebuffer->m_Width = depthStencilAttachment->GetWidth();
+ framebuffer->m_Height = depthStencilAttachment->GetHeight();
+ framebuffer->m_AttachmentMask |= GL_DEPTH_BUFFER_BIT;
+ if (depthStencilAttachment->GetFormat() == Format::D24_S8)
+ framebuffer->m_AttachmentMask |= GL_STENCIL_BUFFER_BIT;
+ if (colorAttachment)
+ {
+ ENSURE(colorAttachment->GetWidth() == depthStencilAttachment->GetWidth());
+ ENSURE(colorAttachment->GetHeight() == depthStencilAttachment->GetHeight());
+ ENSURE(colorAttachment->GetType() == depthStencilAttachment->GetType());
+ }
+ ENSURE(
+ depthStencilAttachment->GetFormat() == Format::D16 ||
+ depthStencilAttachment->GetFormat() == Format::D24 ||
+ depthStencilAttachment->GetFormat() == Format::D32 ||
+ depthStencilAttachment->GetFormat() == Format::D24_S8);
+#if CONFIG2_GLES
+ ENSURE(depthStencilAttachment->GetFormat() != Format::D24_S8);
+ const GLenum attachment = GL_DEPTH_ATTACHMENT;
+ ENSURE(depthStencilAttachment->GetType() == CTexture::Type::TEXTURE_2D);
+ const GLenum textureTarget = GL_TEXTURE_2D;
+#else
+ const GLenum attachment = depthStencilAttachment->GetFormat() == Format::D24_S8 ?
+ GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
+ const GLenum textureTarget = depthStencilAttachment->GetType() == CTexture::Type::TEXTURE_2D_MULTISAMPLE ?
+ GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
+#endif
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment,
+ textureTarget, depthStencilAttachment->GetHandle(), 0);
+ }
+ else
+ {
+ framebuffer->m_Width = colorAttachment->GetWidth();
+ framebuffer->m_Height = colorAttachment->GetHeight();
+ }
+
+ ogl_WarnIfError();
+
+#if !CONFIG2_GLES
+ if (!colorAttachment)
+ {
+ glReadBuffer(GL_NONE);
+ glDrawBuffer(GL_NONE);
+ }
+ else
+ glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
+#endif
+
+ ogl_WarnIfError();
+
+ const GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+ if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
+ {
+ LOGERROR("CFramebuffer object incomplete: 0x%04X", status);
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ return nullptr;
+ }
+
+ ogl_WarnIfError();
+
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
+ return framebuffer;
+}
+
+// static
+std::unique_ptr CFramebuffer::CreateBackbuffer()
+{
+ // Backbuffer for GL is a special case with a zero framebuffer.
+ std::unique_ptr framebuffer(new CFramebuffer());
+ framebuffer->m_AttachmentMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ CStr skyString = "0 0 0";
+ CFG_GET_VAL("skycolor", skyString);
+ framebuffer->m_ClearColor.ParseString(skyString, 0.0f);
+ return framebuffer;
+}
+
+CFramebuffer::CFramebuffer() = default;
+
+CFramebuffer::~CFramebuffer()
+{
+ if (m_Handle)
+ glDeleteFramebuffersEXT(1, &m_Handle);
+}
+
+} // namespace GL
+
+} // namespace Backend
+
+} // namespace Renderer
Property changes on: ps/trunk/source/renderer/backend/gl/Framebuffer.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/renderer/backend/gl/Framebuffer.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/Framebuffer.h (nonexistent)
+++ ps/trunk/source/renderer/backend/gl/Framebuffer.h (revision 26302)
@@ -0,0 +1,75 @@
+/* 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_RENDERER_BACKEND_GL_FRAMEBUFFER
+#define INCLUDED_RENDERER_BACKEND_GL_FRAMEBUFFER
+
+#include "graphics/Color.h"
+#include "lib/ogl.h"
+#include "renderer/backend/gl/Texture.h"
+
+#include
+#include
+
+namespace Renderer
+{
+
+namespace Backend
+{
+
+namespace GL
+{
+
+class CDevice;
+
+class CFramebuffer
+{
+public:
+ static std::unique_ptr Create(
+ CTexture* colorAttachment, CTexture* depthStencilAttachment);
+ static std::unique_ptr Create(
+ CTexture* colorAttachment, CTexture* depthStencilAttachment, const CColor& clearColor);
+
+ ~CFramebuffer();
+
+ GLuint GetHandle() const { return m_Handle; }
+ GLbitfield GetAttachmentMask() const { return m_AttachmentMask; }
+ const CColor& GetClearColor() const { return m_ClearColor; }
+
+ uint32_t GetWidth() const { return m_Width; }
+ uint32_t GetHeight() const { return m_Height; }
+
+private:
+ friend class CDevice;
+
+ static std::unique_ptr CreateBackbuffer();
+
+ CFramebuffer();
+
+ GLuint m_Handle = 0;
+ uint32_t m_Width = 0, m_Height = 0;
+ GLbitfield m_AttachmentMask = 0;
+ CColor m_ClearColor;
+};
+
+} // namespace GL
+
+} // namespace Backend
+
+} // namespace Renderer
+
+#endif // INCLUDED_RENDERER_BACKEND_GL_FRAMEBUFFER
Property changes on: ps/trunk/source/renderer/backend/gl/Framebuffer.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property