Index: ps/trunk/source/graphics/FontManager.cpp
===================================================================
--- ps/trunk/source/graphics/FontManager.cpp (revision 26587)
+++ ps/trunk/source/graphics/FontManager.cpp (revision 26588)
@@ -1,148 +1,148 @@
/* 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 "FontManager.h"
#include "graphics/Font.h"
#include "graphics/TextureManager.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "renderer/Renderer.h"
#include
std::shared_ptr CFontManager::LoadFont(CStrIntern fontName)
{
FontsMap::iterator it = m_Fonts.find(fontName);
if (it != m_Fonts.end())
return it->second;
std::shared_ptr font(new CFont());
if (!ReadFont(font.get(), fontName))
{
// Fall back to default font (unless this is the default font)
if (fontName == str_sans_10)
font.reset();
else
font = LoadFont(str_sans_10);
}
m_Fonts[fontName] = font;
return font;
}
bool CFontManager::ReadFont(CFont* font, CStrIntern fontName)
{
const VfsPath path(L"fonts/");
// Read font definition file into a stringstream
std::shared_ptr buffer;
size_t size;
const VfsPath fntName(fontName.string() + ".fnt");
if (g_VFS->LoadFile(path / fntName, buffer, size) < 0)
{
LOGERROR("Failed to open font file %s", (path / fntName).string8());
return false;
}
std::istringstream fontStream(
std::string(reinterpret_cast(buffer.get()), size));
int version;
fontStream >> version;
// Make sure this is from a recent version of the font builder.
if (version != 101)
{
LOGERROR("Font %s has invalid version", fontName.c_str());
return false;
}
int textureWidth, textureHeight;
fontStream >> textureWidth >> textureHeight;
std::string format;
fontStream >> format;
if (format == "rgba")
font->m_HasRGB = true;
else if (format == "a")
font->m_HasRGB = false;
else
{
LOGWARNING("Invalid .fnt format string");
return false;
}
int mumberOfGlyphs;
fontStream >> mumberOfGlyphs;
fontStream >> font->m_LineSpacing;
fontStream >> font->m_Height;
font->m_BoundsX0 = std::numeric_limits::max();
font->m_BoundsY0 = std::numeric_limits::max();
font->m_BoundsX1 = -std::numeric_limits::max();
font->m_BoundsY1 = -std::numeric_limits::max();
for (int i = 0; i < mumberOfGlyphs; ++i)
{
int codepoint, textureX, textureY, width, height, offsetX, offsetY, advance;
fontStream >> codepoint
>> textureX >> textureY >> width >> height
>> offsetX >> offsetY >> advance;
if (codepoint < 0 || codepoint > 0xFFFF)
{
LOGWARNING("Font %s has invalid codepoint 0x%x", fontName.c_str(), codepoint);
continue;
}
const float u = static_cast(textureX) / textureWidth;
const float v = static_cast(textureY) / textureHeight;
const float w = static_cast(width) / textureWidth;
const float h = static_cast(height) / textureHeight;
CFont::GlyphData g =
{
u, -v, u + w, -v + h,
static_cast(offsetX), static_cast(-offsetY),
static_cast(offsetX + width), static_cast(-offsetY + height),
static_cast(advance)
};
font->m_Glyphs.set(static_cast(codepoint), g);
font->m_BoundsX0 = std::min(font->m_BoundsX0, static_cast(g.x0));
font->m_BoundsY0 = std::min(font->m_BoundsY0, static_cast(g.y0));
font->m_BoundsX1 = std::max(font->m_BoundsX1, static_cast(g.x1));
font->m_BoundsY1 = std::max(font->m_BoundsY1, static_cast(g.y1));
}
// Ensure the height has been found (which should always happen if the font includes an 'I').
ENSURE(font->m_Height);
// Load glyph texture
const VfsPath imageName(fontName.string() + ".png");
CTextureProperties textureProps(path / imageName,
- font->m_HasRGB ? Renderer::Backend::Format::R8G8B8A8 : Renderer::Backend::Format::A8);
+ font->m_HasRGB ? Renderer::Backend::Format::R8G8B8A8_UNORM : Renderer::Backend::Format::A8_UNORM);
textureProps.SetIgnoreQuality(true);
font->m_Texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
return true;
}
Index: ps/trunk/source/graphics/LOSTexture.cpp
===================================================================
--- ps/trunk/source/graphics/LOSTexture.cpp (revision 26587)
+++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26588)
@@ -1,428 +1,428 @@
/* 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()
{
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;
}
return true;
}
void CLOSTexture::DeleteTexture()
{
m_Texture.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_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;
GPU_SCOPED_LABEL(deviceCommandContext, "Render LOS texture");
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_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();
deviceCommandContext->Draw(0, 6);
g_Renderer.SetViewport(oldVp);
m_SmoothTech->EndPass();
deviceCommandContext->SetFramebuffer(
deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
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));
Renderer::Backend::GL::CDevice* backendDevice = deviceCommandContext->GetDevice();
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
m_Texture = backendDevice->CreateTexture2D("LOSTexture",
- Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
+ Renderer::Backend::Format::A8_UNORM, 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_SmoothTextures[0] = backendDevice->CreateTexture2D("LOSSmoothTexture0",
- Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
+ Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc);
m_SmoothTextures[1] = backendDevice->CreateTexture2D("LOSSmoothTexture1",
- Renderer::Backend::Format::A8, textureSize, textureSize, defaultSamplerDesc);
+ Renderer::Backend::Format::A8_UNORM, textureSize, textureSize, defaultSamplerDesc);
m_SmoothFramebuffers[0] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer0",
m_SmoothTextures[0].get(), nullptr);
m_SmoothFramebuffers[1] = backendDevice->CreateFramebuffer("LOSSmoothFramebuffer1",
m_SmoothTextures[1].get(), nullptr);
if (!m_SmoothFramebuffers[0] || !m_SmoothFramebuffers[1])
{
LOGERROR("Failed to create LOS framebuffers");
g_RenderingOptions.SetSmoothLOS(false);
}
deviceCommandContext->UploadTexture(
- m_SmoothTextures[0].get(), Renderer::Backend::Format::A8,
+ m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM,
texData.get(), textureSize * textureSize);
deviceCommandContext->UploadTexture(
- m_SmoothTextures[1].get(), Renderer::Backend::Format::A8,
+ m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM,
texData.get(), textureSize * textureSize);
}
deviceCommandContext->UploadTexture(
- m_Texture.get(), Renderer::Backend::Format::A8,
+ m_Texture.get(), Renderer::Backend::Format::A8_UNORM,
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_SmoothTextures[0].get(), Renderer::Backend::Format::A8, losData.get(),
+ m_SmoothTextures[0].get(), Renderer::Backend::Format::A8_UNORM, losData.get(),
pitch * m_MapSize, 0, 0, pitch, m_MapSize);
deviceCommandContext->UploadTextureRegion(
- m_SmoothTextures[1].get(), Renderer::Backend::Format::A8, losData.get(),
+ m_SmoothTextures[1].get(), Renderer::Backend::Format::A8_UNORM, losData.get(),
pitch * m_MapSize, 0, 0, pitch, m_MapSize);
}
deviceCommandContext->UploadTextureRegion(
- m_Texture.get(), Renderer::Backend::Format::A8, losData.get(),
+ m_Texture.get(), Renderer::Backend::Format::A8_UNORM, 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/MiniMapTexture.cpp
===================================================================
--- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26587)
+++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26588)
@@ -1,566 +1,566 @@
/* 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 "maths/Vector2D.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 64K / 4 for now, which is more than enough.
// 4 is the number of vertices per entity.
// TODO: we should be cleverer about drawing them to reduce clutter,
// f.e. use instancing.
const size_t MAX_ENTITIES_DRAWN = 65536 / 4;
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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const 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();
deviceCommandContext->Draw(0, 6);
}
struct MinimapUnitVertex
{
// This struct is copyable for convenience and because to move is to copy for primitives.
u8 r, g, b, a;
CVector2D position;
};
// Adds a vertex to the passed VertexArray
inline void AddEntity(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos,
const float entityRadius)
{
const CVector2D offsets[4] =
{
{-entityRadius, 0.0f},
{0.0f, -entityRadius},
{entityRadius, 0.0f},
{0.0f, entityRadius}
};
for (const CVector2D& offset : offsets)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.position.X + offset.X;
(*attrPos)[1] = v.position.Y + offset.Y;
++attrPos;
}
}
} // anonymous namespace
CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
: m_Simulation(simulation), m_IndexArray(false),
m_VertexArray(Renderer::Backend::GL::CBuffer::Type::VERTEX, true)
{
// 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.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 4);
m_VertexArray.Layout();
m_IndexArray.SetNumberOfVertices(MAX_ENTITIES_DRAWN * 6);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (size_t i = 0; i < m_IndexArray.GetNumberOfVertices(); ++i)
*index++ = 0;
m_IndexArray.Upload();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (size_t i = 0; i < m_VertexArray.GetNumberOfVertices(); ++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);
Renderer::Backend::GL::CDevice* backendDevice = deviceCommandContext->GetDevice();
// Create terrain texture
m_TerrainTexture = backendDevice->CreateTexture2D("MiniMapTerrainTexture",
- Renderer::Backend::Format::R8G8B8A8, textureSize, textureSize, defaultSamplerDesc);
+ Renderer::Backend::Format::R8G8B8A8_UNORM, 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,
+ m_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
texData.get(), textureSize * textureSize * 4);
texData.reset();
m_TerrainData = std::make_unique((m_MapSize - 1) * (m_MapSize - 1));
m_FinalTexture = backendDevice->CreateTexture2D("MiniMapFinalTexture",
- Renderer::Backend::Format::R8G8B8A8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc);
+ Renderer::Backend::Format::R8G8B8A8_UNORM, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc);
m_FinalTextureFramebuffer = backendDevice->CreateFramebuffer("MiniMapFinalFramebuffer",
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_TerrainTexture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
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;
GPU_SCOPED_LABEL(deviceCommandContext, "Render minimap texture");
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(deviceCommandContext, 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(deviceCommandContext, 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(deviceCommandContext, 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);
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);
// We might scale entities properly in the vertex shader but it requires
// additional space in the vertex buffer. So we assume that we don't need
// to change an entity size so often.
const float entityRadius = static_cast(m_MapSize) / 128.0f * 6.0f;
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.position.X = posX.ToFloat();
v.position.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
{
AddEntity(v, attrColor, attrPos, entityRadius);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (const MinimapUnitVertex& vertex : pingingVertices)
{
AddEntity(vertex, attrColor, attrPos, entityRadius);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
VertexArrayIterator index = m_IndexArray.GetIterator();
for (size_t entityIndex = 0; entityIndex < m_EntitiesDrawn; ++entityIndex)
{
index[entityIndex * 6 + 0] = static_cast(entityIndex * 4 + 0);
index[entityIndex * 6 + 1] = static_cast(entityIndex * 4 + 1);
index[entityIndex * 6 + 2] = static_cast(entityIndex * 4 + 2);
index[entityIndex * 6 + 3] = static_cast(entityIndex * 4 + 0);
index[entityIndex * 6 + 4] = static_cast(entityIndex * 4 + 2);
index[entityIndex * 6 + 5] = static_cast(entityIndex * 4 + 3);
}
m_VertexArray.Upload();
m_IndexArray.Upload();
}
m_VertexArray.PrepareForRendering();
if (m_EntitiesDrawn > 0)
{
Renderer::Backend::GL::CDeviceCommandContext::Rect scissorRect;
scissorRect.x = scissorRect.y = 1;
scissorRect.width = scissorRect.height = FINAL_TEXTURE_SIZE - 2;
deviceCommandContext->SetScissors(1, &scissorRect);
m_IndexArray.UploadIfNeeded(deviceCommandContext);
u8* base = m_VertexArray.Bind(deviceCommandContext);
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();
deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer());
deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_EntitiesDrawn * 6, 0);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind(deviceCommandContext);
deviceCommandContext->SetScissors(0, nullptr);
}
tech->EndPass();
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/TerrainTextureManager.cpp
===================================================================
--- ps/trunk/source/graphics/TerrainTextureManager.cpp (revision 26587)
+++ ps/trunk/source/graphics/TerrainTextureManager.cpp (revision 26588)
@@ -1,338 +1,338 @@
/* 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 "TerrainTextureManager.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainProperties.h"
#include "graphics/TextureManager.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/bits.h"
#include "lib/ogl.h"
#include "lib/tex/tex.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/VideoMode.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include
#include
#include
CTerrainTextureManager::CTerrainTextureManager()
: m_LastGroupIndex(0)
{
if (!VfsDirectoryExists(L"art/terrains/"))
return;
if (!CXeromyces::AddValidator(g_VFS, "terrain", "art/terrains/terrain.rng"))
LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain.rng'");
if (!CXeromyces::AddValidator(g_VFS, "terrain_texture", "art/terrains/terrain_texture.rng"))
LOGERROR("CTerrainTextureManager: failed to load grammar file 'art/terrains/terrain_texture.rng'");
}
CTerrainTextureManager::~CTerrainTextureManager()
{
UnloadTerrainTextures();
for (std::pair& ta : m_TerrainAlphas)
ta.second.m_CompositeAlphaMap.reset();
}
void CTerrainTextureManager::UnloadTerrainTextures()
{
for (CTerrainTextureEntry* const& te : m_TextureEntries)
delete te;
m_TextureEntries.clear();
for (const std::pair& tg : m_TerrainGroups)
delete tg.second;
m_TerrainGroups.clear();
m_LastGroupIndex = 0;
}
CTerrainTextureEntry* CTerrainTextureManager::FindTexture(const CStr& tag_) const
{
CStr tag = tag_.BeforeLast("."); // Strip extension
for (CTerrainTextureEntry* const& te : m_TextureEntries)
if (te->GetTag() == tag)
return te;
LOGWARNING("CTerrainTextureManager: Couldn't find terrain %s", tag.c_str());
return 0;
}
CTerrainTextureEntry* CTerrainTextureManager::AddTexture(const CTerrainPropertiesPtr& props, const VfsPath& path)
{
CTerrainTextureEntry* entry = new CTerrainTextureEntry(props, path);
m_TextureEntries.push_back(entry);
return entry;
}
void CTerrainTextureManager::DeleteTexture(CTerrainTextureEntry* entry)
{
std::vector::iterator it = std::find(m_TextureEntries.begin(), m_TextureEntries.end(), entry);
if (it != m_TextureEntries.end())
m_TextureEntries.erase(it);
delete entry;
}
struct AddTextureCallbackData
{
CTerrainTextureManager* self;
CTerrainPropertiesPtr props;
};
static Status AddTextureDirCallback(const VfsPath& pathname, const uintptr_t cbData)
{
AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
VfsPath path = pathname / L"terrains.xml";
if (!VfsFileExists(path))
LOGMESSAGE("'%s' does not exist. Using previous properties.", path.string8());
else
data.props = CTerrainProperties::FromXML(data.props, path);
return INFO::OK;
}
static Status AddTextureCallback(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
AddTextureCallbackData& data = *(AddTextureCallbackData*)cbData;
if (pathname.Basename() != L"terrains")
data.self->AddTexture(data.props, pathname);
return INFO::OK;
}
int CTerrainTextureManager::LoadTerrainTextures()
{
AddTextureCallbackData data = {this, CTerrainPropertiesPtr(new CTerrainProperties(CTerrainPropertiesPtr()))};
vfs::ForEachFile(g_VFS, L"art/terrains/", AddTextureCallback, (uintptr_t)&data, L"*.xml", vfs::DIR_RECURSIVE, AddTextureDirCallback, (uintptr_t)&data);
return 0;
}
CTerrainGroup* CTerrainTextureManager::FindGroup(const CStr& name)
{
TerrainGroupMap::const_iterator it = m_TerrainGroups.find(name);
if (it != m_TerrainGroups.end())
return it->second;
else
return m_TerrainGroups[name] = new CTerrainGroup(name, ++m_LastGroupIndex);
}
// LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
// calculate the coordinate of each alphamap within this packed texture.
CTerrainTextureManager::TerrainAlphaMap::iterator
CTerrainTextureManager::LoadAlphaMap(const VfsPath& alphaMapType)
{
const std::wstring key = L"(alpha map composite" + alphaMapType.string() + L")";
CTerrainTextureManager::TerrainAlphaMap::iterator it = m_TerrainAlphas.find(alphaMapType);
if (it != g_TexMan.m_TerrainAlphas.end())
return it;
m_TerrainAlphas[alphaMapType] = TerrainAlpha();
it = m_TerrainAlphas.find(alphaMapType);
TerrainAlpha& result = it->second;
//
// load all textures and store Handle in array
//
Tex textures[NUM_ALPHA_MAPS] = {};
const VfsPath path = VfsPath("art/textures/terrain/alphamaps") / alphaMapType;
const wchar_t* fnames[NUM_ALPHA_MAPS] =
{
L"blendcircle.png",
L"blendlshape.png",
L"blendedge.png",
L"blendedgecorner.png",
L"blendedgetwocorners.png",
L"blendfourcorners.png",
L"blendtwooppositecorners.png",
L"blendlshapecorner.png",
L"blendtwocorners.png",
L"blendcorner.png",
L"blendtwoedges.png",
L"blendthreecorners.png",
L"blendushape.png",
L"blendbad.png"
};
size_t base = 0; // texture width/height (see below)
// For convenience, we require all alpha maps to be of the same BPP.
size_t bpp = 0;
for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
{
// note: these individual textures can be discarded afterwards;
// we cache the composite.
std::shared_ptr fileData;
size_t fileSize;
if (g_VFS->LoadFile(path / fnames[i], fileData, fileSize) != INFO::OK ||
textures[i].decode(fileData, fileSize) != INFO::OK)
{
m_TerrainAlphas.erase(it);
LOGERROR("Failed to load alphamap: %s", alphaMapType.string8());
const VfsPath standard("standard");
if (path != standard)
return LoadAlphaMap(standard);
return m_TerrainAlphas.end();
}
// Get its size and make sure they are all equal.
// (the packing algo assumes this).
if (textures[i].m_Width != textures[i].m_Height)
DEBUG_DISPLAY_ERROR(L"Alpha maps are not square");
// .. first iteration: establish size
if (i == 0)
{
base = textures[i].m_Width;
bpp = textures[i].m_Bpp;
}
// .. not first: make sure texture size matches
else if (base != textures[i].m_Width || bpp != textures[i].m_Bpp)
DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
}
//
// copy each alpha map (tile) into one buffer, arrayed horizontally.
//
const size_t tileWidth = 2 + base + 2; // 2 pixel border (avoids bilinear filtering artifacts)
const size_t totalWidth = round_up_to_pow2(tileWidth * NUM_ALPHA_MAPS);
const size_t totalHeight = base; ENSURE(is_pow2(totalHeight));
std::shared_ptr data;
AllocateAligned(data, totalWidth * totalHeight, maxSectorSize);
// for each tile on row
for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
{
// get src of copy
u8* src = textures[i].get_data();
ENSURE(src);
const size_t srcStep = bpp / 8;
// get destination of copy
u8* dst = data.get() + (i * tileWidth);
// for each row of image
for (size_t j = 0; j < base; ++j)
{
// duplicate first pixel
*dst++ = *src;
*dst++ = *src;
// copy a row
for (size_t k = 0; k < base; ++k)
{
*dst++ = *src;
src += srcStep;
}
// duplicate last pixel
*dst++ = *(src - srcStep);
*dst++ = *(src - srcStep);
// advance write pointer for next row
dst += totalWidth - tileWidth;
}
result.m_AlphaMapCoords[i].u0 = static_cast(i * tileWidth + 2) / totalWidth;
result.m_AlphaMapCoords[i].u1 = static_cast((i + 1) * tileWidth - 2) / totalWidth;
result.m_AlphaMapCoords[i].v0 = 0.0f;
result.m_AlphaMapCoords[i].v1 = 1.0f;
}
for (size_t i = 0; i < NUM_ALPHA_MAPS; ++i)
textures[i].free();
// Enable the following to save a png of the generated texture
// in the public/ directory, for debugging.
#if 0
Tex t;
ignore_result(t.wrap(totalWidth, totalHeight, 8, TEX_GREY, data, 0));
const VfsPath filename("blendtex.png");
DynArray da;
RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da));
// write to disk
//Status ret = INFO::OK;
{
std::shared_ptr file = DummySharedPtr(da.base);
const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
if (bytes_written > 0)
ENSURE(bytes_written == (ssize_t)da.pos);
//else
// ret = (Status)bytes_written;
}
ignore_result(da_free(&da));
#endif
result.m_CompositeAlphaMap = g_VideoMode.GetBackendDevice()->CreateTexture2D("CompositeAlphaMap",
- Renderer::Backend::Format::A8, totalWidth, totalHeight,
+ Renderer::Backend::Format::A8_UNORM, totalWidth, totalHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
result.m_CompositeDataToUpload = std::move(data);
m_AlphaMapsToUpload.emplace_back(it);
return it;
}
void CTerrainTextureManager::UploadResourcesIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
for (const CTerrainTextureManager::TerrainAlphaMap::iterator& it : m_AlphaMapsToUpload)
{
TerrainAlpha& alphaMap = it->second;
if (!alphaMap.m_CompositeDataToUpload)
continue;
// Upload the composite texture.
Renderer::Backend::GL::CTexture* texture = alphaMap.m_CompositeAlphaMap.get();
deviceCommandContext->UploadTexture(
- texture, Renderer::Backend::Format::A8, alphaMap.m_CompositeDataToUpload.get(),
+ texture, Renderer::Backend::Format::A8_UNORM, alphaMap.m_CompositeDataToUpload.get(),
texture->GetWidth() * texture->GetHeight());
alphaMap.m_CompositeDataToUpload.reset();
}
m_AlphaMapsToUpload.clear();
}
void CTerrainGroup::AddTerrain(CTerrainTextureEntry* pTerrain)
{
m_Terrains.push_back(pTerrain);
}
void CTerrainGroup::RemoveTerrain(CTerrainTextureEntry* pTerrain)
{
std::vector::iterator it = find(m_Terrains.begin(), m_Terrains.end(), pTerrain);
if (it != m_Terrains.end())
m_Terrains.erase(it);
}
Index: ps/trunk/source/graphics/TerritoryTexture.cpp
===================================================================
--- ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26587)
+++ ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26588)
@@ -1,253 +1,253 @@
/* 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 "TerritoryTexture.h"
#include "graphics/Color.h"
#include "graphics/Terrain.h"
#include "lib/bits.h"
#include "ps/Profile.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/Renderer.h"
#include "simulation2/Simulation2.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/helpers/Pathfinding.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpTerritoryManager.h"
// TODO: There's a lot of duplication with CLOSTexture - might be nice to refactor a bit
CTerritoryTexture::CTerritoryTexture(CSimulation2& simulation) :
m_Simulation(simulation), m_DirtyID(0), m_MapSize(0)
{
}
CTerritoryTexture::~CTerritoryTexture()
{
DeleteTexture();
}
void CTerritoryTexture::DeleteTexture()
{
m_Texture.reset();
}
bool CTerritoryTexture::UpdateDirty()
{
CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY);
return cmpTerritoryManager && cmpTerritoryManager->NeedUpdateTexture(&m_DirtyID);
}
Renderer::Backend::GL::CTexture* CTerritoryTexture::GetTexture()
{
ENSURE(!UpdateDirty());
return m_Texture.get();
}
const float* CTerritoryTexture::GetTextureMatrix()
{
ENSURE(!UpdateDirty());
return &m_TextureMatrix._11;
}
const CMatrix3D& CTerritoryTexture::GetMinimapTextureMatrix()
{
ENSURE(!UpdateDirty());
return m_MinimapTextureMatrix;
}
void CTerritoryTexture::ConstructTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (!cmpTerrain)
return;
// Convert size from terrain tiles to territory tiles
m_MapSize = cmpTerrain->GetMapSize() * Pathfinding::NAVCELL_SIZE_INT / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE;
const uint32_t textureSize = round_up_to_pow2(static_cast(m_MapSize));
m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerritoryTexture",
- Renderer::Backend::Format::R8G8B8A8, textureSize, textureSize,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
// Initialise texture with transparency, for the areas we don't
// overwrite with uploading later.
std::unique_ptr texData = std::make_unique(textureSize * textureSize * 4);
memset(texData.get(), 0x00, textureSize * textureSize * 4);
deviceCommandContext->UploadTexture(
- m_Texture.get(), Renderer::Backend::Format::R8G8B8A8,
+ m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
texData.get(), textureSize * textureSize * 4);
texData.reset();
{
// Texture matrix: We want to map
// world pos (0, y, 0) (i.e. bottom-left of first tile)
// onto texcoord (0, 0) (i.e. bottom-left of first texel);
// world pos (mapsize*cellsize, y, mapsize*cellsize) (i.e. top-right of last tile)
// onto texcoord (mapsize / texsize, mapsize / texsize) (i.e. top-right of last texel)
float s = 1.f / static_cast(textureSize * TERRAIN_TILE_SIZE);
float t = 0.f;
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 / static_cast(textureSize);
m_MinimapTextureMatrix.SetZero();
m_MinimapTextureMatrix._11 = s;
m_MinimapTextureMatrix._22 = s;
m_MinimapTextureMatrix._44 = 1;
}
}
void CTerritoryTexture::RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
// If the map was resized, delete and regenerate the texture
if (m_Texture)
{
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide())
DeleteTexture();
}
if (!m_Texture)
ConstructTexture(deviceCommandContext);
PROFILE("recompute territory texture");
CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpTerritoryManager)
return;
std::unique_ptr bitmap = std::make_unique(m_MapSize * m_MapSize * 4);
GenerateBitmap(cmpTerritoryManager->GetTerritoryGrid(), bitmap.get(), m_MapSize, m_MapSize);
deviceCommandContext->UploadTextureRegion(
- m_Texture.get(), Renderer::Backend::Format::R8G8B8A8, bitmap.get(), m_MapSize * m_MapSize * 4,
+ m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, bitmap.get(), m_MapSize * m_MapSize * 4,
0, 0, m_MapSize, m_MapSize);
}
void CTerritoryTexture::GenerateBitmap(const Grid& territories, u8* bitmap, ssize_t w, ssize_t h)
{
int alphaMax = 0xC0;
int alphaFalloff = 0x20;
CmpPtr cmpPlayerManager(m_Simulation, SYSTEM_ENTITY);
std::vector colors;
i32 numPlayers = cmpPlayerManager->GetNumPlayers();
for (i32 p = 0; p < numPlayers; ++p)
{
CColor color(1, 0, 1, 1);
CmpPtr cmpPlayer(m_Simulation, cmpPlayerManager->GetPlayerByID(p));
if (cmpPlayer)
color = cmpPlayer->GetDisplayedColor();
colors.push_back(color);
}
u8* p = bitmap;
for (ssize_t j = 0; j < h; ++j)
for (ssize_t i = 0; i < w; ++i)
{
u8 val = territories.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
CColor color(1, 0, 1, 1);
if (val < colors.size())
color = colors[val];
*p++ = (int)(color.r * 255.f);
*p++ = (int)(color.g * 255.f);
*p++ = (int)(color.b * 255.f);
// Use alphaMax for borders and gaia territory; these tiles will be deleted later
if (val == 0 ||
(i > 0 && (territories.get(i-1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) ||
(i < w-1 && (territories.get(i+1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) ||
(j > 0 && (territories.get(i, j-1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) ||
(j < h-1 && (territories.get(i, j+1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val))
*p++ = alphaMax;
else
*p++ = 0x00;
}
// Do a low-quality cheap blur effect
for (ssize_t j = 0; j < h; ++j)
{
int a;
a = 0;
for (ssize_t i = 0; i < w; ++i)
{
a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]);
bitmap[(j*w+i)*4 + 3] = a;
}
a = 0;
for (ssize_t i = w-1; i >= 0; --i)
{
a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]);
bitmap[(j*w+i)*4 + 3] = a;
}
}
for (ssize_t i = 0; i < w; ++i)
{
int a;
a = 0;
for (ssize_t j = 0; j < w; ++j)
{
a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]);
bitmap[(j*w+i)*4 + 3] = a;
}
a = 0;
for (ssize_t j = w-1; j >= 0; --j)
{
a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]);
bitmap[(j*w+i)*4 + 3] = a;
}
}
// Add a gap between the boundaries, by deleting the max-alpha tiles
for (ssize_t j = 0; j < h; ++j)
for (ssize_t i = 0; i < w; ++i)
if (bitmap[(j*w+i)*4 + 3] == alphaMax)
bitmap[(j*w+i)*4 + 3] = 0;
}
void CTerritoryTexture::UpdateIfNeeded(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (UpdateDirty())
RecomputeTexture(deviceCommandContext);
}
Index: ps/trunk/source/graphics/TextureManager.cpp
===================================================================
--- ps/trunk/source/graphics/TextureManager.cpp (revision 26587)
+++ ps/trunk/source/graphics/TextureManager.cpp (revision 26588)
@@ -1,1027 +1,1027 @@
/* 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 "TextureManager.h"
#include "graphics/Color.h"
#include "graphics/TextureConverter.h"
#include "lib/allocators/shared_ptr.h"
#include "lib/file/vfs/vfs_tree.h"
#include "lib/hash.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "maths/MD5.h"
#include "ps/CacheLoader.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/VideoMode.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include
#include
#include
#include
#include
#include
#include
namespace
{
Renderer::Backend::Format ChooseFormatAndTransformTextureDataIfNeeded(Tex& textureData, const bool hasS3TC)
{
const bool alpha = (textureData.m_Flags & TEX_ALPHA) != 0;
const bool grey = (textureData.m_Flags & TEX_GREY) != 0;
const size_t dxt = textureData.m_Flags & TEX_DXT;
// Some backends don't support BGR as an internal format (like GLES).
// TODO: add a check that the format is internally supported.
if ((textureData.m_Flags & TEX_BGR) != 0)
{
LOGWARNING("Using slow path to convert BGR texture.");
textureData.transform_to(textureData.m_Flags & ~TEX_BGR);
}
if (dxt)
{
if (hasS3TC)
{
switch (dxt)
{
case DXT1A:
- return Renderer::Backend::Format::BC1_RGBA;
+ return Renderer::Backend::Format::BC1_RGBA_UNORM;
case 1:
- return Renderer::Backend::Format::BC1_RGB;
+ return Renderer::Backend::Format::BC1_RGB_UNORM;
case 3:
- return Renderer::Backend::Format::BC2;
+ return Renderer::Backend::Format::BC2_UNORM;
case 5:
- return Renderer::Backend::Format::BC3;
+ return Renderer::Backend::Format::BC3_UNORM;
default:
LOGERROR("Unknown DXT compression.");
return Renderer::Backend::Format::UNDEFINED;
}
}
else
textureData.transform_to(textureData.m_Flags & ~TEX_DXT);
}
switch (textureData.m_Bpp)
{
case 8:
ENSURE(grey);
- return Renderer::Backend::Format::L8;
+ return Renderer::Backend::Format::L8_UNORM;
case 24:
ENSURE(!alpha);
- return Renderer::Backend::Format::R8G8B8;
+ return Renderer::Backend::Format::R8G8B8_UNORM;
case 32:
ENSURE(alpha);
- return Renderer::Backend::Format::R8G8B8A8;
+ return Renderer::Backend::Format::R8G8B8A8_UNORM;
default:
LOGERROR("Unsupported BPP: %zu", textureData.m_Bpp);
}
return Renderer::Backend::Format::UNDEFINED;
}
} // anonymous namespace
class CPredefinedTexture
{
public:
const CTexturePtr& GetTexture()
{
return m_Texture;
}
void CreateTexture(
std::unique_ptr backendTexture,
CTextureManagerImpl* textureManager)
{
Renderer::Backend::GL::CTexture* fallback = backendTexture.get();
CTextureProperties props(VfsPath{});
m_Texture = CTexturePtr(new CTexture(
std::move(backendTexture), fallback, props, textureManager));
m_Texture->m_State = CTexture::UPLOADED;
m_Texture->m_Self = m_Texture;
}
private:
CTexturePtr m_Texture;
};
class CSingleColorTexture final : public CPredefinedTexture
{
public:
CSingleColorTexture(const CColor& color, const bool disableGL,
CTextureManagerImpl* textureManager)
: m_Color(color)
{
if (disableGL)
return;
std::stringstream textureName;
textureName << "SingleColorTexture (";
textureName << "R:" << m_Color.r << ", ";
textureName << "G:" << m_Color.g << ", ";
textureName << "B:" << m_Color.b << ", ";
textureName << "A:" << m_Color.a << ")";
std::unique_ptr backendTexture =
g_VideoMode.GetBackendDevice()->CreateTexture2D(
textureName.str().c_str(),
- Renderer::Backend::Format::R8G8B8A8,
+ Renderer::Backend::Format::R8G8B8A8_UNORM,
1, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
CreateTexture(std::move(backendTexture), textureManager);
}
void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!GetTexture() || !GetTexture()->GetBackendTexture())
return;
const SColor4ub color32 = m_Color.AsSColor4ub();
// Construct 1x1 32-bit texture
const u8 data[4] =
{
color32.R,
color32.G,
color32.B,
color32.A
};
deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
- Renderer::Backend::Format::R8G8B8A8, data, std::size(data));
+ Renderer::Backend::Format::R8G8B8A8_UNORM, data, std::size(data));
}
private:
CColor m_Color;
};
class CGradientTexture final : public CPredefinedTexture
{
public:
static const uint32_t WIDTH = 256;
static const uint32_t NUMBER_OF_LEVELS = 9;
CGradientTexture(const CColor& colorFrom, const CColor& colorTo,
const bool disableGL, CTextureManagerImpl* textureManager)
: m_ColorFrom(colorFrom), m_ColorTo(colorTo)
{
if (disableGL)
return;
std::stringstream textureName;
textureName << "GradientTexture";
textureName << " From (";
textureName << "R:" << m_ColorFrom.r << ", ";
textureName << "G:" << m_ColorFrom.g << ", ";
textureName << "B:" << m_ColorFrom.b << ", ";
textureName << "A:" << m_ColorFrom.a << ")";
textureName << " To (";
textureName << "R:" << m_ColorTo.r << ",";
textureName << "G:" << m_ColorTo.g << ",";
textureName << "B:" << m_ColorTo.b << ",";
textureName << "A:" << m_ColorTo.a << ")";
std::unique_ptr backendTexture =
g_VideoMode.GetBackendDevice()->CreateTexture2D(
textureName.str().c_str(),
- Renderer::Backend::Format::R8G8B8A8,
+ Renderer::Backend::Format::R8G8B8A8_UNORM,
WIDTH, 1, Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE),
NUMBER_OF_LEVELS);
CreateTexture(std::move(backendTexture), textureManager);
}
void Upload(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!GetTexture() || !GetTexture()->GetBackendTexture())
return;
std::array, WIDTH> data;
for (uint32_t x = 0; x < WIDTH; ++x)
{
const float t = static_cast(x) / (WIDTH - 1);
const CColor color(
Interpolate(m_ColorFrom.r, m_ColorTo.r, t),
Interpolate(m_ColorFrom.g, m_ColorTo.g, t),
Interpolate(m_ColorFrom.b, m_ColorTo.b, t),
Interpolate(m_ColorFrom.a, m_ColorTo.a, t));
const SColor4ub color32 = color.AsSColor4ub();
data[x][0] = color32.R;
data[x][1] = color32.G;
data[x][2] = color32.B;
data[x][3] = color32.A;
}
for (uint32_t level = 0; level < NUMBER_OF_LEVELS; ++level)
{
deviceCommandContext->UploadTexture(GetTexture()->GetBackendTexture(),
- Renderer::Backend::Format::R8G8B8A8, data.data(), (WIDTH >> level) * data[0].size(), level);
+ Renderer::Backend::Format::R8G8B8A8_UNORM, data.data(), (WIDTH >> level) * data[0].size(), level);
// Prepare data for the next level.
const uint32_t nextLevelWidth = (WIDTH >> (level + 1));
if (nextLevelWidth > 0)
{
for (uint32_t x = 0; x < nextLevelWidth; ++x)
data[x] = data[(x << 1)];
// Border values should be the same.
data[nextLevelWidth - 1] = data[(WIDTH >> level) - 1];
}
}
}
private:
CColor m_ColorFrom, m_ColorTo;
};
struct TPhash
{
std::size_t operator()(const CTextureProperties& textureProperties) const
{
std::size_t seed = 0;
hash_combine(seed, m_PathHash(textureProperties.m_Path));
hash_combine(seed, textureProperties.m_AddressModeU);
hash_combine(seed, textureProperties.m_AddressModeV);
hash_combine(seed, textureProperties.m_AnisotropicFilterEnabled);
hash_combine(seed, textureProperties.m_FormatOverride);
hash_combine(seed, textureProperties.m_IgnoreQuality);
return seed;
}
std::size_t operator()(const CTexturePtr& texture) const
{
return this->operator()(texture->m_Properties);
}
private:
std::hash m_PathHash;
};
struct TPequal_to
{
bool operator()(const CTextureProperties& lhs, const CTextureProperties& rhs) const
{
return
lhs.m_Path == rhs.m_Path &&
lhs.m_AddressModeU == rhs.m_AddressModeU &&
lhs.m_AddressModeV == rhs.m_AddressModeV &&
lhs.m_AnisotropicFilterEnabled == rhs.m_AnisotropicFilterEnabled &&
lhs.m_FormatOverride == rhs.m_FormatOverride &&
lhs.m_IgnoreQuality == rhs.m_IgnoreQuality;
}
bool operator()(const CTexturePtr& lhs, const CTexturePtr& rhs) const
{
return this->operator()(lhs->m_Properties, rhs->m_Properties);
}
};
class CTextureManagerImpl
{
friend class CTexture;
public:
CTextureManagerImpl(PIVFS vfs, bool highQuality, bool disableGL) :
m_VFS(vfs), m_CacheLoader(vfs, L".dds"), m_DisableGL(disableGL),
m_TextureConverter(vfs, highQuality),
m_DefaultTexture(CColor(0.25f, 0.25f, 0.25f, 1.0f), disableGL, this),
m_ErrorTexture(CColor(1.0f, 0.0f, 1.0f, 1.0f), disableGL, this),
m_WhiteTexture(CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this),
m_TransparentTexture(CColor(0.0f, 0.0f, 0.0f, 0.0f), disableGL, this),
m_AlphaGradientTexture(
CColor(1.0f, 1.0f, 1.0f, 0.0f), CColor(1.0f, 1.0f, 1.0f, 1.0f), disableGL, this)
{
// Allow hotloading of textures
RegisterFileReloadFunc(ReloadChangedFileCB, this);
if (disableGL)
return;
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
m_HasS3TC =
- backendDevice->IsFormatSupported(Renderer::Backend::Format::BC1_RGB) &&
- backendDevice->IsFormatSupported(Renderer::Backend::Format::BC1_RGBA) &&
- backendDevice->IsFormatSupported(Renderer::Backend::Format::BC2) &&
- backendDevice->IsFormatSupported(Renderer::Backend::Format::BC3);
+ backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGB_UNORM) &&
+ backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC1_RGBA_UNORM) &&
+ backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC2_UNORM) &&
+ backendDevice->IsTextureFormatSupported(Renderer::Backend::Format::BC3_UNORM);
}
~CTextureManagerImpl()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
const CTexturePtr& GetErrorTexture()
{
return m_ErrorTexture.GetTexture();
}
const CTexturePtr& GetWhiteTexture()
{
return m_WhiteTexture.GetTexture();
}
const CTexturePtr& GetTransparentTexture()
{
return m_TransparentTexture.GetTexture();
}
const CTexturePtr& GetAlphaGradientTexture()
{
return m_AlphaGradientTexture.GetTexture();
}
/**
* See CTextureManager::CreateTexture
*/
CTexturePtr CreateTexture(const CTextureProperties& props)
{
// Construct a new default texture with the given properties to use as the search key
CTexturePtr texture(new CTexture(
nullptr, m_DisableGL ? nullptr : m_DefaultTexture.GetTexture()->GetBackendTexture(),
props, this));
// Try to find an existing texture with the given properties
TextureCache::iterator it = m_TextureCache.find(texture);
if (it != m_TextureCache.end())
return *it;
// Can't find an existing texture - finish setting up this new texture
texture->m_Self = texture;
m_TextureCache.insert(texture);
m_HotloadFiles[props.m_Path].insert(texture);
return texture;
}
CTexturePtr WrapBackendTexture(
std::unique_ptr backendTexture)
{
ENSURE(backendTexture);
Renderer::Backend::GL::CTexture* fallback = backendTexture.get();
CTextureProperties props(VfsPath{});
CTexturePtr texture(new CTexture(
std::move(backendTexture), fallback, props, this));
texture->m_State = CTexture::UPLOADED;
texture->m_Self = texture;
return texture;
}
/**
* Load the given file into the texture object and upload it to OpenGL.
* Assumes the file already exists.
*/
void LoadTexture(const CTexturePtr& texture, const VfsPath& path)
{
if (m_DisableGL)
return;
PROFILE2("load texture");
PROFILE2_ATTR("name: %ls", path.string().c_str());
std::shared_ptr fileData;
size_t fileSize;
texture->m_TextureData = std::make_unique();
Tex& textureData = *texture->m_TextureData;
if (g_VFS->LoadFile(path, fileData, fileSize) != INFO::OK ||
textureData.decode(fileData, fileSize) != INFO::OK)
{
LOGERROR("Texture failed to load; \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
return;
}
// Initialise base color from the texture
texture->m_BaseColor = textureData.get_average_color();
Renderer::Backend::Format format = Renderer::Backend::Format::UNDEFINED;
if (texture->m_Properties.m_FormatOverride != Renderer::Backend::Format::UNDEFINED)
{
format = texture->m_Properties.m_FormatOverride;
// TODO: it'd be good to remove the override hack and provide information
// via XML.
ENSURE((textureData.m_Flags & TEX_DXT) == 0);
- if (format == Renderer::Backend::Format::A8)
+ if (format == Renderer::Backend::Format::A8_UNORM)
{
ENSURE(textureData.m_Bpp == 8 && (textureData.m_Flags & TEX_GREY));
}
- else if (format == Renderer::Backend::Format::R8G8B8A8)
+ else if (format == Renderer::Backend::Format::R8G8B8A8_UNORM)
{
ENSURE(textureData.m_Bpp == 32 && (textureData.m_Flags & TEX_ALPHA));
}
else
debug_warn("Unsupported format override.");
}
else
{
format = ChooseFormatAndTransformTextureDataIfNeeded(textureData, m_HasS3TC);
}
if (format == Renderer::Backend::Format::UNDEFINED)
{
LOGERROR("Texture failed to choose format; \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
return;
}
const uint32_t width = texture->m_TextureData->m_Width;
const uint32_t height = texture->m_TextureData->m_Height ;
const uint32_t MIPLevelCount = texture->m_TextureData->GetMIPLevels().size();
texture->m_BaseLevelOffset = 0;
Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT);
defaultSamplerDesc.addressModeU = texture->m_Properties.m_AddressModeU;
defaultSamplerDesc.addressModeV = texture->m_Properties.m_AddressModeV;
if (texture->m_Properties.m_AnisotropicFilterEnabled)
{
int maxAnisotropy = 1;
CFG_GET_VAL("textures.maxanisotropy", maxAnisotropy);
const int allowedValues[] = {2, 4, 8, 16};
if (std::find(std::begin(allowedValues), std::end(allowedValues), maxAnisotropy) != std::end(allowedValues))
{
defaultSamplerDesc.anisotropyEnabled = true;
defaultSamplerDesc.maxAnisotropy = maxAnisotropy;
}
}
if (!texture->m_Properties.m_IgnoreQuality)
{
int quality = 2;
CFG_GET_VAL("textures.quality", quality);
if (quality == 1)
{
if (MIPLevelCount > 1 && std::min(width, height) > 8)
texture->m_BaseLevelOffset += 1;
}
else if (quality == 0)
{
if (MIPLevelCount > 2 && std::min(width, height) > 16)
texture->m_BaseLevelOffset += 2;
while (std::min(width >> texture->m_BaseLevelOffset, height >> texture->m_BaseLevelOffset) > 256 &&
MIPLevelCount > texture->m_BaseLevelOffset + 1)
{
texture->m_BaseLevelOffset += 1;
}
defaultSamplerDesc.mipFilter = Renderer::Backend::Sampler::Filter::NEAREST;
defaultSamplerDesc.anisotropyEnabled = false;
}
}
texture->m_BackendTexture = g_VideoMode.GetBackendDevice()->CreateTexture2D(
texture->m_Properties.m_Path.string8().c_str(),
format, (width >> texture->m_BaseLevelOffset), (height >> texture->m_BaseLevelOffset),
defaultSamplerDesc, MIPLevelCount - texture->m_BaseLevelOffset);
}
/**
* Set up some parameters for the loose cache filename code.
*/
void PrepareCacheKey(const CTexturePtr& texture, MD5& hash, u32& version)
{
// Hash the settings, so we won't use an old loose cache file if the
// settings have changed
CTextureConverter::Settings settings = GetConverterSettings(texture);
settings.Hash(hash);
// Arbitrary version number - change this if we update the code and
// need to invalidate old users' caches
version = 1;
}
/**
* Attempts to load a cached version of a texture.
* If the texture is loaded (or there was an error), returns true.
* Otherwise, returns false to indicate the caller should generate the cached version.
*/
bool TryLoadingCached(const CTexturePtr& texture)
{
MD5 hash;
u32 version;
PrepareCacheKey(texture, hash, version);
VfsPath loadPath;
Status ret = m_CacheLoader.TryLoadingCached(texture->m_Properties.m_Path, hash, version, loadPath);
if (ret == INFO::OK)
{
// Found a cached texture - load it
LoadTexture(texture, loadPath);
return true;
}
else if (ret == INFO::SKIPPED)
{
// No cached version was found - we'll need to create it
return false;
}
else
{
ENSURE(ret < 0);
// No source file or archive cache was found, so we can't load the
// real texture at all - return the error texture instead
LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
return true;
}
}
/**
* Initiates an asynchronous conversion process, from the texture's
* source file to the corresponding loose cache file.
*/
void ConvertTexture(const CTexturePtr& texture)
{
VfsPath sourcePath = texture->m_Properties.m_Path;
PROFILE2("convert texture");
PROFILE2_ATTR("name: %ls", sourcePath.string().c_str());
MD5 hash;
u32 version;
PrepareCacheKey(texture, hash, version);
VfsPath looseCachePath = m_CacheLoader.LooseCachePath(sourcePath, hash, version);
// LOGWARNING("Converting texture \"%s\"", srcPath.c_str());
CTextureConverter::Settings settings = GetConverterSettings(texture);
m_TextureConverter.ConvertTexture(texture, sourcePath, looseCachePath, settings);
}
bool TextureExists(const VfsPath& path) const
{
return m_VFS->GetFileInfo(m_CacheLoader.ArchiveCachePath(path), 0) == INFO::OK ||
m_VFS->GetFileInfo(path, 0) == INFO::OK;
}
bool GenerateCachedTexture(const VfsPath& sourcePath, VfsPath& archiveCachePath)
{
archiveCachePath = m_CacheLoader.ArchiveCachePath(sourcePath);
CTextureProperties textureProps(sourcePath);
CTexturePtr texture = CreateTexture(textureProps);
CTextureConverter::Settings settings = GetConverterSettings(texture);
if (!m_TextureConverter.ConvertTexture(texture, sourcePath, VfsPath("cache") / archiveCachePath, settings))
return false;
while (true)
{
CTexturePtr textureOut;
VfsPath dest;
bool ok;
if (m_TextureConverter.Poll(textureOut, dest, ok))
return ok;
std::this_thread::sleep_for(std::chrono::microseconds(1));
}
}
bool MakeProgress()
{
// Process any completed conversion tasks
{
CTexturePtr texture;
VfsPath dest;
bool ok;
if (m_TextureConverter.Poll(texture, dest, ok))
{
if (ok)
{
LoadTexture(texture, dest);
}
else
{
LOGERROR("Texture failed to convert: \"%s\"", texture->m_Properties.m_Path.string8());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
}
texture->m_State = CTexture::LOADED;
return true;
}
}
// We'll only push new conversion requests if it's not already busy
bool converterBusy = m_TextureConverter.IsBusy();
if (!converterBusy)
{
// Look for all high-priority textures needing conversion.
// (Iterating over all textures isn't optimally efficient, but it
// doesn't seem to be a problem yet and it's simpler than maintaining
// multiple queues.)
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::HIGH_NEEDS_CONVERTING)
{
// Start converting this texture
(*it)->m_State = CTexture::HIGH_IS_CONVERTING;
ConvertTexture(*it);
return true;
}
}
}
// Try loading prefetched textures from their cache
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::PREFETCH_NEEDS_LOADING)
{
if (TryLoadingCached(*it))
{
(*it)->m_State = CTexture::LOADED;
}
else
{
(*it)->m_State = CTexture::PREFETCH_NEEDS_CONVERTING;
}
return true;
}
}
// If we've got nothing better to do, then start converting prefetched textures.
if (!converterBusy)
{
for (TextureCache::iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
{
if ((*it)->m_State == CTexture::PREFETCH_NEEDS_CONVERTING)
{
(*it)->m_State = CTexture::PREFETCH_IS_CONVERTING;
ConvertTexture(*it);
return true;
}
}
}
return false;
}
bool MakeUploadProgress(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!m_PredefinedTexturesUploaded)
{
m_DefaultTexture.Upload(deviceCommandContext);
m_ErrorTexture.Upload(deviceCommandContext);
m_WhiteTexture.Upload(deviceCommandContext);
m_TransparentTexture.Upload(deviceCommandContext);
m_AlphaGradientTexture.Upload(deviceCommandContext);
m_PredefinedTexturesUploaded = true;
return true;
}
return false;
}
/**
* Compute the conversion settings that apply to a given texture, by combining
* the textures.xml files from its directory and all parent directories
* (up to the VFS root).
*/
CTextureConverter::Settings GetConverterSettings(const CTexturePtr& texture)
{
fs::wpath srcPath = texture->m_Properties.m_Path.string();
std::vector files;
VfsPath p;
for (fs::wpath::iterator it = srcPath.begin(); it != srcPath.end(); ++it)
{
VfsPath settingsPath = p / "textures.xml";
m_HotloadFiles[settingsPath].insert(texture);
CTextureConverter::SettingsFile* f = GetSettingsFile(settingsPath);
if (f)
files.push_back(f);
p = p / GetWstringFromWpath(*it);
}
return m_TextureConverter.ComputeSettings(GetWstringFromWpath(srcPath.leaf()), files);
}
/**
* Return the (cached) settings file with the given filename,
* or NULL if it doesn't exist.
*/
CTextureConverter::SettingsFile* GetSettingsFile(const VfsPath& path)
{
SettingsFilesMap::iterator it = m_SettingsFiles.find(path);
if (it != m_SettingsFiles.end())
return it->second.get();
if (m_VFS->GetFileInfo(path, NULL) >= 0)
{
std::shared_ptr settings(m_TextureConverter.LoadSettings(path));
m_SettingsFiles.insert(std::make_pair(path, settings));
return settings.get();
}
else
{
m_SettingsFiles.insert(std::make_pair(path, std::shared_ptr()));
return NULL;
}
}
static Status ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
Status ReloadChangedFile(const VfsPath& path)
{
// Uncache settings file, if this is one
m_SettingsFiles.erase(path);
// Find all textures using this file
HotloadFilesMap::iterator files = m_HotloadFiles.find(path);
if (files != m_HotloadFiles.end())
{
// Flag all textures using this file as needing reloading
for (std::set>::iterator it = files->second.begin(); it != files->second.end(); ++it)
{
if (std::shared_ptr texture = it->lock())
{
texture->m_State = CTexture::UNLOADED;
texture->ResetBackendTexture(
nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
}
}
}
return INFO::OK;
}
void ReloadAllTextures()
{
for (const CTexturePtr& texture : m_TextureCache)
{
texture->m_State = CTexture::UNLOADED;
texture->ResetBackendTexture(
nullptr, m_DefaultTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
}
}
size_t GetBytesUploaded() const
{
size_t size = 0;
for (TextureCache::const_iterator it = m_TextureCache.begin(); it != m_TextureCache.end(); ++it)
size += (*it)->GetUploadedSize();
return size;
}
void OnQualityChanged()
{
ReloadAllTextures();
}
private:
PIVFS m_VFS;
CCacheLoader m_CacheLoader;
bool m_DisableGL;
CTextureConverter m_TextureConverter;
CSingleColorTexture m_DefaultTexture;
CSingleColorTexture m_ErrorTexture;
CSingleColorTexture m_WhiteTexture;
CSingleColorTexture m_TransparentTexture;
CGradientTexture m_AlphaGradientTexture;
bool m_PredefinedTexturesUploaded = false;
// Cache of all loaded textures
using TextureCache =
std::unordered_set;
TextureCache m_TextureCache;
// TODO: we ought to expire unused textures from the cache eventually
// Store the set of textures that need to be reloaded when the given file
// (a source file or settings.xml) is modified
using HotloadFilesMap =
std::unordered_map, std::owner_less>>>;
HotloadFilesMap m_HotloadFiles;
// Cache for the conversion settings files
using SettingsFilesMap =
std::unordered_map>;
SettingsFilesMap m_SettingsFiles;
bool m_HasS3TC = false;
};
CTexture::CTexture(
std::unique_ptr texture,
Renderer::Backend::GL::CTexture* fallback,
const CTextureProperties& props, CTextureManagerImpl* textureManager) :
m_BackendTexture(std::move(texture)), m_FallbackBackendTexture(fallback),
m_BaseColor(0), m_State(UNLOADED), m_Properties(props),
m_TextureManager(textureManager)
{
}
CTexture::~CTexture() = default;
void CTexture::UploadBackendTextureIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (IsUploaded())
return;
if (!IsLoaded())
TryLoad();
if (!IsLoaded())
return;
else if (!m_TextureData)
{
ResetBackendTexture(nullptr, m_TextureManager->GetErrorTexture()->GetBackendTexture());
m_State = UPLOADED;
return;
}
m_UploadedSize = 0;
for (uint32_t textureDataLevel = m_BaseLevelOffset, level = 0; textureDataLevel < m_TextureData->GetMIPLevels().size(); ++textureDataLevel)
{
const Tex::MIPLevel& levelData = m_TextureData->GetMIPLevels()[textureDataLevel];
deviceCommandContext->UploadTexture(m_BackendTexture.get(), m_BackendTexture->GetFormat(),
levelData.data, levelData.dataSize, level++);
m_UploadedSize += levelData.dataSize;
}
m_TextureData.reset();
m_State = UPLOADED;
}
Renderer::Backend::GL::CTexture* CTexture::GetBackendTexture()
{
return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
}
const Renderer::Backend::GL::CTexture* CTexture::GetBackendTexture() const
{
return m_BackendTexture && IsUploaded() ? m_BackendTexture.get() : m_FallbackBackendTexture;
}
bool CTexture::TryLoad()
{
// If we haven't started loading, then try loading, and if that fails then request conversion.
// If we have already tried prefetch loading, and it failed, bump the conversion request to HIGH priority.
if (m_State == UNLOADED || m_State == PREFETCH_NEEDS_LOADING || m_State == PREFETCH_NEEDS_CONVERTING)
{
if (std::shared_ptr self = m_Self.lock())
{
if (m_State != PREFETCH_NEEDS_CONVERTING && m_TextureManager->TryLoadingCached(self))
m_State = LOADED;
else
m_State = HIGH_NEEDS_CONVERTING;
}
}
return IsLoaded() || IsUploaded();
}
void CTexture::Prefetch()
{
if (m_State == UNLOADED)
{
if (std::shared_ptr self = m_Self.lock())
{
m_State = PREFETCH_NEEDS_LOADING;
}
}
}
void CTexture::ResetBackendTexture(
std::unique_ptr backendTexture,
Renderer::Backend::GL::CTexture* fallbackBackendTexture)
{
m_BackendTexture = std::move(backendTexture);
m_FallbackBackendTexture = fallbackBackendTexture;
}
size_t CTexture::GetWidth() const
{
return GetBackendTexture()->GetWidth();
}
size_t CTexture::GetHeight() const
{
return GetBackendTexture()->GetHeight();
}
bool CTexture::HasAlpha() const
{
const Renderer::Backend::Format format = GetBackendTexture()->GetFormat();
return
- format == Renderer::Backend::Format::A8 ||
- format == Renderer::Backend::Format::R8G8B8A8 ||
- format == Renderer::Backend::Format::BC1_RGBA ||
- format == Renderer::Backend::Format::BC2 ||
- format == Renderer::Backend::Format::BC3;
+ format == Renderer::Backend::Format::A8_UNORM ||
+ format == Renderer::Backend::Format::R8G8B8A8_UNORM ||
+ format == Renderer::Backend::Format::BC1_RGBA_UNORM ||
+ format == Renderer::Backend::Format::BC2_UNORM ||
+ format == Renderer::Backend::Format::BC3_UNORM;
}
u32 CTexture::GetBaseColor() const
{
return m_BaseColor;
}
size_t CTexture::GetUploadedSize() const
{
return m_UploadedSize;
}
// CTextureManager: forward all calls to impl:
CTextureManager::CTextureManager(PIVFS vfs, bool highQuality, bool disableGL) :
m(new CTextureManagerImpl(vfs, highQuality, disableGL))
{
}
CTextureManager::~CTextureManager()
{
delete m;
}
CTexturePtr CTextureManager::CreateTexture(const CTextureProperties& props)
{
return m->CreateTexture(props);
}
CTexturePtr CTextureManager::WrapBackendTexture(
std::unique_ptr backendTexture)
{
return m->WrapBackendTexture(std::move(backendTexture));
}
bool CTextureManager::TextureExists(const VfsPath& path) const
{
return m->TextureExists(path);
}
const CTexturePtr& CTextureManager::GetErrorTexture()
{
return m->GetErrorTexture();
}
const CTexturePtr& CTextureManager::GetWhiteTexture()
{
return m->GetWhiteTexture();
}
const CTexturePtr& CTextureManager::GetTransparentTexture()
{
return m->GetTransparentTexture();
}
const CTexturePtr& CTextureManager::GetAlphaGradientTexture()
{
return m->GetAlphaGradientTexture();
}
bool CTextureManager::MakeProgress()
{
return m->MakeProgress();
}
bool CTextureManager::MakeUploadProgress(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
return m->MakeUploadProgress(deviceCommandContext);
}
bool CTextureManager::GenerateCachedTexture(const VfsPath& path, VfsPath& outputPath)
{
return m->GenerateCachedTexture(path, outputPath);
}
size_t CTextureManager::GetBytesUploaded() const
{
return m->GetBytesUploaded();
}
void CTextureManager::OnQualityChanged()
{
m->OnQualityChanged();
}
Index: ps/trunk/source/renderer/PostprocManager.cpp
===================================================================
--- ps/trunk/source/renderer/PostprocManager.cpp (revision 26587)
+++ ps/trunk/source/renderer/PostprocManager.cpp (revision 26588)
@@ -1,768 +1,768 @@
/* 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_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;
m_CaptureFramebuffer.reset();
m_PingFramebuffer.reset();
m_PongFramebuffer.reset();
m_ColorTex1.reset();
m_ColorTex2.reset();
m_DepthTex.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;
const uint32_t maxSamples = g_VideoMode.GetBackendDevice()->GetCapabilities().maxSampleCount;
const uint32_t possibleSampleCounts[] = {2, 4, 8, 16};
std::copy_if(
std::begin(possibleSampleCounts), std::end(possibleSampleCounts),
std::back_inserter(m_AllowedSampleCounts),
[maxSamples](const uint32_t 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();
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
#define GEN_BUFFER_RGBA(name, w, h) \
name = backendDevice->CreateTexture2D( \
- "PostProc" #name, Renderer::Backend::Format::R8G8B8A8, w, h, \
+ "PostProc" #name, Renderer::Backend::Format::R8G8B8A8_UNORM, 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.
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 = backendDevice->CreateFramebuffer("BlurScaleSteoFramebuffer",
step.texture.get(), nullptr);
}
width /= 2;
height /= 2;
}
#undef GEN_BUFFER_RGBA
// Allocate the Depth/Stencil texture.
m_DepthTex = backendDevice->CreateTexture2D("PostPRocDepthTexture",
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));
// Set up the framebuffers with some initial textures.
m_CaptureFramebuffer = backendDevice->CreateFramebuffer("PostprocCaptureFramebuffer",
m_ColorTex1.get(), m_DepthTex.get(),
g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor());
m_PingFramebuffer = backendDevice->CreateFramebuffer("PostprocPingFramebuffer",
m_ColorTex1.get(), nullptr);
m_PongFramebuffer = backendDevice->CreateFramebuffer("PostprocPongFramebuffer",
m_ColorTex2.get(), nullptr);
if (!m_CaptureFramebuffer || !m_PingFramebuffer || !m_PongFramebuffer)
{
LOGWARNING("Failed to create postproc framebuffers");
g_RenderingOptions.SetPostProc(false);
}
if (m_UsingMultisampleBuffer)
{
DestroyMultisampleBuffer();
CreateMultisampleBuffer();
}
}
void CPostprocManager::ApplyBlurDownscale2x(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
Renderer::Backend::GL::CFramebuffer* framebuffer,
Renderer::Backend::GL::CTexture* inTex, int inWidth, int inHeight)
{
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();
deviceCommandContext->Draw(0, 6);
g_Renderer.SetViewport(oldVp);
tech->EndPass();
}
void CPostprocManager::ApplyBlurGauss(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
Renderer::Backend::GL::CTexture* inTex,
Renderer::Backend::GL::CTexture* tempTex,
Renderer::Backend::GL::CFramebuffer* tempFramebuffer,
Renderer::Backend::GL::CFramebuffer* outFramebuffer,
int inWidth, int inHeight)
{
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, 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();
deviceCommandContext->Draw(0, 6);
g_Renderer.SetViewport(oldVp);
tech->EndPass();
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();
deviceCommandContext->Draw(0, 6);
g_Renderer.SetViewport(oldVp);
tech->EndPass();
}
void CPostprocManager::ApplyBlur(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
uint32_t width = m_Width, height = m_Height;
Renderer::Backend::GL::CTexture* previousTexture =
(m_WhichBuffer ? m_ColorTex1 : m_ColorTex2).get();
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(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
ENSURE(m_IsInitialized);
// Leaves m_PingFbo selected for rendering; m_WhichBuffer stays true at this point.
if (m_UsingMultisampleBuffer)
deviceCommandContext->SetFramebuffer(m_MultisampleFramebuffer.get());
else
deviceCommandContext->SetFramebuffer(m_CaptureFramebuffer.get());
m_WhichBuffer = true;
}
void CPostprocManager::ReleaseRenderOutput(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
ENSURE(m_IsInitialized);
GPU_SCOPED_LABEL(deviceCommandContext, "Copy postproc to backbuffer");
// We blit to the backbuffer from the previous active buffer.
deviceCommandContext->BlitFramebuffer(
deviceCommandContext->GetDevice()->GetCurrentBackbuffer(),
(m_WhichBuffer ? m_PingFramebuffer : m_PongFramebuffer).get());
deviceCommandContext->SetFramebuffer(
deviceCommandContext->GetDevice()->GetCurrentBackbuffer());
}
void CPostprocManager::ApplyEffect(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CShaderTechniquePtr& shaderTech, int pass)
{
// select the other FBO for rendering
deviceCommandContext->SetFramebuffer(
(m_WhichBuffer ? m_PongFramebuffer : m_PingFramebuffer).get());
shaderTech->BeginPass(pass);
deviceCommandContext->SetGraphicsPipelineState(
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_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();
deviceCommandContext->Draw(0, 6);
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;
GPU_SCOPED_LABEL(deviceCommandContext, "Render postproc");
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);
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);
}
}
// 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)
{
// We don't want to enable MSAA in Atlas, because it uses wxWidgets and its canvas.
if (g_AtlasGameLoop && g_AtlasGameLoop->running)
return;
if (!g_VideoMode.GetBackendDevice()->GetCapabilities().multisampling && !m_AllowedSampleCounts.empty())
{
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();
}
}
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()
{
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
m_MultisampleColorTex = backendDevice->CreateTexture("PostProcColorMS",
Renderer::Backend::GL::CTexture::Type::TEXTURE_2D_MULTISAMPLE,
- Renderer::Backend::Format::R8G8B8A8, m_Width, m_Height,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, 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 = backendDevice->CreateTexture("PostProcDepthMS",
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.
m_MultisampleFramebuffer = backendDevice->CreateFramebuffer("PostprocMultisampleFramebuffer",
m_MultisampleColorTex.get(), m_MultisampleDepthTex.get(),
g_VideoMode.GetBackendDevice()->GetCurrentBackbuffer()->GetClearColor());
if (!m_MultisampleFramebuffer)
{
LOGERROR("Failed to create postproc multisample framebuffer");
m_UsingMultisampleBuffer = false;
DestroyMultisampleBuffer();
}
}
void CPostprocManager::DestroyMultisampleBuffer()
{
if (m_UsingMultisampleBuffer)
return;
m_MultisampleFramebuffer.reset();
m_MultisampleColorTex.reset();
m_MultisampleDepthTex.reset();
}
bool CPostprocManager::IsMultisampleEnabled() const
{
return m_UsingMultisampleBuffer;
}
void CPostprocManager::ResolveMultisampleFramebuffer(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!m_UsingMultisampleBuffer)
return;
GPU_SCOPED_LABEL(deviceCommandContext, "Resolve postproc multisample");
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),
int UNUSED(inWidth), int UNUSED(inHeight))
{
}
void CPostprocManager::ApplyBlurGauss(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext),
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(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(
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(
Renderer::Backend::GL::CDeviceCommandContext* UNUSED(deviceCommandContext))
{
}
#endif
Index: ps/trunk/source/renderer/ShadowMap.cpp
===================================================================
--- ps/trunk/source/renderer/ShadowMap.cpp (revision 26587)
+++ ps/trunk/source/renderer/ShadowMap.cpp (revision 26588)
@@ -1,772 +1,772 @@
/* 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
{
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();
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();
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();
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
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(backendDevice->GetCapabilities().maxTextureSize));
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 = backendDevice->CreateTexture2D("ShadowMapDummy",
- Renderer::Backend::Format::R8G8B8A8, Width, Height,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
}
Renderer::Backend::Sampler::Desc samplerDesc =
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 LINEAR to trigger automatic PCF on some devices.
Renderer::Backend::Sampler::Filter::LINEAR,
#endif
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
// Enable automatic depth comparisons
samplerDesc.compareEnabled = true;
samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL;
Texture = backendDevice->CreateTexture2D("ShadowMapDepth",
backendFormat, Width, Height, samplerDesc);
Framebuffer = backendDevice->CreateFramebuffer("ShadowMapFramebuffer",
g_RenderingOptions.GetShadowAlphaFix() ? DummyTexture.get() : nullptr, Texture.get());
if (!Framebuffer)
{
LOGERROR("Failed to create shadows framebuffer");
// 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");
ENSURE(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.
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::Rect 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");
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().DrawBoundingBox(
m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true);
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().DrawBoundingBox(
m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true);
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().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true);
}
ogl_WarnIfError();
}
void ShadowMap::RenderDebugTexture(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (!m->Texture)
return;
#if !CONFIG2_GLES
deviceCommandContext->BindTexture(0, GL_TEXTURE_2D, 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();
deviceCommandContext->Draw(0, 6);
texTech->EndPass();
#if !CONFIG2_GLES
deviceCommandContext->BindTexture(0, GL_TEXTURE_2D, 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/SkyManager.cpp
===================================================================
--- ps/trunk/source/renderer/SkyManager.cpp (revision 26587)
+++ ps/trunk/source/renderer/SkyManager.cpp (revision 26588)
@@ -1,336 +1,336 @@
/* 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/SkyManager.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "lib/bits.h"
#include "lib/tex/tex.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/ConfigDB.h"
#include "ps/CStr.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/Loader.h"
#include "ps/VideoMode.h"
#include "ps/World.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/RenderingOptions.h"
#include
SkyManager::SkyManager()
: m_VertexArray(Renderer::Backend::GL::CBuffer::Type::VERTEX, false)
{
CFG_GET_VAL("showsky", m_RenderSky);
}
void SkyManager::LoadAndUploadSkyTexturesIfNeeded(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
if (m_SkyCubeMap)
return;
GPU_SCOPED_LABEL(deviceCommandContext, "Load Sky Textures");
static const CStrW images[NUMBER_OF_TEXTURES + 1] = {
L"front",
L"back",
L"top",
L"top",
L"right",
L"left"
};
/*for (size_t i = 0; i < ARRAY_SIZE(m_SkyTexture); ++i)
{
VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(s_imageNames[i])+L".dds");
CTextureProperties textureProps(path);
textureProps.SetWrap(GL_CLAMP_TO_EDGE);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_SkyTexture[i] = texture;
}*/
///////////////////////////////////////////////////////////////////////////
// HACK: THE HORRIBLENESS HERE IS OVER 9000. The following code is a HUGE hack and will be removed completely
// as soon as all the hardcoded GL_TEXTURE_2D references are corrected in the TextureManager/OGL/tex libs.
Tex textures[NUMBER_OF_TEXTURES + 1];
for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i)
{
VfsPath path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds");
std::shared_ptr file;
size_t fileSize;
if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK)
{
path = VfsPath("art/textures/skies") / m_SkySet / (Path::String(images[i]) + L".dds.cached.dds");
if (g_VFS->LoadFile(path, file, fileSize) != INFO::OK)
{
LOGERROR("Error creating sky cubemap '%s', can't load file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str());
return;
}
}
textures[i].decode(file, fileSize);
textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS));
if (!is_pow2(textures[i].m_Width) || !is_pow2(textures[i].m_Height))
{
LOGERROR("Error creating sky cubemap '%s', cube textures should have power of 2 sizes.", m_SkySet.ToUTF8().c_str());
return;
}
if (textures[i].m_Width != textures[0].m_Width || textures[i].m_Height != textures[0].m_Height)
{
LOGERROR("Error creating sky cubemap '%s', cube textures have different sizes.", m_SkySet.ToUTF8().c_str());
return;
}
}
m_SkyCubeMap = g_VideoMode.GetBackendDevice()->CreateTexture("SkyCubeMap",
Renderer::Backend::GL::CTexture::Type::TEXTURE_CUBE,
- Renderer::Backend::Format::R8G8B8A8, textures[0].m_Width, textures[0].m_Height,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, textures[0].m_Width, textures[0].m_Height,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE), 1, 1);
std::vector rotated;
for (size_t i = 0; i < NUMBER_OF_TEXTURES + 1; ++i)
{
u8* data = textures[i].get_data();
// We need to rotate the side if it's looking up or down.
// TODO: maybe it should be done during texture conversion.
if (i == 2 || i == 3)
{
rotated.resize(textures[i].m_DataSize);
for (size_t y = 0; y < textures[i].m_Height; ++y)
{
for (size_t x = 0; x < textures[i].m_Width; ++x)
{
const size_t invX = y;
const size_t invY = textures[i].m_Width - x - 1;
rotated[(y * textures[i].m_Width + x) * 4 + 0] = data[(invY * textures[i].m_Width + invX) * 4 + 0];
rotated[(y * textures[i].m_Width + x) * 4 + 1] = data[(invY * textures[i].m_Width + invX) * 4 + 1];
rotated[(y * textures[i].m_Width + x) * 4 + 2] = data[(invY * textures[i].m_Width + invX) * 4 + 2];
rotated[(y * textures[i].m_Width + x) * 4 + 3] = data[(invY * textures[i].m_Width + invX) * 4 + 3];
}
}
deviceCommandContext->UploadTexture(
- m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8,
+ m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
&rotated[0], textures[i].m_DataSize, 0, i);
}
else
{
deviceCommandContext->UploadTexture(
- m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8,
+ m_SkyCubeMap.get(), Renderer::Backend::Format::R8G8B8A8_UNORM,
data, textures[i].m_DataSize, 0, i);
}
}
///////////////////////////////////////////////////////////////////////////
}
void SkyManager::SetSkySet(const CStrW& newSet)
{
if (newSet == m_SkySet)
return;
m_SkyCubeMap.reset();
m_SkySet = newSet;
}
std::vector SkyManager::GetSkySets() const
{
std::vector skies;
// Find all subdirectories in art/textures/skies
const VfsPath path(L"art/textures/skies/");
DirectoryNames subdirectories;
if (g_VFS->GetDirectoryEntries(path, 0, &subdirectories) != INFO::OK)
{
LOGERROR("Error opening directory '%s'", path.string8());
return std::vector(1, GetSkySet()); // just return what we currently have
}
for(size_t i = 0; i < subdirectories.size(); i++)
skies.push_back(subdirectories[i].string());
sort(skies.begin(), skies.end());
return skies;
}
void SkyManager::RenderSky(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext)
{
GPU_SCOPED_LABEL(deviceCommandContext, "Render sky");
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
#warning TODO: implement SkyManager::RenderSky for GLES
#else
if (!m_RenderSky)
return;
// Do nothing unless SetSkySet was called
if (m_SkySet.empty() || !m_SkyCubeMap)
return;
if (m_VertexArray.GetNumberOfVertices() == 0)
CreateSkyCube();
const CCamera& camera = g_Renderer.GetSceneRenderer().GetViewCamera();
CShaderTechniquePtr skytech =
g_Renderer.GetShaderManager().LoadEffect(str_sky_simple);
skytech->BeginPass();
deviceCommandContext->SetGraphicsPipelineState(
skytech->GetGraphicsPipelineStateDesc());
const CShaderProgramPtr& shader = skytech->GetShader();
shader->BindTexture(str_baseTex, m_SkyCubeMap.get());
// Translate so the sky center is at the camera space origin.
CMatrix3D translate;
translate.SetTranslation(camera.GetOrientation().GetTranslation());
// Currently we have a hardcoded near plane in the projection matrix.
CMatrix3D scale;
scale.SetScaling(10.0f, 10.0f, 10.0f);
// Rotate so that the "left" face, which contains the brightest part of
// each skymap, is in the direction of the sun from our light
// environment.
CMatrix3D rotate;
rotate.SetYRotation(M_PI + g_Renderer.GetSceneRenderer().GetLightEnv().GetRotation());
shader->Uniform(
str_transform,
camera.GetViewProjection() * translate * rotate * scale);
m_VertexArray.PrepareForRendering();
u8* base = m_VertexArray.Bind(deviceCommandContext);
const GLsizei stride = static_cast(m_VertexArray.GetStride());
shader->VertexPointer(
3, GL_FLOAT, stride, base + m_AttributePosition.offset);
shader->TexCoordPointer(
GL_TEXTURE0, 3, GL_FLOAT, stride, base + m_AttributeUV.offset);
shader->AssertPointersBound();
deviceCommandContext->Draw(0, m_VertexArray.GetNumberOfVertices());
skytech->EndPass();
#endif
}
void SkyManager::CreateSkyCube()
{
m_AttributePosition.type = GL_FLOAT;
m_AttributePosition.elems = 3;
m_VertexArray.AddAttribute(&m_AttributePosition);
m_AttributeUV.type = GL_FLOAT;
m_AttributeUV.elems = 3;
m_VertexArray.AddAttribute(&m_AttributeUV);
// 6 sides of cube with 6 vertices.
m_VertexArray.SetNumberOfVertices(6 * 6);
m_VertexArray.Layout();
VertexArrayIterator attrPosition = m_AttributePosition.GetIterator();
VertexArrayIterator attrUV = m_AttributeUV.GetIterator();
#define ADD_VERTEX(U, V, W, VX, VY, VZ) \
STMT( \
attrPosition->X = VX; \
attrPosition->Y = VY; \
attrPosition->Z = VZ; \
++attrPosition; \
attrUV->X = U; \
attrUV->Y = V; \
attrUV->Z = W; \
++attrUV;)
// Axis -X
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
// Axis +X
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
// Axis -Y
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
// Axis +Y
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
// Axis -Z
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, +1, +1, -1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, +1, +1, +1.0f, -1.0f, -1.0f);
ADD_VERTEX(+1, -1, +1, -1.0f, +1.0f, -1.0f);
ADD_VERTEX(-1, -1, +1, +1.0f, +1.0f, -1.0f);
// Axis +Z
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, +1, -1, +1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, +1, -1, -1.0f, -1.0f, +1.0f);
ADD_VERTEX(-1, -1, -1, +1.0f, +1.0f, +1.0f);
ADD_VERTEX(+1, -1, -1, -1.0f, +1.0f, +1.0f);
#undef ADD_VERTEX
m_VertexArray.Upload();
m_VertexArray.FreeBackingStore();
}
Index: ps/trunk/source/renderer/TerrainOverlay.cpp
===================================================================
--- ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26587)
+++ ps/trunk/source/renderer/TerrainOverlay.cpp (revision 26588)
@@ -1,394 +1,394 @@
/* 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/backend/gl/Device.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)");
GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays before water");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderBeforeWater(deviceCommandContext);
}
void ITerrainOverlay::RenderOverlaysAfterWater(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext, int cullGroup)
{
if (g_TerrainOverlayList.empty())
return;
PROFILE3_GPU("terrain overlays (after)");
GPU_SCOPED_LABEL(deviceCommandContext, "Render terrain overlays after water");
for (size_t i = 0; i < g_TerrainOverlayList.size(); ++i)
g_TerrainOverlayList[i].first->RenderAfterWater(deviceCommandContext, cullGroup);
}
//////////////////////////////////////////////////////////////////////////
TerrainOverlay::TerrainOverlay(const CSimContext& simContext, int priority /* = 100 */)
: ITerrainOverlay(priority), m_Terrain(&simContext.GetTerrain())
{
}
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
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();
#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;
// To ensure that outlines are drawn on top of the terrain correctly (and
// don't Z-fight and flicker nastily), use detph bias to pull them towards
// the camera.
pipelineStateDesc.rasterizationState.depthBiasEnabled = true;
pipelineStateDesc.rasterizationState.depthBiasConstantFactor = -1.0f;
pipelineStateDesc.rasterizationState.depthBiasSlopeFactor = -1.0f;
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();
deviceCommandContext->Draw(0, vertices.size() / 3);
overlayTech->EndPass();
#endif
}
void TerrainOverlay::RenderTileOutline(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden)
{
RenderTileOutline(deviceCommandContext, color, drawHidden, m_i, m_j);
}
void TerrainOverlay::RenderTileOutline(
Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext,
const CColor& color, bool drawHidden, ssize_t i, ssize_t j)
{
#if CONFIG2_GLES
UNUSED2(deviceCommandContext);
UNUSED2(color);
UNUSED2(drawHidden);
UNUSED2(i);
UNUSED2(j);
#warning TODO: implement TerrainOverlay::RenderTileOutline for GLES
#else
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);
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;
pipelineStateDesc.rasterizationState.polygonMode = Renderer::Backend::PolygonMode::LINE;
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();
deviceCommandContext->Draw(0, vertices.size() / 3);
overlayTech->EndPass();
#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);
// Recreate the texture with new size if necessary
if (!m_Texture || m_Texture->GetWidth() != requiredWidth || m_Texture->GetHeight() != requiredHeight)
{
m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerrainOverlayTexture",
- Renderer::Backend::Format::R8G8B8A8, requiredWidth, requiredHeight,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, requiredWidth, requiredHeight,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
}
u8* data = (u8*)calloc(w * h, 4);
BuildTextureRGBA(data, w, h);
deviceCommandContext->UploadTextureRegion(
- m_Texture.get(), Renderer::Backend::Format::R8G8B8A8, data, w * h * 4, 0, 0, w, h);
+ m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, 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/WaterManager.cpp
===================================================================
--- ps/trunk/source/renderer/WaterManager.cpp (revision 26587)
+++ ps/trunk/source/renderer/WaterManager.cpp (revision 26588)
@@ -1,1024 +1,1024 @@
/* 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/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_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();
m_FancyEffectsFramebuffer.reset();
m_RefractionFramebuffer.reset();
m_ReflectionFramebuffer.reset();
m_FancyTexture.reset();
m_FancyTextureDepth.reset();
m_ReflFboDepthTexture.reset();
m_RefrFboDepthTexture.reset();
}
///////////////////////////////////////////////////////////////////
// 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.SetAddressMode(
Renderer::Backend::Sampler::AddressMode::REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_WaterTexture[i] = texture;
}
m_RenderWater = true;
if (!WillRenderFancyWater())
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.SetAddressMode(
Renderer::Backend::Sampler::AddressMode::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.SetAddressMode(
Renderer::Backend::Sampler::AddressMode::REPEAT);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
m_FoamTex = texture;
}
// Use screen-sized textures for minimum artifacts.
m_RefTextureSize = round_up_to_pow2(g_Renderer.GetHeight());
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
// Create reflection texture
m_ReflectionTexture = backendDevice->CreateTexture2D("WaterReflectionTexture",
- Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
// Create refraction texture
m_RefractionTexture = backendDevice->CreateTexture2D("WaterRefractionTexture",
- Renderer::Backend::Format::R8G8B8A8, m_RefTextureSize, m_RefTextureSize,
+ Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
// Create depth textures
m_ReflFboDepthTexture = backendDevice->CreateTexture2D("WaterReflectionDepthTexture",
Renderer::Backend::Format::D32, m_RefTextureSize, m_RefTextureSize,
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::NEAREST,
Renderer::Backend::Sampler::AddressMode::REPEAT));
m_RefrFboDepthTexture = backendDevice->CreateTexture2D("WaterRefractionDepthTexture",
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_ReflectionFramebuffer = backendDevice->CreateFramebuffer("ReflectionFramebuffer",
m_ReflectionTexture.get(), m_ReflFboDepthTexture.get(), CColor(0.5f, 0.5f, 1.0f, 0.0f));
if (!m_ReflectionFramebuffer)
{
g_RenderingOptions.SetWaterReflection(false);
UpdateQuality();
}
m_RefractionFramebuffer = backendDevice->CreateFramebuffer("RefractionFramebuffer",
m_RefractionTexture.get(), m_RefrFboDepthTexture.get(), CColor(1.0f, 0.0f, 0.0f, 0.0f));
if (!m_RefractionFramebuffer)
{
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
m_FancyEffectsFramebuffer = backendDevice->CreateFramebuffer("FancyEffectsFramebuffer",
m_FancyTexture.get(), m_FancyTextureDepth.get());
if (!m_FancyEffectsFramebuffer)
{
g_RenderingOptions.SetWaterRefraction(false);
UpdateQuality();
}
#endif
return 0;
}
///////////////////////////////////////////////////////////////////
// Resize: Updates the fancy water textures.
void WaterManager::Resize()
{
Renderer::Backend::GL::CDevice* backendDevice = g_VideoMode.GetBackendDevice();
// Create the Fancy Effects texture
m_FancyTexture = backendDevice->CreateTexture2D("WaterFancyTexture",
- Renderer::Backend::Format::R8G8B8A8, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
+ Renderer::Backend::Format::R8G8B8A8_UNORM, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::REPEAT));
m_FancyTextureDepth = backendDevice->CreateTexture2D("WaterFancyDepthTexture",
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.SetAddressMode(
Renderer::Backend::Sampler::AddressMode::REPEAT);
textureProps.SetAnisotropicFilter(true);
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();
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();
}
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()
{
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(),
Renderer::Backend::GL::CBuffer::Type::INDEX, false,
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(),
Renderer::Backend::GL::CBuffer::Type::VERTEX, false,
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)
{
GPU_SCOPED_LABEL(deviceCommandContext, "Render Waves");
#if CONFIG2_GLES
UNUSED2(frustrum);
#warning Fix WaterManager::RenderWaves on GLES
#else
if (!m_WaterFancyEffects)
return;
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();
m_WaveTex->UploadBackendTextureIfNeeded(deviceCommandContext);
m_FoamTex->UploadBackendTextureIfNeeded(deviceCommandContext);
shader->BindTexture(str_waveTex, m_WaveTex->GetBackendTexture());
shader->BindTexture(str_foamTex, m_FoamTex->GetBackendTexture());
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(deviceCommandContext);
// 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);
m_ShoreWavesVBIndices->m_Owner->UploadIfNeeded(deviceCommandContext);
deviceCommandContext->SetIndexBuffer(m_ShoreWavesVBIndices->m_Owner->GetBuffer());
deviceCommandContext->DrawIndexed(m_ShoreWavesVBIndices->m_Index, (m_ShoreWaves[a]->m_Width - 1) * (7 * 6), 0);
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(deviceCommandContext);
}
tech->EndPass();
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();
}
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/backend/Format.h
===================================================================
--- ps/trunk/source/renderer/backend/Format.h (revision 26587)
+++ ps/trunk/source/renderer/backend/Format.h (revision 26588)
@@ -1,51 +1,56 @@
/* 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_FORMAT
#define INCLUDED_RENDERER_BACKEND_FORMAT
namespace Renderer
{
namespace Backend
{
enum class Format
{
UNDEFINED,
- R8G8B8,
- R8G8B8A8,
+ R8G8B8_UNORM,
+ R8G8B8A8_UNORM,
- A8,
- L8,
+ A8_UNORM,
+ L8_UNORM,
+
+ R32_SFLOAT,
+ R32G32_SFLOAT,
+ R32G32B32_SFLOAT,
+ R32G32B32A32_SFLOAT,
D16,
D24,
D24_S8,
D32,
- BC1_RGB,
- BC1_RGBA,
- BC2,
- BC3
+ BC1_RGB_UNORM,
+ BC1_RGBA_UNORM,
+ BC2_UNORM,
+ BC3_UNORM
};
} // namespace Backend
} // namespace Renderer
#endif // INCLUDED_RENDERER_BACKEND_FORMAT
Index: ps/trunk/source/renderer/backend/gl/Device.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26587)
+++ ps/trunk/source/renderer/backend/gl/Device.cpp (revision 26588)
@@ -1,877 +1,883 @@
/* 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 "renderer/backend/gl/Texture.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;
}
void GLAD_API_PTR OnDebugMessage(
GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei UNUSED(length), const GLchar* message, const void* UNUSED(user_param))
{
std::string debugSource = "unknown";
std::string debugType = "unknown";
std::string debugSeverity = "unknown";
switch (source)
{
case GL_DEBUG_SOURCE_API:
debugSource = "the API";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
debugSource = "the window system";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
debugSource = "the shader compiler";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
debugSource = "a third party";
break;
case GL_DEBUG_SOURCE_APPLICATION:
debugSource = "the application";
break;
case GL_DEBUG_SOURCE_OTHER:
debugSource = "somewhere";
break;
}
switch (type)
{
case GL_DEBUG_TYPE_ERROR:
debugType = "error";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
debugType = "deprecated behaviour";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
debugType = "undefined behaviour";
break;
case GL_DEBUG_TYPE_PORTABILITY:
debugType = "portability";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
debugType = "performance";
break;
case GL_DEBUG_TYPE_OTHER:
debugType = "other";
break;
case GL_DEBUG_TYPE_MARKER:
debugType = "marker";
break;
case GL_DEBUG_TYPE_PUSH_GROUP:
debugType = "push group";
break;
case GL_DEBUG_TYPE_POP_GROUP:
debugType = "pop group";
break;
}
switch (severity)
{
case GL_DEBUG_SEVERITY_HIGH:
debugSeverity = "high";
break;
case GL_DEBUG_SEVERITY_MEDIUM:
debugSeverity = "medium";
break;
case GL_DEBUG_SEVERITY_LOW:
debugSeverity = "low";
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
debugSeverity = "notification";
break;
}
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
{
debug_printf(
"OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message);
}
else
{
LOGWARNING(
"OpenGL | %s: %s source: %s id %u: %s\n", debugSeverity.c_str(), debugType.c_str(), debugSource.c_str(), id, message);
}
}
} // 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);
glEnable(GL_TEXTURE_2D);
device->m_Backbuffer = CFramebuffer::CreateBackbuffer(device.get());
Capabilities& capabilities = device->m_Capabilities;
capabilities.ARBShaders = !ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", nullptr);
if (capabilities.ARBShaders)
capabilities.ARBShadersShadow = ogl_HaveExtension("GL_ARB_fragment_program_shadow");
#if CONFIG2_GLES
// Some GLES implementations have GL_EXT_texture_compression_dxt1
// but that only supports DXT1 so we can't use it.
capabilities.S3TC = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", nullptr) == 0;
#else
// Note: we don't bother checking for GL_S3_s3tc - it is incompatible
// and irrelevant (was never widespread).
capabilities.S3TC = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", nullptr) == 0;
#endif
#if CONFIG2_GLES
capabilities.multisampling = false;
capabilities.maxSampleCount = 1;
#else
capabilities.multisampling =
ogl_HaveVersion(3, 3) &&
ogl_HaveExtension("GL_ARB_multisample") &&
ogl_HaveExtension("GL_ARB_texture_multisample");
if (capabilities.multisampling)
{
// By default GL_MULTISAMPLE should be enabled, but enable it for buggy drivers.
glEnable(GL_MULTISAMPLE);
GLint maxSamples = 1;
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
capabilities.maxSampleCount = maxSamples;
}
#endif
capabilities.anisotropicFiltering = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic");
if (capabilities.anisotropicFiltering)
{
GLfloat maxAnisotropy = 1.0f;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
capabilities.maxAnisotropy = maxAnisotropy;
}
GLint maxTextureSize = 1024;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
capabilities.maxTextureSize = maxTextureSize;
#if CONFIG2_GLES
const bool isDebugInCore = ogl_HaveVersion(3, 2);
#else
const bool isDebugInCore = ogl_HaveVersion(4, 3);
#endif
const bool hasDebug = isDebugInCore || ogl_HaveExtension("GL_KHR_debug");
if (hasDebug)
{
#ifdef NDEBUG
bool enableDebugMessages = false;
CFG_GET_VAL("renderer.backend.debugmessages", enableDebugMessages);
capabilities.debugLabels = false;
CFG_GET_VAL("renderer.backend.debuglabels", capabilities.debugLabels);
capabilities.debugScopedLabels = false;
CFG_GET_VAL("renderer.backend.debugscopedlabels", capabilities.debugScopedLabels);
#else
const bool enableDebugMessages = true;
capabilities.debugLabels = true;
capabilities.debugScopedLabels = true;
#endif
if (enableDebugMessages)
{
glEnable(GL_DEBUG_OUTPUT);
#if !CONFIG2_GLES
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
#else
#warning GLES without GL_DEBUG_OUTPUT_SYNCHRONOUS might call the callback from different threads which might be unsafe.
#endif
glDebugMessageCallback(OnDebugMessage, nullptr);
// Filter out our own debug group messages
const GLuint id = 0x0AD;
glDebugMessageControl(
GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_PUSH_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE);
glDebugMessageControl(
GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_POP_GROUP, GL_DONT_CARE, 1, &id, GL_FALSE);
}
}
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()
{
std::unique_ptr commandContet = CDeviceCommandContext::Create(this);
m_ActiveCommandContext = commandContet.get();
return commandContet;
}
std::unique_ptr CDevice::CreateTexture(const char* name, const CTexture::Type type,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount)
{
return CTexture::Create(this, name, type,
format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount);
}
std::unique_ptr CDevice::CreateTexture2D(const char* name,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount)
{
return CreateTexture(name, CTexture::Type::TEXTURE_2D,
format, width, height, defaultSamplerDesc, MIPLevelCount, sampleCount);
}
std::unique_ptr CDevice::CreateFramebuffer(
const char* name, CTexture* colorAttachment,
CTexture* depthStencilAttachment)
{
return CreateFramebuffer(name, colorAttachment, depthStencilAttachment, CColor(0.0f, 0.0f, 0.0f, 0.0f));
}
std::unique_ptr CDevice::CreateFramebuffer(
const char* name, CTexture* colorAttachment,
CTexture* depthStencilAttachment, const CColor& clearColor)
{
return CFramebuffer::Create(this, name, colorAttachment, depthStencilAttachment, clearColor);
}
std::unique_ptr CDevice::CreateBuffer(
const char* name, const CBuffer::Type type, const uint32_t size, const bool dynamic)
{
return CBuffer::Create(this, name, type, size, dynamic);
}
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));
}
-bool CDevice::IsFormatSupported(const Format format) const
+bool CDevice::IsTextureFormatSupported(const Format format) const
{
bool supported = false;
switch (format)
{
case Format::UNDEFINED:
break;
- case Format::R8G8B8: FALLTHROUGH;
- case Format::R8G8B8A8: FALLTHROUGH;
- case Format::A8: FALLTHROUGH;
- case Format::L8:
+ case Format::R8G8B8_UNORM: FALLTHROUGH;
+ case Format::R8G8B8A8_UNORM: FALLTHROUGH;
+ case Format::A8_UNORM: FALLTHROUGH;
+ case Format::L8_UNORM:
supported = true;
break;
+ case Format::R32_SFLOAT: FALLTHROUGH;
+ case Format::R32G32_SFLOAT: FALLTHROUGH;
+ case Format::R32G32B32_SFLOAT: FALLTHROUGH;
+ case Format::R32G32B32A32_SFLOAT:
+ break;
+
case Format::D16: FALLTHROUGH;
case Format::D24: FALLTHROUGH;
case Format::D32:
supported = true;
break;
case Format::D24_S8:
#if !CONFIG2_GLES
supported = true;
#endif
break;
- case Format::BC1_RGB: FALLTHROUGH;
- case Format::BC1_RGBA: FALLTHROUGH;
- case Format::BC2: FALLTHROUGH;
- case Format::BC3:
+ case Format::BC1_RGB_UNORM: FALLTHROUGH;
+ case Format::BC1_RGBA_UNORM: FALLTHROUGH;
+ case Format::BC2_UNORM: FALLTHROUGH;
+ case Format::BC3_UNORM:
supported = m_Capabilities.S3TC;
break;
}
return supported;
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Device.h
===================================================================
--- ps/trunk/source/renderer/backend/gl/Device.h (revision 26587)
+++ ps/trunk/source/renderer/backend/gl/Device.h (revision 26588)
@@ -1,134 +1,134 @@
/* 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/Format.h"
#include "renderer/backend/gl/Buffer.h"
#include "renderer/backend/gl/Framebuffer.h"
#include "renderer/backend/gl/Texture.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:
struct Capabilities
{
bool S3TC;
bool ARBShaders;
bool ARBShadersShadow;
bool debugLabels;
bool debugScopedLabels;
bool multisampling;
bool anisotropicFiltering;
uint32_t maxSampleCount;
float maxAnisotropy;
uint32_t maxTextureSize;
};
~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();
CDeviceCommandContext* GetActiveCommandContext() { return m_ActiveCommandContext; }
std::unique_ptr CreateTexture(const char* name, const CTexture::Type type,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount);
std::unique_ptr CreateTexture2D(const char* name,
const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount = 1, const uint32_t sampleCount = 1);
std::unique_ptr CreateFramebuffer(
const char* name, CTexture* colorAttachment,
CTexture* depthStencilAttachment);
std::unique_ptr CreateFramebuffer(
const char* name, CTexture* colorAttachment,
CTexture* depthStencilAttachment, const CColor& clearColor);
std::unique_ptr CreateBuffer(
const char* name, const CBuffer::Type type, const uint32_t size, const bool dynamic);
void Present();
- bool IsFormatSupported(const Format format) const;
+ bool IsTextureFormatSupported(const Format format) const;
const Capabilities& GetCapabilities() const { return m_Capabilities; }
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;
// GL can have the only one command context at once.
// TODO: remove as soon as we have no GL code outside backend, currently
// it's used only as a helper for transition.
CDeviceCommandContext* m_ActiveCommandContext = nullptr;
std::unique_ptr m_Backbuffer;
Capabilities m_Capabilities{};
};
} // 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 26587)
+++ ps/trunk/source/renderer/backend/gl/DeviceCommandContext.cpp (revision 26588)
@@ -1,809 +1,809 @@
/* 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 "ps/CLogger.h"
#include "renderer/backend/gl/Buffer.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
#include
#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::Rect& lhs,
const CDeviceCommandContext::Rect& rhs)
{
return
lhs.x == rhs.x && lhs.y == rhs.y &&
lhs.width == rhs.width && lhs.height == rhs.height;
}
bool operator!=(
const CDeviceCommandContext::Rect& lhs,
const CDeviceCommandContext::Rect& 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);
}
GLenum BufferTypeToGLTarget(const CBuffer::Type type)
{
GLenum target = GL_ARRAY_BUFFER;
switch (type)
{
case CBuffer::Type::VERTEX:
target = GL_ARRAY_BUFFER;
break;
case CBuffer::Type::INDEX:
target = GL_ELEMENT_ARRAY_BUFFER;
break;
};
return target;
}
void UploadBufferRegionImpl(
const GLenum target, const uint32_t dataOffset, const uint32_t dataSize,
const CDeviceCommandContext::UploadBufferFunction& uploadFunction)
{
ENSURE(dataOffset < dataSize);
while (true)
{
void* mappedData = glMapBufferARB(target, GL_WRITE_ONLY);
if (mappedData == nullptr)
{
// This shouldn't happen unless we run out of virtual address space
LOGERROR("glMapBuffer failed");
break;
}
uploadFunction(static_cast(mappedData) + dataOffset);
if (glUnmapBufferARB(target) == GL_TRUE)
break;
// Unmap might fail on e.g. resolution switches, so just try again
// and hope it will eventually succeed
LOGMESSAGE("glUnmapBuffer failed, trying again...\n");
}
}
} // anonymous namespace
// static
std::unique_ptr CDeviceCommandContext::Create(CDevice* device)
{
std::unique_ptr deviceCommandContext(new CDeviceCommandContext(device));
deviceCommandContext->m_Framebuffer = device->GetCurrentBackbuffer();
deviceCommandContext->ResetStates();
return deviceCommandContext;
}
CDeviceCommandContext::CDeviceCommandContext(CDevice* device)
: m_Device(device)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
for (std::pair& unit : m_BoundTextures)
unit.first = unit.second = 0;
}
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,
std::max(1u, texture->GetWidth() >> level),
std::max(1u, texture->GetHeight() >> level),
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);
ENSURE(width > 0 && height > 0);
if (texture->GetType() == CTexture::Type::TEXTURE_2D)
{
ENSURE(layer == 0);
- if (texture->GetFormat() == Format::R8G8B8A8 ||
- texture->GetFormat() == Format::R8G8B8 ||
- texture->GetFormat() == Format::A8)
+ if (texture->GetFormat() == Format::R8G8B8A8_UNORM ||
+ texture->GetFormat() == Format::R8G8B8_UNORM ||
+ texture->GetFormat() == Format::A8_UNORM)
{
ENSURE(texture->GetFormat() == dataFormat);
size_t bytesPerPixel = 4;
GLenum pixelFormat = GL_RGBA;
switch (dataFormat)
{
- case Format::R8G8B8A8:
+ case Format::R8G8B8A8_UNORM:
break;
- case Format::R8G8B8:
+ case Format::R8G8B8_UNORM:
pixelFormat = GL_RGB;
bytesPerPixel = 3;
break;
- case Format::A8:
+ case Format::A8_UNORM:
pixelFormat = GL_ALPHA;
bytesPerPixel = 1;
break;
- case Format::L8:
+ case Format::L8_UNORM:
pixelFormat = GL_LUMINANCE;
bytesPerPixel = 1;
break;
default:
debug_warn("Unexpected format.");
break;
}
ENSURE(dataSize == width * height * bytesPerPixel);
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, level,
xOffset, yOffset, width, height,
pixelFormat, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
else if (
- texture->GetFormat() == Format::BC1_RGB ||
- texture->GetFormat() == Format::BC1_RGBA ||
- texture->GetFormat() == Format::BC2 ||
- texture->GetFormat() == Format::BC3)
+ texture->GetFormat() == Format::BC1_RGB_UNORM ||
+ texture->GetFormat() == Format::BC1_RGBA_UNORM ||
+ texture->GetFormat() == Format::BC2_UNORM ||
+ texture->GetFormat() == Format::BC3_UNORM)
{
ENSURE(xOffset == 0 && yOffset == 0);
ENSURE(texture->GetFormat() == dataFormat);
// TODO: add data size check.
GLenum internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
switch (texture->GetFormat())
{
- case Format::BC1_RGBA:
+ case Format::BC1_RGBA_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
- case Format::BC2:
+ case Format::BC2_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
- case Format::BC3:
+ case Format::BC3_UNORM:
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
break;
}
ScopedBind scopedBind(this, GL_TEXTURE_2D, texture->GetHandle());
glCompressedTexImage2DARB(GL_TEXTURE_2D, level, internalFormat, width, height, 0, dataSize, data);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else if (texture->GetType() == CTexture::Type::TEXTURE_CUBE)
{
- if (texture->GetFormat() == Format::R8G8B8A8)
+ if (texture->GetFormat() == Format::R8G8B8A8_UNORM)
{
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
};
ScopedBind scopedBind(this, GL_TEXTURE_CUBE_MAP, texture->GetHandle());
glTexImage2D(targets[layer], level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
ogl_WarnIfError();
}
else
debug_warn("Unsupported format");
}
else
debug_warn("Unsupported type");
}
void CDeviceCommandContext::UploadBuffer(CBuffer* buffer, const void* data, const uint32_t dataSize)
{
UploadBufferRegion(buffer, data, dataSize, 0);
}
void CDeviceCommandContext::UploadBuffer(
CBuffer* buffer, const UploadBufferFunction& uploadFunction)
{
UploadBufferRegion(buffer, 0, buffer->GetSize(), uploadFunction);
}
void CDeviceCommandContext::UploadBufferRegion(
CBuffer* buffer, const void* data, const uint32_t dataOffset, const uint32_t dataSize)
{
ENSURE(data);
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
glBindBufferARB(target, buffer->GetHandle());
if (buffer->IsDynamic())
{
// Tell the driver that it can reallocate the whole VBO
glBufferDataARB(target, buffer->GetSize(), nullptr, buffer->IsDynamic() ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
// (In theory, glMapBufferRange with GL_MAP_INVALIDATE_BUFFER_BIT could be used
// here instead of glBufferData(..., NULL, ...) plus glMapBuffer(), but with
// current Intel Windows GPU drivers (as of 2015-01) it's much faster if you do
// the explicit glBufferData.)
UploadBufferRegion(buffer, dataOffset, dataSize, [data, dataOffset, dataSize](u8* mappedData)
{
std::memcpy(mappedData, data, dataSize);
});
}
else
{
glBufferSubDataARB(target, dataOffset, dataSize, data);
}
glBindBufferARB(target, 0);
}
void CDeviceCommandContext::UploadBufferRegion(
CBuffer* buffer, const uint32_t dataOffset, const uint32_t dataSize,
const UploadBufferFunction& uploadFunction)
{
ENSURE(dataOffset + dataSize <= buffer->GetSize());
const GLenum target = BufferTypeToGLTarget(buffer->GetType());
glBindBufferARB(target, buffer->GetHandle());
ENSURE(buffer->IsDynamic());
UploadBufferRegionImpl(target, dataOffset, dataSize, uploadFunction);
glBindBufferARB(target, 0);
}
void CDeviceCommandContext::BeginScopedLabel(const char* name)
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
++m_ScopedLabelDepth;
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0x0AD, -1, name);
}
void CDeviceCommandContext::EndScopedLabel()
{
if (!m_Device->GetCapabilities().debugScopedLabels)
return;
ENSURE(m_ScopedLabelDepth > 0);
--m_ScopedLabelDepth;
glPopDebugGroup();
}
void CDeviceCommandContext::BindTexture(const uint32_t unit, const GLenum target, const GLuint handle)
{
ENSURE(unit < m_BoundTextures.size());
#if CONFIG2_GLES
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP);
#else
ENSURE(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP || target == GL_TEXTURE_2D_MULTISAMPLE);
#endif
if (m_BoundTextures[unit].first == target && m_BoundTextures[unit].second == handle)
return;
if (m_ActiveTextureUnit != unit)
{
glActiveTexture(GL_TEXTURE0 + unit);
m_ActiveTextureUnit = unit;
}
if (m_BoundTextures[unit].first != target && m_BoundTextures[unit].first && m_BoundTextures[unit].second)
glBindTexture(m_BoundTextures[unit].first, 0);
if (m_BoundTextures[unit].second != handle)
glBindTexture(target, handle);
m_BoundTextures[unit] = {target, handle};
}
void CDeviceCommandContext::BindBuffer(const CBuffer::Type type, CBuffer* buffer)
{
ENSURE(!buffer || buffer->GetType() == type);
if (type == CBuffer::Type::INDEX)
{
if (!buffer)
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
}
glBindBufferARB(BufferTypeToGLTarget(type), buffer ? buffer->GetHandle() : 0);
}
void CDeviceCommandContext::Flush()
{
ResetStates();
m_IndexBuffer = nullptr;
m_IndexBufferData = nullptr;
BindTexture(0, GL_TEXTURE_2D, 0);
BindBuffer(CBuffer::Type::INDEX, nullptr);
BindBuffer(CBuffer::Type::VERTEX, nullptr);
ENSURE(m_ScopedLabelDepth == 0);
}
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)
{
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)
{
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)
{
ApplyColorMask(nextBlendStateDesc.colorWriteMask);
}
const RasterizationStateDesc& currentRasterizationStateDesc =
m_GraphicsPipelineStateDesc.rasterizationState;
const RasterizationStateDesc& nextRasterizationStateDesc =
pipelineStateDesc.rasterizationState;
if (force ||
currentRasterizationStateDesc.polygonMode != nextRasterizationStateDesc.polygonMode)
{
#if !CONFIG2_GLES
glPolygonMode(
GL_FRONT_AND_BACK,
nextRasterizationStateDesc.polygonMode == PolygonMode::LINE ? GL_LINE : GL_FILL);
#endif
}
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);
}
#if !CONFIG2_GLES
if (force ||
currentRasterizationStateDesc.depthBiasEnabled != nextRasterizationStateDesc.depthBiasEnabled)
{
if (nextRasterizationStateDesc.depthBiasEnabled)
glEnable(GL_POLYGON_OFFSET_FILL);
else
glDisable(GL_POLYGON_OFFSET_FILL);
}
if (force ||
currentRasterizationStateDesc.depthBiasConstantFactor != nextRasterizationStateDesc.depthBiasConstantFactor ||
currentRasterizationStateDesc.depthBiasSlopeFactor != nextRasterizationStateDesc.depthBiasSlopeFactor)
{
glPolygonOffset(
nextRasterizationStateDesc.depthBiasSlopeFactor,
nextRasterizationStateDesc.depthBiasConstantFactor);
}
#endif
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 Rect* 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])
{
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;
}
void CDeviceCommandContext::SetViewports(const uint32_t viewportCount, const Rect* viewports)
{
ENSURE(viewportCount == 1);
glViewport(viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height);
}
void CDeviceCommandContext::SetIndexBuffer(CBuffer* buffer)
{
ENSURE(buffer->GetType() == CBuffer::Type::INDEX);
m_IndexBuffer = buffer;
m_IndexBufferData = nullptr;
BindBuffer(CBuffer::Type::INDEX, m_IndexBuffer);
}
void CDeviceCommandContext::SetIndexBufferData(const void* data)
{
if (m_IndexBuffer)
{
BindBuffer(CBuffer::Type::INDEX, nullptr);
m_IndexBuffer = nullptr;
}
m_IndexBufferData = data;
}
void CDeviceCommandContext::Draw(
const uint32_t firstVertex, const uint32_t vertexCount)
{
// Some drivers apparently don't like count = 0 in glDrawArrays here, so skip
// all drawing in that case.
if (vertexCount == 0)
return;
glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount);
}
void CDeviceCommandContext::DrawIndexed(
const uint32_t firstIndex, const uint32_t indexCount, const int32_t vertexOffset)
{
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
ENSURE(vertexOffset == 0);
if (m_IndexBuffer)
{
ENSURE(sizeof(uint16_t) * (firstIndex + indexCount) <= m_IndexBuffer->GetSize());
}
// Don't use glMultiDrawElements here since it doesn't have a significant
// performance impact and it suffers from various driver bugs (e.g. it breaks
// in Mesa 7.10 swrast with index VBOs).
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT,
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex)));
}
void CDeviceCommandContext::DrawIndexedInRange(
const uint32_t firstIndex, const uint32_t indexCount,
const uint32_t start, const uint32_t end)
{
if (indexCount == 0)
return;
ENSURE(m_IndexBuffer || m_IndexBufferData);
const void* indices =
static_cast((static_cast(m_IndexBufferData) + sizeof(uint16_t) * firstIndex));
// Draw with DrawRangeElements where available, since it might be more
// efficient for slow hardware.
#if CONFIG2_GLES
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);
#else
glDrawRangeElementsEXT(GL_TRIANGLES, start, end, indexCount, GL_UNSIGNED_SHORT, indices);
#endif
}
CDeviceCommandContext::ScopedBind::ScopedBind(
CDeviceCommandContext* deviceCommandContext,
const GLenum target, const GLuint handle)
: m_DeviceCommandContext(deviceCommandContext),
m_OldBindUnit(deviceCommandContext->m_BoundTextures[deviceCommandContext->m_ActiveTextureUnit])
{
m_DeviceCommandContext->BindTexture(
m_DeviceCommandContext->m_ActiveTextureUnit, target, handle);
}
CDeviceCommandContext::ScopedBind::~ScopedBind()
{
m_DeviceCommandContext->BindTexture(
m_DeviceCommandContext->m_ActiveTextureUnit, m_OldBindUnit.first, m_OldBindUnit.second);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer
Index: ps/trunk/source/renderer/backend/gl/Texture.cpp
===================================================================
--- ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26587)
+++ ps/trunk/source/renderer/backend/gl/Texture.cpp (revision 26588)
@@ -1,302 +1,304 @@
/* 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 "Texture.h"
#include "lib/code_annotation.h"
#include "lib/config2.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/backend/gl/DeviceCommandContext.h"
#include "renderer/backend/gl/Mapping.h"
#include
namespace Renderer
{
namespace Backend
{
namespace GL
{
namespace
{
GLint CalculateMinFilter(const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount)
{
if (MIPLevelCount == 1)
return defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST;
if (defaultSamplerDesc.minFilter == Sampler::Filter::LINEAR)
return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST;
return defaultSamplerDesc.mipFilter == Sampler::Filter::LINEAR ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST;
}
GLint AddressModeToGLEnum(Sampler::AddressMode addressMode)
{
switch (addressMode)
{
case Sampler::AddressMode::REPEAT: return GL_REPEAT;
case Sampler::AddressMode::MIRRORED_REPEAT: return GL_MIRRORED_REPEAT;
case Sampler::AddressMode::CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE;
case Sampler::AddressMode::CLAMP_TO_BORDER: return GL_CLAMP_TO_BORDER;
}
return GL_REPEAT;
}
GLenum TypeToGLEnum(CTexture::Type type)
{
GLenum target = GL_TEXTURE_2D;
switch (type)
{
case CTexture::Type::TEXTURE_2D:
target = GL_TEXTURE_2D;
break;
case CTexture::Type::TEXTURE_2D_MULTISAMPLE:
#if CONFIG2_GLES
ENSURE(false && "Multisample textures are unsupported on GLES");
#else
target = GL_TEXTURE_2D_MULTISAMPLE;
#endif
break;
case CTexture::Type::TEXTURE_CUBE:
target = GL_TEXTURE_CUBE_MAP;
break;
}
return target;
}
} // anonymous namespace
// static
std::unique_ptr CTexture::Create(CDevice* device, const char* name,
const Type type, const Format format, const uint32_t width, const uint32_t height,
const Sampler::Desc& defaultSamplerDesc, const uint32_t MIPLevelCount, const uint32_t sampleCount)
{
std::unique_ptr texture(new CTexture());
ENSURE(format != Format::UNDEFINED);
ENSURE(width > 0 && height > 0 && MIPLevelCount > 0);
ENSURE((type == Type::TEXTURE_2D_MULTISAMPLE && sampleCount > 1) || sampleCount == 1);
texture->m_Device = device;
texture->m_Format = format;
texture->m_Type = type;
texture->m_Width = width;
texture->m_Height = height;
texture->m_MIPLevelCount = MIPLevelCount;
glGenTextures(1, &texture->m_Handle);
ogl_WarnIfError();
const GLenum target = TypeToGLEnum(type);
texture->m_Device->GetActiveCommandContext()->BindTexture(0, target, texture->m_Handle);
// It's forbidden to set sampler state for multisample textures.
if (type != Type::TEXTURE_2D_MULTISAMPLE)
{
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, CalculateMinFilter(defaultSamplerDesc, MIPLevelCount));
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, defaultSamplerDesc.magFilter == Sampler::Filter::LINEAR ? GL_LINEAR : GL_NEAREST);
ogl_WarnIfError();
glTexParameteri(target, GL_TEXTURE_WRAP_S, AddressModeToGLEnum(defaultSamplerDesc.addressModeU));
glTexParameteri(target, GL_TEXTURE_WRAP_T, AddressModeToGLEnum(defaultSamplerDesc.addressModeV));
}
#if !CONFIG2_GLES
if (type == Type::TEXTURE_CUBE)
glTexParameteri(target, GL_TEXTURE_WRAP_R, AddressModeToGLEnum(defaultSamplerDesc.addressModeW));
#endif
ogl_WarnIfError();
#if !CONFIG2_GLES
glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, MIPLevelCount - 1);
if (defaultSamplerDesc.mipLODBias != 0.0f)
glTexParameteri(target, GL_TEXTURE_LOD_BIAS, defaultSamplerDesc.mipLODBias);
#endif // !CONFIG2_GLES
if (type == Type::TEXTURE_2D && defaultSamplerDesc.anisotropyEnabled &&
texture->m_Device->GetCapabilities().anisotropicFiltering)
{
const float maxAnisotropy = std::min(
defaultSamplerDesc.maxAnisotropy, texture->m_Device->GetCapabilities().maxAnisotropy);
glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);
}
if (defaultSamplerDesc.addressModeU == Sampler::AddressMode::CLAMP_TO_BORDER ||
defaultSamplerDesc.addressModeV == Sampler::AddressMode::CLAMP_TO_BORDER ||
defaultSamplerDesc.addressModeW == Sampler::AddressMode::CLAMP_TO_BORDER)
{
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, defaultSamplerDesc.borderColor.AsFloatArray());
}
ogl_WarnIfError();
if (type == CTexture::Type::TEXTURE_2D)
{
bool compressedFormat = false;
GLint internalFormat = GL_RGBA;
// Actually pixel data is nullptr so it doesn't make sense to account
// it, but in theory some buggy drivers might complain about invalid
// pixel format.
GLenum pixelFormat = GL_RGBA;
GLenum pixelType = GL_UNSIGNED_BYTE;
switch (format)
{
case Format::UNDEFINED:
debug_warn("Texture should defined format");
break;
- case Format::R8G8B8A8:
+ case Format::R8G8B8A8_UNORM:
break;
- case Format::R8G8B8:
+ case Format::R8G8B8_UNORM:
internalFormat = GL_RGB;
pixelFormat = GL_RGB;
pixelType = GL_UNSIGNED_BYTE;
break;
- case Format::A8:
+ case Format::A8_UNORM:
internalFormat = GL_ALPHA;
pixelFormat = GL_ALPHA;
pixelType = GL_UNSIGNED_BYTE;
break;
- case Format::L8:
+ case Format::L8_UNORM:
internalFormat = GL_LUMINANCE;
pixelFormat = GL_LUMINANCE;
pixelType = GL_UNSIGNED_BYTE;
break;
#if CONFIG2_GLES
// GLES requires pixel type == UNSIGNED_SHORT or UNSIGNED_INT for depth.
case Format::D16: FALLTHROUGH;
case Format::D24: FALLTHROUGH;
case Format::D32:
internalFormat = GL_DEPTH_COMPONENT;
pixelFormat = GL_DEPTH_COMPONENT;
pixelType = GL_UNSIGNED_SHORT;
break;
case Format::D24_S8:
debug_warn("Unsupported format");
break;
#else
case Format::D16:
internalFormat = GL_DEPTH_COMPONENT16;
pixelFormat = GL_DEPTH_COMPONENT;
pixelType = GL_UNSIGNED_SHORT;
break;
case Format::D24:
internalFormat = GL_DEPTH_COMPONENT24;
pixelFormat = GL_DEPTH_COMPONENT;
pixelType = GL_UNSIGNED_SHORT;
break;
case Format::D32:
internalFormat = GL_DEPTH_COMPONENT32;
pixelFormat = GL_DEPTH_COMPONENT;
pixelType = GL_UNSIGNED_SHORT;
break;
case Format::D24_S8:
internalFormat = GL_DEPTH24_STENCIL8_EXT;
pixelFormat = GL_DEPTH_STENCIL_EXT;
pixelType = GL_UNSIGNED_INT_24_8_EXT;
break;
#endif
- case Format::BC1_RGB: FALLTHROUGH;
- case Format::BC1_RGBA: FALLTHROUGH;
- case Format::BC2: FALLTHROUGH;
- case Format::BC3:
+ case Format::BC1_RGB_UNORM: FALLTHROUGH;
+ case Format::BC1_RGBA_UNORM: FALLTHROUGH;
+ case Format::BC2_UNORM: FALLTHROUGH;
+ case Format::BC3_UNORM:
compressedFormat = true;
break;
+ default:
+ debug_warn("Unsupported format.");
}
// glCompressedTexImage2D can't accept a null data, so we will initialize it during uploading.
if (!compressedFormat)
{
for (uint32_t level = 0; level < MIPLevelCount; ++level)
{
glTexImage2D(target, level, internalFormat,
std::max(1u, width >> level), std::max(1u, height >> level),
0, pixelFormat, pixelType, nullptr);
}
}
}
else if (type == CTexture::Type::TEXTURE_2D_MULTISAMPLE)
{
ENSURE(MIPLevelCount == 1);
#if !CONFIG2_GLES
- if (format == Format::R8G8B8A8)
+ if (format == Format::R8G8B8A8_UNORM)
{
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_RGBA8, width, height, GL_TRUE);
}
else if (format == Format::D24_S8)
{
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, GL_DEPTH24_STENCIL8_EXT, width, height, GL_TRUE);
}
else
#endif // !CONFIG2_GLES
{
debug_warn("Unsupported format");
}
}
#if !CONFIG2_GLES
if (format == Format::D16 || format == Format::D24 || format == Format::D32 ||
format == Format::D24_S8)
{
if (defaultSamplerDesc.compareEnabled)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
glTexParameteri(
GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC,
Mapping::FromCompareOp(defaultSamplerDesc.compareOp));
}
else
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
}
#endif
ogl_WarnIfError();
if (texture->m_Device->GetCapabilities().debugLabels)
{
glObjectLabel(GL_TEXTURE, texture->m_Handle, -1, name);
}
texture->m_Device->GetActiveCommandContext()->BindTexture(0, target, 0);
return texture;
}
CTexture::CTexture() = default;
CTexture::~CTexture()
{
if (m_Handle)
glDeleteTextures(1, &m_Handle);
}
} // namespace GL
} // namespace Backend
} // namespace Renderer