Index: ps/trunk/source/graphics/LOSTexture.cpp
===================================================================
--- ps/trunk/source/graphics/LOSTexture.cpp (revision 26142)
+++ ps/trunk/source/graphics/LOSTexture.cpp (revision 26143)
@@ -1,418 +1,418 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "LOSTexture.h"
#include "graphics/ShaderManager.h"
#include "lib/bits.h"
#include "lib/config2.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/TimeManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/helpers/Los.h"
/*
The LOS bitmap is computed with one value per LOS vertex, based on
CCmpRangeManager's visibility information.
The bitmap is then blurred using an NxN filter (in particular a
7-tap Binomial filter as an efficient integral approximation of a Gaussian).
To implement the blur efficiently without using extra memory for a second copy
of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side,
then the blur shifts the image back into the corner.
The blurred bitmap is then uploaded into a GL texture for use by the renderer.
*/
// Blur with a NxN filter, where N = g_BlurSize must be an odd number.
// Keep it in relation to the number of impassable tiles in MAP_EDGE_TILES.
static const size_t g_BlurSize = 7;
// Alignment (in bytes) of the pixel data passed into glTexSubImage2D.
// 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), m_Dirty(true), m_ShaderInitialized(false),
m_smoothFbo(0), m_MapSize(0), m_TextureSize(0), whichTex(true)
{
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
CreateShader();
}
CLOSTexture::~CLOSTexture()
{
if (m_smoothFbo)
glDeleteFramebuffersEXT(1, &m_smoothFbo);
if (m_Texture)
DeleteTexture();
}
// Create the LOS texture engine. Should be ran only once.
bool CLOSTexture::CreateShader()
{
m_smoothShader = g_Renderer.GetShaderManager().LoadEffect(str_los_interp);
CShaderProgramPtr shader = m_smoothShader->GetShader();
m_ShaderInitialized = m_smoothShader && shader;
if (!m_ShaderInitialized)
{
LOGERROR("Failed to load SmoothLOS shader, disabling.");
g_RenderingOptions.SetSmoothLOS(false);
return false;
}
glGenFramebuffersEXT(1, &m_smoothFbo);
return true;
}
void CLOSTexture::DeleteTexture()
{
m_Texture.reset();
m_TextureSmooth1.reset();
m_TextureSmooth2.reset();
}
void CLOSTexture::MakeDirty()
{
m_Dirty = true;
}
-GLuint CLOSTexture::GetTextureSmooth()
+Renderer::Backend::GL::CTexture* CLOSTexture::GetTextureSmooth()
{
if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS())
return GetTexture();
else
- return (whichTex ? m_TextureSmooth1 : m_TextureSmooth2)->GetHandle();
+ return (whichTex ? m_TextureSmooth1 : m_TextureSmooth2).get();
}
void CLOSTexture::InterpolateLOS()
{
if (CRenderer::IsInitialised() && !g_RenderingOptions.GetSmoothLOS())
return;
if (!m_ShaderInitialized)
{
if (!CreateShader())
return;
// RecomputeTexture(0) will not cause the ConstructTexture to run.
// Force the textures to be created.
DeleteTexture();
ConstructTexture();
m_Dirty = true;
}
if (m_Dirty)
{
RecomputeTexture();
m_Dirty = false;
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_smoothFbo);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
(whichTex ? m_TextureSmooth2 : m_TextureSmooth1)->GetHandle(), 0);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("LOS framebuffer object incomplete: 0x%04X", status);
}
m_smoothShader->BeginPass();
CShaderProgramPtr shader = m_smoothShader->GetShader();
glDisable(GL_BLEND);
shader->Bind();
- shader->BindTexture(str_losTex1, m_Texture->GetHandle());
- shader->BindTexture(str_losTex2, (whichTex ? m_TextureSmooth1 : m_TextureSmooth2)->GetHandle());
+ shader->BindTexture(str_losTex1, m_Texture.get());
+ shader->BindTexture(str_losTex2, (whichTex ? m_TextureSmooth1 : m_TextureSmooth2).get());
shader->Uniform(str_delta, (float)g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f, 0.0f, 0.0f, 0.0f);
const SViewPort oldVp = g_Renderer.GetViewport();
const SViewPort vp = { 0, 0, m_TextureSize, m_TextureSize };
g_Renderer.SetViewport(vp);
float quadVerts[] = {
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] = {
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
g_Renderer.SetViewport(oldVp);
shader->Unbind();
m_smoothShader->EndPass();
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
whichTex = !whichTex;
}
-GLuint CLOSTexture::GetTexture()
+Renderer::Backend::GL::CTexture* CLOSTexture::GetTexture()
{
if (m_Dirty)
{
RecomputeTexture();
m_Dirty = false;
}
- return m_Texture->GetHandle();
+ 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()
{
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
m_MapSize = cmpRangeManager->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment));
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
m_Texture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::A8, m_TextureSize, m_TextureSize, defaultSamplerDesc);
// Initialise texture with SoD color, for the areas we don't
// overwrite with glTexSubImage2D later
u8* texData = new u8[m_TextureSize * m_TextureSize * 4];
memset(texData, 0x00, m_TextureSize * m_TextureSize * 4);
if (CRenderer::IsInitialised() && g_RenderingOptions.GetSmoothLOS())
{
m_TextureSmooth1 = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, defaultSamplerDesc);
m_TextureSmooth2 = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, defaultSamplerDesc);
g_Renderer.BindTexture(0, m_TextureSmooth1->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
g_Renderer.BindTexture(0, m_TextureSmooth2->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
}
g_Renderer.BindTexture(0, m_Texture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
delete[] texData;
{
// 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) / (float)(m_TextureSize * (m_MapSize-1) * LOS_TILE_SIZE);
float t = 0.5f / m_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)m_TextureSize;
m_MinimapTextureMatrix.SetZero();
m_MinimapTextureMatrix._11 = s;
m_MinimapTextureMatrix._22 = s;
m_MinimapTextureMatrix._44 = 1;
}
}
void CLOSTexture::RecomputeTexture()
{
// 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();
recreated = true;
}
PROFILE("recompute LOS texture");
std::vector losData;
size_t pitch;
losData.resize(GetBitmapSize(m_MapSize, m_MapSize, &pitch));
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)
{
g_Renderer.BindTexture(0, m_TextureSmooth1->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]);
g_Renderer.BindTexture(0, m_TextureSmooth2->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]);
}
g_Renderer.BindTexture(0, m_Texture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]);
}
size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch)
{
*pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment);
return *pitch * (h + g_BlurSize - 1);
}
void CLOSTexture::GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch)
{
u8 *dataPtr = losData;
// Initialise the top padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
for (size_t j = 0; j < h; ++j)
{
// Initialise the left padding
for (size_t i = 0; i < g_BlurSize/2; ++i)
*dataPtr++ = 0;
// Fill in the visibility data
for (size_t i = 0; i < w; ++i)
{
if (los.IsVisible_UncheckedRange(i, j))
*dataPtr++ = 255;
else if (los.IsExplored_UncheckedRange(i, j))
*dataPtr++ = 127;
else
*dataPtr++ = 0;
}
// Initialise the right padding
for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i)
*dataPtr++ = 0;
}
// Initialise the bottom padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
// Horizontal blur:
for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0] +
6*d[1] +
15*d[2] +
20*d[3] +
15*d[4] +
6*d[5] +
1*d[6]
) / 64;
}
}
// Vertical blur:
for (size_t j = 0; j < h; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0*pitch] +
6*d[1*pitch] +
15*d[2*pitch] +
20*d[3*pitch] +
15*d[4*pitch] +
6*d[5*pitch] +
1*d[6*pitch]
) / 64;
}
}
}
Index: ps/trunk/source/graphics/LOSTexture.h
===================================================================
--- ps/trunk/source/graphics/LOSTexture.h (revision 26142)
+++ ps/trunk/source/graphics/LOSTexture.h (revision 26143)
@@ -1,103 +1,103 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_LOSTEXTURE
#define INCLUDED_LOSTEXTURE
#include "lib/ogl.h"
#include "graphics/ShaderTechniquePtr.h"
#include "maths/Matrix3D.h"
#include "renderer/backend/gl/Texture.h"
class CLosQuerier;
class CSimulation2;
/**
* Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for
* rendering and for the minimap.
*/
class CLOSTexture
{
NONCOPYABLE(CLOSTexture);
friend class TestLOSTexture;
public:
CLOSTexture(CSimulation2& simulation);
~CLOSTexture();
/**
* Marks the LOS texture as needing recomputation. Call this after each
* simulation update, to ensure responsive updates.
*/
void MakeDirty();
/**
* Recomputes the LOS texture if necessary, and returns the texture handle.
* Also potentially switches the current active texture unit, and enables texturing on it.
* The texture is in 8-bit ALPHA format.
*/
- GLuint GetTexture();
+ Renderer::Backend::GL::CTexture* GetTexture();
void InterpolateLOS();
- GLuint GetTextureSmooth();
+ Renderer::Backend::GL::CTexture* GetTextureSmooth();
/**
* Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture
* coordinates, in the form expected by a matrix uniform.
* This must only be called after BindTexture.
*/
const CMatrix3D& GetTextureMatrix();
/**
* Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture
* coordinates, in the form expected by a matrix uniform.
* This must only be called after BindTexture.
*/
const CMatrix3D* GetMinimapTextureMatrix();
private:
void DeleteTexture();
bool CreateShader();
void ConstructTexture();
void RecomputeTexture();
size_t GetBitmapSize(size_t w, size_t h, size_t* pitch);
void GenerateBitmap(const CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch);
CSimulation2& m_Simulation;
bool m_Dirty;
bool m_ShaderInitialized;
std::unique_ptr
m_Texture, m_TextureSmooth1, m_TextureSmooth2;
bool whichTex;
GLuint m_smoothFbo;
CShaderTechniquePtr m_smoothShader;
size_t m_MapSize; // vertexes per side
GLsizei m_TextureSize; // texels per side
CMatrix3D m_TextureMatrix;
CMatrix3D m_MinimapTextureMatrix;
};
#endif // INCLUDED_LOSTEXTURE
Index: ps/trunk/source/graphics/MiniMapTexture.cpp
===================================================================
--- ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26142)
+++ ps/trunk/source/graphics/MiniMapTexture.cpp (revision 26143)
@@ -1,528 +1,528 @@
/* Copyright (C) 2021 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 "lib/bits.h"
#include "lib/timer.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/Renderer.h"
#include "renderer/RenderingOptions.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/Object.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/system/ParamNode.h"
namespace
{
// Set max drawn entities to UINT16_MAX for now, which is more than enough
// TODO: we should be cleverer about drawing them to reduce clutter
const u16 MAX_ENTITIES_DRAWN = 65535;
const size_t FINAL_TEXTURE_SIZE = 512;
unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color >> 8) & 0xff) * x);
unsigned int b = unsigned(float((color >> 16) & 0xff) * x);
return (0xff000000 | b | g << 8 | r << 16);
}
void DrawTexture(CShaderProgramPtr shader)
{
const float quadUVs[] =
{
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f
};
const float quadVertices[] =
{
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadUVs);
shader->VertexPointer(3, GL_FLOAT, 0, quadVertices);
shader->AssertPointersBound();
if (!g_Renderer.DoSkipSubmit())
glDrawArrays(GL_TRIANGLES, 0, 6);
}
struct MinimapUnitVertex
{
// This struct is copyable for convenience and because to move is to copy for primitives.
u8 r, g, b, a;
float x, y;
};
// Adds a vertex to the passed VertexArray
static void inline addVertex(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.x;
(*attrPos)[1] = v.y;
++attrPos;
}
} // anonymous namespace
CMiniMapTexture::CMiniMapTexture(CSimulation2& simulation)
: m_Simulation(simulation), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW)
{
// Register Relax NG validator.
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
m_ShallowPassageHeight = GetShallowPassageHeight();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
}
m_HalfBlinkDuration = blinkDuration / 2.0;
m_AttributePos.type = GL_FLOAT;
m_AttributePos.elems = 2;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.type = GL_UNSIGNED_BYTE;
m_AttributeColor.elems = 4;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_VertexArray.Layout();
m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
*index++ = i;
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
}
CMiniMapTexture::~CMiniMapTexture()
{
DestroyTextures();
}
void CMiniMapTexture::Update(const float UNUSED(deltaRealTime))
{
if (m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight)
{
m_TerrainTextureDirty = true;
m_FinalTextureDirty = true;
}
}
void CMiniMapTexture::Render()
{
const CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
if (!terrain)
return;
if (!m_TerrainTexture)
CreateTextures(terrain);
if (m_TerrainTextureDirty)
RebuildTerrainTexture(terrain);
RenderFinalTexture();
}
void CMiniMapTexture::CreateTextures(const CTerrain* terrain)
{
DestroyTextures();
m_MapSize = terrain->GetVerticesPerSide();
m_TextureSize = static_cast(round_up_to_pow2(static_cast(m_MapSize)));
const Renderer::Backend::Sampler::Desc defaultSamplerDesc =
Renderer::Backend::Sampler::MakeDefaultSampler(
Renderer::Backend::Sampler::Filter::LINEAR,
Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
// Create terrain texture
m_TerrainTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, m_TextureSize, m_TextureSize, defaultSamplerDesc);
// Initialise texture with solid black, for the areas we don't
// overwrite with glTexSubImage2D later
u32* texData = new u32[m_TextureSize * m_TextureSize];
for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i)
texData[i] = 0xFF000000;
g_Renderer.BindTexture(0, m_TerrainTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
delete[] texData;
m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
m_FinalTexture = Renderer::Backend::GL::CTexture::Create2D(
Renderer::Backend::Format::R8G8B8A8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, defaultSamplerDesc);
g_Renderer.BindTexture(0, m_FinalTexture->GetHandle());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, FINAL_TEXTURE_SIZE, FINAL_TEXTURE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glGenFramebuffersEXT(1, &m_FinalTextureFBO);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FinalTextureFBO);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, m_FinalTexture->GetHandle(), 0);
GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("MiniMapTexture Framebuffer object incomplete (A): 0x%04X", status);
}
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}
void CMiniMapTexture::DestroyTextures()
{
m_TerrainTexture.reset();
m_FinalTexture.reset();
SAFE_ARRAY_DELETE(m_TerrainData);
}
void CMiniMapTexture::RebuildTerrainTexture(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.GetWaterManager()->m_WaterHeight;
m_TerrainTextureDirty = false;
for (u32 j = 0; j < height; ++j)
{
u32* dataPtr = m_TerrainData + ((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
g_Renderer.BindTexture(0, m_TerrainTexture->GetHandle());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
g_Renderer.BindTexture(0, 0);
}
void CMiniMapTexture::RenderFinalTexture()
{
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
const double currentTime = timer_Time();
const bool doUpdate = (currentTime - m_LastFinalTextureUpdate > 0.5) || m_FinalTextureDirty;
if (doUpdate)
m_LastFinalTextureUpdate = currentTime;
else
return;
m_FinalTextureDirty = false;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_FinalTextureFBO);
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 = (float)(m_MapSize - 1) / (float)m_TextureSize;
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, baseDefines);
tech->BeginPass();
shader = tech->GetShader();
if (m_TerrainTexture)
- shader->BindTexture(str_baseTex, m_TerrainTexture->GetHandle());
+ shader->BindTexture(str_baseTex, m_TerrainTexture.get());
CMatrix3D baseTransform;
baseTransform.SetIdentity();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
CMatrix3D terrainTransform;
terrainTransform.SetIdentity();
terrainTransform.Scale(texCoordMax, texCoordMax, 1.0f);
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, terrainTransform);
if (m_TerrainTexture)
DrawTexture(shader);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(1, 1, 1, 0);
// Draw territory boundaries
CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
shader->Uniform(str_transform, baseTransform);
CMatrix3D territoryTransform = *territoryTexture.GetMinimapTextureMatrix();
shader->Uniform(str_textureTransform, territoryTransform);
DrawTexture(shader);
glDisable(GL_BLEND);
glColorMask(0, 0, 0, 1);
shader->BindTexture(str_baseTex, losTexture.GetTexture());
CMatrix3D losTransform = *losTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, losTransform);
DrawTexture(shader);
tech->EndPass();
glColorMask(1, 1, 1, 1);
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, pointDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_pointSize, 9.0f);
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Convert world space coordinates into [0, 2].
const float unitScale = invTileMapSize;
unitMatrix.Scale(unitScale * 2.0f, unitScale * 2.0f, 1.0f);
// Offset the coordinates to [-1, 1].
unitMatrix.Translate(CVector3D(-1.0f, -1.0f, 0.0f));
shader->Uniform(str_transform, unitMatrix);
CSimulation2::InterfaceList ents = m_Simulation.GetEntitiesWithInterface(IID_Minimap);
if (doUpdate)
{
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
if (currentTime > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = currentTime + m_HalfBlinkDuration;
}
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, m_Simulation.GetSimContext().GetCurrentDisplayedPlayer());
if (vis != LosVisibility::HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat();
v.y = posZ.ToFloat();
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(currentTime, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
addVertex(v, attrColor, attrPos);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (const MinimapUnitVertex& vertex : pingingVertices)
{
addVertex(vertex, attrColor, attrPos);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
m_VertexArray.Upload();
}
m_VertexArray.PrepareForRendering();
if (m_EntitiesDrawn > 0)
{
glEnable(GL_SCISSOR_TEST);
glScissor(1, 1, FINAL_TEXTURE_SIZE - 1, FINAL_TEXTURE_SIZE - 1);
#if !CONFIG2_GLES
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
u8* indexBase = m_IndexArray.Bind();
u8* base = m_VertexArray.Bind();
const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
shader->AssertPointersBound();
if (!g_Renderer.DoSkipSubmit())
glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind();
#if !CONFIG2_GLES
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
glDisable(GL_SCISSOR_TEST);
}
tech->EndPass();
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
g_Renderer.SetViewport(oldViewPort);
}
// static
float CMiniMapTexture::GetShallowPassageHeight()
{
float shallowPassageHeight = 0.0f;
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
return shallowPassageHeight;
}
Index: ps/trunk/source/graphics/MiniMapTexture.h
===================================================================
--- ps/trunk/source/graphics/MiniMapTexture.h (revision 26142)
+++ ps/trunk/source/graphics/MiniMapTexture.h (revision 26143)
@@ -1,96 +1,96 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_MINIMAPTEXTURE
#define INCLUDED_MINIMAPTEXTURE
#include "lib/ogl.h"
#include "renderer/backend/gl/Texture.h"
#include "renderer/VertexArray.h"
class CSimulation2;
class CTerrain;
class CMiniMapTexture
{
NONCOPYABLE(CMiniMapTexture);
public:
CMiniMapTexture(CSimulation2& simulation);
~CMiniMapTexture();
/**
* Marks the texture as dirty if it's old enough to redraw it on Render.
*/
void Update(const float deltaRealTime);
/**
* Redraws the texture if it's dirty.
*/
void Render();
- GLuint GetTexture() const { return m_FinalTexture->GetHandle(); }
+ Renderer::Backend::GL::CTexture* GetTexture() const { return m_FinalTexture.get(); }
/**
* @return The maximum height for unit passage in water.
*/
static float GetShallowPassageHeight();
private:
void CreateTextures(const CTerrain* terrain);
void DestroyTextures();
void RebuildTerrainTexture(const CTerrain* terrain);
void RenderFinalTexture();
CSimulation2& m_Simulation;
bool m_TerrainTextureDirty = true;
bool m_FinalTextureDirty = true;
double m_LastFinalTextureUpdate = 0.0;
// minimap texture handles
std::unique_ptr
m_TerrainTexture, m_FinalTexture;
GLuint m_FinalTextureFBO = 0;
// texture data
u32* m_TerrainData = nullptr;
// map size
ssize_t m_MapSize = 0;
// texture size
GLsizei m_TextureSize = 0;
// Maximal water height to allow the passage of a unit (for underwater shallows).
float m_ShallowPassageHeight = 0.0f;
float m_WaterHeight = 0.0f;
VertexIndexArray m_IndexArray;
VertexArray m_VertexArray;
VertexArray::Attribute m_AttributePos;
VertexArray::Attribute m_AttributeColor;
size_t m_EntitiesDrawn = 0;
double m_PingDuration = 25.0;
double m_HalfBlinkDuration = 0.0;
double m_NextBlinkTime = 0.0;
bool m_BlinkState = false;
};
#endif // INCLUDED_MINIMAPTEXTURE
Index: ps/trunk/source/graphics/ShaderProgram.cpp
===================================================================
--- ps/trunk/source/graphics/ShaderProgram.cpp (revision 26142)
+++ ps/trunk/source/graphics/ShaderProgram.cpp (revision 26143)
@@ -1,930 +1,940 @@
/* Copyright (C) 2021 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 "ShaderProgram.h"
#include "graphics/Color.h"
#include "graphics/PreprocessorWrapper.h"
#include "graphics/ShaderManager.h"
#include "graphics/TextureManager.h"
#include "lib/timer.h"
#include "lib/res/graphics/ogl_tex.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include
#if !CONFIG2_GLES
class CShaderProgramARB : public CShaderProgram
{
public:
CShaderProgramARB(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexIndexes, const std::map& fragmentIndexes,
int streamflags) :
CShaderProgram(streamflags),
m_VertexFile(vertexFile), m_FragmentFile(fragmentFile),
m_Defines(defines),
m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes)
{
glGenProgramsARB(1, &m_VertexProgram);
glGenProgramsARB(1, &m_FragmentProgram);
}
~CShaderProgramARB()
{
Unload();
glDeleteProgramsARB(1, &m_VertexProgram);
glDeleteProgramsARB(1, &m_FragmentProgram);
}
bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code)
{
ogl_WarnIfError();
glBindProgramARB(target, program);
ogl_WarnIfError();
glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str());
if (ogl_SquelchError(GL_INVALID_OPERATION))
{
GLint errPos = 0;
glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos);
int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1;
char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB);
LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr);
return false;
}
glBindProgramARB(target, 0);
ogl_WarnIfError();
return true;
}
void Reload() override
{
Unload();
CVFSFile vertexFile;
if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK)
return;
CVFSFile fragmentFile;
if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK)
return;
CPreprocessorWrapper preprocessor;
preprocessor.AddDefines(m_Defines);
CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString());
CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString());
if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, m_VertexFile, vertexCode))
return;
if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, m_FragmentFile, fragmentCode))
return;
m_IsValid = true;
}
void Unload()
{
m_IsValid = false;
}
void Bind() override
{
glEnable(GL_VERTEX_PROGRAM_ARB);
glEnable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram);
BindClientStates();
}
void Unbind() override
{
glDisable(GL_VERTEX_PROGRAM_ARB);
glDisable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0);
UnbindClientStates();
// TODO: should unbind textures, probably
}
int GetUniformVertexIndex(CStrIntern id)
{
std::map::iterator it = m_VertexIndexes.find(id);
if (it == m_VertexIndexes.end())
return -1;
return it->second;
}
frag_index_pair_t GetUniformFragmentIndex(CStrIntern id)
{
std::map::iterator it = m_FragmentIndexes.find(id);
if (it == m_FragmentIndexes.end())
return std::make_pair(-1, 0);
return it->second;
}
Binding GetTextureBinding(texture_id_t id) override
{
frag_index_pair_t fPair = GetUniformFragmentIndex(id);
int index = fPair.first;
if (index == -1)
return Binding();
else
return Binding((int)fPair.second, index);
}
void BindTexture(texture_id_t id, GLuint tex) override
{
frag_index_pair_t fPair = GetUniformFragmentIndex(id);
int index = fPair.first;
if (index != -1)
{
glActiveTextureARB(GL_TEXTURE0 + index);
glBindTexture(fPair.second, tex);
}
}
void BindTexture(Binding id, GLuint tex) override
{
int index = id.second;
if (index != -1)
{
glActiveTextureARB(GL_TEXTURE0 + index);
glBindTexture(id.first, tex);
}
}
Binding GetUniformBinding(uniform_id_t id) override
{
return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first);
}
void Uniform(Binding id, float v0, float v1, float v2, float v3) override
{
if (id.first != -1)
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3);
if (id.second != -1)
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3);
}
void Uniform(Binding id, const CMatrix3D& v) override
{
if (id.first != -1)
{
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14);
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24);
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34);
glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44);
}
if (id.second != -1)
{
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34);
glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44);
}
}
void Uniform(Binding id, size_t count, const CMatrix3D* v) override
{
ENSURE(count == 1);
Uniform(id, v[0]);
}
void Uniform(Binding id, size_t count, const float* v) override
{
ENSURE(count == 4);
Uniform(id, v[0], v[1], v[2], v[3]);
}
std::vector GetFileDependencies() const override
{
return {m_VertexFile, m_FragmentFile};
}
private:
VfsPath m_VertexFile;
VfsPath m_FragmentFile;
CShaderDefines m_Defines;
GLuint m_VertexProgram;
GLuint m_FragmentProgram;
std::map m_VertexIndexes;
// pair contains
std::map m_FragmentIndexes;
};
#endif // #if !CONFIG2_GLES
//////////////////////////////////////////////////////////////////////////
TIMER_ADD_CLIENT(tc_ShaderGLSLCompile);
TIMER_ADD_CLIENT(tc_ShaderGLSLLink);
class CShaderProgramGLSL : public CShaderProgram
{
public:
CShaderProgramGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexAttribs,
int streamflags) :
CShaderProgram(streamflags),
m_VertexFile(vertexFile), m_FragmentFile(fragmentFile),
m_Defines(defines),
m_VertexAttribs(vertexAttribs)
{
m_Program = 0;
m_VertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER);
m_FragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER);
m_FileDependencies = {m_VertexFile, m_FragmentFile};
}
~CShaderProgramGLSL()
{
Unload();
glDeleteShader(m_VertexShader);
glDeleteShader(m_FragmentShader);
}
bool Compile(GLhandleARB shader, const VfsPath& file, const CStr& code)
{
TIMER_ACCRUE(tc_ShaderGLSLCompile);
ogl_WarnIfError();
const char* code_string = code.c_str();
GLint code_length = code.length();
glShaderSourceARB(shader, 1, &code_string, &code_length);
glCompileShaderARB(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
GLint length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
// Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0
// (http://code.google.com/p/android/issues/detail?id=9953)
if (!ok && length == 0)
length = 4096;
if (length > 1)
{
char* infolog = new char[length];
glGetShaderInfoLog(shader, length, NULL, infolog);
if (ok)
LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog);
else
LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog);
delete[] infolog;
}
ogl_WarnIfError();
return (ok ? true : false);
}
bool Link()
{
TIMER_ACCRUE(tc_ShaderGLSLLink);
ENSURE(!m_Program);
m_Program = glCreateProgramObjectARB();
glAttachObjectARB(m_Program, m_VertexShader);
ogl_WarnIfError();
glAttachObjectARB(m_Program, m_FragmentShader);
ogl_WarnIfError();
// Set up the attribute bindings explicitly, since apparently drivers
// don't always pick the most efficient bindings automatically,
// and also this lets us hardcode indexes into VertexPointer etc
for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
glBindAttribLocationARB(m_Program, it->second, it->first.c_str());
glLinkProgramARB(m_Program);
GLint ok = 0;
glGetProgramiv(m_Program, GL_LINK_STATUS, &ok);
GLint length = 0;
glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length);
if (!ok && length == 0)
length = 4096;
if (length > 1)
{
char* infolog = new char[length];
glGetProgramInfoLog(m_Program, length, NULL, infolog);
if (ok)
LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog);
else
LOGERROR("Failed to link program '%s'+'%s':\n%s", m_VertexFile.string8(), m_FragmentFile.string8(), infolog);
delete[] infolog;
}
ogl_WarnIfError();
if (!ok)
return false;
m_Uniforms.clear();
m_Samplers.clear();
Bind();
ogl_WarnIfError();
GLint numUniforms = 0;
glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms);
ogl_WarnIfError();
for (GLint i = 0; i < numUniforms; ++i)
{
char name[256] = {0};
GLsizei nameLength = 0;
GLint size = 0;
GLenum type = 0;
glGetActiveUniformARB(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name);
ogl_WarnIfError();
GLint loc = glGetUniformLocationARB(m_Program, name);
CStrIntern nameIntern(name);
m_Uniforms[nameIntern] = std::make_pair(loc, type);
// Assign sampler uniforms to sequential texture units
if (type == GL_SAMPLER_2D
|| type == GL_SAMPLER_CUBE
#if !CONFIG2_GLES
|| type == GL_SAMPLER_2D_SHADOW
#endif
)
{
int unit = (int)m_Samplers.size();
m_Samplers[nameIntern].first = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D);
m_Samplers[nameIntern].second = unit;
glUniform1iARB(loc, unit); // link uniform to unit
ogl_WarnIfError();
}
}
// TODO: verify that we're not using more samplers than is supported
Unbind();
ogl_WarnIfError();
return true;
}
void Reload() override
{
Unload();
CVFSFile vertexFile;
if (vertexFile.Load(g_VFS, m_VertexFile) != PSRETURN_OK)
return;
CVFSFile fragmentFile;
if (fragmentFile.Load(g_VFS, m_FragmentFile) != PSRETURN_OK)
return;
std::vector newFileDependencies = {m_VertexFile, m_FragmentFile};
CPreprocessorWrapper preprocessor([&newFileDependencies](const CStr& includePath, CStr& out) -> bool {
const VfsPath includeFilePath(L"shaders/glsl/" + wstring_from_utf8(includePath));
// Add dependencies anyway to reload the shader when the file is
// appeared.
newFileDependencies.push_back(includeFilePath);
CVFSFile includeFile;
if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK)
return false;
out = includeFile.GetAsString();
return true;
});
preprocessor.AddDefines(m_Defines);
#if CONFIG2_GLES
// GLES defines the macro "GL_ES" in its GLSL preprocessor,
// but since we run our own preprocessor first, we need to explicitly
// define it here
preprocessor.AddDefine("GL_ES", "1");
#endif
CStr vertexCode = preprocessor.Preprocess(vertexFile.GetAsString());
CStr fragmentCode = preprocessor.Preprocess(fragmentFile.GetAsString());
m_FileDependencies = std::move(newFileDependencies);
if (vertexCode.empty())
LOGERROR("Failed to preprocess vertex shader: '%s'", m_VertexFile.string8());
if (fragmentCode.empty())
LOGERROR("Failed to preprocess fragment shader: '%s'", m_FragmentFile.string8());
#if CONFIG2_GLES
// Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00,
// and also to set default float precision for fragment shaders
vertexCode.Replace("#version 110\n", "#version 100\n");
vertexCode.Replace("#version 110\r\n", "#version 100\n");
vertexCode.Replace("#version 120\n", "#version 100\n");
vertexCode.Replace("#version 120\r\n", "#version 100\n");
fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n");
fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n");
fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n");
fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n");
#endif
if (!Compile(m_VertexShader, m_VertexFile, vertexCode))
return;
if (!Compile(m_FragmentShader, m_FragmentFile, fragmentCode))
return;
if (!Link())
return;
m_IsValid = true;
}
void Unload()
{
m_IsValid = false;
if (m_Program)
glDeleteProgram(m_Program);
m_Program = 0;
// The shader objects can be reused and don't need to be deleted here
}
void Bind() override
{
glUseProgramObjectARB(m_Program);
for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
glEnableVertexAttribArrayARB(it->second);
}
void Unbind() override
{
glUseProgramObjectARB(0);
for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it)
glDisableVertexAttribArrayARB(it->second);
// TODO: should unbind textures, probably
}
Binding GetTextureBinding(texture_id_t id) override
{
std::map >::iterator it = m_Samplers.find(CStrIntern(id));
if (it == m_Samplers.end())
return Binding();
else
return Binding((int)it->second.first, it->second.second);
}
void BindTexture(texture_id_t id, GLuint tex) override
{
std::map >::iterator it = m_Samplers.find(CStrIntern(id));
if (it == m_Samplers.end())
return;
glActiveTextureARB(GL_TEXTURE0 + it->second.second);
glBindTexture(it->second.first, tex);
}
void BindTexture(Binding id, GLuint tex) override
{
if (id.second == -1)
return;
glActiveTextureARB(GL_TEXTURE0 + id.second);
glBindTexture(id.first, tex);
}
Binding GetUniformBinding(uniform_id_t id) override
{
std::map >::iterator it = m_Uniforms.find(id);
if (it == m_Uniforms.end())
return Binding();
else
return Binding(it->second.first, (int)it->second.second);
}
void Uniform(Binding id, float v0, float v1, float v2, float v3) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT)
glUniform1fARB(id.first, v0);
else if (id.second == GL_FLOAT_VEC2)
glUniform2fARB(id.first, v0, v1);
else if (id.second == GL_FLOAT_VEC3)
glUniform3fARB(id.first, v0, v1, v2);
else if (id.second == GL_FLOAT_VEC4)
glUniform4fARB(id.first, v0, v1, v2, v3);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)");
}
}
void Uniform(Binding id, const CMatrix3D& v) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT_MAT4)
glUniformMatrix4fvARB(id.first, 1, GL_FALSE, &v._11);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)");
}
}
void Uniform(Binding id, size_t count, const CMatrix3D* v) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT_MAT4)
glUniformMatrix4fvARB(id.first, count, GL_FALSE, &v->_11);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)");
}
}
void Uniform(Binding id, size_t count, const float* v) override
{
if (id.first != -1)
{
if (id.second == GL_FLOAT)
glUniform1fvARB(id.first, count, v);
else
LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float)");
}
}
// Map the various fixed-function Pointer functions onto generic vertex attributes
// (matching the attribute indexes from ShaderManager's ParseAttribSemantics):
void VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(0, size, type, GL_FALSE, stride, pointer);
m_ValidStreams |= STREAM_POS;
}
void NormalPointer(GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(2, 3, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer);
m_ValidStreams |= STREAM_NORMAL;
}
void ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(3, size, type, (type == GL_FLOAT ? GL_FALSE : GL_TRUE), stride, pointer);
m_ValidStreams |= STREAM_COLOR;
}
void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
glVertexAttribPointerARB(8 + texture - GL_TEXTURE0, size, type, GL_FALSE, stride, pointer);
m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0);
}
void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) override
{
std::map::iterator it = m_VertexAttribs.find(id);
if (it != m_VertexAttribs.end())
{
glVertexAttribPointerARB(it->second, size, type, normalized, stride, pointer);
}
}
void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer) override
{
std::map::iterator it = m_VertexAttribs.find(id);
if (it != m_VertexAttribs.end())
{
#if CONFIG2_GLES
debug_warn(L"glVertexAttribIPointer not supported on GLES");
#else
glVertexAttribIPointerEXT(it->second, size, type, stride, pointer);
#endif
}
}
std::vector GetFileDependencies() const override
{
return m_FileDependencies;
}
private:
VfsPath m_VertexFile;
VfsPath m_FragmentFile;
std::vector m_FileDependencies;
CShaderDefines m_Defines;
std::map m_VertexAttribs;
GLhandleARB m_Program;
GLhandleARB m_VertexShader;
GLhandleARB m_FragmentShader;
std::map > m_Uniforms;
std::map > m_Samplers; // texture target & unit chosen for each uniform sampler
};
//////////////////////////////////////////////////////////////////////////
CShaderProgram::CShaderProgram(int streamflags)
: m_IsValid(false), m_StreamFlags(streamflags), m_ValidStreams(0)
{
}
#if CONFIG2_GLES
/*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& UNUSED(defines),
const std::map& UNUSED(vertexIndexes), const std::map& UNUSED(fragmentIndexes),
int UNUSED(streamflags))
{
LOGERROR("CShaderProgram::ConstructARB: '%s'+'%s': ARB shaders not supported on this device",
vertexFile.string8(), fragmentFile.string8());
return NULL;
}
#else
/*static*/ CShaderProgram* CShaderProgram::ConstructARB(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexIndexes, const std::map& fragmentIndexes,
int streamflags)
{
return new CShaderProgramARB(vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags);
}
#endif
/*static*/ CShaderProgram* CShaderProgram::ConstructGLSL(const VfsPath& vertexFile, const VfsPath& fragmentFile,
const CShaderDefines& defines,
const std::map& vertexAttribs,
int streamflags)
{
return new CShaderProgramGLSL(vertexFile, fragmentFile, defines, vertexAttribs, streamflags);
}
bool CShaderProgram::IsValid() const
{
return m_IsValid;
}
int CShaderProgram::GetStreamFlags() const
{
return m_StreamFlags;
}
void CShaderProgram::BindTexture(texture_id_t id, const CTexturePtr& tex)
{
GLuint h;
ogl_tex_get_texture_id(tex->GetHandle(), &h);
BindTexture(id, h);
}
void CShaderProgram::BindTexture(Binding id, const CTexturePtr& tex)
{
GLuint h;
ogl_tex_get_texture_id(tex->GetHandle(), &h);
BindTexture(id, h);
}
+void CShaderProgram::BindTexture(texture_id_t id, const Renderer::Backend::GL::CTexture* tex)
+{
+ BindTexture(id, tex->GetHandle());
+}
+
+void CShaderProgram::BindTexture(Binding id, const Renderer::Backend::GL::CTexture* tex)
+{
+ BindTexture(id, tex->GetHandle());
+}
+
void CShaderProgram::Uniform(Binding id, int v)
{
Uniform(id, (float)v, (float)v, (float)v, (float)v);
}
void CShaderProgram::Uniform(Binding id, float v)
{
Uniform(id, v, v, v, v);
}
void CShaderProgram::Uniform(Binding id, float v0, float v1)
{
Uniform(id, v0, v1, 0.0f, 0.0f);
}
void CShaderProgram::Uniform(Binding id, const CVector3D& v)
{
Uniform(id, v.X, v.Y, v.Z, 0.0f);
}
void CShaderProgram::Uniform(Binding id, const CColor& v)
{
Uniform(id, v.r, v.g, v.b, v.a);
}
void CShaderProgram::Uniform(uniform_id_t id, int v)
{
Uniform(GetUniformBinding(id), (float)v, (float)v, (float)v, (float)v);
}
void CShaderProgram::Uniform(uniform_id_t id, float v)
{
Uniform(GetUniformBinding(id), v, v, v, v);
}
void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1)
{
Uniform(GetUniformBinding(id), v0, v1, 0.0f, 0.0f);
}
void CShaderProgram::Uniform(uniform_id_t id, const CVector3D& v)
{
Uniform(GetUniformBinding(id), v.X, v.Y, v.Z, 0.0f);
}
void CShaderProgram::Uniform(uniform_id_t id, const CColor& v)
{
Uniform(GetUniformBinding(id), v.r, v.g, v.b, v.a);
}
void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1, float v2, float v3)
{
Uniform(GetUniformBinding(id), v0, v1, v2, v3);
}
void CShaderProgram::Uniform(uniform_id_t id, const CMatrix3D& v)
{
Uniform(GetUniformBinding(id), v);
}
void CShaderProgram::Uniform(uniform_id_t id, size_t count, const CMatrix3D* v)
{
Uniform(GetUniformBinding(id), count, v);
}
void CShaderProgram::Uniform(uniform_id_t id, size_t count, const float* v)
{
Uniform(GetUniformBinding(id), count, v);
}
// These should all be overridden by CShaderProgramGLSL, and not used
// if a non-GLSL shader was loaded instead:
void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type),
GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("Shader type doesn't support VertexAttribPointer");
}
void CShaderProgram::VertexAttribIPointer(attrib_id_t UNUSED(id), GLint UNUSED(size), GLenum UNUSED(type),
GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("Shader type doesn't support VertexAttribIPointer");
}
#if CONFIG2_GLES
// These should all be overridden by CShaderProgramGLSL
// (GLES doesn't support any other types of shader program):
void CShaderProgram::VertexPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::VertexPointer should be overridden");
}
void CShaderProgram::NormalPointer(GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::NormalPointer should be overridden");
}
void CShaderProgram::ColorPointer(GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::ColorPointer should be overridden");
}
void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), GLint UNUSED(size), GLenum UNUSED(type), GLsizei UNUSED(stride), const void* UNUSED(pointer))
{
debug_warn("CShaderProgram::TexCoordPointer should be overridden");
}
#else
// These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders
// both use the fixed-function vertex attribute pointers so we'll share their
// definitions here:
void CShaderProgram::VertexPointer(GLint size, GLenum type, GLsizei stride, const void* pointer)
{
glVertexPointer(size, type, stride, pointer);
m_ValidStreams |= STREAM_POS;
}
void CShaderProgram::NormalPointer(GLenum type, GLsizei stride, const void* pointer)
{
glNormalPointer(type, stride, pointer);
m_ValidStreams |= STREAM_NORMAL;
}
void CShaderProgram::ColorPointer(GLint size, GLenum type, GLsizei stride, const void* pointer)
{
glColorPointer(size, type, stride, pointer);
m_ValidStreams |= STREAM_COLOR;
}
void CShaderProgram::TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer)
{
glClientActiveTextureARB(texture);
glTexCoordPointer(size, type, stride, pointer);
glClientActiveTextureARB(GL_TEXTURE0);
m_ValidStreams |= STREAM_UV0 << (texture - GL_TEXTURE0);
}
void CShaderProgram::BindClientStates()
{
ENSURE(m_StreamFlags == (m_StreamFlags & (STREAM_POS|STREAM_NORMAL|STREAM_COLOR|STREAM_UV0|STREAM_UV1)));
// Enable all the desired client states for non-GLSL rendering
if (m_StreamFlags & STREAM_POS) glEnableClientState(GL_VERTEX_ARRAY);
if (m_StreamFlags & STREAM_NORMAL) glEnableClientState(GL_NORMAL_ARRAY);
if (m_StreamFlags & STREAM_COLOR) glEnableClientState(GL_COLOR_ARRAY);
if (m_StreamFlags & STREAM_UV0)
{
glClientActiveTextureARB(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (m_StreamFlags & STREAM_UV1)
{
glClientActiveTextureARB(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0);
}
// Rendering code must subsequently call VertexPointer etc for all of the streams
// that were activated in this function, else AssertPointersBound will complain
// that some arrays were unspecified
m_ValidStreams = 0;
}
void CShaderProgram::UnbindClientStates()
{
if (m_StreamFlags & STREAM_POS) glDisableClientState(GL_VERTEX_ARRAY);
if (m_StreamFlags & STREAM_NORMAL) glDisableClientState(GL_NORMAL_ARRAY);
if (m_StreamFlags & STREAM_COLOR) glDisableClientState(GL_COLOR_ARRAY);
if (m_StreamFlags & STREAM_UV0)
{
glClientActiveTextureARB(GL_TEXTURE0);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (m_StreamFlags & STREAM_UV1)
{
glClientActiveTextureARB(GL_TEXTURE1);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0);
}
}
#endif // !CONFIG2_GLES
void CShaderProgram::AssertPointersBound()
{
ENSURE((m_StreamFlags & ~m_ValidStreams) == 0);
}
Index: ps/trunk/source/graphics/ShaderProgram.h
===================================================================
--- ps/trunk/source/graphics/ShaderProgram.h (revision 26142)
+++ ps/trunk/source/graphics/ShaderProgram.h (revision 26143)
@@ -1,206 +1,209 @@
/* Copyright (C) 2021 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_SHADERPROGRAM
#define INCLUDED_SHADERPROGRAM
#include "graphics/ShaderProgramPtr.h"
#include "graphics/Texture.h"
#include "lib/ogl.h"
#include "lib/file/vfs/vfs_path.h"
+#include "renderer/backend/gl/Texture.h"
#include