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 }