Index: ps/trunk/source/graphics/ModelDef.cpp =================================================================== --- ps/trunk/source/graphics/ModelDef.cpp (revision 26833) +++ ps/trunk/source/graphics/ModelDef.cpp (revision 26834) @@ -1,537 +1,535 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ -/* - * Defines a raw 3d model. - */ - #include "precompiled.h" #include "ModelDef.h" + #include "graphics/SkeletonAnimDef.h" #include "lib/sysdep/arch/x86_x64/simd.h" -#include "ps/FileIo.h" #include "maths/Vector4D.h" +#include "ps/FileIo.h" #if COMPILER_HAS_SSE # include #endif void CModelDef::GetMaxBounds(CSkeletonAnimDef* anim, bool loop, CBoundingBoxAligned& result) { const u32 animIndex = anim ? anim->m_UID : 0; std::unordered_map::const_iterator it = m_MaxBoundsPerAnimDef.find(animIndex); if (it != m_MaxBoundsPerAnimDef.end()) { result = it->second; return; } size_t numverts = GetNumVertices(); SModelVertex* verts = GetVertices(); if (!anim) { for (size_t i = 0; i < numverts; ++i) result += verts[i].m_Coords; m_MaxBoundsPerAnimDef[animIndex] = result; return; } ENSURE(animIndex != 0); CMatrix3D* inverseBindBoneMatrix = GetInverseBindBoneMatrices(); std::vector boneMatrix(anim->GetNumKeys()); const size_t numFrames = anim->GetNumFrames(); const float frameTime = anim->GetFrameTime(); const size_t numBones = GetNumBones(); // NB: by using frames, the bounds are technically pessimistic, // since interpolation between frames can put vertices farther. for (size_t j = 0; j < numFrames; ++j) { anim->BuildBoneMatrices(j * frameTime, boneMatrix.data(), loop); for (size_t i = 0; i < numBones; ++i) boneMatrix[i] = boneMatrix[i] * inverseBindBoneMatrix[i]; for (size_t i = 0; i < numverts; ++i) result += SkinPoint(verts[i], boneMatrix.data()); } m_MaxBoundsPerAnimDef[animIndex] = result; } CVector3D CModelDef::SkinPoint(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]) { CVector3D result (0, 0, 0); for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i) { result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Transform(vtx.m_Coords) * vtx.m_Blend.m_Weight[i]; } return result; } CVector3D CModelDef::SkinNormal(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]) { // To be correct, the normal vectors apparently need to be multiplied by the // inverse of the transpose. Unfortunately inverses are slow. // If a matrix is orthogonal, M * M^T = I and so the inverse of the transpose // is the original matrix. But that's not entirely relevant here, because // the bone matrices include translation components and so they're not // orthogonal. // But that's okay because we have // M = T * R // and want to find // n' = (M^T^-1) * n // = (T * R)^T^-1 * n // = (R^T * T^T)^-1 * n // = (T^T^-1 * R^T^-1) * n // R is indeed orthogonal so R^T^-1 = R. T isn't orthogonal at all. // But n is only a 3-vector, and from the forms of T and R (which have // lots of zeroes) I can convince myself that replacing T with T^T^-1 has no // effect on anything but the fourth component of M^T^-1 - and the fourth // component is discarded since it has no effect on n', and so we can happily // use n' = M*n. // // (This isn't very good as a proof, but it's better than assuming M is // orthogonal when it's clearly not.) CVector3D result (0, 0, 0); for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i) { result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Rotate(vtx.m_Norm) * vtx.m_Blend.m_Weight[i]; } // If there was more than one influence, the result is probably not going // to be of unit length (since it's a weighted sum of several independent // unit vectors), so we need to normalise it. // (It's fairly common to only have one influence, so it seems sensible to // optimise that case a bit.) if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence result.Normalize(); return result; } void(*CModelDef::SkinPointsAndNormals)( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]) {}; static void SkinPointsAndNormalsFallback( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]) { // To avoid some performance overhead, get the raw vertex array pointers char* PositionData = Position.GetData(); size_t PositionStride = Position.GetStride(); char* NormalData = Normal.GetData(); size_t NormalStride = Normal.GetStride(); for (size_t j = 0; j < numVertices; ++j) { const SModelVertex& vtx = vertices[j]; CVector3D pos = newPoseMatrices[blendIndices[j]].Transform(vtx.m_Coords); CVector3D norm = newPoseMatrices[blendIndices[j]].Rotate(vtx.m_Norm); // If there was more than one influence, the result is probably not going // to be of unit length (since it's a weighted sum of several independent // unit vectors), so we need to normalise it. // (It's fairly common to only have one influence, so it seems sensible to // optimise that case a bit.) if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence norm.Normalize(); memcpy(PositionData + PositionStride*j, &pos.X, 3*sizeof(float)); memcpy(NormalData + NormalStride*j, &norm.X, 3*sizeof(float)); } } #if COMPILER_HAS_SSE static void SkinPointsAndNormalsSSE( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]) { // To avoid some performance overhead, get the raw vertex array pointers char* PositionData = Position.GetData(); size_t PositionStride = Position.GetStride(); char* NormalData = Normal.GetData(); size_t NormalStride = Normal.GetStride(); // Must be aligned correctly for SSE ASSERT((intptr_t)newPoseMatrices % 16 == 0); ASSERT((intptr_t)PositionData % 16 == 0); ASSERT((intptr_t)PositionStride % 16 == 0); ASSERT((intptr_t)NormalData % 16 == 0); ASSERT((intptr_t)NormalStride % 16 == 0); __m128 col0, col1, col2, col3, vec0, vec1, vec2; for (size_t j = 0; j < numVertices; ++j) { const SModelVertex& vtx = vertices[j]; const CMatrix3D& mtx = newPoseMatrices[blendIndices[j]]; // Loads matrix to xmm registers. - col0 = _mm_load_ps(mtx._data); - col1 = _mm_load_ps(mtx._data + 4); - col2 = _mm_load_ps(mtx._data + 8); - col3 = _mm_load_ps(mtx._data + 12); + const float* data = mtx.AsFloatArray(); + col0 = _mm_load_ps(data); + col1 = _mm_load_ps(data + 4); + col2 = _mm_load_ps(data + 8); + col3 = _mm_load_ps(data + 12); // Loads and computes vertex coordinates. vec0 = _mm_load1_ps(&vtx.m_Coords.X); // v0 = [x, x, x, x] vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x] vec1 = _mm_load1_ps(&vtx.m_Coords.Y); // v1 = [y, y, y, y] vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...] vec1 = _mm_load1_ps(&vtx.m_Coords.Z); // v1 = [z, z, z, z] vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z] vec1 = _mm_add_ps(vec1, col3); // v1 = [_13*z + _14, ...] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z + _14, ...] _mm_store_ps((float*)(PositionData + PositionStride*j), vec0); // Loads and computes normal vectors. vec0 = _mm_load1_ps(&vtx.m_Norm.X); // v0 = [x, x, x, x] vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x] vec1 = _mm_load1_ps(&vtx.m_Norm.Y); // v1 = [y, y, y, y] vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...] vec1 = _mm_load1_ps(&vtx.m_Norm.Z); // v1 = [z, z, z, z] vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z] vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z, ...] // If there was more than one influence, the result is probably not going // to be of unit length (since it's a weighted sum of several independent // unit vectors), so we need to normalise it. // (It's fairly common to only have one influence, so it seems sensible to // optimise that case a bit.) if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence { // Normalization. // vec1 = [x*x, y*y, z*z, ?*?] vec1 = _mm_mul_ps(vec0, vec0); // vec2 = [y*y, z*z, x*x, ?*?] vec2 = _mm_shuffle_ps(vec1, vec1, _MM_SHUFFLE(3, 0, 2, 1)); vec1 = _mm_add_ps(vec1, vec2); // vec2 = [z*z, x*x, y*y, ?*?] vec2 = _mm_shuffle_ps(vec2, vec2, _MM_SHUFFLE(3, 0, 2, 1)); vec1 = _mm_add_ps(vec1, vec2); // rsqrt(a) = 1 / sqrt(a) vec1 = _mm_rsqrt_ps(vec1); vec0 = _mm_mul_ps(vec0, vec1); } _mm_store_ps((float*)(NormalData + NormalStride*j), vec0); } } #endif void CModelDef::BlendBoneMatrices( CMatrix3D boneMatrices[]) { for (size_t i = 0; i < m_NumBlends; ++i) { const SVertexBlend& blend = m_pBlends[i]; CMatrix3D& boneMatrix = boneMatrices[m_NumBones + 1 + i]; // Note: there is a special case of joint influence, in which the vertex // is influenced by the bind-shape matrix instead of a particular bone, // which we indicate by setting the bone ID to the total number of bones. // It should be blended with the world space transform and we have already // set up this matrix in boneMatrices. // (see http://trac.wildfiregames.com/ticket/1012) boneMatrix.Blend(boneMatrices[blend.m_Bone[0]], blend.m_Weight[0]); for (size_t j = 1; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j) boneMatrix.AddBlend(boneMatrices[blend.m_Bone[j]], blend.m_Weight[j]); } } // CModelDef Constructor CModelDef::CModelDef() : m_NumVertices(0), m_NumUVsPerVertex(0), m_pVertices(0), m_NumFaces(0), m_pFaces(0), m_NumBones(0), m_Bones(0), m_InverseBindBoneMatrices(NULL), m_NumBlends(0), m_pBlends(0), m_pBlendIndices(0), m_Name(L"[not loaded]") { } // CModelDef Destructor CModelDef::~CModelDef() { for(RenderDataMap::iterator it = m_RenderData.begin(); it != m_RenderData.end(); ++it) delete it->second; delete[] m_pVertices; delete[] m_pFaces; delete[] m_Bones; delete[] m_InverseBindBoneMatrices; delete[] m_pBlends; delete[] m_pBlendIndices; } // FindPropPoint: find and return pointer to prop point matching given name; // return null if no match (case insensitive search) const SPropPoint* CModelDef::FindPropPoint(const char* name) const { for (size_t i = 0; i < m_PropPoints.size(); ++i) if (m_PropPoints[i].m_Name == name) return &m_PropPoints[i]; return 0; } // Load: read and return a new CModelDef initialised with data from given file CModelDef* CModelDef::Load(const VfsPath& filename, const VfsPath& name) { CFileUnpacker unpacker; // read everything in from file unpacker.Read(filename,"PSMD"); // check version if (unpacker.GetVersion() mdef = std::make_unique(); mdef->m_Name = name; // now unpack everything mdef->m_NumVertices = unpacker.UnpackSize(); // versions prior to 4 only support 1 UV set, 4 and later store it here if (unpacker.GetVersion() <= 3) { mdef->m_NumUVsPerVertex = 1; } else { mdef->m_NumUVsPerVertex = unpacker.UnpackSize(); } mdef->m_pVertices = new SModelVertex[mdef->m_NumVertices]; mdef->m_UVCoordinates.reserve(mdef->m_NumVertices * mdef->m_NumUVsPerVertex); for (size_t i = 0; i < mdef->m_NumVertices; ++i) { unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Coords, 12); unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Norm, 12); for (size_t s = 0; s < mdef->m_NumUVsPerVertex; ++s) { float uv[2]; unpacker.UnpackRaw(&uv[0], 8); mdef->m_UVCoordinates.emplace_back(uv[0], uv[1]); } unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Blend, sizeof(SVertexBlend)); } mdef->m_NumFaces = unpacker.UnpackSize(); mdef->m_pFaces=new SModelFace[mdef->m_NumFaces]; unpacker.UnpackRaw(mdef->m_pFaces,sizeof(SModelFace)*mdef->m_NumFaces); mdef->m_NumBones = unpacker.UnpackSize(); if (mdef->m_NumBones) { mdef->m_Bones=new CBoneState[mdef->m_NumBones]; unpacker.UnpackRaw(mdef->m_Bones,mdef->m_NumBones*sizeof(CBoneState)); mdef->m_pBlendIndices = new size_t[mdef->m_NumVertices]; std::vector blends; for (size_t i = 0; i < mdef->m_NumVertices; i++) { const SVertexBlend &blend = mdef->m_pVertices[i].m_Blend; if (blend.m_Bone[1] == 0xFF) { mdef->m_pBlendIndices[i] = blend.m_Bone[0]; } else { // If there's already a vertex using the same blend as this, then // reuse its entry from blends; otherwise add the new one to blends size_t j; for (j = 0; j < blends.size(); j++) { if (blend == blends[j]) break; } if (j >= blends.size()) blends.push_back(blend); // This index is offset by one to allow the special case of a // weighted influence relative to the bind-shape rather than // a particular bone. See comment in BlendBoneMatrices. mdef->m_pBlendIndices[i] = mdef->m_NumBones + 1 + j; } } mdef->m_NumBlends = blends.size(); mdef->m_pBlends = new SVertexBlend[mdef->m_NumBlends]; std::copy(blends.begin(), blends.end(), mdef->m_pBlends); } if (unpacker.GetVersion() >= 2) { // versions >=2 also have prop point data size_t numPropPoints = unpacker.UnpackSize(); mdef->m_PropPoints.resize(numPropPoints); if (numPropPoints) { for (size_t i = 0; i < numPropPoints; i++) { unpacker.UnpackString(mdef->m_PropPoints[i].m_Name); unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position)); unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation)); unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex)); // build prop point transform mdef->m_PropPoints[i].m_Transform.SetIdentity(); mdef->m_PropPoints[i].m_Transform.Rotate(mdef->m_PropPoints[i].m_Rotation); mdef->m_PropPoints[i].m_Transform.Translate(mdef->m_PropPoints[i].m_Position); } } } if (unpacker.GetVersion() <= 2) { // Versions <=2 don't include the default 'root' prop point, so add it here SPropPoint prop; prop.m_Name = "root"; prop.m_Transform.SetIdentity(); prop.m_BoneIndex = 0xFF; mdef->m_PropPoints.push_back(prop); } if (unpacker.GetVersion() <= 2) { // Versions <=2 store the vertexes relative to the bind pose. That // isn't useful when you want to do correct skinning, so later versions // store them in world space. So, fix the old models by skinning each // vertex: if (mdef->m_NumBones) // only do skinned models { std::vector bindPose (mdef->m_NumBones); for (size_t i = 0; i < mdef->m_NumBones; ++i) { bindPose[i].SetIdentity(); bindPose[i].Rotate(mdef->m_Bones[i].m_Rotation); bindPose[i].Translate(mdef->m_Bones[i].m_Translation); } for (size_t i = 0; i < mdef->m_NumVertices; ++i) { mdef->m_pVertices[i].m_Coords = SkinPoint(mdef->m_pVertices[i], &bindPose[0]); mdef->m_pVertices[i].m_Norm = SkinNormal(mdef->m_pVertices[i], &bindPose[0]); } } } // Compute the inverse bind-pose matrices, needed by the skinning code mdef->m_InverseBindBoneMatrices = new CMatrix3D[mdef->m_NumBones]; CBoneState* defpose = mdef->GetBones(); for (size_t i = 0; i < mdef->m_NumBones; ++i) { mdef->m_InverseBindBoneMatrices[i].SetIdentity(); mdef->m_InverseBindBoneMatrices[i].Translate(-defpose[i].m_Translation); mdef->m_InverseBindBoneMatrices[i].Rotate(defpose[i].m_Rotation.GetInverse()); } return mdef.release(); } // Save: write the given CModelDef to the given file void CModelDef::Save(const VfsPath& filename, const CModelDef* mdef) { CFilePacker packer(FILE_VERSION, "PSMD"); // pack everything up const size_t numVertices = mdef->GetNumVertices(); packer.PackSize(numVertices); packer.PackRaw(mdef->GetVertices(), sizeof(SModelVertex) * numVertices); const size_t numFaces = mdef->GetNumFaces(); packer.PackSize(numFaces); packer.PackRaw(mdef->GetFaces(), sizeof(SModelFace) * numFaces); const size_t numBones = mdef->m_NumBones; packer.PackSize(numBones); if (numBones) packer.PackRaw(mdef->m_Bones, sizeof(CBoneState) * numBones); const size_t numPropPoints = mdef->m_PropPoints.size(); packer.PackSize(numPropPoints); for (size_t i = 0; i < numPropPoints; i++) { packer.PackString(mdef->m_PropPoints[i].m_Name); packer.PackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position)); packer.PackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation)); packer.PackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex)); } // flush everything out to file packer.Write(filename); } // SetRenderData: Set the render data object for the given key, void CModelDef::SetRenderData(const void* key, CModelDefRPrivate* data) { delete m_RenderData[key]; m_RenderData[key] = data; } // GetRenderData: Get the render data object for the given key, // or 0 if no such object exists. // Reference count of the render data object is automatically increased. CModelDefRPrivate* CModelDef::GetRenderData(const void* key) const { RenderDataMap::const_iterator it = m_RenderData.find(key); if (it != m_RenderData.end()) return it->second; return 0; } void ModelDefActivateFastImpl() { #if COMPILER_HAS_SSE if (HostHasSSE()) { CModelDef::SkinPointsAndNormals = SkinPointsAndNormalsSSE; return; } #endif CModelDef::SkinPointsAndNormals = SkinPointsAndNormalsFallback; } Index: ps/trunk/source/graphics/TerrainTextureEntry.cpp =================================================================== --- ps/trunk/source/graphics/TerrainTextureEntry.cpp (revision 26833) +++ ps/trunk/source/graphics/TerrainTextureEntry.cpp (revision 26834) @@ -1,192 +1,187 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "TerrainTextureEntry.h" #include "graphics/MaterialManager.h" #include "graphics/Terrain.h" #include "graphics/TerrainProperties.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TextureManager.h" #include "lib/ogl.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/SceneRenderer.h" #include CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, const VfsPath& path): m_pProperties(properties), m_BaseColor(0), m_BaseColorValid(false) { ENSURE(properties); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, path, "terrain_texture") != PSRETURN_OK) { LOGERROR("Terrain xml not found (%s)", path.string8()); return; } #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(tag); EL(terrain); EL(texture); EL(textures); EL(material); EL(props); EL(alphamap); AT(file); AT(name); #undef AT #undef EL XMBElement root = XeroFile.GetRoot(); if (root.GetNodeName() != el_terrain) { LOGERROR("Invalid terrain format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName())); return; } std::vector > samplers; VfsPath alphamap("standard"); m_Tag = utf8_from_wstring(path.Basename().string()); XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if (child_name == el_textures) { XERO_ITER_EL(child, textures_element) { ENSURE(textures_element.GetNodeName() == el_texture); CStr name; VfsPath terrainTexturePath; XERO_ITER_ATTR(textures_element, relativePath) { if (relativePath.Name == at_file) terrainTexturePath = VfsPath("art/textures/terrain") / relativePath.Value.FromUTF8(); else if (relativePath.Name == at_name) name = relativePath.Value; } samplers.emplace_back(name, terrainTexturePath); if (name == str_baseTex.string()) m_DiffuseTexturePath = terrainTexturePath; } } else if (child_name == el_material) { VfsPath mat = VfsPath("art/materials") / child.GetText().FromUTF8(); if (CRenderer::IsInitialised()) m_Material = g_Renderer.GetSceneRenderer().GetMaterialManager().LoadMaterial(mat); } else if (child_name == el_alphamap) { alphamap = child.GetText().FromUTF8(); } else if (child_name == el_props) { CTerrainPropertiesPtr ret (new CTerrainProperties(properties)); ret->LoadXml(child, &XeroFile, path); if (ret) m_pProperties = ret; } else if (child_name == el_tag) { m_Tag = child.GetText(); } } for (size_t i = 0; i < samplers.size(); ++i) { CTextureProperties texture(samplers[i].second); texture.SetAddressMode(Renderer::Backend::Sampler::AddressMode::REPEAT); texture.SetAnisotropicFilter(true); if (CRenderer::IsInitialised()) { CTexturePtr texptr = g_Renderer.GetTextureManager().CreateTexture(texture); m_Material.AddSampler(CMaterial::TextureSampler(samplers[i].first, texptr)); } } if (CRenderer::IsInitialised()) m_TerrainAlpha = g_TexMan.LoadAlphaMap(alphamap); float texAngle = 0.f; float texSize = 1.f; if (m_pProperties) { m_Groups = m_pProperties->GetGroups(); texAngle = m_pProperties->GetTextureAngle(); texSize = m_pProperties->GetTextureSize(); } m_TextureMatrix.SetZero(); m_TextureMatrix._11 = cosf(texAngle) / texSize; m_TextureMatrix._13 = -sinf(texAngle) / texSize; m_TextureMatrix._21 = -sinf(texAngle) / texSize; m_TextureMatrix._23 = -cosf(texAngle) / texSize; m_TextureMatrix._44 = 1.f; GroupVector::iterator it=m_Groups.begin(); for (;it!=m_Groups.end();++it) (*it)->AddTerrain(this); } CTerrainTextureEntry::~CTerrainTextureEntry() { for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it) (*it)->RemoveTerrain(this); } // BuildBaseColor: calculate the root color of the texture, used for coloring minimap, and store // in m_BaseColor member void CTerrainTextureEntry::BuildBaseColor() { // Use the explicit properties value if possible if (m_pProperties && m_pProperties->HasBaseColor()) { m_BaseColor=m_pProperties->GetBaseColor(); m_BaseColorValid = true; return; } // Use the texture color if available if (GetTexture()->TryLoad()) { m_BaseColor = GetTexture()->GetBaseColor(); m_BaseColorValid = true; } } - -const float* CTerrainTextureEntry::GetTextureMatrix() const -{ - return &m_TextureMatrix._11; -} Index: ps/trunk/source/graphics/TerrainTextureEntry.h =================================================================== --- ps/trunk/source/graphics/TerrainTextureEntry.h (revision 26833) +++ ps/trunk/source/graphics/TerrainTextureEntry.h (revision 26834) @@ -1,95 +1,95 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_TERRAINTEXTUREENTRY #define INCLUDED_TERRAINTEXTUREENTRY #include "graphics/Material.h" #include "graphics/TerrainTextureManager.h" #include "graphics/Texture.h" #include "lib/file/vfs/vfs_path.h" #include "maths/Matrix3D.h" #include "ps/CStr.h" #include // CTerrainTextureEntry: class wrapping a terrain texture object; contains various other required // elements - color of minimap, terrain "group" it belongs to, etc class CTerrainTextureEntry { public: using GroupVector = std::vector; // Most of the texture's data is delay-loaded, so after the constructor has // been called, the texture entry is ready to be used. CTerrainTextureEntry(CTerrainPropertiesPtr props, const VfsPath& path); ~CTerrainTextureEntry(); const CStr& GetTag() const { return m_Tag; } const CTerrainProperties& GetProperties() const { return *m_pProperties; } // Get texture handle, load texture if not loaded. const CTexturePtr& GetTexture() const { return m_Material.GetDiffuseTexture(); } const CMaterial& GetMaterial() const { return m_Material; } // Returns a matrix of the form [c 0 -s 0; -s 0 -c 0; 0 0 0 0; 0 0 0 1] // mapping world-space (x,y,z,1) coordinates onto (u,v,0,1) texcoords - const float* GetTextureMatrix() const; + const CMatrix3D& GetTextureMatrix() const { return m_TextureMatrix; } // Used in Atlas to retrieve a texture for previews. Can't use textures // directly because they're required on CPU side. Another solution is to // retrieve path from diffuse texture from material. const VfsPath& GetDiffuseTexturePath() const { return m_DiffuseTexturePath; } // Get mipmap color in BGRA format u32 GetBaseColor() { if (!m_BaseColorValid) BuildBaseColor(); return m_BaseColor; } CTerrainTextureManager::TerrainAlphaMap::iterator m_TerrainAlpha; private: // Tag = file name stripped of path and extension (grass_dark_1) CStr m_Tag; VfsPath m_DiffuseTexturePath; // The property sheet used by this texture CTerrainPropertiesPtr m_pProperties; CMaterial m_Material; CMatrix3D m_TextureMatrix; // BGRA color of topmost mipmap level, for coloring minimap, or a color // specified by the terrain properties u32 m_BaseColor; // ..Valid is true if the base color has been cached bool m_BaseColorValid; // All terrain type groups we're a member of GroupVector m_Groups; // calculate the root color of the texture, used for coloring minimap void BuildBaseColor(); }; #endif Index: ps/trunk/source/graphics/TerritoryTexture.cpp =================================================================== --- ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26833) +++ ps/trunk/source/graphics/TerritoryTexture.cpp (revision 26834) @@ -1,253 +1,253 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "TerritoryTexture.h" #include "graphics/Color.h" #include "graphics/Terrain.h" #include "lib/bits.h" #include "ps/Profile.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include "simulation2/helpers/Grid.h" #include "simulation2/helpers/Pathfinding.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpTerritoryManager.h" // TODO: There's a lot of duplication with CLOSTexture - might be nice to refactor a bit CTerritoryTexture::CTerritoryTexture(CSimulation2& simulation) : m_Simulation(simulation), m_DirtyID(0), m_MapSize(0) { } CTerritoryTexture::~CTerritoryTexture() { DeleteTexture(); } void CTerritoryTexture::DeleteTexture() { m_Texture.reset(); } bool CTerritoryTexture::UpdateDirty() { CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); return cmpTerritoryManager && cmpTerritoryManager->NeedUpdateTexture(&m_DirtyID); } Renderer::Backend::GL::CTexture* CTerritoryTexture::GetTexture() { ENSURE(!UpdateDirty()); return m_Texture.get(); } -const float* CTerritoryTexture::GetTextureMatrix() +const CMatrix3D& CTerritoryTexture::GetTextureMatrix() { ENSURE(!UpdateDirty()); - return &m_TextureMatrix._11; + return m_TextureMatrix; } const CMatrix3D& CTerritoryTexture::GetMinimapTextureMatrix() { ENSURE(!UpdateDirty()); return m_MinimapTextureMatrix; } void CTerritoryTexture::ConstructTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (!cmpTerrain) return; // Convert size from terrain tiles to territory tiles m_MapSize = cmpTerrain->GetMapSize() * Pathfinding::NAVCELL_SIZE_INT / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE; const uint32_t textureSize = round_up_to_pow2(static_cast(m_MapSize)); m_Texture = deviceCommandContext->GetDevice()->CreateTexture2D("TerritoryTexture", Renderer::Backend::Format::R8G8B8A8_UNORM, textureSize, textureSize, Renderer::Backend::Sampler::MakeDefaultSampler( Renderer::Backend::Sampler::Filter::LINEAR, Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE)); // Initialise texture with transparency, for the areas we don't // overwrite with uploading later. std::unique_ptr texData = std::make_unique(textureSize * textureSize * 4); memset(texData.get(), 0x00, textureSize * textureSize * 4); deviceCommandContext->UploadTexture( m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, texData.get(), textureSize * textureSize * 4); texData.reset(); { // Texture matrix: We want to map // world pos (0, y, 0) (i.e. bottom-left of first tile) // onto texcoord (0, 0) (i.e. bottom-left of first texel); // world pos (mapsize*cellsize, y, mapsize*cellsize) (i.e. top-right of last tile) // onto texcoord (mapsize / texsize, mapsize / texsize) (i.e. top-right of last texel) float s = 1.f / static_cast(textureSize * TERRAIN_TILE_SIZE); float t = 0.f; m_TextureMatrix.SetZero(); m_TextureMatrix._11 = s; m_TextureMatrix._23 = s; m_TextureMatrix._14 = t; m_TextureMatrix._24 = t; m_TextureMatrix._44 = 1; } { // Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize) float s = m_MapSize / static_cast(textureSize); m_MinimapTextureMatrix.SetZero(); m_MinimapTextureMatrix._11 = s; m_MinimapTextureMatrix._22 = s; m_MinimapTextureMatrix._44 = 1; } } void CTerritoryTexture::RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { // If the map was resized, delete and regenerate the texture if (m_Texture) { CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY); if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide()) DeleteTexture(); } if (!m_Texture) ConstructTexture(deviceCommandContext); PROFILE("recompute territory texture"); CmpPtr cmpTerritoryManager(m_Simulation, SYSTEM_ENTITY); if (!cmpTerritoryManager) return; std::unique_ptr bitmap = std::make_unique(m_MapSize * m_MapSize * 4); GenerateBitmap(cmpTerritoryManager->GetTerritoryGrid(), bitmap.get(), m_MapSize, m_MapSize); deviceCommandContext->UploadTextureRegion( m_Texture.get(), Renderer::Backend::Format::R8G8B8A8_UNORM, bitmap.get(), m_MapSize * m_MapSize * 4, 0, 0, m_MapSize, m_MapSize); } void CTerritoryTexture::GenerateBitmap(const Grid& territories, u8* bitmap, ssize_t w, ssize_t h) { int alphaMax = 0xC0; int alphaFalloff = 0x20; CmpPtr cmpPlayerManager(m_Simulation, SYSTEM_ENTITY); std::vector colors; i32 numPlayers = cmpPlayerManager->GetNumPlayers(); for (i32 p = 0; p < numPlayers; ++p) { CColor color(1, 0, 1, 1); CmpPtr cmpPlayer(m_Simulation, cmpPlayerManager->GetPlayerByID(p)); if (cmpPlayer) color = cmpPlayer->GetDisplayedColor(); colors.push_back(color); } u8* p = bitmap; for (ssize_t j = 0; j < h; ++j) for (ssize_t i = 0; i < w; ++i) { u8 val = territories.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK; CColor color(1, 0, 1, 1); if (val < colors.size()) color = colors[val]; *p++ = (int)(color.r * 255.f); *p++ = (int)(color.g * 255.f); *p++ = (int)(color.b * 255.f); // Use alphaMax for borders and gaia territory; these tiles will be deleted later if (val == 0 || (i > 0 && (territories.get(i-1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) || (i < w-1 && (territories.get(i+1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) || (j > 0 && (territories.get(i, j-1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val) || (j < h-1 && (territories.get(i, j+1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val)) *p++ = alphaMax; else *p++ = 0x00; } // Do a low-quality cheap blur effect for (ssize_t j = 0; j < h; ++j) { int a; a = 0; for (ssize_t i = 0; i < w; ++i) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } a = 0; for (ssize_t i = w-1; i >= 0; --i) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } } for (ssize_t i = 0; i < w; ++i) { int a; a = 0; for (ssize_t j = 0; j < w; ++j) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } a = 0; for (ssize_t j = w-1; j >= 0; --j) { a = std::max(a - alphaFalloff, (int)bitmap[(j*w+i)*4 + 3]); bitmap[(j*w+i)*4 + 3] = a; } } // Add a gap between the boundaries, by deleting the max-alpha tiles for (ssize_t j = 0; j < h; ++j) for (ssize_t i = 0; i < w; ++i) if (bitmap[(j*w+i)*4 + 3] == alphaMax) bitmap[(j*w+i)*4 + 3] = 0; } void CTerritoryTexture::UpdateIfNeeded(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext) { if (UpdateDirty()) RecomputeTexture(deviceCommandContext); } Index: ps/trunk/source/graphics/TerritoryTexture.h =================================================================== --- ps/trunk/source/graphics/TerritoryTexture.h (revision 26833) +++ ps/trunk/source/graphics/TerritoryTexture.h (revision 26834) @@ -1,87 +1,87 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "maths/Matrix3D.h" #include "renderer/backend/gl/Texture.h" #include "renderer/backend/gl/DeviceCommandContext.h" class CSimulation2; template class Grid; /** * Maintains the territory boundary texture, used for * rendering and for the minimap. */ class CTerritoryTexture { NONCOPYABLE(CTerritoryTexture); public: CTerritoryTexture(CSimulation2& simulation); ~CTerritoryTexture(); /** * Recomputes the territory texture if necessary, and returns the texture handle. * Also potentially switches the current active texture unit, and enables texturing on it. * The texture is in 32-bit BGRA format. */ Renderer::Backend::GL::CTexture* GetTexture(); /** * Returns a matrix to map (x,y,z) world coordinates onto (u,v) texture * coordinates, in the form expected by a matrix uniform. * This must only be called after UpdateIfNeeded. */ - const float* GetTextureMatrix(); + const CMatrix3D& GetTextureMatrix(); /** * Returns a matrix to map (0,0)-(1,1) texture coordinates onto texture * coordinates, in the form expected by a matrix uniform. * This must only be called after UpdateIfNeeded. */ const CMatrix3D& GetMinimapTextureMatrix(); /** * Updates the texture if needed (territory was changed or the texture * wasn't created). */ void UpdateIfNeeded(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext); private: /** * Returns true if the territory state has changed since the last call to this function */ bool UpdateDirty(); void DeleteTexture(); void ConstructTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext); void RecomputeTexture(Renderer::Backend::GL::CDeviceCommandContext* deviceCommandContext); void GenerateBitmap(const Grid& territories, u8* bitmap, ssize_t w, ssize_t h); CSimulation2& m_Simulation; size_t m_DirtyID; std::unique_ptr m_Texture; ssize_t m_MapSize; // tiles per side CMatrix3D m_TextureMatrix; CMatrix3D m_MinimapTextureMatrix; }; Index: ps/trunk/source/maths/Matrix3D.h =================================================================== --- ps/trunk/source/maths/Matrix3D.h (revision 26833) +++ ps/trunk/source/maths/Matrix3D.h (revision 26834) @@ -1,325 +1,340 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * A Matrix class used for holding and manipulating transformation * info. */ #ifndef INCLUDED_MATRIX3D #define INCLUDED_MATRIX3D #include "maths/Vector3D.h" #include "maths/Vector4D.h" class CQuaternion; -///////////////////////////////////////////////////////////////////////// // CMatrix3D: a 4x4 matrix class for common operations in 3D class CMatrix3D { public: // the matrix data itself - accessible as either longhand names // or via a flat or 2d array // NOTE: _xy means row x, column y in the mathematical notation of this matrix, so don't be // fooled by the way they're listed below - union { - struct { + union + { + struct + { float _11, _21, _31, _41; float _12, _22, _32, _42; float _13, _23, _33, _43; float _14, _24, _34, _44; }; float _data[16]; float _data2d[4][4]; // (Be aware that m(0,2) == _data2d[2][0] == _13, etc. This is to be considered a feature.) }; public: // constructors CMatrix3D () { } CMatrix3D( float a11, float a12, float a13, float a14, float a21, float a22, float a23, float a24, float a31, float a32, float a33, float a34, float a41, float a42, float a43, float a44) : _11(a11), _12(a12), _13(a13), _14(a14), _21(a21), _22(a22), _23(a23), _24(a24), _31(a31), _32(a32), _33(a33), _34(a34), _41(a41), _42(a42), _43(a43), _44(a44) { } CMatrix3D(float data[]) : _11(data[0]), _21(data[1]), _31(data[2]), _41(data[3]), _12(data[4]), _22(data[5]), _32(data[6]), _42(data[7]), _13(data[8]), _23(data[9]), _33(data[10]), _43(data[11]), _14(data[12]), _24(data[13]), _34(data[14]), _44(data[15]) { } // accessors to individual elements of matrix // NOTE: in this function definition, 'col' and 'row' represent the column and row into the // internal element matrix which is the transposed of the mathematical notation, so the first // and second arguments here are actually the row and column into the mathematical notation. float& operator()(int col, int row) { return _data[row*4+col]; } const float& operator()(int col, int row) const { return _data[row*4+col]; } float& operator[](int idx) { return _data[idx]; } const float& operator[](int idx) const { return _data[idx]; } // matrix multiplication CMatrix3D operator*(const CMatrix3D &matrix) const { return CMatrix3D( _11*matrix._11 + _12*matrix._21 + _13*matrix._31 + _14*matrix._41, _11*matrix._12 + _12*matrix._22 + _13*matrix._32 + _14*matrix._42, _11*matrix._13 + _12*matrix._23 + _13*matrix._33 + _14*matrix._43, _11*matrix._14 + _12*matrix._24 + _13*matrix._34 + _14*matrix._44, _21*matrix._11 + _22*matrix._21 + _23*matrix._31 + _24*matrix._41, _21*matrix._12 + _22*matrix._22 + _23*matrix._32 + _24*matrix._42, _21*matrix._13 + _22*matrix._23 + _23*matrix._33 + _24*matrix._43, _21*matrix._14 + _22*matrix._24 + _23*matrix._34 + _24*matrix._44, _31*matrix._11 + _32*matrix._21 + _33*matrix._31 + _34*matrix._41, _31*matrix._12 + _32*matrix._22 + _33*matrix._32 + _34*matrix._42, _31*matrix._13 + _32*matrix._23 + _33*matrix._33 + _34*matrix._43, _31*matrix._14 + _32*matrix._24 + _33*matrix._34 + _34*matrix._44, _41*matrix._11 + _42*matrix._21 + _43*matrix._31 + _44*matrix._41, _41*matrix._12 + _42*matrix._22 + _43*matrix._32 + _44*matrix._42, _41*matrix._13 + _42*matrix._23 + _43*matrix._33 + _44*matrix._43, _41*matrix._14 + _42*matrix._24 + _43*matrix._34 + _44*matrix._44 ); } // matrix multiplication/assignment CMatrix3D& operator*=(const CMatrix3D &matrix) { Concatenate(matrix); return *this; } // matrix scaling CMatrix3D operator*(float f) const { return CMatrix3D( _11*f, _12*f, _13*f, _14*f, _21*f, _22*f, _23*f, _24*f, _31*f, _32*f, _33*f, _34*f, _41*f, _42*f, _43*f, _44*f ); } // matrix addition CMatrix3D operator+(const CMatrix3D &m) const { return CMatrix3D( _11+m._11, _12+m._12, _13+m._13, _14+m._14, _21+m._21, _22+m._22, _23+m._23, _24+m._24, _31+m._31, _32+m._32, _33+m._33, _34+m._34, _41+m._41, _42+m._42, _43+m._43, _44+m._44 ); } // matrix addition/assignment CMatrix3D& operator+=(const CMatrix3D &m) { _11 += m._11; _21 += m._21; _31 += m._31; _41 += m._41; _12 += m._12; _22 += m._22; _32 += m._32; _42 += m._42; _13 += m._13; _23 += m._23; _33 += m._33; _43 += m._43; _14 += m._14; _24 += m._24; _34 += m._34; _44 += m._44; return *this; } // equality bool operator==(const CMatrix3D &m) const { return _11 == m._11 && _21 == m._21 && _31 == m._31 && _41 == m._41 && _12 == m._12 && _22 == m._22 && _32 == m._32 && _42 == m._42 && _13 == m._13 && _23 == m._23 && _33 == m._33 && _43 == m._43 && _14 == m._14 && _24 == m._24 && _34 == m._34 && _44 == m._44; } // inequality bool operator!=(const CMatrix3D& m) const { return !(*this == m); } // set this matrix to the identity matrix void SetIdentity(); // set this matrix to the zero matrix void SetZero(); // set this matrix to the orthogonal projection matrix void SetOrtho(float left, float right, float bottom, float top, float near, float far); // set this matrix to the perspective projection matrix void SetPerspective(float fov, float aspect, float near, float far); void SetPerspectiveTile(float fov, float aspect, float near, float far, int tiles, int tile_x, int tile_y); // concatenate arbitrary matrix onto this matrix void Concatenate(const CMatrix3D& m) { (*this) = m * (*this); } // blend matrix using only 4x3 subset void Blend(const CMatrix3D& m, float f) { _11 = m._11*f; _21 = m._21*f; _31 = m._31*f; _12 = m._12*f; _22 = m._22*f; _32 = m._32*f; _13 = m._13*f; _23 = m._23*f; _33 = m._33*f; _14 = m._14*f; _24 = m._24*f; _34 = m._34*f; } // blend matrix using only 4x3 and add onto existing blend void AddBlend(const CMatrix3D& m, float f) { _11 += m._11*f; _21 += m._21*f; _31 += m._31*f; _12 += m._12*f; _22 += m._22*f; _32 += m._32*f; _13 += m._13*f; _23 += m._23*f; _33 += m._33*f; _14 += m._14*f; _24 += m._24*f; _34 += m._34*f; } // set this matrix to a rotation matrix for a rotation about X axis of given angle void SetXRotation(float angle); // set this matrix to a rotation matrix for a rotation about Y axis of given angle void SetYRotation(float angle); // set this matrix to a rotation matrix for a rotation about Z axis of given angle void SetZRotation(float angle); // set this matrix to a rotation described by given quaternion void SetRotation(const CQuaternion& quat); // concatenate a rotation about the X axis onto this matrix void RotateX(float angle); // concatenate a rotation about the Y axis onto this matrix void RotateY(float angle); // concatenate a rotation about the Z axis onto this matrix void RotateZ(float angle); // concatenate a rotation described by given quaternion void Rotate(const CQuaternion& quat); // sets this matrix to the given translation matrix (any existing transformation will be overwritten) void SetTranslation(float x, float y, float z); void SetTranslation(const CVector3D& vector); // concatenate given translation onto this matrix. Assumes the current // matrix is an affine transformation (i.e. the bottom row is [0,0,0,1]) // as an optimisation. void Translate(float x, float y, float z); void Translate(const CVector3D& vector); // apply translation after this matrix (M = M * T) void PostTranslate(float x, float y, float z); // set this matrix to the given scaling matrix void SetScaling(float x_scale, float y_scale, float z_scale); // concatenate given scaling matrix onto this matrix void Scale(float x_scale, float y_scale, float z_scale); // calculate the inverse of this matrix, store in dst void GetInverse(CMatrix3D& dst) const; // return the inverse of this matrix CMatrix3D GetInverse() const; // calculate the transpose of this matrix, store in dst CMatrix3D GetTranspose() const; // return the translation component of this matrix CVector3D GetTranslation() const; // return left vector, derived from rotation CVector3D GetLeft() const; // return up vector, derived from rotation CVector3D GetUp() const; // return forward vector, derived from rotation CVector3D GetIn() const; // return a quaternion representing the matrix's rotation CQuaternion GetRotation() const; // return the angle of rotation around the Y axis in range [-pi,pi] // (based on projecting the X axis onto the XZ plane) float GetYRotation() const; // transform a 3D vector by this matrix CVector3D Transform(const CVector3D &vector) const { CVector3D result; Transform(vector, result); return result; } void Transform(const CVector3D& vector, CVector3D& result) const { result.X = _11*vector.X + _12*vector.Y + _13*vector.Z + _14; result.Y = _21*vector.X + _22*vector.Y + _23*vector.Z + _24; result.Z = _31*vector.X + _32*vector.Y + _33*vector.Z + _34; } // transform a 4D vector by this matrix CVector4D Transform(const CVector4D &vector) const { CVector4D result; Transform(vector, result); return result; } void Transform(const CVector4D& vector, CVector4D& result) const { result.X = _11*vector.X + _12*vector.Y + _13*vector.Z + _14*vector.W; result.Y = _21*vector.X + _22*vector.Y + _23*vector.Z + _24*vector.W; result.Z = _31*vector.X + _32*vector.Y + _33*vector.Z + _34*vector.W; result.W = _41*vector.X + _42*vector.Y + _43*vector.Z + _44*vector.W; } // rotate a vector by this matrix CVector3D Rotate(const CVector3D& vector) const { CVector3D result; Rotate(vector, result); return result; } void Rotate(const CVector3D& vector, CVector3D& result) const { result.X = _11*vector.X + _12*vector.Y + _13*vector.Z; result.Y = _21*vector.X + _22*vector.Y + _23*vector.Z; result.Z = _31*vector.X + _32*vector.Y + _33*vector.Z; } // rotate a vector by the transpose of this matrix void RotateTransposed(const CVector3D& vector,CVector3D& result) const; CVector3D RotateTransposed(const CVector3D& vector) const; + + // Returns 16 element array of floats, e.g. for mat4 uniforms. + const float* AsFloatArray() const + { + // Additional check to prevent a weird compiler has a different + // alignement for an array and a class members. + static_assert( + sizeof(CMatrix3D) == sizeof(float) * 16u && + offsetof(CMatrix3D, _data) == 0 && + offsetof(CMatrix3D, _11) == 0 && + offsetof(CMatrix3D, _44) == sizeof(float) * 15u, + "CMatrix3D should be properly layouted to use AsFloatArray"); + return _data; + } }; #endif // INCLUDED_MATRIX3D Index: ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp =================================================================== --- ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp (revision 26833) +++ ps/trunk/source/renderer/backend/gl/ShaderProgram.cpp (revision 26834) @@ -1,1329 +1,1329 @@ /* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ShaderProgram.h" #include "graphics/Color.h" #include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/gl/Device.h" #include "renderer/backend/gl/DeviceCommandContext.h" #define USE_SHADER_XML_VALIDATION 1 #if USE_SHADER_XML_VALIDATION #include "ps/XML/RelaxNG.h" #include "ps/XML/XMLWriter.h" #endif #include namespace Renderer { namespace Backend { namespace GL { namespace { int GetStreamMask(const VertexAttributeStream stream) { return 1 << static_cast(stream); } GLint GLSizeFromFormat(const Format format) { GLint size = 1; if (format == Renderer::Backend::Format::R32_SFLOAT || format == Renderer::Backend::Format::R16_SINT) size = 1; else if ( format == Renderer::Backend::Format::R8G8_UNORM || format == Renderer::Backend::Format::R8G8_UINT || format == Renderer::Backend::Format::R16G16_SINT || format == Renderer::Backend::Format::R32G32_SFLOAT) size = 2; else if (format == Renderer::Backend::Format::R32G32B32_SFLOAT) size = 3; else if ( format == Renderer::Backend::Format::R32G32B32A32_SFLOAT || format == Renderer::Backend::Format::R8G8B8A8_UNORM || format == Renderer::Backend::Format::R8G8B8A8_UINT) size = 4; else debug_warn("Unsupported format."); return size; } GLenum GLTypeFromFormat(const Format format) { GLenum type = GL_FLOAT; if (format == Renderer::Backend::Format::R32_SFLOAT || format == Renderer::Backend::Format::R32G32_SFLOAT || format == Renderer::Backend::Format::R32G32B32_SFLOAT || format == Renderer::Backend::Format::R32G32B32A32_SFLOAT) type = GL_FLOAT; else if ( format == Renderer::Backend::Format::R16_SINT || format == Renderer::Backend::Format::R16G16_SINT) type = GL_SHORT; else if ( format == Renderer::Backend::Format::R8G8_UNORM || format == Renderer::Backend::Format::R8G8_UINT || format == Renderer::Backend::Format::R8G8B8A8_UNORM || format == Renderer::Backend::Format::R8G8B8A8_UINT) type = GL_UNSIGNED_BYTE; else debug_warn("Unsupported format."); return type; } GLboolean NormalizedFromFormat(const Format format) { switch (format) { case Format::R8G8_UNORM: FALLTHROUGH; case Format::R8G8B8_UNORM: FALLTHROUGH; case Format::R8G8B8A8_UNORM: FALLTHROUGH; case Format::R16_UNORM: FALLTHROUGH; case Format::R16G16_UNORM: return GL_TRUE; default: break; } return GL_FALSE; } int GetAttributeLocationFromStream( CDevice* device, const VertexAttributeStream stream) { // Old mapping makes sense only if we have an old/low-end hardware. Else we // need to use sequential numbering to fix #3054. We use presence of // compute shaders as a check that the hardware has universal CUs. if (device->GetCapabilities().computeShaders) { return static_cast(stream); } else { // Map known semantics onto the attribute locations documented by NVIDIA: // https://download.nvidia.com/developer/Papers/2005/OpenGL_2.0/NVIDIA_OpenGL_2.0_Support.pdf // https://developer.download.nvidia.com/opengl/glsl/glsl_release_notes.pdf switch (stream) { case VertexAttributeStream::POSITION: return 0; case VertexAttributeStream::NORMAL: return 2; case VertexAttributeStream::COLOR: return 3; case VertexAttributeStream::UV0: return 8; case VertexAttributeStream::UV1: return 9; case VertexAttributeStream::UV2: return 10; case VertexAttributeStream::UV3: return 11; case VertexAttributeStream::UV4: return 12; case VertexAttributeStream::UV5: return 13; case VertexAttributeStream::UV6: return 14; case VertexAttributeStream::UV7: return 15; } } debug_warn("Invalid attribute semantics"); return 0; } bool PreprocessShaderFile( bool arb, const CShaderDefines& defines, const VfsPath& path, CStr& source, std::vector& fileDependencies) { CVFSFile file; if (file.Load(g_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to load shader file: '%s'", path.string8()); return false; } CPreprocessorWrapper preprocessor( [arb, &fileDependencies](const CStr& includePath, CStr& out) -> bool { const VfsPath includeFilePath( (arb ? L"shaders/arb/" : L"shaders/glsl/") + wstring_from_utf8(includePath)); // Add dependencies anyway to reload the shader when the file is // appeared. fileDependencies.push_back(includeFilePath); CVFSFile includeFile; if (includeFile.Load(g_VFS, includeFilePath) != PSRETURN_OK) { LOGERROR("Failed to load shader include file: '%s'", includeFilePath.string8()); return false; } out = includeFile.GetAsString(); return true; }); preprocessor.AddDefines(defines); #if CONFIG2_GLES if (!arb) { // GLES defines the macro "GL_ES" in its GLSL preprocessor, // but since we run our own preprocessor first, we need to explicitly // define it here preprocessor.AddDefine("GL_ES", "1"); } #endif source = preprocessor.Preprocess(file.GetAsString()); return true; } } // anonymous namespace #if !CONFIG2_GLES class CShaderProgramARB final : public CShaderProgram { public: CShaderProgramARB( CDevice* device, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) : CShaderProgram(streamflags), m_Device(device), m_VertexIndexes(vertexIndexes), m_FragmentIndexes(fragmentIndexes) { glGenProgramsARB(1, &m_VertexProgram); glGenProgramsARB(1, &m_FragmentProgram); std::vector newFileDependencies = {vertexFilePath, fragmentFilePath}; CStr vertexCode; if (!PreprocessShaderFile(true, defines, vertexFilePath, vertexCode, newFileDependencies)) return; CStr fragmentCode; if (!PreprocessShaderFile(true, defines, fragmentFilePath, fragmentCode, newFileDependencies)) return; m_FileDependencies = std::move(newFileDependencies); // TODO: replace by scoped bind. m_Device->GetActiveCommandContext()->SetGraphicsPipelineState( MakeDefaultGraphicsPipelineStateDesc()); if (!Compile(GL_VERTEX_PROGRAM_ARB, "vertex", m_VertexProgram, vertexFilePath, vertexCode)) return; if (!Compile(GL_FRAGMENT_PROGRAM_ARB, "fragment", m_FragmentProgram, fragmentFilePath, fragmentCode)) return; } ~CShaderProgramARB() override { glDeleteProgramsARB(1, &m_VertexProgram); glDeleteProgramsARB(1, &m_FragmentProgram); } bool Compile(GLuint target, const char* targetName, GLuint program, const VfsPath& file, const CStr& code) { ogl_WarnIfError(); glBindProgramARB(target, program); ogl_WarnIfError(); glProgramStringARB(target, GL_PROGRAM_FORMAT_ASCII_ARB, (GLsizei)code.length(), code.c_str()); if (ogl_SquelchError(GL_INVALID_OPERATION)) { GLint errPos = 0; glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errPos); int errLine = std::count(code.begin(), code.begin() + std::min((int)code.length(), errPos + 1), '\n') + 1; char* errStr = (char*)glGetString(GL_PROGRAM_ERROR_STRING_ARB); LOGERROR("Failed to compile %s program '%s' (line %d):\n%s", targetName, file.string8(), errLine, errStr); return false; } glBindProgramARB(target, 0); ogl_WarnIfError(); return true; } void Bind(CShaderProgram* previousShaderProgram) override { CShaderProgramARB* previousShaderProgramARB = nullptr; if (previousShaderProgram) previousShaderProgramARB = static_cast(previousShaderProgramARB); if (previousShaderProgramARB) previousShaderProgramARB->UnbindClientStates(); if (!previousShaderProgramARB || previousShaderProgramARB->m_VertexProgram != m_VertexProgram || previousShaderProgramARB->m_FragmentProgram != m_FragmentProgram) { glBindProgramARB(GL_VERTEX_PROGRAM_ARB, m_VertexProgram); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_FragmentProgram); } BindClientStates(); } void Unbind() override { glBindProgramARB(GL_VERTEX_PROGRAM_ARB, 0); glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, 0); UnbindClientStates(); // TODO: should unbind textures, probably } int GetUniformVertexIndex(CStrIntern id) { std::map::iterator it = m_VertexIndexes.find(id); if (it == m_VertexIndexes.end()) return -1; return it->second; } frag_index_pair_t GetUniformFragmentIndex(CStrIntern id) { std::map::iterator it = m_FragmentIndexes.find(id); if (it == m_FragmentIndexes.end()) return std::make_pair(-1, 0); return it->second; } Binding GetTextureBinding(texture_id_t id) override { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index == -1) return Binding(); else return Binding((int)fPair.second, index); } void BindTexture(texture_id_t id, GLuint tex) override { frag_index_pair_t fPair = GetUniformFragmentIndex(id); int index = fPair.first; if (index != -1) { m_Device->GetActiveCommandContext()->BindTexture(index, fPair.second, tex); } } void BindTexture(Binding id, GLuint tex) override { int index = id.second; if (index != -1) { m_Device->GetActiveCommandContext()->BindTexture(index, id.first, tex); } } Binding GetUniformBinding(uniform_id_t id) override { return Binding(GetUniformVertexIndex(id), GetUniformFragmentIndex(id).first); } void Uniform(Binding id, float v0, float v1, float v2, float v3) override { if (id.first != -1) glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first, v0, v1, v2, v3); if (id.second != -1) glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second, v0, v1, v2, v3); } void Uniform(Binding id, const CMatrix3D& v) override { if (id.first != -1) { glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+0, v._11, v._12, v._13, v._14); glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+1, v._21, v._22, v._23, v._24); glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+2, v._31, v._32, v._33, v._34); glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, (GLuint)id.first+3, v._41, v._42, v._43, v._44); } if (id.second != -1) { glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+0, v._11, v._12, v._13, v._14); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+1, v._21, v._22, v._23, v._24); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+2, v._31, v._32, v._33, v._34); glProgramLocalParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, (GLuint)id.second+3, v._41, v._42, v._43, v._44); } } void Uniform(Binding id, size_t count, const CMatrix3D* v) override { ENSURE(count == 1); Uniform(id, v[0]); } void Uniform(Binding id, size_t count, const float* v) override { ENSURE(count == 4); Uniform(id, v[0], v[1], v[2], v[3]); } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: CDevice* m_Device = nullptr; std::vector m_FileDependencies; GLuint m_VertexProgram; GLuint m_FragmentProgram; std::map m_VertexIndexes; // pair contains std::map m_FragmentIndexes; }; #endif // !CONFIG2_GLES class CShaderProgramGLSL final : public CShaderProgram { public: CShaderProgramGLSL( CDevice* device, const CStr& name, const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) : CShaderProgram(streamflags), m_Device(device), m_Name(name), m_VertexAttribs(vertexAttribs) { for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) m_ActiveVertexAttributes.emplace_back(it->second); std::sort(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end()); m_Program = 0; m_VertexShader = glCreateShader(GL_VERTEX_SHADER); m_FragmentShader = glCreateShader(GL_FRAGMENT_SHADER); m_FileDependencies = {vertexFilePath, fragmentFilePath}; #if !CONFIG2_GLES if (m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_SHADER, m_VertexShader, -1, vertexFilePath.string8().c_str()); glObjectLabel(GL_SHADER, m_FragmentShader, -1, fragmentFilePath.string8().c_str()); } #endif std::vector newFileDependencies = {vertexFilePath, fragmentFilePath}; CStr vertexCode; if (!PreprocessShaderFile(false, defines, vertexFilePath, vertexCode, newFileDependencies)) return; CStr fragmentCode; if (!PreprocessShaderFile(false, defines, fragmentFilePath, fragmentCode, newFileDependencies)) return; m_FileDependencies = std::move(newFileDependencies); if (vertexCode.empty()) { LOGERROR("Failed to preprocess vertex shader: '%s'", vertexFilePath.string8()); return; } if (fragmentCode.empty()) { LOGERROR("Failed to preprocess fragment shader: '%s'", fragmentFilePath.string8()); return; } #if CONFIG2_GLES // Ugly hack to replace desktop GLSL 1.10/1.20 with GLSL ES 1.00, // and also to set default float precision for fragment shaders vertexCode.Replace("#version 110\n", "#version 100\n"); vertexCode.Replace("#version 110\r\n", "#version 100\n"); vertexCode.Replace("#version 120\n", "#version 100\n"); vertexCode.Replace("#version 120\r\n", "#version 100\n"); fragmentCode.Replace("#version 110\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 110\r\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\n", "#version 100\nprecision mediump float;\n"); fragmentCode.Replace("#version 120\r\n", "#version 100\nprecision mediump float;\n"); #endif // TODO: replace by scoped bind. m_Device->GetActiveCommandContext()->SetGraphicsPipelineState( MakeDefaultGraphicsPipelineStateDesc()); if (!Compile(m_VertexShader, vertexFilePath, vertexCode)) return; if (!Compile(m_FragmentShader, fragmentFilePath, fragmentCode)) return; if (!Link(vertexFilePath, fragmentFilePath)) return; } ~CShaderProgramGLSL() override { if (m_Program) glDeleteProgram(m_Program); glDeleteShader(m_VertexShader); glDeleteShader(m_FragmentShader); } bool Compile(GLuint shader, const VfsPath& file, const CStr& code) { const char* code_string = code.c_str(); GLint code_length = code.length(); glShaderSource(shader, 1, &code_string, &code_length); ogl_WarnIfError(); glCompileShader(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); GLint length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); // Apparently sometimes GL_INFO_LOG_LENGTH is incorrectly reported as 0 // (http://code.google.com/p/android/issues/detail?id=9953) if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetShaderInfoLog(shader, length, NULL, infolog); if (ok) LOGMESSAGE("Info when compiling shader '%s':\n%s", file.string8(), infolog); else LOGERROR("Failed to compile shader '%s':\n%s", file.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); return ok; } bool Link(const VfsPath& vertexFilePath, const VfsPath& fragmentFilePath) { ENSURE(!m_Program); m_Program = glCreateProgram(); #if !CONFIG2_GLES if (m_Device->GetCapabilities().debugLabels) { glObjectLabel(GL_PROGRAM, m_Program, -1, m_Name.c_str()); } #endif glAttachShader(m_Program, m_VertexShader); ogl_WarnIfError(); glAttachShader(m_Program, m_FragmentShader); ogl_WarnIfError(); // Set up the attribute bindings explicitly, since apparently drivers // don't always pick the most efficient bindings automatically, // and also this lets us hardcode indexes into VertexPointer etc for (std::map::iterator it = m_VertexAttribs.begin(); it != m_VertexAttribs.end(); ++it) glBindAttribLocation(m_Program, it->second, it->first.c_str()); glLinkProgram(m_Program); GLint ok = 0; glGetProgramiv(m_Program, GL_LINK_STATUS, &ok); GLint length = 0; glGetProgramiv(m_Program, GL_INFO_LOG_LENGTH, &length); if (!ok && length == 0) length = 4096; if (length > 1) { char* infolog = new char[length]; glGetProgramInfoLog(m_Program, length, NULL, infolog); if (ok) LOGMESSAGE("Info when linking program '%s'+'%s':\n%s", vertexFilePath.string8(), fragmentFilePath.string8(), infolog); else LOGERROR("Failed to link program '%s'+'%s':\n%s", vertexFilePath.string8(), fragmentFilePath.string8(), infolog); delete[] infolog; } ogl_WarnIfError(); if (!ok) return false; m_Uniforms.clear(); m_Samplers.clear(); Bind(nullptr); ogl_WarnIfError(); GLint numUniforms = 0; glGetProgramiv(m_Program, GL_ACTIVE_UNIFORMS, &numUniforms); ogl_WarnIfError(); for (GLint i = 0; i < numUniforms; ++i) { // TODO: use GL_ACTIVE_UNIFORM_MAX_LENGTH for the size. char name[256] = {0}; GLsizei nameLength = 0; GLint size = 0; GLenum type = 0; glGetActiveUniform(m_Program, i, ARRAY_SIZE(name), &nameLength, &size, &type, name); ogl_WarnIfError(); const GLint location = glGetUniformLocation(m_Program, name); // OpenGL specification is a bit vague about a name returned by glGetActiveUniform. // NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without; while (nameLength > 3 && name[nameLength - 3] == '[' && name[nameLength - 2] == '0' && name[nameLength - 1] == ']') { nameLength -= 3; } name[nameLength] = 0; CStrIntern nameIntern(name); m_Uniforms[nameIntern] = std::make_pair(location, type); // Assign sampler uniforms to sequential texture units if (type == GL_SAMPLER_2D || type == GL_SAMPLER_CUBE #if !CONFIG2_GLES || type == GL_SAMPLER_2D_SHADOW #endif ) { const int unit = static_cast(m_Samplers.size()); m_Samplers[nameIntern].first = (type == GL_SAMPLER_CUBE ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D); m_Samplers[nameIntern].second = unit; glUniform1i(location, unit); // link uniform to unit ogl_WarnIfError(); } } // TODO: verify that we're not using more samplers than is supported Unbind(); ogl_WarnIfError(); return true; } void Bind(CShaderProgram* previousShaderProgram) override { CShaderProgramGLSL* previousShaderProgramGLSL = nullptr; if (previousShaderProgram) previousShaderProgramGLSL = static_cast(previousShaderProgram); ENSURE(this != previousShaderProgramGLSL); glUseProgram(m_Program); if (previousShaderProgramGLSL) { std::vector::iterator itPrevious = previousShaderProgramGLSL->m_ActiveVertexAttributes.begin(); std::vector::iterator itNext = m_ActiveVertexAttributes.begin(); while ( itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() || itNext != m_ActiveVertexAttributes.end()) { if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end() && itNext != m_ActiveVertexAttributes.end()) { if (*itPrevious == *itNext) { ++itPrevious; ++itNext; } else if (*itPrevious < *itNext) { glDisableVertexAttribArray(*itPrevious); ++itPrevious; } else if (*itPrevious > *itNext) { glEnableVertexAttribArray(*itNext); ++itNext; } } else if (itPrevious != previousShaderProgramGLSL->m_ActiveVertexAttributes.end()) { glDisableVertexAttribArray(*itPrevious); ++itPrevious; } else if (itNext != m_ActiveVertexAttributes.end()) { glEnableVertexAttribArray(*itNext); ++itNext; } } } else { for (const int index : m_ActiveVertexAttributes) glEnableVertexAttribArray(index); } m_ValidStreams = 0; } void Unbind() override { glUseProgram(0); for (const int index : m_ActiveVertexAttributes) glDisableVertexAttribArray(index); // TODO: should unbind textures, probably } Binding GetTextureBinding(texture_id_t id) override { std::map>::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return Binding(); else return Binding((int)it->second.first, it->second.second); } void BindTexture(texture_id_t id, GLuint tex) override { std::map>::iterator it = m_Samplers.find(CStrIntern(id)); if (it == m_Samplers.end()) return; m_Device->GetActiveCommandContext()->BindTexture(it->second.second, it->second.first, tex); } void BindTexture(Binding id, GLuint tex) override { if (id.second == -1) return; m_Device->GetActiveCommandContext()->BindTexture(id.second, id.first, tex); } Binding GetUniformBinding(uniform_id_t id) override { std::map>::iterator it = m_Uniforms.find(id); if (it == m_Uniforms.end()) return Binding(); else return Binding(it->second.first, (int)it->second.second); } void Uniform(Binding id, float v0, float v1, float v2, float v3) override { if (id.first != -1) { if (id.second == GL_FLOAT) glUniform1f(id.first, v0); else if (id.second == GL_FLOAT_VEC2) glUniform2f(id.first, v0, v1); else if (id.second == GL_FLOAT_VEC3) glUniform3f(id.first, v0, v1, v2); else if (id.second == GL_FLOAT_VEC4) glUniform4f(id.first, v0, v1, v2, v3); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float, vec2, vec3, vec4)"); } } void Uniform(Binding id, const CMatrix3D& v) override { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) - glUniformMatrix4fv(id.first, 1, GL_FALSE, &v._11); + glUniformMatrix4fv(id.first, 1, GL_FALSE, v.AsFloatArray()); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } void Uniform(Binding id, size_t count, const CMatrix3D* v) override { if (id.first != -1) { if (id.second == GL_FLOAT_MAT4) glUniformMatrix4fv(id.first, count, GL_FALSE, &v->_11); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected mat4)"); } } void Uniform(Binding id, size_t count, const float* v) override { if (id.first != -1) { if (id.second == GL_FLOAT) glUniform1fv(id.first, count, v); else LOGERROR("CShaderProgramGLSL::Uniform(): Invalid uniform type (expected float)"); } } void VertexAttribPointer( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, const void* data) override { const int attributeLocation = GetAttributeLocationFromStream(m_Device, stream); std::vector::const_iterator it = std::lower_bound(m_ActiveVertexAttributes.begin(), m_ActiveVertexAttributes.end(), attributeLocation); if (it == m_ActiveVertexAttributes.end() || *it != attributeLocation) return; const GLint size = GLSizeFromFormat(format); const GLenum type = GLTypeFromFormat(format); const GLboolean normalized = NormalizedFromFormat(format); glVertexAttribPointer( attributeLocation, size, type, normalized, stride, static_cast(data) + offset); m_ValidStreams |= GetStreamMask(stream); } std::vector GetFileDependencies() const override { return m_FileDependencies; } private: CDevice* m_Device = nullptr; CStr m_Name; std::vector m_FileDependencies; std::map m_VertexAttribs; // Sorted list of active vertex attributes. std::vector m_ActiveVertexAttributes; GLuint m_Program; GLuint m_VertexShader, m_FragmentShader; std::map> m_Uniforms; std::map> m_Samplers; // texture target & unit chosen for each uniform sampler }; CShaderProgram::CShaderProgram(int streamflags) : m_StreamFlags(streamflags), m_ValidStreams(0) { } CShaderProgram::~CShaderProgram() = default; // static std::unique_ptr CShaderProgram::Create(CDevice* device, const CStr& name, const CShaderDefines& baseDefines) { PROFILE2("loading shader"); PROFILE2_ATTR("name: %s", name.c_str()); VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; CXeromyces XeroFile; PSRETURN ret = XeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return nullptr; #if USE_SHADER_XML_VALIDATION { // Serialize the XMB data and pass it to the validator XMLWriter_File shaderFile; shaderFile.SetPrettyPrint(false); shaderFile.XMB(XeroFile); bool ok = CXeromyces::ValidateEncoded("shader", name, shaderFile.GetOutput()); if (!ok) return nullptr; } #endif // Define all the elements and attributes used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(define); EL(fragment); EL(stream); EL(uniform); EL(vertex); AT(attribute); AT(file); AT(if); AT(loc); AT(name); AT(type); AT(value); #undef AT #undef EL CPreprocessorWrapper preprocessor; preprocessor.AddDefines(baseDefines); XMBElement root = XeroFile.GetRoot(); const bool isGLSL = root.GetAttributes().GetNamedItem(at_type) == "glsl"; VfsPath vertexFile; VfsPath fragmentFile; CShaderDefines defines = baseDefines; std::map vertexUniforms; std::map fragmentUniforms; std::map vertexAttribs; int streamFlags = 0; XERO_ITER_EL(root, child) { if (child.GetNodeName() == el_define) { defines.Add(CStrIntern(child.GetAttributes().GetNamedItem(at_name)), CStrIntern(child.GetAttributes().GetNamedItem(at_value))); } else if (child.GetNodeName() == el_vertex) { vertexFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(child, param) { XMBAttributeList attributes = param.GetAttributes(); CStr cond = attributes.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (param.GetNodeName() == el_uniform) { vertexUniforms[CStrIntern(attributes.GetNamedItem(at_name))] = attributes.GetNamedItem(at_loc).ToInt(); } else if (param.GetNodeName() == el_stream) { const CStr streamName = attributes.GetNamedItem(at_name); const CStr attributeName = attributes.GetNamedItem(at_attribute); if (attributeName.empty() && isGLSL) LOGERROR("Empty attribute name in vertex shader description '%s'", vertexFile.string8().c_str()); VertexAttributeStream stream = VertexAttributeStream::UV7; if (streamName == "pos") stream = VertexAttributeStream::POSITION; else if (streamName == "normal") stream = VertexAttributeStream::NORMAL; else if (streamName == "color") stream = VertexAttributeStream::COLOR; else if (streamName == "uv0") stream = VertexAttributeStream::UV0; else if (streamName == "uv1") stream = VertexAttributeStream::UV1; else if (streamName == "uv2") stream = VertexAttributeStream::UV2; else if (streamName == "uv3") stream = VertexAttributeStream::UV3; else if (streamName == "uv4") stream = VertexAttributeStream::UV4; else if (streamName == "uv5") stream = VertexAttributeStream::UV5; else if (streamName == "uv6") stream = VertexAttributeStream::UV6; else if (streamName == "uv7") stream = VertexAttributeStream::UV7; else LOGERROR("Unknown stream '%s' in vertex shader description '%s'", streamName.c_str(), vertexFile.string8().c_str()); if (isGLSL) { const int attributeLocation = GetAttributeLocationFromStream(device, stream); vertexAttribs[CStrIntern(attributeName)] = attributeLocation; } streamFlags |= GetStreamMask(stream); } } } else if (child.GetNodeName() == el_fragment) { fragmentFile = L"shaders/" + child.GetAttributes().GetNamedItem(at_file).FromUTF8(); XERO_ITER_EL(child, param) { XMBAttributeList attributes = param.GetAttributes(); CStr cond = attributes.GetNamedItem(at_if); if (!cond.empty() && !preprocessor.TestConditional(cond)) continue; if (param.GetNodeName() == el_uniform) { // A somewhat incomplete listing, missing "shadow" and "rect" versions // which are interpreted as 2D (NB: our shadowmaps may change // type based on user config). GLenum type = GL_TEXTURE_2D; const CStr t = attributes.GetNamedItem(at_type); if (t == "sampler1D") #if CONFIG2_GLES debug_warn(L"sampler1D not implemented on GLES"); #else type = GL_TEXTURE_1D; #endif else if (t == "sampler2D") type = GL_TEXTURE_2D; else if (t == "sampler3D") #if CONFIG2_GLES debug_warn(L"sampler3D not implemented on GLES"); #else type = GL_TEXTURE_3D; #endif else if (t == "samplerCube") type = GL_TEXTURE_CUBE_MAP; fragmentUniforms[CStrIntern(attributes.GetNamedItem(at_name))] = std::make_pair(attributes.GetNamedItem(at_loc).ToInt(), type); } } } } if (isGLSL) return CShaderProgram::ConstructGLSL(device, name, vertexFile, fragmentFile, defines, vertexAttribs, streamFlags); else return CShaderProgram::ConstructARB(device, vertexFile, fragmentFile, defines, vertexUniforms, fragmentUniforms, streamFlags); } #if CONFIG2_GLES // static std::unique_ptr CShaderProgram::ConstructARB(CDevice* UNUSED(device), const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& UNUSED(defines), const std::map& UNUSED(vertexIndexes), const std::map& UNUSED(fragmentIndexes), int UNUSED(streamflags)) { LOGERROR("CShaderProgram::ConstructARB: '%s'+'%s': ARB shaders not supported on this device", vertexFile.string8(), fragmentFile.string8()); return nullptr; } #else // static std::unique_ptr CShaderProgram::ConstructARB( CDevice* device, const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexIndexes, const std::map& fragmentIndexes, int streamflags) { return std::make_unique( device, vertexFile, fragmentFile, defines, vertexIndexes, fragmentIndexes, streamflags); } #endif // static std::unique_ptr CShaderProgram::ConstructGLSL( CDevice* device, const CStr& name, const VfsPath& vertexFile, const VfsPath& fragmentFile, const CShaderDefines& defines, const std::map& vertexAttribs, int streamflags) { return std::make_unique( device, name, vertexFile, fragmentFile, defines, vertexAttribs, streamflags); } void CShaderProgram::BindTexture(texture_id_t id, const Renderer::Backend::GL::CTexture* tex) { BindTexture(id, tex->GetHandle()); } void CShaderProgram::BindTexture(Binding id, const Renderer::Backend::GL::CTexture* tex) { BindTexture(id, tex->GetHandle()); } void CShaderProgram::Uniform(Binding id, int v) { Uniform(id, (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(Binding id, float v) { Uniform(id, v, v, v, v); } void CShaderProgram::Uniform(Binding id, float v0, float v1) { Uniform(id, v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(Binding id, const CVector3D& v) { Uniform(id, v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(Binding id, const CColor& v) { Uniform(id, v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, int v) { Uniform(GetUniformBinding(id), (float)v, (float)v, (float)v, (float)v); } void CShaderProgram::Uniform(uniform_id_t id, float v) { Uniform(GetUniformBinding(id), v, v, v, v); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1) { Uniform(GetUniformBinding(id), v0, v1, 0.0f, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CVector3D& v) { Uniform(GetUniformBinding(id), v.X, v.Y, v.Z, 0.0f); } void CShaderProgram::Uniform(uniform_id_t id, const CColor& v) { Uniform(GetUniformBinding(id), v.r, v.g, v.b, v.a); } void CShaderProgram::Uniform(uniform_id_t id, float v0, float v1, float v2, float v3) { Uniform(GetUniformBinding(id), v0, v1, v2, v3); } void CShaderProgram::Uniform(uniform_id_t id, const CMatrix3D& v) { Uniform(GetUniformBinding(id), v); } void CShaderProgram::Uniform(uniform_id_t id, size_t count, const CMatrix3D* v) { Uniform(GetUniformBinding(id), count, v); } void CShaderProgram::Uniform(uniform_id_t id, size_t count, const float* v) { Uniform(GetUniformBinding(id), count, v); } // These should all be overridden by CShaderProgramGLSL, and not used // if a non-GLSL shader was loaded instead: void CShaderProgram::VertexAttribPointer(attrib_id_t UNUSED(id), const Renderer::Backend::Format UNUSED(format), GLboolean UNUSED(normalized), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("Shader type doesn't support VertexAttribPointer"); } #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL // (GLES doesn't support any other types of shader program): void CShaderProgram::VertexPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::VertexPointer should be overridden"); } void CShaderProgram::NormalPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::NormalPointer should be overridden"); } void CShaderProgram::ColorPointer(const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::ColorPointer should be overridden"); } void CShaderProgram::TexCoordPointer(GLenum UNUSED(texture), const Renderer::Backend::Format UNUSED(format), GLsizei UNUSED(stride), const void* UNUSED(pointer)) { debug_warn("CShaderProgram::TexCoordPointer should be overridden"); } #else // These are overridden by CShaderProgramGLSL, but fixed-function and ARB shaders // both use the fixed-function vertex attribute pointers so we'll share their // definitions here: void CShaderProgram::VertexPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { const GLint size = GLSizeFromFormat(format); ENSURE(2 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glVertexPointer(size, type, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::POSITION); } void CShaderProgram::NormalPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { ENSURE(format == Renderer::Backend::Format::R32G32B32_SFLOAT); glNormalPointer(GL_FLOAT, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::NORMAL); } void CShaderProgram::ColorPointer(const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { const GLint size = GLSizeFromFormat(format); ENSURE(3 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glColorPointer(size, type, stride, pointer); m_ValidStreams |= GetStreamMask(VertexAttributeStream::COLOR); } void CShaderProgram::TexCoordPointer(GLenum texture, const Renderer::Backend::Format format, GLsizei stride, const void* pointer) { glClientActiveTextureARB(texture); const GLint size = GLSizeFromFormat(format); ENSURE(1 <= size && size <= 4); const GLenum type = GLTypeFromFormat(format); glTexCoordPointer(size, type, stride, pointer); glClientActiveTextureARB(GL_TEXTURE0); m_ValidStreams |= GetStreamMask(VertexAttributeStream::UV0) << (texture - GL_TEXTURE0); } void CShaderProgram::BindClientStates() { ENSURE(m_StreamFlags == (m_StreamFlags & ( GetStreamMask(VertexAttributeStream::POSITION) | GetStreamMask(VertexAttributeStream::NORMAL) | GetStreamMask(VertexAttributeStream::COLOR) | GetStreamMask(VertexAttributeStream::UV0) | GetStreamMask(VertexAttributeStream::UV1)))); // Enable all the desired client states for non-GLSL rendering if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION)) glEnableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL)) glEnableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR)) glEnableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0)) { glClientActiveTextureARB(GL_TEXTURE0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1)) { glClientActiveTextureARB(GL_TEXTURE1); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } // Rendering code must subsequently call VertexPointer etc for all of the streams // that were activated in this function, else AssertPointersBound will complain // that some arrays were unspecified m_ValidStreams = 0; } void CShaderProgram::UnbindClientStates() { if (m_StreamFlags & GetStreamMask(VertexAttributeStream::POSITION)) glDisableClientState(GL_VERTEX_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::NORMAL)) glDisableClientState(GL_NORMAL_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::COLOR)) glDisableClientState(GL_COLOR_ARRAY); if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV0)) { glClientActiveTextureARB(GL_TEXTURE0); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } if (m_StreamFlags & GetStreamMask(VertexAttributeStream::UV1)) { glClientActiveTextureARB(GL_TEXTURE1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glClientActiveTextureARB(GL_TEXTURE0); } } #endif // !CONFIG2_GLES bool CShaderProgram::IsStreamActive(const VertexAttributeStream stream) const { return (m_StreamFlags & GetStreamMask(stream)) != 0; } void CShaderProgram::VertexAttribPointer( const VertexAttributeStream stream, const Format format, const uint32_t offset, const uint32_t stride, const void* data) { switch (stream) { case VertexAttributeStream::POSITION: VertexPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::NORMAL: NormalPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::COLOR: ColorPointer(format, stride, static_cast(data) + offset); break; case VertexAttributeStream::UV0: FALLTHROUGH; case VertexAttributeStream::UV1: FALLTHROUGH; case VertexAttributeStream::UV2: FALLTHROUGH; case VertexAttributeStream::UV3: FALLTHROUGH; case VertexAttributeStream::UV4: FALLTHROUGH; case VertexAttributeStream::UV5: FALLTHROUGH; case VertexAttributeStream::UV6: FALLTHROUGH; case VertexAttributeStream::UV7: { const int indexOffset = static_cast(stream) - static_cast(VertexAttributeStream::UV0); TexCoordPointer(GL_TEXTURE0 + indexOffset, format, stride, static_cast(data) + offset); break; } default: debug_warn("Unsupported stream"); }; } void CShaderProgram::AssertPointersBound() { ENSURE((m_StreamFlags & ~m_ValidStreams) == 0); } } // namespace GL } // namespace Backend } // namespace Renderer