Index: ps/trunk/source/graphics/Model.cpp
===================================================================
--- ps/trunk/source/graphics/Model.cpp (revision 26121)
+++ ps/trunk/source/graphics/Model.cpp (revision 26122)
@@ -1,618 +1,617 @@
/* 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 "Model.h"
#include "graphics/Decal.h"
#include "graphics/MeshManager.h"
#include "graphics/ModelDef.h"
#include "graphics/ObjectEntry.h"
#include "graphics/SkeletonAnim.h"
#include "graphics/SkeletonAnimDef.h"
#include "graphics/SkeletonAnimManager.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Quaternion.h"
-#include "lib/res/h_mgr.h"
#include "lib/sysdep/rtl.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Profile.h"
#include "renderer/RenderingOptions.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/Simulation2.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor
CModel::CModel(CSkeletonAnimManager& skeletonAnimManager, CSimulation2& simulation)
: m_Flags(0), m_Anim(NULL), m_AnimTime(0), m_Simulation(simulation),
m_BoneMatrices(NULL), m_AmmoPropPoint(NULL), m_AmmoLoadedProp(0),
m_SkeletonAnimManager(skeletonAnimManager)
{
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Destructor
CModel::~CModel()
{
ReleaseData();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete anything allocated by the model
void CModel::ReleaseData()
{
rtl_FreeAligned(m_BoneMatrices);
for (size_t i = 0; i < m_Props.size(); ++i)
delete m_Props[i].m_Model;
m_Props.clear();
m_pModelDef = CModelDefPtr();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InitModel: setup model from given geometry
bool CModel::InitModel(const CModelDefPtr& modeldef)
{
// clean up any existing data first
ReleaseData();
m_pModelDef = modeldef;
size_t numBones = modeldef->GetNumBones();
if (numBones != 0)
{
size_t numBlends = modeldef->GetNumBlends();
// allocate matrices for bone transformations
// (one extra matrix is used for the special case of bind-shape relative weighting)
m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + 1 + numBlends), 16);
for (size_t i = 0; i < numBones + 1 + numBlends; ++i)
{
m_BoneMatrices[i].SetIdentity();
}
}
m_PositionValid = true;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcBound: calculate the world space bounds of this model
void CModel::CalcBounds()
{
// Need to calculate the object bounds first, if that hasn't already been done
if (! (m_Anim && m_Anim->m_AnimDef))
{
if (m_ObjectBounds.IsEmpty())
CalcStaticObjectBounds();
}
else
{
if (m_Anim->m_ObjectBounds.IsEmpty())
CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
m_ObjectBounds = m_Anim->m_ObjectBounds;
}
// Ensure the transform is set correctly before we use it
ValidatePosition();
// Now transform the object-space bounds to world-space bounds
m_ObjectBounds.Transform(GetTransform(), m_WorldBounds);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
void CModel::CalcStaticObjectBounds()
{
PROFILE2("CalcStaticObjectBounds");
m_pModelDef->GetMaxBounds(nullptr, !(m_Flags & MODELFLAG_NOLOOPANIMATION), m_ObjectBounds);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation
void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& result)
{
PROFILE2("CalcAnimatedObjectBounds");
m_pModelDef->GetMaxBounds(anim, !(m_Flags & MODELFLAG_NOLOOPANIMATION), result);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
const CBoundingBoxAligned CModel::GetWorldBoundsRec()
{
CBoundingBoxAligned bounds = GetWorldBounds();
for (size_t i = 0; i < m_Props.size(); ++i)
bounds += m_Props[i].m_Model->GetWorldBoundsRec();
return bounds;
}
const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec()
{
CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary
// now extend these bounds to include the props' selection bounds (if any)
for (size_t i = 0; i < m_Props.size(); ++i)
{
const Prop& prop = m_Props[i];
if (prop.m_Hidden || !prop.m_Selectable)
continue; // prop is hidden from rendering, so it also shouldn't be used for selection
CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec();
if (propSelectionBounds.IsEmpty())
continue; // submodel does not wish to participate in selection box, exclude it
// We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added
// to the bounds in our object-space. For that, we need the transform of the prop attachment point.
//
// We have the prop point information; however, it's not trivial to compute its exact location in our object-space
// since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of
// an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation
// system and are quite opaque to use from the outside (see @ref ValidatePosition).
//
// However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of
// our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's
// world-space transform as either
//
// T' = T x B x O
// or
// T' = T x O
//
// where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local
// offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to
// (taking into account animation and everything).
//
// From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So,
// all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily,
// this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch.
CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T'
propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T'
// Transform the prop's bounds into our object coordinate space
CBoundingBoxAligned transformedPropSelectionBounds;
propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds);
objBounds += transformedPropSelectionBounds;
}
return objBounds;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildAnimation: load raw animation frame animation from given file, and build a
// animation specific to this model
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos)
{
CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname);
if (!def)
return NULL;
CSkeletonAnim* anim = new CSkeletonAnim();
anim->m_Name = name;
anim->m_ID = ID;
anim->m_Frequency = frequency;
anim->m_AnimDef = def;
anim->m_Speed = speed;
if (actionpos == -1.f)
anim->m_ActionPos = -1.f;
else
anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration();
if (actionpos2 == -1.f)
anim->m_ActionPos2 = -1.f;
else
anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration();
if (soundpos == -1.f)
anim->m_SoundPos = -1.f;
else
anim->m_SoundPos = soundpos * anim->m_AnimDef->GetDuration();
anim->m_ObjectBounds.SetEmpty();
InvalidateBounds();
return anim;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Update: update this model to the given time, in msec
void CModel::UpdateTo(float time)
{
// update animation time, but don't calculate bone matrices - do that (lazily) when
// something requests them; that saves some calculation work for offscreen models,
// and also assures the world space, inverted bone matrices (required for normal
// skinning) are up to date with respect to m_Transform
m_AnimTime = time;
// mark vertices as dirty
SetDirty(RENDERDATA_UPDATE_VERTICES);
// mark matrices as dirty
InvalidatePosition();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InvalidatePosition
void CModel::InvalidatePosition()
{
m_PositionValid = false;
for (size_t i = 0; i < m_Props.size(); ++i)
m_Props[i].m_Model->InvalidatePosition();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ValidatePosition: ensure that current transform and bone matrices are both uptodate
void CModel::ValidatePosition()
{
if (m_PositionValid)
{
ENSURE(!m_Parent || m_Parent->m_PositionValid);
return;
}
if (m_Parent && !m_Parent->m_PositionValid)
{
// Make sure we don't base our calculations on
// a parent animation state that is out of date.
m_Parent->ValidatePosition();
// Parent will recursively call our validation.
ENSURE(m_PositionValid);
return;
}
if (m_Anim && m_BoneMatrices)
{
// PROFILE( "generating bone matrices" );
ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys());
m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
}
else if (m_BoneMatrices)
{
// Bones but no animation - probably a buggy actor forgot to set up the animation,
// so just render it in its bind pose
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
{
m_BoneMatrices[i].SetIdentity();
m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation);
m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation);
}
}
// For CPU skinning, we precompute as much as possible so that the only
// per-vertex work is a single matrix*vec multiplication.
// For GPU skinning, we try to minimise CPU work by doing most computation
// in the vertex shader instead.
// Using g_RenderingOptions to detect CPU vs GPU is a bit hacky,
// and this doesn't allow the setting to change at runtime, but there isn't
// an obvious cleaner way to determine what data needs to be computed,
// and GPU skinning is a rarely-used experimental feature anyway.
bool worldSpaceBoneMatrices = !g_RenderingOptions.GetGPUSkinning();
bool computeBlendMatrices = !g_RenderingOptions.GetGPUSkinning();
if (m_BoneMatrices && worldSpaceBoneMatrices)
{
// add world-space transformation to m_BoneMatrices
const CMatrix3D transform = GetTransform();
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
m_BoneMatrices[i].Concatenate(transform);
}
// our own position is now valid; now we can safely update our props' positions without fearing
// that doing so will cause a revalidation of this model (see recursion above).
m_PositionValid = true;
CMatrix3D translate;
CVector3D objTranslation = m_Transform.GetTranslation();
float objectHeight = 0.0f;
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (cmpTerrain)
objectHeight = cmpTerrain->GetExactGroundLevel(objTranslation.X, objTranslation.Z);
// Object height is incorrect for floating objects. We use water height instead.
CmpPtr cmpWaterManager(m_Simulation, SYSTEM_ENTITY);
if (cmpWaterManager)
{
float waterHeight = cmpWaterManager->GetExactWaterLevel(objTranslation.X, objTranslation.Z);
if (waterHeight >= objectHeight && m_Flags & MODELFLAG_FLOATONWATER)
objectHeight = waterHeight;
}
// re-position and validate all props
for (const Prop& prop : m_Props)
{
CMatrix3D proptransform = prop.m_Point->m_Transform;
if (prop.m_Point->m_BoneIndex != 0xff)
{
CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex];
if (!worldSpaceBoneMatrices)
boneMatrix.Concatenate(GetTransform());
proptransform.Concatenate(boneMatrix);
}
else
{
// not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin)
proptransform.Concatenate(m_Transform);
}
// Adjust prop height to terrain level when needed
if (cmpTerrain && (prop.m_MaxHeight != 0.f || prop.m_MinHeight != 0.f))
{
const CVector3D& propTranslation = proptransform.GetTranslation();
const float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.X, propTranslation.Z);
const float translateHeight = std::min(prop.m_MaxHeight, std::max(prop.m_MinHeight, propTerrain - objectHeight));
translate.SetTranslation(0.f, translateHeight, 0.f);
proptransform.Concatenate(translate);
}
prop.m_Model->SetTransform(proptransform);
prop.m_Model->ValidatePosition();
}
if (m_BoneMatrices)
{
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
{
m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i];
}
// Note: there is a special case of joint influence, in which the vertex
// is influenced by the bind-shape transform instead of a particular bone,
// which we indicate with the blending bone ID set to the total number
// of bones. But since we're skinning in world space, we use the model's
// world space transform and store that matrix in this special index.
// (see http://trac.wildfiregames.com/ticket/1012)
m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform;
if (computeBlendMatrices)
m_pModelDef->BlendBoneMatrices(m_BoneMatrices);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetAnimation: set the given animation as the current animation on this model;
// return false on error, else true
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once)
{
m_Anim = nullptr; // in case something fails
if (anim)
{
m_Flags &= ~MODELFLAG_NOLOOPANIMATION;
if (once)
m_Flags |= MODELFLAG_NOLOOPANIMATION;
// Not rigged or animation is not valid.
if (!m_BoneMatrices || !anim->m_AnimDef)
return false;
if (anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones())
{
LOGERROR("Mismatch between model's skeleton and animation's skeleton (%s.dae has %lu model bones while the animation %s has %lu animation keys.)",
m_pModelDef->GetName().string8().c_str() ,
static_cast(m_pModelDef->GetNumBones()),
anim->m_Name.c_str(),
static_cast(anim->m_AnimDef->GetNumKeys()));
return false;
}
// Reset the cached bounds when the animation is changed.
m_ObjectBounds.SetEmpty();
InvalidateBounds();
// Start anim from beginning.
m_AnimTime = 0;
}
m_Anim = anim;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CopyAnimation
void CModel::CopyAnimationFrom(CModel* source)
{
m_Anim = source->m_Anim;
m_AnimTime = source->m_AnimTime;
m_ObjectBounds.SetEmpty();
InvalidateBounds();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AddProp: add a prop to the model on the given point
void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight, float maxHeight, bool selectable)
{
// position model according to prop point position
// this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box
model->SetTransform(point->m_Transform);
model->m_Parent = this;
Prop prop;
prop.m_Point = point;
prop.m_Model = model;
prop.m_ObjectEntry = objectentry;
prop.m_MinHeight = minHeight;
prop.m_MaxHeight = maxHeight;
prop.m_Selectable = selectable;
m_Props.push_back(prop);
}
void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry)
{
AddProp(point, model, objectentry);
m_AmmoPropPoint = point;
m_AmmoLoadedProp = m_Props.size() - 1;
m_Props[m_AmmoLoadedProp].m_Hidden = true;
// we only need to invalidate the selection box here if it is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
void CModel::ShowAmmoProp()
{
if (m_AmmoPropPoint == NULL)
return;
// Show the ammo prop, hide all others on the same prop point
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Point == m_AmmoPropPoint)
m_Props[i].m_Hidden = (i != m_AmmoLoadedProp);
// we only need to invalidate the selection box here if it is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
void CModel::HideAmmoProp()
{
if (m_AmmoPropPoint == NULL)
return;
// Hide the ammo prop, show all others on the same prop point
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Point == m_AmmoPropPoint)
m_Props[i].m_Hidden = (i == m_AmmoLoadedProp);
// we only need to invalidate here if the selection box is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
CModelAbstract* CModel::FindFirstAmmoProp()
{
if (m_AmmoPropPoint)
return m_Props[m_AmmoLoadedProp].m_Model;
for (size_t i = 0; i < m_Props.size(); ++i)
{
CModel* propModel = m_Props[i].m_Model->ToCModel();
if (propModel)
{
CModelAbstract* model = propModel->FindFirstAmmoProp();
if (model)
return model;
}
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Clone: return a clone of this model
CModelAbstract* CModel::Clone() const
{
CModel* clone = new CModel(m_SkeletonAnimManager, m_Simulation);
clone->m_ObjectBounds = m_ObjectBounds;
clone->InitModel(m_pModelDef);
clone->SetMaterial(m_Material);
clone->SetAnimation(m_Anim);
clone->SetFlags(m_Flags);
for (size_t i = 0; i < m_Props.size(); i++)
{
// eek! TODO, RC - need to investigate shallow clone here
if (m_AmmoPropPoint && i == m_AmmoLoadedProp)
clone->AddAmmoProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry);
else
clone->AddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry, m_Props[i].m_MinHeight, m_Props[i].m_MaxHeight, m_Props[i].m_Selectable);
}
return clone;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetTransform: set the transform on this object, and reorientate props accordingly
void CModel::SetTransform(const CMatrix3D& transform)
{
// call base class to set transform on this object
CRenderableObject::SetTransform(transform);
InvalidatePosition();
}
//////////////////////////////////////////////////////////////////////////
void CModel::AddFlagsRec(int flags)
{
m_Flags |= flags;
if (flags & MODELFLAG_IGNORE_LOS)
{
m_Material.AddShaderDefine(str_IGNORE_LOS, str_1);
m_Material.RecomputeCombinedShaderDefines();
}
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Model->ToCModel())
m_Props[i].m_Model->ToCModel()->AddFlagsRec(flags);
}
void CModel::RemoveShadowsRec()
{
m_Flags &= ~MODELFLAG_CASTSHADOWS;
m_Material.AddShaderDefine(str_DISABLE_RECEIVE_SHADOWS, str_1);
m_Material.RecomputeCombinedShaderDefines();
for (size_t i = 0; i < m_Props.size(); ++i)
{
if (m_Props[i].m_Model->ToCModel())
m_Props[i].m_Model->ToCModel()->RemoveShadowsRec();
else if (m_Props[i].m_Model->ToCModelDecal())
m_Props[i].m_Model->ToCModelDecal()->RemoveShadows();
}
}
void CModel::SetMaterial(const CMaterial &material)
{
m_Material = material;
}
void CModel::SetPlayerID(player_id_t id)
{
CModelAbstract::SetPlayerID(id);
for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
it->m_Model->SetPlayerID(id);
}
void CModel::SetShadingColor(const CColor& color)
{
CModelAbstract::SetShadingColor(color);
for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
it->m_Model->SetShadingColor(color);
}
Index: ps/trunk/source/graphics/tests/test_TextureConverter.h
===================================================================
--- ps/trunk/source/graphics/tests/test_TextureConverter.h (revision 26121)
+++ ps/trunk/source/graphics/tests/test_TextureConverter.h (revision 26122)
@@ -1,96 +1,95 @@
/* 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 "lib/self_test.h"
#include "graphics/TextureConverter.h"
#include "lib/file/vfs/vfs.h"
-#include "lib/res/h_mgr.h"
#include "lib/tex/tex.h"
#include "ps/XML/Xeromyces.h"
class TestTextureConverter : public CxxTest::TestSuite
{
PIVFS m_VFS;
public:
void setUp()
{
DeleteDirectory(DataDir()/"_testcache"); // clean up in case the last test run failed
m_VFS = CreateVfs();
TS_ASSERT_OK(m_VFS->Mount(L"", DataDir() / "mods" / "_test.tex" / "", VFS_MOUNT_MUST_EXIST));
TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY));
}
void tearDown()
{
m_VFS.reset();
DeleteDirectory(DataDir()/"_testcache");
}
void test_convert_quality()
{
// Test for the bug in http://code.google.com/p/nvidia-texture-tools/issues/detail?id=139
VfsPath src = L"art/textures/b/test.png";
CTextureConverter converter(m_VFS, false);
CTextureConverter::Settings settings = converter.ComputeSettings(L"", std::vector());
TS_ASSERT(converter.ConvertTexture(CTexturePtr(), src, L"cache/test.png", settings));
VfsPath dest;
for (size_t i = 0; i < 100; ++i)
{
CTexturePtr texture;
bool ok;
if (converter.Poll(texture, dest, ok))
{
TS_ASSERT(ok);
break;
}
SDL_Delay(10);
}
std::shared_ptr file;
size_t fileSize = 0;
TS_ASSERT_OK(m_VFS->LoadFile(dest, file, fileSize));
Tex tex;
TS_ASSERT_OK(tex.decode(file, fileSize));
TS_ASSERT_OK(tex.transform_to((tex.m_Flags | TEX_BGR | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)));
u8* texdata = tex.get_data();
// The source texture is repeated after 4 pixels, so the compressed texture
// should be identical after 4 pixels
TS_ASSERT_EQUALS(texdata[0*4], texdata[4*4]);
// 1st, 2nd and 4th rows should be unequal
TS_ASSERT_DIFFERS(texdata[0*4], texdata[8*4]);
TS_ASSERT_EQUALS(texdata[8*4], texdata[16*4]);
TS_ASSERT_DIFFERS(texdata[16*4], texdata[24*4]);
// for (size_t i = 0; i < tex.dataSize; ++i)
// {
// if (i % 4 == 0) printf("\n");
// printf("%02x ", texdata[i]);
// }
}
};
Index: ps/trunk/source/gui/GUIRenderer.cpp
===================================================================
--- ps/trunk/source/gui/GUIRenderer.cpp (revision 26121)
+++ ps/trunk/source/gui/GUIRenderer.cpp (revision 26122)
@@ -1,350 +1,349 @@
/* 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 "GUIRenderer.h"
#include "graphics/Canvas2D.h"
#include "graphics/TextureManager.h"
#include "gui/CGUI.h"
#include "gui/CGUISprite.h"
#include "gui/GUIMatrix.h"
#include "gui/SettingTypes/CGUIColor.h"
#include "i18n/L10n.h"
#include "lib/ogl.h"
#include "lib/res/graphics/ogl_tex.h"
-#include "lib/res/h_mgr.h"
#include "lib/tex/tex.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/CStrInternStatic.h"
#include "ps/Filesystem.h"
#include "renderer/Renderer.h"
using namespace GUIRenderer;
DrawCalls::DrawCalls()
{
}
// DrawCalls needs to be copyable, so it can be used in other copyable types.
// But actually copying data is hard, since we'd need to avoid losing track of
// who owns various pointers, so instead we just return an empty list.
// The list should get filled in again (by GUIRenderer::UpdateDrawCallCache)
// before it's used for rendering. (TODO: Is this class actually used safely
// in practice?)
DrawCalls::DrawCalls(const DrawCalls&)
: std::vector()
{
}
DrawCalls& DrawCalls::operator=(const DrawCalls&)
{
return *this;
}
void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, std::map>& Sprites)
{
// This is called only when something has changed (like the size of the
// sprite), so it doesn't need to be particularly efficient.
// Clean up the old data
Calls.clear();
// If this object has zero size, there's nothing to render. (This happens
// with e.g. tooltips that have zero size before they're first drawn, so
// it isn't necessarily an error.)
if (Size.left == Size.right && Size.top == Size.bottom)
return;
std::map>::iterator it(Sprites.find(SpriteName));
if (it == Sprites.end())
{
/*
* Sprite not found. Check whether this a special sprite,
* and if so create a new sprite:
* "stretched:filename.ext" - stretched image
* "stretched:grayscale:filename.ext" - stretched grayscale image.
* "cropped:0.5, 0.25" - stretch this ratio (x,y) of the top left of the image
* "color:r g b a" - solid color
* > "textureAsMask" - when using color, use the (optional) texture alpha channel as mask.
* These can be combined, but they must be separated by a ":"
* so you can have a white overlay over an stretched grayscale image with:
* "grayscale:color:255 255 255 100:stretched:filename.ext"
*/
// Check that this can be a special sprite.
if (SpriteName.ReverseFind(":") == -1 && SpriteName.Find("color(") == -1)
{
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
return;
}
auto sprite = std::make_unique();
VfsPath TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.AfterLast(":"));
if (SpriteName.Find("stretched:") != -1)
{
// TODO: Should check (nicely) that this is a valid file?
auto image = std::make_unique();
image->m_TextureName = TextureName;
if (SpriteName.Find("grayscale:") != -1)
{
image->m_Effects = std::make_shared();
image->m_Effects->m_Greyscale = true;
}
sprite->AddImage(std::move(image));
}
else if (SpriteName.Find("cropped:") != -1)
{
// TODO: Should check (nicely) that this is a valid file?
auto image = std::make_unique();
const bool centered = SpriteName.Find("center:") != -1;
CStr info = SpriteName.AfterLast("cropped:").BeforeFirst(":");
double xRatio = info.BeforeFirst(",").ToDouble();
double yRatio = info.AfterLast(",").ToDouble();
const CRect percentSize = centered
? CRect(50 - 50 / xRatio, 50 - 50 / yRatio, 50 + 50 / xRatio, 50 + 50 / yRatio)
: CRect(0, 0, 100 / xRatio, 100 / yRatio);
image->m_TextureSize = CGUISize(CRect(0, 0, 0, 0), percentSize);
image->m_TextureName = TextureName;
if (SpriteName.Find("grayscale:") != -1)
{
image->m_Effects = std::make_shared();
image->m_Effects->m_Greyscale = true;
}
sprite->AddImage(std::move(image));
}
if (SpriteName.Find("color:") != -1)
{
CStrW value = wstring_from_utf8(SpriteName.AfterLast("color:").BeforeFirst(":"));
auto image = std::make_unique();
CGUIColor* color;
// If we are using a mask, this is an effect.
// Otherwise we can fallback to the "back color" attribute
// TODO: we are assuming there is a filename here.
if (SpriteName.Find("textureAsMask:") != -1)
{
image->m_TextureName = TextureName;
image->m_Effects = std::make_shared();
color = &image->m_Effects->m_SolidColor;
}
else
color = &image->m_BackColor;
// Check color is valid
if (!CGUI::ParseString(&pGUI, value, *color))
{
LOGERROR("GUI: Error parsing sprite 'color' (\"%s\")", utf8_from_wstring(value));
return;
}
sprite->AddImage(std::move(image));
}
if (sprite->m_Images.empty())
{
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
return;
}
it = Sprites.emplace(SpriteName, std::move(sprite)).first;
}
Calls.reserve(it->second->m_Images.size());
// Iterate through all the sprite's images, loading the texture and
// calculating the texture coordinates
std::vector>::const_iterator cit;
for (cit = it->second->m_Images.begin(); cit != it->second->m_Images.end(); ++cit)
{
SDrawCall Call(cit->get()); // pointers are safe since we never modify sprites/images after startup
CRect ObjectSize = (*cit)->m_Size.GetSize(Size);
if (ObjectSize.GetWidth() == 0.0 || ObjectSize.GetHeight() == 0.0)
{
// Zero sized object. Don't report as an error, since it's common for e.g. hitpoint bars.
continue; // i.e. don't continue with this image
}
Call.m_Vertices = ObjectSize;
if ((*cit)->m_RoundCoordinates)
{
// Round the vertex coordinates to integers, to avoid ugly filtering artifacts
Call.m_Vertices.left = (int)(Call.m_Vertices.left + 0.5f);
Call.m_Vertices.right = (int)(Call.m_Vertices.right + 0.5f);
Call.m_Vertices.top = (int)(Call.m_Vertices.top + 0.5f);
Call.m_Vertices.bottom = (int)(Call.m_Vertices.bottom + 0.5f);
}
bool hasTexture = false;
if (!(*cit)->m_TextureName.empty())
{
CTextureProperties textureProps(g_L10n.LocalizePath((*cit)->m_TextureName));
textureProps.SetWrap((*cit)->m_WrapMode);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
hasTexture = true;
Call.m_Texture = texture;
Call.m_ObjectSize = ObjectSize;
}
Call.m_BackColor = &(*cit)->m_BackColor;
Call.m_GrayscaleFactor = 0.0f;
if (!hasTexture)
{
Call.m_ColorAdd = *Call.m_BackColor;
Call.m_ColorMultiply = CColor(0.0f, 0.0f, 0.0f, 0.0f);
Call.m_Texture = g_Renderer.GetTextureManager().GetTransparentTexture();
}
else if ((*cit)->m_Effects)
{
if ((*cit)->m_Effects->m_AddColor != CGUIColor())
{
const CColor color = (*cit)->m_Effects->m_AddColor;
Call.m_ColorAdd = CColor(color.r, color.g, color.b, 0.0f);
Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
}
else if ((*cit)->m_Effects->m_Greyscale)
{
Call.m_ColorAdd = CColor(0.0f, 0.0f, 0.0f, 0.0f);
Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
Call.m_GrayscaleFactor = 1.0f;
}
else if ((*cit)->m_Effects->m_SolidColor != CGUIColor())
{
const CColor color = (*cit)->m_Effects->m_SolidColor;
Call.m_ColorAdd = CColor(color.r, color.g, color.b, 0.0f);
Call.m_ColorMultiply = CColor(0.0f, 0.0f, 0.0f, color.a);
}
else /* Slight confusion - why no effects? */
{
Call.m_ColorAdd = CColor(0.0f, 0.0f, 0.0f, 0.0f);
Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
}
}
else
{
Call.m_ColorAdd = CColor(0.0f, 0.0f, 0.0f, 0.0f);
Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
}
Calls.push_back(Call);
}
}
CRect SDrawCall::ComputeTexCoords() const
{
float TexWidth = m_Texture->GetWidth();
float TexHeight = m_Texture->GetHeight();
if (!TexWidth || !TexHeight)
return CRect(0, 0, 1, 1);
// Textures are positioned by defining a rectangular block of the
// texture (usually the whole texture), and a rectangular block on
// the screen. The texture is positioned to make those blocks line up.
// Get the screen's position/size for the block
CRect BlockScreen = m_Image->m_TextureSize.GetSize(m_ObjectSize);
if (m_Image->m_FixedHAspectRatio)
BlockScreen.right = BlockScreen.left + BlockScreen.GetHeight() * m_Image->m_FixedHAspectRatio;
// Get the texture's position/size for the block:
CRect BlockTex;
// "real_texture_placement" overrides everything
if (m_Image->m_TexturePlacementInFile != CRect())
BlockTex = m_Image->m_TexturePlacementInFile;
// Use the whole texture
else
BlockTex = CRect(0, 0, TexWidth, TexHeight);
// When rendering, BlockTex will be transformed onto BlockScreen.
// Also, TexCoords will be transformed onto ObjectSize (giving the
// UV coords at each vertex of the object). We know everything
// except for TexCoords, so calculate it:
CVector2D translation(BlockTex.TopLeft()-BlockScreen.TopLeft());
float ScaleW = BlockTex.GetWidth()/BlockScreen.GetWidth();
float ScaleH = BlockTex.GetHeight()/BlockScreen.GetHeight();
CRect TexCoords (
// Resize (translating to/from the origin, so the
// topleft corner stays in the same place)
(m_ObjectSize-m_ObjectSize.TopLeft())
.Scale(ScaleW, ScaleH)
+ m_ObjectSize.TopLeft()
// Translate from BlockTex to BlockScreen
+ translation
);
// The tex coords need to be scaled so that (texwidth,texheight) is
// mapped onto (1,1)
TexCoords.left /= TexWidth;
TexCoords.right /= TexWidth;
TexCoords.top /= TexHeight;
TexCoords.bottom /= TexHeight;
return TexCoords;
}
void GUIRenderer::Draw(DrawCalls& Calls, CCanvas2D& canvas)
{
if (Calls.empty())
return;
// Called every frame, to draw the object (based on cached calculations)
// Iterate through each DrawCall, and execute whatever drawing code is being called
for (DrawCalls::const_iterator cit = Calls.begin(); cit != Calls.end(); ++cit)
{
// A hack to preload the handle to get a correct texture size.
GLuint h;
ogl_tex_get_texture_id(cit->m_Texture->GetHandle(), &h);
CRect texCoords = cit->ComputeTexCoords().Scale(
cit->m_Texture->GetWidth(), cit->m_Texture->GetHeight());
// Ensure the quad has the correct winding order
CRect rect = cit->m_Vertices;
if (rect.right < rect.left)
{
std::swap(rect.right, rect.left);
std::swap(texCoords.right, texCoords.left);
}
if (rect.bottom < rect.top)
{
std::swap(rect.bottom, rect.top);
std::swap(texCoords.bottom, texCoords.top);
}
canvas.DrawTexture(cit->m_Texture,
rect, texCoords, cit->m_ColorMultiply, cit->m_ColorAdd, cit->m_GrayscaleFactor);
}
}
Index: ps/trunk/source/lib/ogl.cpp
===================================================================
--- ps/trunk/source/lib/ogl.cpp (revision 26121)
+++ ps/trunk/source/lib/ogl.cpp (revision 26122)
@@ -1,513 +1,512 @@
/* Copyright (C) 2021 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* OpenGL helper functions.
*/
#include "precompiled.h"
#include "lib/ogl.h"
#include "lib/code_annotation.h"
#include "lib/config2.h"
#include "lib/debug.h"
#include "lib/external_libraries/libsdl.h"
-#include "lib/res/h_mgr.h"
#include "ps/CLogger.h"
#if !CONFIG2_GLES
# if OS_WIN
# include
# elif !OS_MACOSX && !OS_MAC
# include
# endif
#endif
#include
#include
#include
//----------------------------------------------------------------------------
// extensions
//----------------------------------------------------------------------------
static const char* exts = nullptr;
static bool have_30 = false;
static bool have_21 = false;
static bool have_20 = false;
static bool have_15 = false;
static bool have_14 = false;
static bool have_13 = false;
static bool have_12 = false;
// return a C string of unspecified length containing a space-separated
// list of all extensions the OpenGL implementation advertises.
// (useful for crash logs).
const char* ogl_ExtensionString()
{
ENSURE(exts && "call ogl_Init before using this function");
return exts;
}
// paranoia: newer drivers may forget to advertise an extension
// indicating support for something that has been folded into the core.
// we therefore check for all extensions known to be offered by the
// GL implementation present on the user's system; ogl_HaveExtension will
// take this into account.
// the app can therefore just ask for extensions and not worry about this.
static bool isImplementedInCore(const char* ext)
{
#define MATCH(known_ext)\
if(!strcmp(ext, #known_ext))\
return true;
if(have_30)
{
MATCH(GL_EXT_gpu_shader4);
MATCH(GL_NV_conditional_render);
MATCH(GL_ARB_color_buffer_float);
MATCH(GL_ARB_depth_buffer_float);
MATCH(GL_ARB_texture_float);
MATCH(GL_EXT_packed_float);
MATCH(GL_EXT_texture_shared_exponent);
MATCH(GL_EXT_framebuffer_object);
MATCH(GL_NV_half_float);
MATCH(GL_ARB_half_float_pixel);
MATCH(GL_EXT_framebuffer_multisample);
MATCH(GL_EXT_framebuffer_blit);
MATCH(GL_EXT_texture_integer);
MATCH(GL_EXT_texture_array);
MATCH(GL_EXT_packed_depth_stencil);
MATCH(GL_EXT_draw_buffers2);
MATCH(GL_EXT_texture_compression_rgtc);
MATCH(GL_EXT_transform_feedback);
MATCH(GL_APPLE_vertex_array_object);
MATCH(GL_EXT_framebuffer_sRGB);
}
if(have_21)
{
MATCH(GL_ARB_pixel_buffer_object);
MATCH(GL_EXT_texture_sRGB);
}
if(have_20)
{
MATCH(GL_ARB_shader_objects);
MATCH(GL_ARB_vertex_shader);
MATCH(GL_ARB_fragment_shader);
MATCH(GL_ARB_shading_language_100);
MATCH(GL_ARB_draw_buffers);
MATCH(GL_ARB_texture_non_power_of_two);
MATCH(GL_ARB_point_sprite);
MATCH(GL_EXT_blend_equation_separate);
}
if(have_15)
{
MATCH(GL_ARB_vertex_buffer_object);
MATCH(GL_ARB_occlusion_query);
MATCH(GL_EXT_shadow_funcs);
}
if(have_14)
{
MATCH(GL_SGIS_generate_mipmap);
MATCH(GL_NV_blend_square);
MATCH(GL_ARB_depth_texture);
MATCH(GL_ARB_shadow);
MATCH(GL_EXT_fog_coord);
MATCH(GL_EXT_multi_draw_arrays);
MATCH(GL_ARB_point_parameters);
MATCH(GL_EXT_secondary_color);
MATCH(GL_EXT_blend_func_separate);
MATCH(GL_EXT_stencil_wrap);
MATCH(GL_ARB_texture_env_crossbar);
MATCH(GL_EXT_texture_lod_bias);
MATCH(GL_ARB_texture_mirrored_repeat);
MATCH(GL_ARB_window_pos);
// These extensions were added to GL 1.2, but as part of the optional
// imaging subset; they're only guaranteed as of GL 1.4:
MATCH(GL_EXT_blend_color);
MATCH(GL_EXT_blend_minmax);
MATCH(GL_EXT_blend_subtract);
}
if(have_13)
{
MATCH(GL_ARB_texture_compression);
MATCH(GL_ARB_texture_cube_map);
MATCH(GL_ARB_multisample);
MATCH(GL_ARB_multitexture);
MATCH(GL_ARB_transpose_matrix);
MATCH(GL_ARB_texture_env_add);
MATCH(GL_ARB_texture_env_combine);
MATCH(GL_ARB_texture_env_dot3);
MATCH(GL_ARB_texture_border_clamp);
}
if(have_12)
{
MATCH(GL_EXT_texture3D);
MATCH(GL_EXT_bgra);
MATCH(GL_EXT_packed_pixels);
MATCH(GL_EXT_rescale_normal);
MATCH(GL_EXT_separate_specular_color);
MATCH(GL_SGIS_texture_edge_clamp);
MATCH(GL_SGIS_texture_lod);
MATCH(GL_EXT_draw_range_elements);
// Skip the extensions that only affect the imaging subset
}
#undef MATCH
return false;
}
// check if the extension is supported by the OpenGL implementation.
// takes subsequently added core support for some extensions into account.
bool ogl_HaveExtension(const char* ext)
{
ENSURE(exts && "call ogl_Init before using this function");
if(isImplementedInCore(ext))
return true;
const char *p = exts, *end;
// make sure ext is valid & doesn't contain spaces
if(!ext || ext[0] == '\0' || strchr(ext, ' '))
return false;
for(;;)
{
p = strstr(p, ext);
if(!p)
return false; // string not found - extension not supported
end = p + strlen(ext); // end of current substring
// make sure the substring found is an entire extension string,
// i.e. it starts and ends with ' '
if((p == exts || p[-1] == ' ') && // valid start AND
(*end == ' ' || *end == '\0')) // valid end
return true;
p = end;
}
}
static int GLVersion;
#if OS_WIN
static int WGLVersion;
#elif !CONFIG2_GLES && !OS_MACOSX && !OS_MAC
static int GLXVersion;
#endif
bool ogl_HaveVersion(int major, int minor)
{
return GLAD_MAKE_VERSION(major, minor) <= GLVersion;
}
// check if all given extension strings (passed as const char* parameters,
// terminated by a 0 pointer) are supported by the OpenGL implementation,
// as determined by ogl_HaveExtension.
// returns 0 if all are present; otherwise, the first extension in the
// list that's not supported (useful for reporting errors).
//
// note: dummy parameter is necessary to access parameter va_list.
//
//
// rationale: this interface is more convenient than individual
// ogl_HaveExtension calls and allows reporting which extension is missing.
//
// one disadvantage is that there is no way to indicate that either one
// of 2 extensions would be acceptable, e.g. (ARB|EXT)_texture_env_dot3.
// this is isn't so bad, since they wouldn't be named differently
// if there weren't non-trivial changes between them. for that reason,
// we refrain from equivalence checks (which would boil down to
// string-matching known extensions to their equivalents).
const char* ogl_HaveExtensions(int dummy, ...)
{
const char* ext;
va_list args;
va_start(args, dummy);
for(;;)
{
ext = va_arg(args, const char*);
// end of list reached; all were present => return 0.
if(!ext)
break;
// not found => return name of missing extension.
if(!ogl_HaveExtension(ext))
break;
}
va_end(args);
return ext;
}
// to help when running with no hardware acceleration and only OpenGL 1.1
// (e.g. testing the game in virtual machines), we define dummy versions of
// some extension functions which our graphics code assumes exist.
// it will render incorrectly but at least it shouldn't crash.
#if CONFIG2_GLES
static void enableDummyFunctions()
{
}
#else
static void GLAD_API_PTR dummy_glDrawRangeElementsEXT(GLenum mode, GLuint, GLuint, GLsizei count, GLenum type, GLvoid* indices)
{
glDrawElements(mode, count, type, indices);
}
static void GLAD_API_PTR dummy_glActiveTextureARB(GLenum UNUSED(texture))
{
}
static void GLAD_API_PTR dummy_glClientActiveTextureARB(GLenum UNUSED(texture))
{
}
static void GLAD_API_PTR dummy_glMultiTexCoord2fARB(GLenum UNUSED(target), GLfloat s, GLfloat t)
{
glTexCoord2f(s, t);
}
static void GLAD_API_PTR dummy_glMultiTexCoord3fARB(GLenum UNUSED(target), GLfloat s, GLfloat t, GLfloat r)
{
glTexCoord3f(s, t, r);
}
static void enableDummyFunctions()
{
// fall back to the dummy functions when extensions (or equivalent core support) are missing
if(!ogl_HaveExtension("GL_EXT_draw_range_elements"))
{
glDrawRangeElementsEXT = reinterpret_cast(&dummy_glDrawRangeElementsEXT);
}
if(!ogl_HaveExtension("GL_ARB_multitexture"))
{
glActiveTextureARB = reinterpret_cast(&dummy_glActiveTextureARB);
glClientActiveTextureARB = reinterpret_cast(&dummy_glClientActiveTextureARB);
glMultiTexCoord2fARB = reinterpret_cast(&dummy_glMultiTexCoord2fARB);
glMultiTexCoord3fARB = reinterpret_cast(&dummy_glMultiTexCoord3fARB);
}
}
#endif // #if CONFIG2_GLES
//----------------------------------------------------------------------------
const char* ogl_GetErrorName(GLenum err)
{
#define E(e) case e: return #e;
switch (err)
{
E(GL_INVALID_ENUM)
E(GL_INVALID_VALUE)
E(GL_INVALID_OPERATION)
#if !CONFIG2_GLES
E(GL_STACK_OVERFLOW)
E(GL_STACK_UNDERFLOW)
#endif
E(GL_OUT_OF_MEMORY)
E(GL_INVALID_FRAMEBUFFER_OPERATION)
default: return "Unknown GL error";
}
#undef E
}
static void dump_gl_error(GLenum err)
{
debug_printf("OGL| %s (%04x)\n", ogl_GetErrorName(err), err);
}
void ogl_WarnIfErrorLoc(const char *file, int line)
{
// glGetError may return multiple errors, so we poll it in a loop.
// the debug_printf should only happen once (if this is set), though.
bool error_enountered = false;
GLenum first_error = 0;
for(;;)
{
GLenum err = glGetError();
if(err == GL_NO_ERROR)
break;
if(!error_enountered)
first_error = err;
error_enountered = true;
dump_gl_error(err);
}
if(error_enountered)
debug_printf("%s:%d: OpenGL error(s) occurred: %s (%04x)\n", file, line, ogl_GetErrorName(first_error), (unsigned int)first_error);
}
// ignore and reset the specified error (as returned by glGetError).
// any other errors that have occurred are reported as ogl_WarnIfError would.
//
// this is useful for suppressing annoying error messages, e.g.
// "invalid enum" for GL_CLAMP_TO_EDGE even though we've already
// warned the user that their OpenGL implementation is too old.
bool ogl_SquelchError(GLenum err_to_ignore)
{
// glGetError may return multiple errors, so we poll it in a loop.
// the debug_printf should only happen once (if this is set), though.
bool error_enountered = false;
bool error_ignored = false;
GLenum first_error = 0;
for(;;)
{
GLenum err = glGetError();
if(err == GL_NO_ERROR)
break;
if(err == err_to_ignore)
{
error_ignored = true;
continue;
}
if(!error_enountered)
first_error = err;
error_enountered = true;
dump_gl_error(err);
}
if(error_enountered)
debug_printf("OpenGL error(s) occurred: %04x\n", (unsigned int)first_error);
return error_ignored;
}
//----------------------------------------------------------------------------
// feature and limit detect
//----------------------------------------------------------------------------
GLint ogl_max_tex_size = -1; // [pixels]
GLint ogl_max_tex_units = -1; // limit on GL_TEXTUREn
#if OS_WIN
bool ogl_Init(void* (load)(const char*), void* hdc)
#elif !CONFIG2_GLES && !OS_MACOSX && !OS_MAC
bool ogl_Init(void* (load)(const char*), void* display)
#else
bool ogl_Init(void* (load)(const char*))
#endif
{
GLADloadfunc loadFunc = reinterpret_cast(load);
if (!loadFunc)
return false;
#define LOAD_ERROR(ERROR_STRING) \
if (g_Logger) \
LOGERROR(ERROR_STRING); \
else \
debug_printf(ERROR_STRING); \
#if !CONFIG2_GLES
GLVersion = gladLoadGL(loadFunc);
if (!GLVersion)
{
LOAD_ERROR("Failed to load OpenGL functions.");
return false;
}
# if OS_WIN
WGLVersion = gladLoadWGL(reinterpret_cast(hdc), loadFunc);
if (!WGLVersion)
{
LOAD_ERROR("Failed to load WGL functions.");
return false;
}
# elif !OS_MACOSX && !OS_MAC
GLXVersion = gladLoadGLX(reinterpret_cast(display), DefaultScreen(display), loadFunc);
if (!GLXVersion)
{
LOAD_ERROR("Failed to load GLX functions.");
return false;
}
# endif
#else
GLVersion = gladLoadGLES2(loadFunc);
if (!GLVersion)
{
LOAD_ERROR("Failed to load GLES2 functions.");
return false;
}
#endif
#undef LOAD_ERROR
// cache extension list and versions for oglHave*.
// note: this is less about performance (since the above are not
// time-critical) than centralizing the 'OpenGL is ready' check.
exts = reinterpret_cast(glGetString(GL_EXTENSIONS));
ENSURE(exts); // else: called before OpenGL is ready for use
have_12 = ogl_HaveVersion(1, 2);
have_13 = ogl_HaveVersion(1, 3);
have_14 = ogl_HaveVersion(1, 4);
have_15 = ogl_HaveVersion(1, 5);
have_20 = ogl_HaveVersion(2, 0);
have_21 = ogl_HaveVersion(2, 1);
have_30 = ogl_HaveVersion(3, 0);
enableDummyFunctions();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &ogl_max_tex_size);
#if !CONFIG2_GLES
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &ogl_max_tex_units);
#endif
glEnable(GL_TEXTURE_2D);
return true;
}
void ogl_SetVsyncEnabled(bool enabled)
{
#if !CONFIG2_GLES && OS_WIN
int interval = enabled ? 1 : 0;
if (ogl_HaveExtension("WGL_EXT_swap_control"))
wglSwapIntervalEXT(interval);
#elif !CONFIG2_GLES && !OS_MACOSX && !OS_MAC
int interval = enabled ? 1 : 0;
if (ogl_HaveExtension("GLX_SGI_swap_control"))
glXSwapIntervalSGI(interval);
#else
UNUSED2(enabled);
#endif
}